16c106e0af55407a274526fa480ce7f1afe9538d
[laas.git] / src / resource_inventory / resource_manager.py
1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
3 #
4 # All rights reserved. This program and the accompanying materials
5 # are made available under the terms of the Apache License, Version 2.0
6 # which accompanies this distribution, and is available at
7 # http://www.apache.org/licenses/LICENSE-2.0
8 ##############################################################################
9
10 from __future__ import annotations  # noqa: F407
11
12 import re
13 from typing import Optional
14 from django.db.models import Q
15
16 from dashboard.exceptions import ResourceAvailabilityException
17
18 from resource_inventory.models import (
19     Resource,
20     ResourceBundle,
21     ResourceTemplate,
22     ResourceConfiguration,
23     Network,
24     Vlan,
25     PhysicalNetwork,
26     InterfaceConfiguration,
27 )
28
29 from account.models import Lab
30 from django.contrib.auth.models import User
31
32
33 class ResourceManager:
34
35     instance = None
36
37     def __init__(self):
38         pass
39
40     @staticmethod
41     def getInstance() -> ResourceManager:
42         if ResourceManager.instance is None:
43             ResourceManager.instance = ResourceManager()
44         return ResourceManager.instance
45
46     def getAvailableResourceTemplates(self, lab: Lab, user: Optional[User] = None) -> list[ResourceTemplate]:
47         filter = Q(public=True)
48         if user:
49             filter = filter | Q(owner=user)
50         filter = filter & Q(temporary=False) & Q(lab=lab)
51         return ResourceTemplate.objects.filter(filter)
52
53     def templateIsReservable(self, resource_template: ResourceTemplate):
54         """
55         Check if the required resources to reserve this template is available.
56
57         No changes to the database
58         """
59         # count up hosts
60         profile_count = {}
61         for config in resource_template.getConfigs():
62             if config.profile not in profile_count:
63                 profile_count[config.profile] = 0
64             profile_count[config.profile] += 1
65
66         # check that all required hosts are available
67         for profile in profile_count.keys():
68             available = len(profile.get_resources(lab=resource_template.lab, unreserved=True))
69             needed = profile_count[profile]
70             if available < needed:
71                 return False
72         return True
73
74     # public interface
75     def deleteResourceBundle(self, resourceBundle: ResourceBundle):
76         raise NotImplementedError("Resource Bundle Deletion Not Implemented")
77
78     def releaseResourceBundle(self, resourceBundle: ResourceBundle):
79         resourceBundle.release()
80
81     def get_vlans(self, resourceTemplate: ResourceTemplate) -> dict[str, int]:
82         """
83         returns: dict from network name to the associated vlan number (backend vlan id)
84         """
85         networks = {}
86         vlan_manager = resourceTemplate.lab.vlan_manager
87         for network in resourceTemplate.networks.all():
88             if network.is_public:
89                 # already throws if can't get requested count, so can always expect public_net to be Some
90                 public_net = vlan_manager.get_public_vlan(within=resourceTemplate.public_vlan_pool_set())
91                 vlan_manager.reserve_public_vlan(public_net.vlan)
92                 networks[network.name] = public_net.vlan
93             else:
94                 # already throws if can't get requested count, so can always index in @ 0
95                 vlans = vlan_manager.get_vlans(count=1, within=resourceTemplate.private_vlan_pool_set())
96                 vlan_manager.reserve_vlans(vlans[0])
97                 networks[network.name] = vlans[0]
98         return networks
99
100     def instantiateTemplate(self, resource_template: ResourceTemplate):
101         """
102         Convert a ResourceTemplate into a ResourceBundle.
103
104         Takes in a ResourceTemplate and reserves all the
105         Resources needed and returns a completed ResourceBundle.
106         """
107         resource_bundle = ResourceBundle.objects.create(template=resource_template)
108         res_configs = resource_template.getConfigs()
109         resources = []
110
111         vlan_map = self.get_vlans(resource_template)
112
113         for config in res_configs:
114             try:
115                 phys_res = self.acquireHost(config)
116                 phys_res.bundle = resource_bundle
117                 phys_res.config = config
118                 resources.append(phys_res)
119
120                 self.configureNetworking(resource_bundle, phys_res, vlan_map)
121                 phys_res.save()
122
123             except Exception as e:
124                 self.fail_acquire(resources, vlan_map, resource_template)
125                 raise e
126
127         return resource_bundle
128
129     def configureNetworking(self, resource_bundle: ResourceBundle, resource: Resource, vlan_map: dict[str, int]):
130         """
131         @vlan_map: dict from network name to the associated vlan number (backend vlan id)
132         """
133         for physical_interface in resource.interfaces.all():
134
135             # assign interface configs
136             iface_config = InterfaceConfiguration.objects.get(
137                 profile=physical_interface.profile,
138                 resource_config=resource.config
139             )
140
141             physical_interface.acts_as = iface_config
142             physical_interface.acts_as.save()
143
144             physical_interface.config.clear()
145             for connection in iface_config.connections.all():
146                 physicalNetwork = PhysicalNetwork.objects.create(
147                     vlan_id=vlan_map[connection.network.name],
148                     generic_network=connection.network,
149                     bundle=resource_bundle,
150                 )
151                 physical_interface.config.add(
152                     Vlan.objects.create(
153                         vlan_id=vlan_map[connection.network.name],
154                         tagged=connection.vlan_is_tagged,
155                         public=connection.network.is_public,
156                         network=physicalNetwork
157                     )
158                 )
159
160     # private interface
161     def acquireHost(self, resource_config: ResourceConfiguration) -> Resource:
162         resources = resource_config.profile.get_resources(
163             lab=resource_config.template.lab,
164             unreserved=True
165         )
166
167         try:
168             resource = resources[0]  # TODO: should we randomize and 'load balance' the servers?
169             resource.config = resource_config
170             resource.reserve()
171             return resource
172         except IndexError:
173             raise ResourceAvailabilityException("No available resources of requested type")
174
175     def releaseNetworks(self, template, vlans):
176         vlan_manager = template.lab.vlan_manager
177         for net_name, vlan_id in vlans.items():
178             net = Network.objects.get(name=net_name, bundle=template)
179             if (net.is_public):
180                 vlan_manager.release_public_vlan(vlan_id)
181             else:
182                 vlan_manager.release_vlans(vlan_id)
183
184     def fail_acquire(self, hosts, vlans, template):
185         self.releaseNetworks(template, vlans)
186         for host in hosts:
187             host.release()
188
189
190 class HostNameValidator(object):
191     regex = r'^[A-Za-z0-9][A-Za-z0-9-]*$'
192     message = "Hostnames can only contain alphanumeric characters and hyphens (-). Hostnames must start with a letter"
193     pattern = re.compile(regex)
194
195     @classmethod
196     def is_valid_hostname(cls, hostname):
197         return len(hostname) < 65 and cls.pattern.fullmatch(hostname) is not None