1 ##############################################################################
2 # Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
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 ##############################################################################
10 from __future__ import annotations # noqa: F407
13 from typing import Optional
14 from django.db.models import Q
16 from dashboard.exceptions import ResourceAvailabilityException
18 from resource_inventory.models import (
22 ResourceConfiguration,
26 InterfaceConfiguration,
29 from account.models import Lab
30 from django.contrib.auth.models import User
33 class ResourceManager:
41 def getInstance() -> ResourceManager:
42 if ResourceManager.instance is None:
43 ResourceManager.instance = ResourceManager()
44 return ResourceManager.instance
46 def getAvailableResourceTemplates(self, lab: Lab, user: Optional[User] = None) -> list[ResourceTemplate]:
47 filter = Q(public=True)
49 filter = filter | Q(owner=user)
50 filter = filter & Q(temporary=False) & Q(lab=lab)
51 return ResourceTemplate.objects.filter(filter)
53 def templateIsReservable(self, resource_template: ResourceTemplate):
55 Check if the required resources to reserve this template is available.
57 No changes to the database
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
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:
75 def deleteResourceBundle(self, resourceBundle: ResourceBundle):
76 raise NotImplementedError("Resource Bundle Deletion Not Implemented")
78 def releaseResourceBundle(self, resourceBundle: ResourceBundle):
79 resourceBundle.release()
81 def get_vlans(self, resourceTemplate: ResourceTemplate) -> dict[str, int]:
83 returns: dict from network name to the associated vlan number (backend vlan id)
86 vlan_manager = resourceTemplate.lab.vlan_manager
87 for network in resourceTemplate.networks.all():
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
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]
100 def instantiateTemplate(self, resource_template: ResourceTemplate):
102 Convert a ResourceTemplate into a ResourceBundle.
104 Takes in a ResourceTemplate and reserves all the
105 Resources needed and returns a completed ResourceBundle.
107 resource_bundle = ResourceBundle.objects.create(template=resource_template)
108 res_configs = resource_template.getConfigs()
111 vlan_map = self.get_vlans(resource_template)
113 for config in res_configs:
115 phys_res = self.acquireHost(config)
116 phys_res.bundle = resource_bundle
117 phys_res.config = config
118 resources.append(phys_res)
120 self.configureNetworking(resource_bundle, phys_res, vlan_map)
123 except Exception as e:
124 self.fail_acquire(resources, vlan_map, resource_template)
127 return resource_bundle
129 def configureNetworking(self, resource_bundle: ResourceBundle, resource: Resource, vlan_map: dict[str, int]):
131 @vlan_map: dict from network name to the associated vlan number (backend vlan id)
133 for physical_interface in resource.interfaces.all():
135 # assign interface configs
136 iface_config = InterfaceConfiguration.objects.get(
137 profile=physical_interface.profile,
138 resource_config=resource.config
141 physical_interface.acts_as = iface_config
142 physical_interface.acts_as.save()
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,
151 physical_interface.config.add(
153 vlan_id=vlan_map[connection.network.name],
154 tagged=connection.vlan_is_tagged,
155 public=connection.network.is_public,
156 network=physicalNetwork
161 def acquireHost(self, resource_config: ResourceConfiguration) -> Resource:
162 resources = resource_config.profile.get_resources(
163 lab=resource_config.template.lab,
168 resource = resources[0] # TODO: should we randomize and 'load balance' the servers?
169 resource.config = resource_config
173 raise ResourceAvailabilityException("No available resources of requested type")
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)
180 vlan_manager.release_public_vlan(vlan_id)
182 vlan_manager.release_vlans(vlan_id)
184 def fail_acquire(self, hosts, vlans, template):
185 self.releaseNetworks(template, vlans)
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)
196 def is_valid_hostname(cls, hostname):
197 return len(hostname) < 65 and cls.pattern.fullmatch(hostname) is not None