From f5cdab1569b26df0c7ffc3df1529f095116fd13a Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Thu, 6 Feb 2020 12:59:51 -0500 Subject: Modifies Resource Models for ongoing refactor Change-Id: Ice88f53135f57aca8e2de4d69274e7d490f981a4 Signed-off-by: Parker Berberian --- src/resource_inventory/admin.py | 4 +- src/resource_inventory/models.py | 465 +++++++++++++++++------------ src/resource_inventory/pdf_templater.py | 6 +- src/resource_inventory/resource_manager.py | 121 +++----- 4 files changed, 320 insertions(+), 276 deletions(-) (limited to 'src/resource_inventory') diff --git a/src/resource_inventory/admin.py b/src/resource_inventory/admin.py index 7ff510b..ab21dd1 100644 --- a/src/resource_inventory/admin.py +++ b/src/resource_inventory/admin.py @@ -11,7 +11,7 @@ from django.contrib import admin from resource_inventory.models import ( - HostProfile, + ResourceProfile, InterfaceProfile, DiskProfile, CpuProfile, @@ -32,7 +32,7 @@ from resource_inventory.models import ( OPNFVConfig, OPNFVRole, Image, - HostConfiguration, + ResourceConfiguration, RemoteInfo ) diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index a8b75d9..20e080b 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -8,21 +8,30 @@ ############################################################################## from django.contrib.auth.models import User +from django.core.exceptions import ValidationError from django.db import models -from django.core.validators import RegexValidator +from django.db.models import Q import re from account.models import Lab +from dashboard.utils import AbstractModelQuery -# profile of resources hosted by labs -class HostProfile(models.Model): +""" +Profiles of resources hosted by labs. + +These describe hardware attributes of the different Resources a lab hosts. +A single Resource subclass (e.g. Server) may have instances that point to different +Profile models (e.g. an x86 server profile and armv8 server profile. +""" + + +class ResourceProfile(models.Model): id = models.AutoField(primary_key=True) - host_type = models.PositiveSmallIntegerField(default=0) name = models.CharField(max_length=200, unique=True) description = models.TextField() - labs = models.ManyToManyField(Lab, related_name="hostprofiles") + labs = models.ManyToManyField(Lab, related_name="resourceprofiles") def validate(self): validname = re.compile(r"^[A-Za-z0-9\-\_\.\/\, ]+$") @@ -34,12 +43,33 @@ class HostProfile(models.Model): def __str__(self): return self.name + def get_resources(self, lab=None, working=True, unreserved=False): + """ + Return a list of Resource objects which have this profile. + + If lab is provided, only resources at that lab will be returned. + If working=True, will only return working hosts + """ + resources = [] + query = Q(profile=self) + if lab: + query = query & Q(lab=lab) + if working: + query = query & Q(working=True) + + resources = ResourceQuery.filter(query) + + if unreserved: + resources = [r for r in resources if not r.is_reserved()] + + return resources + class InterfaceProfile(models.Model): id = models.AutoField(primary_key=True) speed = models.IntegerField() name = models.CharField(max_length=100) - host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='interfaceprofile') + host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='interfaceprofile') nic_type = models.CharField( max_length=50, choices=[ @@ -48,6 +78,7 @@ class InterfaceProfile(models.Model): ], default="onboard" ) + order = models.IntegerField(default=-1) def __str__(self): return self.name + " for " + str(self.host) @@ -61,7 +92,7 @@ class DiskProfile(models.Model): ("HDD", "HDD") ]) name = models.CharField(max_length=50) - host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='storageprofile') + host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='storageprofile') rotation = models.IntegerField(default=0) interface = models.CharField( max_length=50, @@ -88,7 +119,7 @@ class CpuProfile(models.Model): ("aarch64", "aarch64") ]) cpus = models.IntegerField() - host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='cpuprofile') + host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='cpuprofile') cflags = models.TextField(null=True) def __str__(self): @@ -99,16 +130,115 @@ class RamProfile(models.Model): id = models.AutoField(primary_key=True) amount = models.IntegerField() channels = models.IntegerField() - host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='ramprofile') + host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='ramprofile') def __str__(self): return str(self.amount) + "G for " + str(self.host) +""" +Resource Models + +These models represent actual hardware resources +with varying degrees of abstraction. +""" + + +class ResourceTemplate(models.Model): + """ + Models a "template" of a complete, configured collection of resources that can be booked. + + For example, this may represent a Pharos POD. This model is a template of the actual + resources that will be booked. This model can be "instantiated" into real resource models + across multiple different bookings. + """ + + # TODO: template might not be a good name because this is a collection of lots of configured resources + id = models.AutoField(primary_key=True) + name = models.CharField(max_length=300, unique=True) + xml = models.TextField() + owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL, related_name="resourcetemplates") + description = models.CharField(max_length=1000, default="") + public = models.BooleanField(default=False) + hidden = models.BooleanField(default=False) + + def getConfigs(self): + return list(self.resourceConfigurations.all()) + + def __str__(self): + return self.name + + +class ResourceBundle(models.Model): + """ + Collection of Resource objects. + + This is just a way of aggregating all the resources in a booking into a single model. + """ + + template = models.ForeignKey(ResourceTemplate, on_delete=models.SET_NULL, null=True) + + def __str__(self): + if self.template is None: + return "Resource bundle " + str(self.id) + " with no template" + return "instance of " + str(self.template) + + def get_resources(self): + return ResourceQuery.filter(bundle=self) + + def get_resource_with_role(self, role): + # TODO + pass + + +class ResourceConfiguration(models.Model): + """Model to represent a complete configuration for a single physical Resource.""" + + id = models.AutoField(primary_key=True) + profile = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE) + image = models.ForeignKey("Image", on_delete=models.PROTECT) + template = models.ForeignKey(ResourceTemplate, related_name="resourceConfigurations", null=True, on_delete=models.CASCADE) + is_head_node = models.BooleanField(default=False) + + def __str__(self): + return "config with " + str(self.template) + " and image " + str(self.image) + + +def get_default_remote_info(): + return RemoteInfo.objects.get_or_create( + address="default", + mac_address="default", + password="default", + user="default", + management_type="default", + versions="[default]" + )[0].pk + + class Resource(models.Model): + """ + Super class for all hardware resource models. + + Defines methods that must be implemented and common database fields. + Any new kind of Resource a lab wants to host (White box switch, traffic generator, etc) + should inherit from this class and fulfill the functional interface + """ + class Meta: abstract = True + bundle = models.ForeignKey(ResourceBundle, on_delete=models.SET_NULL, null=True) + profile = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE) + config = models.ForeignKey(ResourceConfiguration, on_delete=models.SET_NULL, null=True) + working = models.BooleanField(default=True) + vendor = models.CharField(max_length=100, default="unknown") + model = models.CharField(max_length=150, default="unknown") + interfaces = models.ManyToManyField("Interface") + remote_management = models.ForeignKey("RemoteInfo", default=get_default_remote_info, on_delete=models.SET(get_default_remote_info)) + labid = models.CharField(max_length=200, default="default_id", unique=True) + lab = models.ForeignKey(Lab, on_delete=models.CASCADE) + def get_configuration(self, state): """ Get configuration of Resource. @@ -129,38 +259,122 @@ class Resource(models.Model): def get_interfaces(self): """ - Returns a list of interfaces on this resource. + Return a list of interfaces on this resource. + The ordering of interfaces should be consistent. """ raise NotImplementedError("Must implement in concrete Resource classes") + def is_reserved(self): + """Return True if this Resource is reserved.""" + raise NotImplementedError("Must implement in concrete Resource classes") + + def same_instance(self, other): + """Return True if this Resource is the same instance as other.""" + raise NotImplementedError("Must implement in concrete Resource classes") + + def save(self, *args, **kwargs): + """Assert that labid is unique across all Resource models.""" + res = ResourceQuery.filter(labid=self.labid) + if len(res) > 1: + raise ValidationError("Too many resources with labid " + str(self.labid)) + + if len(res) == 1: + if not self.same_instance(res[0]): + raise ValidationError("Too many resources with labid " + str(self.labid)) + super().save(*args, **kwargs) -# Generic resource templates -class GenericResourceBundle(models.Model): + +class RemoteInfo(models.Model): + address = models.CharField(max_length=15) + mac_address = models.CharField(max_length=17) + password = models.CharField(max_length=100) + user = models.CharField(max_length=100) + management_type = models.CharField(max_length=50, default="ipmi") + versions = models.CharField(max_length=100) # json serialized list of floats + + +class Server(Resource): + """Resource subclass - a basic baremetal server.""" + + booked = models.BooleanField(default=False) + name = models.CharField(max_length=200, unique=True) + + def __str__(self): + return self.name + + def get_configuration(self, state): + ipmi = state == ConfigState.NEW + power = "off" if state == ConfigState.CLEAN else "on" + + return { + "id": self.labid, + "image": self.config.image.lab_id, + "hostname": self.template.resource.name, + "power": power, + "ipmi_create": str(ipmi) + } + + def get_interfaces(self): + return list(self.interfaces.all().order_by('bus_address')) + + def release(self): + self.booked = False + self.save() + + def reserve(self): + self.booked = True + self.save() + + def is_reserved(self): + return self.booked + + def same_instance(self, other): + return isinstance(other, Server) and other.name == self.name + + +class Opsys(models.Model): id = models.AutoField(primary_key=True) - name = models.CharField(max_length=300, unique=True) - xml = models.TextField() - owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) - lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL) - description = models.CharField(max_length=1000, default="") - public = models.BooleanField(default=False) - hidden = models.BooleanField(default=False) + name = models.CharField(max_length=100) + sup_installers = models.ManyToManyField("Installer", blank=True) + + def __str__(self): + return self.name - def getResources(self): - my_resources = [] - for genericResource in self.generic_resources.all(): - my_resources.append(genericResource.getResource()) - return my_resources +class Image(models.Model): + """Model for representing OS images / snapshots of hosts.""" + + id = models.AutoField(primary_key=True) + lab_id = models.IntegerField() # ID the lab who holds this image knows + from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE) + name = models.CharField(max_length=200) + owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) + public = models.BooleanField(default=True) + host_type = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE) + description = models.TextField() + os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE) def __str__(self): return self.name + def in_use(self): + for resource in ResourceQuery.filter(config__image=self): + if resource.is_reserved(): + return True + + return False + + +""" +Networking configuration models +""" + class Network(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=100) - bundle = models.ForeignKey(GenericResourceBundle, on_delete=models.CASCADE, related_name="networks") + bundle = models.ForeignKey(ResourceTemplate, on_delete=models.CASCADE, related_name="networks") is_public = models.BooleanField() def __str__(self): @@ -205,65 +419,21 @@ class Vlan(models.Model): return str(self.vlan_id) + ("_T" if self.tagged else "") -class ConfigState: - NEW = 0 - RESET = 100 - CLEAN = 200 - - -class GenericResource(models.Model): - bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE) - hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)") - name = models.CharField(max_length=200, validators=[hostname_validchars]) - - def getResource(self): - # TODO: This will have to be dealt with - return self.generic_host - - def __str__(self): - return self.name - - def validate(self): - validname = re.compile(r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))') - if not validname.match(self.name): - return "Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)" - else: - return None - - -# Host template -class GenericHost(models.Model): - id = models.AutoField(primary_key=True) - profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE) - resource = models.OneToOneField(GenericResource, related_name='generic_host', on_delete=models.CASCADE) - - def __str__(self): - return self.resource.name - - -# Physical, actual resources -class ResourceBundle(Resource): - template = models.ForeignKey(GenericResourceBundle, on_delete=models.SET_NULL, null=True) - - def __str__(self): - if self.template is None: - return "Resource bundle " + str(self.id) + " with no template" - return "instance of " + str(self.template) - - def get_host(self, role="Jumphost"): - return Host.objects.filter(bundle=self, config__is_head_node=True).first() # should only ever be one, but it is not an invariant in the models - - -class GenericInterface(models.Model): +class InterfaceConfiguration(models.Model): id = models.AutoField(primary_key=True) profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE) - host = models.ForeignKey(GenericHost, on_delete=models.CASCADE, related_name='generic_interfaces') + resource_config = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE, related_name='interface_configs') connections = models.ManyToManyField(NetworkConnection) def __str__(self): return "type " + str(self.profile) + " on host " + str(self.host) +""" +OPNFV / Software configuration models +""" + + class Scenario(models.Model): id = models.AutoField(primary_key=True) name = models.CharField(max_length=300) @@ -281,38 +451,16 @@ class Installer(models.Model): return self.name -class Opsys(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=100) - sup_installers = models.ManyToManyField(Installer, blank=True) - - def __str__(self): - return self.name - - class NetworkRole(models.Model): name = models.CharField(max_length=100) network = models.ForeignKey(Network, on_delete=models.CASCADE) -class ConfigBundle(models.Model): - id = models.AutoField(primary_key=True) - owner = models.ForeignKey(User, on_delete=models.CASCADE) - name = models.CharField(max_length=200, unique=True) - description = models.CharField(max_length=1000, default="") - bundle = models.ForeignKey(GenericResourceBundle, null=True, on_delete=models.CASCADE) - public = models.BooleanField(default=False) - hidden = models.BooleanField(default=False) - - def __str__(self): - return self.name - - class OPNFVConfig(models.Model): id = models.AutoField(primary_key=True) installer = models.ForeignKey(Installer, on_delete=models.CASCADE) scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE) - bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE) + template = models.ForeignKey(ResourceTemplate, related_name="opnfv_config", on_delete=models.CASCADE) networks = models.ManyToManyField(NetworkRole) name = models.CharField(max_length=300, blank=True, default="") description = models.CharField(max_length=600, blank=True, default="") @@ -330,105 +478,14 @@ class OPNFVRole(models.Model): return self.name -class Image(models.Model): - """Model for representing OS images / snapshots of hosts.""" - - id = models.AutoField(primary_key=True) - lab_id = models.IntegerField() # ID the lab who holds this image knows - from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - name = models.CharField(max_length=200) - owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) - public = models.BooleanField(default=True) - host_type = models.ForeignKey(HostProfile, on_delete=models.CASCADE) - description = models.TextField() - os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE) - - def __str__(self): - return self.name - - def in_use(self): - return Host.objects.filter(booked=True, config__image=self).exists() - - def get_sentinal_opnfv_role(): return OPNFVRole.objects.get_or_create(name="deleted", description="Role was deleted.") -class HostConfiguration(models.Model): - """Model to represent a complete configuration for a single physical host.""" - - id = models.AutoField(primary_key=True) - host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE) - image = models.ForeignKey(Image, on_delete=models.PROTECT) - bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE) - is_head_node = models.BooleanField(default=False) - - def __str__(self): - return "config with " + str(self.host) + " and image " + str(self.image) - - -class HostOPNFVConfig(models.Model): - role = models.ForeignKey(OPNFVRole, related_name="host_opnfv_configs", on_delete=models.CASCADE) - host_config = models.ForeignKey(HostConfiguration, related_name="host_opnfv_config", on_delete=models.CASCADE) - opnfv_config = models.ForeignKey(OPNFVConfig, related_name="host_opnfv_config", on_delete=models.CASCADE) - - -class RemoteInfo(models.Model): - address = models.CharField(max_length=15) - mac_address = models.CharField(max_length=17) - password = models.CharField(max_length=100) - user = models.CharField(max_length=100) - management_type = models.CharField(max_length=50, default="ipmi") - versions = models.CharField(max_length=100) # json serialized list of floats - - -def get_default_remote_info(): - return RemoteInfo.objects.get_or_create( - address="default", - mac_address="default", - password="default", - user="default", - management_type="default", - versions="[default]" - )[0].pk - - -# Concrete host, actual machine in a lab -class Host(Resource): - template = models.ForeignKey(GenericHost, on_delete=models.SET_NULL, null=True) - booked = models.BooleanField(default=False) - name = models.CharField(max_length=200, unique=True) - bundle = models.ForeignKey(ResourceBundle, related_name='hosts', on_delete=models.SET_NULL, null=True) - config = models.ForeignKey(HostConfiguration, null=True, related_name="configuration", on_delete=models.SET_NULL) - labid = models.CharField(max_length=200, default="default_id") - profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE) - lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - working = models.BooleanField(default=True) - vendor = models.CharField(max_length=100, default="unknown") - model = models.CharField(max_length=150, default="unknown") - remote_management = models.ForeignKey(RemoteInfo, default=get_default_remote_info, on_delete=models.SET(get_default_remote_info)) - - def __str__(self): - return self.name - - def get_configuration(self, state): - ipmi = state == ConfigState.NEW - power = "off" if state == ConfigState.CLEAN else "on" - - return { - "id": self.labid, - "image": self.config.image.lab_id, - "hostname": self.template.resource.name, - "power": power, - "ipmi_create": str(ipmi) - } - - def release(self): - self.booked = False - self.save() - - def get_interfaces(self): - return list(self.interfaces.all().order_by('bus_address')) +class ResourceOPNFVConfig(models.Model): + role = models.ForeignKey(OPNFVRole, related_name="resource_opnfv_configs", on_delete=models.CASCADE) + resource_config = models.ForeignKey(ResourceConfiguration, related_name="resource_opnfv_config", on_delete=models.CASCADE) + opnfv_config = models.ForeignKey(OPNFVConfig, related_name="resource_opnfv_config", on_delete=models.CASCADE) class Interface(models.Model): @@ -436,15 +493,33 @@ class Interface(models.Model): mac_address = models.CharField(max_length=17) bus_address = models.CharField(max_length=50) config = models.ManyToManyField(Vlan) - host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='interfaces') + acts_as = models.OneToOneField(InterfaceConfiguration, null=True, on_delete=models.SET_NULL) profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE) def __str__(self): return self.mac_address + " on host " + str(self.host) +""" +Some Enums for dealing with global constants. +""" + + class OPNFV_SETTINGS(): """This is a static configuration class.""" # all the required network types in PDF/IDF spec NETWORK_ROLES = ["public", "private", "admin", "mgmt"] + + +class ConfigState: + NEW = 0 + RESET = 100 + CLEAN = 200 + + +RESOURCE_TYPES = [Server] + + +class ResourceQuery(AbstractModelQuery): + model_list = [Server] diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py index 51e3746..6844b09 100644 --- a/src/resource_inventory/pdf_templater.py +++ b/src/resource_inventory/pdf_templater.py @@ -10,7 +10,7 @@ from django.template.loader import render_to_string import booking -from resource_inventory.models import Host, InterfaceProfile +from resource_inventory.models import Server, InterfaceProfile class PDFTemplater: @@ -66,7 +66,7 @@ class PDFTemplater: ) jumphost = booking.resource.hosts.get(config=jumphost_opnfv_config.host_config) else: # if there is no opnfv config, use headnode - jumphost = Host.objects.filter( + jumphost = Server.objects.filter( bundle=booking.resource, config__is_head_node=True ).first() @@ -85,7 +85,7 @@ class PDFTemplater: def get_pdf_nodes(cls, booking): """Return a list of all the "nodes" (every host except jumphost).""" pdf_nodes = [] - nodes = set(Host.objects.filter(bundle=booking.resource)) + nodes = set(Server.objects.filter(bundle=booking.resource)) nodes.discard(cls.get_jumphost(booking)) for node in nodes: diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index 242d21a..e14218b 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -8,17 +8,10 @@ ############################################################################## import re -from dashboard.exceptions import ( - ResourceExistenceException, - ResourceAvailabilityException, - ResourceProvisioningException, - ModelValidationException, -) +from dashboard.exceptions import ResourceAvailabilityException + from resource_inventory.models import ( - Host, - HostConfiguration, ResourceBundle, - HostProfile, Network, Vlan, PhysicalNetwork, @@ -38,32 +31,22 @@ class ResourceManager: ResourceManager.instance = ResourceManager() return ResourceManager.instance - def getAvailableHostTypes(self, lab): - hostset = Host.objects.filter(lab=lab).filter(booked=False).filter(working=True) - hostprofileset = HostProfile.objects.filter(host__in=hostset, labs=lab) - return set(hostprofileset) - - def hostsAvailable(self, grb): + def templateIsReservable(self, resource_template): """ - Check if the given GenericResourceBundle is available. + Check if the required resources to reserve this template is available. No changes to the database """ # count up hosts profile_count = {} - for host in grb.getResources(): - if host.profile not in profile_count: - profile_count[host.profile] = 0 - profile_count[host.profile] += 1 + for config in resource_template.getConfigs(): + if config.profile not in profile_count: + profile_count[config.profile] = 0 + profile_count[config.profile] += 1 # check that all required hosts are available for profile in profile_count.keys(): - available = Host.objects.filter( - booked=False, - working=True, - lab=grb.lab, - profile=profile - ).count() + available = len(profile.get_resources(lab=resource_template.lab, unreserved=True)) needed = profile_count[profile] if available < needed: return False @@ -71,8 +54,8 @@ class ResourceManager: # public interface def deleteResourceBundle(self, resourceBundle): - for host in Host.objects.filter(bundle=resourceBundle): - host.release() + for resource in resourceBundle.get_resources(): + resource.release() resourceBundle.delete() def get_vlans(self, genericResourceBundle): @@ -89,43 +72,32 @@ class ResourceManager: networks[network.name] = vlan return networks - def convertResourceBundle(self, genericResourceBundle, config=None): + def instantiateTemplate(self, resource_template, config=None): """ - Convert a GenericResourceBundle into a ResourceBundle. + Convert a ResourceTemplate into a ResourceBundle. - Takes in a genericResourceBundle and reserves all the + Takes in a ResourceTemplate and reserves all the Resources needed and returns a completed ResourceBundle. """ - resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle) - generic_hosts = genericResourceBundle.getResources() - physical_hosts = [] + resource_bundle = ResourceBundle.objects.create(template=resource_template) + res_configs = resource_template.getConfigs() + resources = [] - vlan_map = self.get_vlans(genericResourceBundle) + vlan_map = self.get_vlans(resource_template) - for generic_host in generic_hosts: - host_config = None - if config: - host_config = HostConfiguration.objects.get(bundle=config, host=generic_host) + for config in res_configs: try: - physical_host = self.acquireHost(generic_host, genericResourceBundle.lab.name) - except ResourceAvailabilityException: - self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) - raise ResourceAvailabilityException("Could not provision hosts, not enough available") - try: - physical_host.bundle = resource_bundle - physical_host.template = generic_host - physical_host.config = host_config - physical_hosts.append(physical_host) - - self.configureNetworking(physical_host, vlan_map) - except Exception: - self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) - raise ResourceProvisioningException("Network configuration failed.") - try: - physical_host.save() - except Exception: - self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle) - raise ModelValidationException("Saving hosts failed") + phys_res = self.acquireHost(config) + phys_res.bundle = resource_bundle + phys_res.config = config + resources.append(phys_res) + + self.configureNetworking(phys_res, vlan_map) + phys_res.save() + + except Exception as e: + self.fail_acquire(resources, vlan_map, resource_template) + raise e return resource_bundle @@ -149,30 +121,27 @@ class ResourceManager: ) # private interface - def acquireHost(self, genericHost, labName): - host_full_set = Host.objects.filter(lab__name__exact=labName, profile=genericHost.profile) - if not host_full_set.first(): - raise ResourceExistenceException("No matching servers found") - host_set = host_full_set.filter(booked=False, working=True) - if not host_set.first(): - raise ResourceAvailabilityException("No unbooked hosts match requested hosts") - host = host_set.first() - host.booked = True - host.template = genericHost - host.save() - return host - - def releaseNetworks(self, grb, vlan_manager, vlans): + def acquireHost(self, resource_config): + resources = resource_config.profile.get_resources(lab=resource_config.lab, unreserved=True) + try: + resource = resources[0] # TODO: should we randomize and 'load balance' the servers? + resource.config = resource_config + resource.reserve() + return resource + except IndexError: + raise ResourceAvailabilityException("No available resources of requested type") + + def releaseNetworks(self, template, vlans): + vlan_manager = template.lab.vlan_manager for net_name, vlan_id in vlans.items(): - net = Network.objects.get(name=net_name, bundle=grb) + net = Network.objects.get(name=net_name, bundle=template) if(net.is_public): vlan_manager.release_public_vlan(vlan_id) else: vlan_manager.release_vlans(vlan_id) - def fail_acquire(self, hosts, vlans, grb): - vlan_manager = grb.lab.vlan_manager - self.releaseNetworks(grb, vlan_manager, vlans) + def fail_acquire(self, hosts, vlans, template): + self.releaseNetworks(template, vlans) for host in hosts: host.release() -- cgit 1.2.3-korg