diff options
Diffstat (limited to 'src/resource_inventory')
-rw-r--r-- | src/resource_inventory/admin.py | 59 | ||||
-rw-r--r-- | src/resource_inventory/forms.py | 31 | ||||
-rw-r--r-- | src/resource_inventory/idf_templater.py | 148 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0023_cloudinitfile_generated.py | 2 | ||||
-rw-r--r-- | src/resource_inventory/migrations/0024_auto_20230608_1913.py | 262 | ||||
-rw-r--r-- | src/resource_inventory/models.py | 689 | ||||
-rw-r--r-- | src/resource_inventory/pdf_templater.py | 176 | ||||
-rw-r--r-- | src/resource_inventory/resource_manager.py | 197 | ||||
-rw-r--r-- | src/resource_inventory/tests/test_managers.py | 301 | ||||
-rw-r--r-- | src/resource_inventory/tests/test_models.py | 173 | ||||
-rw-r--r-- | src/resource_inventory/urls.py | 27 | ||||
-rw-r--r-- | src/resource_inventory/views.py | 30 |
12 files changed, 274 insertions, 1821 deletions
diff --git a/src/resource_inventory/admin.py b/src/resource_inventory/admin.py index 2444a98..da9cba3 100644 --- a/src/resource_inventory/admin.py +++ b/src/resource_inventory/admin.py @@ -9,62 +9,3 @@ from django.contrib import admin - -from resource_inventory.forms import InterfaceConfigurationForm - -from resource_inventory.models import ( - ResourceProfile, - InterfaceProfile, - DiskProfile, - CpuProfile, - RamProfile, - ResourceTemplate, - ResourceConfiguration, - InterfaceConfiguration, - Server, - Interface, - Network, - Vlan, - ResourceBundle, - Scenario, - Installer, - Opsys, - OPNFVConfig, - OPNFVRole, - Image, - RemoteInfo, - PhysicalNetwork, - NetworkConnection, -) - - -admin.site.register([ - ResourceProfile, - InterfaceProfile, - DiskProfile, - CpuProfile, - RamProfile, - ResourceTemplate, - ResourceConfiguration, - Server, - Interface, - Network, - Vlan, - ResourceBundle, - Scenario, - Installer, - Opsys, - OPNFVConfig, - OPNFVRole, - Image, - PhysicalNetwork, - NetworkConnection, - RemoteInfo] -) - - -class InterfaceConfigurationAdmin(admin.ModelAdmin): - form = InterfaceConfigurationForm - - -admin.site.register(InterfaceConfiguration, InterfaceConfigurationAdmin) diff --git a/src/resource_inventory/forms.py b/src/resource_inventory/forms.py deleted file mode 100644 index fb8c102..0000000 --- a/src/resource_inventory/forms.py +++ /dev/null @@ -1,31 +0,0 @@ -############################################################################## -# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from django.core.exceptions import ValidationError -from django import forms - -from resource_inventory.models import Network, InterfaceConfiguration - - -class InterfaceConfigurationForm(forms.ModelForm): - class Meta: - model = InterfaceConfiguration - fields = ['profile', 'resource_config', 'connections'] - - def clean(self): - connections = self.cleaned_data.get('connections') - resource_config = self.cleaned_data.get('resource_config') - - valid_nets = set(Network.objects.filter(bundle=resource_config.template)) - curr_nets = set([conn.network for conn in connections]) - - if not curr_nets.issubset(valid_nets): - raise ValidationError("Cannot have network connection to network outside pod") - - return self.cleaned_data diff --git a/src/resource_inventory/idf_templater.py b/src/resource_inventory/idf_templater.py deleted file mode 100644 index 8f0f924..0000000 --- a/src/resource_inventory/idf_templater.py +++ /dev/null @@ -1,148 +0,0 @@ -############################################################################## -# Copyright (c) 2019 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.template.loader import render_to_string - -from account.models import PublicNetwork - -from resource_inventory.models import Vlan - - -class IDFTemplater: - """Utility class to create a full Installer Descriptor File (IDF) yaml file.""" - - net_names = ["admin", "mgmt", "private", "public"] - bridge_names = { - "admin": "br-admin", - "mgmt": "br-mgmt", - "private": "br-private", - "public": "br-public" - } - - def __init__(self): - self.networks = {} - for i, name in enumerate(self.net_names): - self.networks[name] = { - "name": name, - "vlan": -1, - "interface": i, - "ip": "10.250." + str(i) + ".0", - "netmask": 24 - } - - def makeIDF(self, booking): - """Fill the IDF template with info about the resource.""" - template = "dashboard/idf.yaml" - info = {} - info['version'] = "0.1" - info['net_config'] = self.get_net_config(booking) - info['fuel'] = self.get_fuel_config(booking) - - return render_to_string(template, context=info) - - def get_net_config(self, booking): - net_config = {} - try: - net_config['oob'] = self.get_oob_net(booking) - except Exception: - net_config['oob'] = {} - try: - net_config['public'] = self.get_public_net(booking) - except Exception: - net_config['public'] = {} - - for net in [net for net in self.net_names if net != "public"]: - try: - net_config[net] = self.get_single_net_config(booking, net) - except Exception: - net_config[net] = {} - - return net_config - - def get_public_net(self, booking): - public = {} - config = booking.opnfv_config - public_role = config.networks.get(name="public") - public_vlan = Vlan.objects.filter(network=public_role.network).first() - public_network = PublicNetwork.objects.get(vlan=public_vlan.vlan_id, lab=booking.lab) - self.networks['public']['vlan'] = public_vlan.vlan_id - public['interface'] = self.networks['public']['interface'] - public['vlan'] = public_network.vlan # untagged?? - public['network'] = public_network.cidr.split("/")[0] - public['mask'] = public_network.cidr.split("/")[1] - # public['ip_range'] = 4 # necesary? - public['gateway'] = public_network.gateway - public['dns'] = ["1.1.1.1", "8.8.8.8"] - - return public - - def get_oob_net(self, booking): - net = {} - hosts = booking.resource.hosts.all() - addrs = [host.remote_management.address for host in hosts] - net['ip_range'] = ",".join(addrs) - net['vlan'] = "native" - return net - - def get_single_net_config(self, booking, net_name): - config = booking.opnfv_config - role = config.networks.get(name=net_name) - vlan = Vlan.objects.filter(network=role.network).first() - self.networks[net_name]['vlan'] = vlan.vlan_id - net = {} - net['interface'] = self.networks[net_name]['interface'] - net['vlan'] = vlan.vlan_id - net['network'] = self.networks[net_name]['ip'] - net['mask'] = self.networks[net_name]['netmask'] - - return net - - def get_fuel_config(self, booking): - fuel = {} - fuel['jumphost'] = {} - try: - fuel['jumphost']['bridges'] = self.get_fuel_bridges() - except Exception: - fuel['jumphost']['bridges'] = {} - - fuel['network'] = {} - try: - fuel['network']['nodes'] = self.get_fuel_nodes(booking) - except Exception: - fuel['network']['nodes'] = {} - - return fuel - - def get_fuel_bridges(self): - return self.bridge_names - - def get_fuel_nodes(self, booking): - jumphost = booking.opnfv_config.host_opnfv_config.get( - role__name__iexact="jumphost" - ) - hosts = booking.resource.hosts.exclude(pk=jumphost.pk) - nodes = [] - for host in hosts: - node = {} - ordered_interfaces = self.get_node_interfaces(host) - node['interfaces'] = [iface['name'] for iface in ordered_interfaces] - node['bus_addrs'] = [iface['bus'] for iface in ordered_interfaces] - nodes.append(node) - - return nodes - - def get_node_interfaces(self, node): - # TODO: this should sync with pdf ordering - interfaces = [] - - for iface in node.interfaces.all(): - interfaces.append({"name": iface.name, "bus": iface.bus_address}) - - return interfaces diff --git a/src/resource_inventory/migrations/0023_cloudinitfile_generated.py b/src/resource_inventory/migrations/0023_cloudinitfile_generated.py index b309753..eb10756 100644 --- a/src/resource_inventory/migrations/0023_cloudinitfile_generated.py +++ b/src/resource_inventory/migrations/0023_cloudinitfile_generated.py @@ -1,4 +1,4 @@ -# Generated by Django 2.2 on 2021-12-17 18:54 +# Generated by Django 2.2 on 2023-06-07 19:48 from django.db import migrations, models diff --git a/src/resource_inventory/migrations/0024_auto_20230608_1913.py b/src/resource_inventory/migrations/0024_auto_20230608_1913.py new file mode 100644 index 0000000..1c8ea47 --- /dev/null +++ b/src/resource_inventory/migrations/0024_auto_20230608_1913.py @@ -0,0 +1,262 @@ +# Generated by Django 2.2 on 2023-06-08 19:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('api', '0023_auto_20230608_1913'), + ('booking', '0010_auto_20230608_1913'), + ('resource_inventory', '0023_cloudinitfile_generated'), + ] + + operations = [ + migrations.RemoveField( + model_name='cpuprofile', + name='host', + ), + migrations.RemoveField( + model_name='diskprofile', + name='host', + ), + migrations.RemoveField( + model_name='image', + name='from_lab', + ), + migrations.RemoveField( + model_name='image', + name='os', + ), + migrations.RemoveField( + model_name='image', + name='owner', + ), + migrations.RemoveField( + model_name='installer', + name='sup_scenarios', + ), + migrations.RemoveField( + model_name='interface', + name='acts_as', + ), + migrations.RemoveField( + model_name='interface', + name='config', + ), + migrations.RemoveField( + model_name='interface', + name='profile', + ), + migrations.RemoveField( + model_name='interfaceconfiguration', + name='connections', + ), + migrations.RemoveField( + model_name='interfaceconfiguration', + name='profile', + ), + migrations.RemoveField( + model_name='interfaceconfiguration', + name='resource_config', + ), + migrations.RemoveField( + model_name='interfaceprofile', + name='host', + ), + migrations.RemoveField( + model_name='network', + name='bundle', + ), + migrations.RemoveField( + model_name='networkconnection', + name='network', + ), + migrations.RemoveField( + model_name='networkrole', + name='network', + ), + migrations.RemoveField( + model_name='opnfvconfig', + name='installer', + ), + migrations.RemoveField( + model_name='opnfvconfig', + name='networks', + ), + migrations.RemoveField( + model_name='opnfvconfig', + name='scenario', + ), + migrations.RemoveField( + model_name='opnfvconfig', + name='template', + ), + migrations.RemoveField( + model_name='opsys', + name='from_lab', + ), + migrations.RemoveField( + model_name='physicalnetwork', + name='bundle', + ), + migrations.RemoveField( + model_name='physicalnetwork', + name='generic_network', + ), + migrations.RemoveField( + model_name='ramprofile', + name='host', + ), + migrations.RemoveField( + model_name='resourcebundle', + name='template', + ), + migrations.RemoveField( + model_name='resourceconfiguration', + name='cloud_init_files', + ), + migrations.RemoveField( + model_name='resourceconfiguration', + name='image', + ), + migrations.RemoveField( + model_name='resourceconfiguration', + name='profile', + ), + migrations.RemoveField( + model_name='resourceconfiguration', + name='template', + ), + migrations.RemoveField( + model_name='resourceopnfvconfig', + name='opnfv_config', + ), + migrations.RemoveField( + model_name='resourceopnfvconfig', + name='resource_config', + ), + migrations.RemoveField( + model_name='resourceopnfvconfig', + name='role', + ), + migrations.RemoveField( + model_name='resourceprofile', + name='labs', + ), + migrations.RemoveField( + model_name='resourcetemplate', + name='copy_of', + ), + migrations.RemoveField( + model_name='resourcetemplate', + name='lab', + ), + migrations.RemoveField( + model_name='resourcetemplate', + name='owner', + ), + migrations.RemoveField( + model_name='server', + name='bundle', + ), + migrations.RemoveField( + model_name='server', + name='config', + ), + migrations.RemoveField( + model_name='server', + name='interfaces', + ), + migrations.RemoveField( + model_name='server', + name='lab', + ), + migrations.RemoveField( + model_name='server', + name='profile', + ), + migrations.RemoveField( + model_name='server', + name='remote_management', + ), + migrations.RemoveField( + model_name='vlan', + name='network', + ), + migrations.DeleteModel( + name='CloudInitFile', + ), + migrations.DeleteModel( + name='CpuProfile', + ), + migrations.DeleteModel( + name='DiskProfile', + ), + migrations.DeleteModel( + name='Image', + ), + migrations.DeleteModel( + name='Installer', + ), + migrations.DeleteModel( + name='Interface', + ), + migrations.DeleteModel( + name='InterfaceConfiguration', + ), + migrations.DeleteModel( + name='InterfaceProfile', + ), + migrations.DeleteModel( + name='Network', + ), + migrations.DeleteModel( + name='NetworkConnection', + ), + migrations.DeleteModel( + name='NetworkRole', + ), + migrations.DeleteModel( + name='OPNFVConfig', + ), + migrations.DeleteModel( + name='OPNFVRole', + ), + migrations.DeleteModel( + name='Opsys', + ), + migrations.DeleteModel( + name='PhysicalNetwork', + ), + migrations.DeleteModel( + name='RamProfile', + ), + migrations.DeleteModel( + name='RemoteInfo', + ), + migrations.DeleteModel( + name='ResourceBundle', + ), + migrations.DeleteModel( + name='ResourceConfiguration', + ), + migrations.DeleteModel( + name='ResourceOPNFVConfig', + ), + migrations.DeleteModel( + name='ResourceProfile', + ), + migrations.DeleteModel( + name='ResourceTemplate', + ), + migrations.DeleteModel( + name='Scenario', + ), + migrations.DeleteModel( + name='Server', + ), + migrations.DeleteModel( + name='Vlan', + ), + ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index 5d87430..a143cfd 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -6,6 +6,14 @@ # are made available under the terms of the Apache License, Version 2.0 # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 +############################################################################################################################################################ +# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. +# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## from django.contrib.auth.models import User @@ -22,684 +30,9 @@ from collections import Counter from account.models import Lab from dashboard.utils import AbstractModelQuery -""" -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) - name = models.CharField(max_length=200, unique=True) - architecture = models.CharField(max_length=50, choices=[ - ("x86_64", "x86_64"), - ("aarch64", "aarch64") - ]) - description = models.TextField() - labs = models.ManyToManyField(Lab, related_name="resourceprofiles") - - def validate(self): - validname = re.compile(r"^[A-Za-z0-9\-\_\.\/\, ]+$") - if not validname.match(self.name): - return "Invalid host profile name given. Name must only use A-Z, a-z, 0-9, hyphens, underscores, dots, commas, or spaces." - else: - return None - - 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(ResourceProfile, on_delete=models.CASCADE, related_name='interfaceprofile') - nic_type = models.CharField( - max_length=50, - choices=[ - ("onboard", "onboard"), - ("pcie", "pcie") - ], - default="onboard" - ) - order = models.IntegerField(default=-1) - - def __str__(self): - return self.name + " for " + str(self.host) - - -class DiskProfile(models.Model): - id = models.AutoField(primary_key=True) - size = models.IntegerField() - media_type = models.CharField(max_length=50, choices=[ - ("SSD", "SSD"), - ("HDD", "HDD") - ]) - name = models.CharField(max_length=50) - host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='storageprofile') - rotation = models.IntegerField(default=0) - interface = models.CharField( - max_length=50, - choices=[ - ("sata", "sata"), - ("sas", "sas"), - ("ssd", "ssd"), - ("nvme", "nvme"), - ("scsi", "scsi"), - ("iscsi", "iscsi"), - ], - default="sata" - ) - - def __str__(self): - return self.name + " for " + str(self.host) - - -class CpuProfile(models.Model): - id = models.AutoField(primary_key=True) - cores = models.IntegerField() - architecture = models.CharField(max_length=50, choices=[ - ("x86_64", "x86_64"), - ("aarch64", "aarch64") - ]) - cpus = models.IntegerField() - host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='cpuprofile') - cflags = models.TextField(null=True, blank=True) - - def __str__(self): - return str(self.architecture) + " " + str(self.cpus) + "S" + str(self.cores) + " C for " + str(self.host) - - -class RamProfile(models.Model): - id = models.AutoField(primary_key=True) - amount = models.IntegerField() - channels = models.IntegerField() - 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 CloudInitFile(models.Model): - text = models.TextField() - - # higher priority is applied later, so "on top" of existing files - priority = models.IntegerField() - generated = models.BooleanField(default=False) - - @classmethod - def merge_strategy(cls): - return [ - {'name': 'list', 'settings': ['append']}, - {'name': 'dict', 'settings': ['recurse_list', 'replace']}, - ] - - @classmethod - def create(cls, text="", priority=0): - return CloudInitFile.objects.create(priority=priority, text=text) - - -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) - 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) - temporary = models.BooleanField(default=False) - copy_of = models.ForeignKey("ResourceTemplate", blank=True, null=True, on_delete=models.SET_NULL) - - # if these fields are empty ("") then they are implicitly "every vlan", - # otherwise we filter any allocations we try to instantiate against this list - # they should be represented as a json list of integers - private_vlan_pool = models.TextField(default="") - public_vlan_pool = models.TextField(default="") - - def private_vlan_pool_set(self): - if self.private_vlan_pool != "": - return set(json.loads(self.private_vlan_pool)) - else: - return None - - def public_vlan_pool_set(self): - if self.private_vlan_pool != "": - return set(json.loads(self.public_vlan_pool)) - else: - return None - - def getConfigs(self): - configs = self.resourceConfigurations.all() - return list(configs) - - def get_required_resources(self): - profiles = Counter([str(config.profile) for config in self.getConfigs()]) - return dict(profiles) - - 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 - - def release(self): - for pn in PhysicalNetwork.objects.filter(bundle=self).all(): - try: - pn.release() - except Exception as e: - print("Exception occurred while trying to release resource ", pn.vlan_id) - print(e) - traceback.print_exc() - - for resource in self.get_resources(): - try: - resource.release() - except Exception as e: - print("Exception occurred while trying to release resource ", resource) - print(e) - traceback.print_exc() - - def get_template_name(self): - if not self.template: - return "" - if not self.template.temporary: - return self.template.name - return self.template.copy_of.name - - -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) - name = models.CharField(max_length=3000, default="opnfv_host") - - cloud_init_files = models.ManyToManyField(CloudInitFile, blank=True) - - def __str__(self): - return str(self.name) - - def ci_file_list(self): - return list(self.cloud_init_files.order_by("priority").all()) - - +# Keep for now until migrations are made, otherwise django will get angry 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, blank=True, null=True) - profile = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE) - config = models.ForeignKey(ResourceConfiguration, on_delete=models.SET_NULL, blank=True, 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. - - Returns the desired configuration for this host as a - JSON object as defined in the rest api spec. - state is a ConfigState - """ - raise NotImplementedError("Must implement in concrete Resource classes") - - def reserve(self): - """Reserve this resource for its currently assigned booking.""" - raise NotImplementedError("Must implement in concrete Resource classes") - - def release(self): - """Make this resource available again for new boookings.""" - raise NotImplementedError("Must implement in concrete Resource classes") - - def get_interfaces(self): - """ - 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) - - -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" - image = self.config.image.lab_id if self.config else "unknown" - - return { - "id": self.labid, - "image": image, - "hostname": self.config.name, - "power": power, - "ipmi_create": str(ipmi) - } - - def get_interfaces(self): - return list(self.interfaces.all().order_by('bus_address')) - - def release(self): - self.bundle = None - 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 - - -def is_serializable(data): - try: - json.dumps(data) - return True - except Exception: - return False - - -class Opsys(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=100) - lab_id = models.CharField(max_length=100) - obsolete = models.BooleanField(default=False) - available = models.BooleanField(default=True) # marked true by Cobbler if it exists there - from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - - indexes = [ - models.Index(fields=['cobbler_id']) - ] - - def new_from_data(data): - opsys = Opsys() - opsys.update(data) - return opsys - - def serialize(self): - d = {} - for field in vars(self): - attr = getattr(self, field) - if is_serializable(attr): - d[field] = attr - return d - - def update(self, data): - for field in vars(self): - if field in data: - setattr(self, field, data[field] if data[field] else getattr(self, field)) - - def __str__(self): - return self.name - - -class Image(models.Model): - """Model for representing OS images / snapshots of hosts.""" - - id = models.AutoField(primary_key=True) - from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE) - architecture = models.CharField(max_length=50, choices=[ - ("x86_64", "x86_64"), - ("aarch64", "aarch64"), - ("unknown", "unknown"), - ]) - lab_id = models.CharField(max_length=100) - name = models.CharField(max_length=100) - owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL) - public = models.BooleanField(default=True) - description = models.TextField() - os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE) - - available = models.BooleanField(default=True) # marked True by cobbler if it exists there - obsolete = models.BooleanField(default=False) - - indexes = [ - models.Index(fields=['architecture']), - models.Index(fields=['cobbler_id']) - ] - - def __str__(self): - return self.name - - def is_obsolete(self): - return self.obsolete or self.os.obsolete - - def serialize(self): - d = {} - for field in vars(self): - attr = getattr(self, field) - if is_serializable(attr): - d[field] = attr - return d - - def update(self, data): - for field in vars(self): - if field in data: - setattr(self, field, data[field] if data[field] else getattr(self, field)) - - def new_from_data(data): - img = Image() - img.update(data) - return img - - 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=200) - bundle = models.ForeignKey(ResourceTemplate, on_delete=models.CASCADE, related_name="networks") - is_public = models.BooleanField() - - def __str__(self): - return self.name - - -class PhysicalNetwork(models.Model): - vlan_id = models.IntegerField() - generic_network = models.ForeignKey(Network, on_delete=models.CASCADE) - bundle = models.ForeignKey(ResourceBundle, null=True, blank=True, on_delete=models.CASCADE) - - def get_configuration(self, state): - """ - Get the network configuration. - - Collects info about each attached network interface and vlan, etc - """ - return {} - - def reserve(self): - """Reserve vlan(s) associated with this network.""" - return False - - def release(self): - from booking.models import Booking - - booking = Booking.objects.get(resource=self.bundle) - lab = booking.lab - vlan_manager = lab.vlan_manager - - if self.generic_network.is_public: - vlan_manager.release_public_vlan(self.vlan_id) - else: - vlan_manager.release_vlans([self.vlan_id]) - return False - - def __str__(self): - return 'Physical Network for ' + self.generic_network.name - - -class NetworkConnection(models.Model): - network = models.ForeignKey(Network, on_delete=models.CASCADE) - vlan_is_tagged = models.BooleanField() - - def __str__(self): - return 'Connection to ' + self.network.name - - -class Vlan(models.Model): - id = models.AutoField(primary_key=True) - vlan_id = models.IntegerField() - tagged = models.BooleanField() - public = models.BooleanField(default=False) - network = models.ForeignKey(PhysicalNetwork, on_delete=models.DO_NOTHING, null=True) - - def __str__(self): - return str(self.vlan_id) + ("_T" if self.tagged else "") - - -class InterfaceConfiguration(models.Model): - id = models.AutoField(primary_key=True) - profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE) - resource_config = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE, related_name='interface_configs') - connections = models.ManyToManyField(NetworkConnection, blank=True) - - def __str__(self): - return "type " + str(self.profile) + " on host " + str(self.resource_config) - - -""" -OPNFV / Software configuration models -""" - - -class Scenario(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=300) - - def __str__(self): - return self.name - - -class Installer(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=200) - sup_scenarios = models.ManyToManyField(Scenario, 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) - - -def create_resource_ref_string(for_hosts: [str]) -> str: - # need to sort the list, then do dump - for_hosts.sort() - - return json.dumps(for_hosts) - - -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) - 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="") - - def __str__(self): - return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario) - - -class OPNFVRole(models.Model): - id = models.AutoField(primary_key=True) - name = models.CharField(max_length=200) - description = models.TextField() - - def __str__(self): - return self.name - + pass def get_sentinal_opnfv_role(): - return OPNFVRole.objects.get_or_create(name="deleted", description="Role was deleted.") - - -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): - id = models.AutoField(primary_key=True) - mac_address = models.CharField(max_length=17) - bus_address = models.CharField(max_length=50) - config = models.ManyToManyField(Vlan) - acts_as = models.OneToOneField(InterfaceConfiguration, blank=True, null=True, on_delete=models.CASCADE) - profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE) - - def __str__(self): - return self.mac_address + " on host " + str(self.profile.host.name) - - def clean(self, *args, **kwargs): - if self.acts_as and self.acts_as.profile != self.profile: - raise ValidationError("Interface Configuration's Interface Profile does not match Interface Profile chosen for Interface.") - super().clean(*args, **kwargs) - - def save(self, *args, **kwargs): - self.full_clean() - super().save(*args, **kwargs) - - -""" -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] + pass diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py deleted file mode 100644 index c4b22fe..0000000 --- a/src/resource_inventory/pdf_templater.py +++ /dev/null @@ -1,176 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -from django.template.loader import render_to_string -import booking -from resource_inventory.models import Server - - -class PDFTemplater: - """Utility class to create a full PDF yaml file.""" - - @classmethod - def makePDF(cls, booking): - """Fill the pod descriptor file template with info about the resource.""" - template = "dashboard/pdf.yaml" - info = {} - info['details'] = cls.get_pdf_details(booking.resource) - try: - info['jumphost'] = cls.get_pdf_jumphost(booking) - except Exception: - # filling in jumphost info can be optional in some cases, this shouldn't be a hard error - info['jumphost'] = {} - info['nodes'] = cls.get_pdf_nodes(booking) - - return render_to_string(template, context=info) - - @classmethod - def get_pdf_details(cls, resource): - """Info for the "details" section.""" - details = {} - owner = "Anon" - email = "email@mail.com" - resource_lab = resource.template.lab - lab = resource_lab.name - location = resource_lab.location - pod_type = "development" - link = resource_lab.lab_info_link - - try: - # try to get more specific info that may fail, we dont care if it does - booking_owner = booking.models.Booking.objects.get(resource=resource).owner - owner = booking_owner.username - email = booking_owner.userprofile.email_addr - except Exception: - pass - - details['contact'] = email - details['lab'] = lab - details['link'] = link - details['owner'] = owner - details['location'] = location - details['type'] = pod_type - - return details - - @classmethod - def get_jumphost(cls, booking): - """Return the host designated as the Jumphost for the booking.""" - jumphost = None - if booking.opnfv_config: - jumphost_opnfv_config = booking.opnfv_config.host_opnfv_config.get( - role__name__iexact="jumphost" - ) - jumphost = booking.resource.hosts.get(config=jumphost_opnfv_config.host_config) - else: # if there is no opnfv config, use headnode - jumphost = Server.objects.filter( - bundle=booking.resource, - config__is_head_node=True - ).first() - - return jumphost - - @classmethod - def get_pdf_jumphost(cls, booking): - """Return a dict of all the info for the "jumphost" section.""" - jumphost = cls.get_jumphost(booking) - jumphost_info = cls.get_pdf_host(jumphost) - jumphost_info['os'] = jumphost.config.image.os.name - return jumphost_info - - @classmethod - def get_pdf_nodes(cls, booking): - """Return a list of all the "nodes" (every host except jumphost).""" - pdf_nodes = [] - nodes = set(Server.objects.filter(bundle=booking.resource)) - nodes.discard(cls.get_jumphost(booking)) - - for node in nodes: - pdf_nodes.append(cls.get_pdf_host(node)) - - return pdf_nodes - - @classmethod - def get_pdf_host(cls, host): - """ - Gather all needed info about a host. - - returns a dictionary - """ - host_info = {} - host_info['name'] = host.name - host_info['node'] = cls.get_pdf_host_node(host) - host_info['disks'] = [] - for disk in host.profile.storageprofile.all(): - host_info['disks'].append(cls.get_pdf_host_disk(disk)) - - host_info['interfaces'] = [] - for interface in host.interfaces.all(): - host_info['interfaces'].append(cls.get_pdf_host_iface(interface)) - - host_info['remote'] = cls.get_pdf_host_remote_management(host) - - return host_info - - @classmethod - def get_pdf_host_node(cls, host): - """Return "node" info for a given host.""" - d = {} - d['type'] = "baremetal" - d['vendor'] = host.vendor - d['model'] = host.model - d['memory'] = str(host.profile.ramprofile.first().amount) + "G" - - cpu = host.profile.cpuprofile.first() - d['arch'] = cpu.architecture - d['cpus'] = cpu.cpus - d['cores'] = cpu.cores - cflags = cpu.cflags - if cflags and cflags.strip(): - d['cpu_cflags'] = cflags - else: - d['cpu_cflags'] = "none" - - return d - - @classmethod - def get_pdf_host_disk(cls, disk): - """Return a dict describing the given disk.""" - disk_info = {} - disk_info['name'] = disk.name - disk_info['capacity'] = str(disk.size) + "G" - disk_info['type'] = disk.media_type - disk_info['interface'] = disk.interface - disk_info['rotation'] = disk.rotation - return disk_info - - @classmethod - def get_pdf_host_iface(cls, interface): - """Return a dict describing given interface.""" - iface_info = {} - iface_info['features'] = "none" - iface_info['mac_address'] = interface.mac_address - iface_info['name'] = interface.profile.name - speed = str(int(interface.profile.speed / 1000)) + "gb" - iface_info['speed'] = speed - return iface_info - - @classmethod - def get_pdf_host_remote_management(cls, host): - """Get the remote params of the host.""" - man = host.remote_management - mgmt = {} - mgmt['address'] = man.address - mgmt['mac_address'] = man.mac_address - mgmt['pass'] = man.password - mgmt['type'] = man.management_type - mgmt['user'] = man.user - mgmt['versions'] = [man.versions] - return mgmt diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py deleted file mode 100644 index 16c106e..0000000 --- a/src/resource_inventory/resource_manager.py +++ /dev/null @@ -1,197 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from __future__ import annotations # noqa: F407 - -import re -from typing import Optional -from django.db.models import Q - -from dashboard.exceptions import ResourceAvailabilityException - -from resource_inventory.models import ( - Resource, - ResourceBundle, - ResourceTemplate, - ResourceConfiguration, - Network, - Vlan, - PhysicalNetwork, - InterfaceConfiguration, -) - -from account.models import Lab -from django.contrib.auth.models import User - - -class ResourceManager: - - instance = None - - def __init__(self): - pass - - @staticmethod - def getInstance() -> ResourceManager: - if ResourceManager.instance is None: - ResourceManager.instance = ResourceManager() - return ResourceManager.instance - - def getAvailableResourceTemplates(self, lab: Lab, user: Optional[User] = None) -> list[ResourceTemplate]: - filter = Q(public=True) - if user: - filter = filter | Q(owner=user) - filter = filter & Q(temporary=False) & Q(lab=lab) - return ResourceTemplate.objects.filter(filter) - - def templateIsReservable(self, resource_template: ResourceTemplate): - """ - Check if the required resources to reserve this template is available. - - No changes to the database - """ - # count up hosts - profile_count = {} - 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 = len(profile.get_resources(lab=resource_template.lab, unreserved=True)) - needed = profile_count[profile] - if available < needed: - return False - return True - - # public interface - def deleteResourceBundle(self, resourceBundle: ResourceBundle): - raise NotImplementedError("Resource Bundle Deletion Not Implemented") - - def releaseResourceBundle(self, resourceBundle: ResourceBundle): - resourceBundle.release() - - def get_vlans(self, resourceTemplate: ResourceTemplate) -> dict[str, int]: - """ - returns: dict from network name to the associated vlan number (backend vlan id) - """ - networks = {} - vlan_manager = resourceTemplate.lab.vlan_manager - for network in resourceTemplate.networks.all(): - if network.is_public: - # already throws if can't get requested count, so can always expect public_net to be Some - public_net = vlan_manager.get_public_vlan(within=resourceTemplate.public_vlan_pool_set()) - vlan_manager.reserve_public_vlan(public_net.vlan) - networks[network.name] = public_net.vlan - else: - # already throws if can't get requested count, so can always index in @ 0 - vlans = vlan_manager.get_vlans(count=1, within=resourceTemplate.private_vlan_pool_set()) - vlan_manager.reserve_vlans(vlans[0]) - networks[network.name] = vlans[0] - return networks - - def instantiateTemplate(self, resource_template: ResourceTemplate): - """ - Convert a ResourceTemplate into a ResourceBundle. - - Takes in a ResourceTemplate and reserves all the - Resources needed and returns a completed ResourceBundle. - """ - resource_bundle = ResourceBundle.objects.create(template=resource_template) - res_configs = resource_template.getConfigs() - resources = [] - - vlan_map = self.get_vlans(resource_template) - - for config in res_configs: - try: - phys_res = self.acquireHost(config) - phys_res.bundle = resource_bundle - phys_res.config = config - resources.append(phys_res) - - self.configureNetworking(resource_bundle, phys_res, vlan_map) - phys_res.save() - - except Exception as e: - self.fail_acquire(resources, vlan_map, resource_template) - raise e - - return resource_bundle - - def configureNetworking(self, resource_bundle: ResourceBundle, resource: Resource, vlan_map: dict[str, int]): - """ - @vlan_map: dict from network name to the associated vlan number (backend vlan id) - """ - for physical_interface in resource.interfaces.all(): - - # assign interface configs - iface_config = InterfaceConfiguration.objects.get( - profile=physical_interface.profile, - resource_config=resource.config - ) - - physical_interface.acts_as = iface_config - physical_interface.acts_as.save() - - physical_interface.config.clear() - for connection in iface_config.connections.all(): - physicalNetwork = PhysicalNetwork.objects.create( - vlan_id=vlan_map[connection.network.name], - generic_network=connection.network, - bundle=resource_bundle, - ) - physical_interface.config.add( - Vlan.objects.create( - vlan_id=vlan_map[connection.network.name], - tagged=connection.vlan_is_tagged, - public=connection.network.is_public, - network=physicalNetwork - ) - ) - - # private interface - def acquireHost(self, resource_config: ResourceConfiguration) -> Resource: - resources = resource_config.profile.get_resources( - lab=resource_config.template.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=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, template): - self.releaseNetworks(template, vlans) - for host in hosts: - host.release() - - -class HostNameValidator(object): - regex = r'^[A-Za-z0-9][A-Za-z0-9-]*$' - message = "Hostnames can only contain alphanumeric characters and hyphens (-). Hostnames must start with a letter" - pattern = re.compile(regex) - - @classmethod - def is_valid_hostname(cls, hostname): - return len(hostname) < 65 and cls.pattern.fullmatch(hostname) is not None diff --git a/src/resource_inventory/tests/test_managers.py b/src/resource_inventory/tests/test_managers.py deleted file mode 100644 index 46cee5a..0000000 --- a/src/resource_inventory/tests/test_managers.py +++ /dev/null @@ -1,301 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from django.test import TestCase -from django.contrib.auth.models import User - -from resource.inventory_manager import InventoryManager -from resource.resource_manager import ResourceManager, HostNameValidator -from account.models import Lab -from resource.models import ( - Host, - Vlan, - Interface, - ResourceBundle, - GenericHost, - GenericResourceBundle, - CpuProfile, - RamProfile, - DiskProfile, - HostProfile, - InterfaceProfile -) - - -class InventoryManagerTestCase(TestCase): - - def test_singleton(self): - instance = InventoryManager.getInstance() - self.assertTrue(isinstance(instance, InventoryManager)) - self.assertTrue(instance is InventoryManager.getInstance()) - - def setUp(self): - # setup - # create lab and give it resources - user = User.objects.create(username="username") - self.lab = Lab.objects.create( - lab_user=user, - name='test lab', - contact_email='someone@email.com', - contact_phone='dont call me' - ) - - # create hostProfile - hostProfile = HostProfile.objects.create( - host_type=0, - name='Test profile', - description='a test profile' - ) - InterfaceProfile.objects.create( - speed=1000, - name='eno3', - host=hostProfile - ) - DiskProfile.objects.create( - size=1000, - media_type="SSD", - name='/dev/sda', - host=hostProfile - ) - CpuProfile.objects.create( - cores=96, - architecture="x86_64", - cpus=2, - host=hostProfile - ) - RamProfile.objects.create( - amount=256, - channels=4, - host=hostProfile - ) - - # create GenericResourceBundle - genericBundle = GenericResourceBundle.objects.create() - - self.gHost1 = GenericHost.objects.create( - bundle=genericBundle, - name='generic host 1', - profile=hostProfile - ) - self.gHost2 = GenericHost.objects.create( - bundle=genericBundle, - name='generic host 2', - profile=hostProfile - ) - - # actual resource bundle - bundle = ResourceBundle.objects.create(template=genericBundle) - - self.host1 = Host.objects.create( - template=self.gHost1, - booked=True, - name='host1', - bundle=bundle, - profile=hostProfile, - lab=self.lab - ) - - self.host2 = Host.objects.create( - template=self.gHost2, - booked=True, - name='host2', - bundle=bundle, - profile=hostProfile, - lab=self.lab - ) - - vlan1 = Vlan.objects.create(vlan_id=300, tagged=False) - vlan2 = Vlan.objects.create(vlan_id=300, tagged=False) - - Interface.objects.create( - mac_address='00:11:22:33:44:55', - bus_address='some bus address', - switch_name='switch1', - port_name='port10', - config=vlan1, - host=self.host1 - ) - Interface.objects.create( - mac_address='00:11:22:33:44:56', - bus_address='some bus address', - switch_name='switch1', - port_name='port12', - config=vlan2, - host=self.host2 - ) - - def test_acquire_host(self): - host = InventoryManager.getInstance().acquireHost(self.gHost1, self.lab.name) - self.assertNotEquals(host, None) - self.assertTrue(host.booked) - self.assertEqual(host.template, self.gHost1) - - def test_release_host(self): - host = InventoryManager.getInstance().acquireHost(self.gHost1, self.lab.name) - self.assertTrue(host.booked) - InventoryManager.getInstance().releaseHost(host) - self.assertFalse(host.booked) - - -class ResourceManagerTestCase(TestCase): - def test_singleton(self): - instance = ResourceManager.getInstance() - self.assertTrue(isinstance(instance, ResourceManager)) - self.assertTrue(instance is ResourceManager.getInstance()) - - def setUp(self): - # setup - # create lab and give it resources - user = User.objects.create(username="username") - self.lab = Lab.objects.create( - lab_user=user, - name='test lab', - contact_email='someone@email.com', - contact_phone='dont call me' - ) - - # create hostProfile - hostProfile = HostProfile.objects.create( - host_type=0, - name='Test profile', - description='a test profile' - ) - InterfaceProfile.objects.create( - speed=1000, - name='eno3', - host=hostProfile - ) - DiskProfile.objects.create( - size=1000, - media_type="SSD", - name='/dev/sda', - host=hostProfile - ) - CpuProfile.objects.create( - cores=96, - architecture="x86_64", - cpus=2, - host=hostProfile - ) - RamProfile.objects.create( - amount=256, - channels=4, - host=hostProfile - ) - - # create GenericResourceBundle - genericBundle = GenericResourceBundle.objects.create() - - self.gHost1 = GenericHost.objects.create( - bundle=genericBundle, - name='generic host 1', - profile=hostProfile - ) - self.gHost2 = GenericHost.objects.create( - bundle=genericBundle, - name='generic host 2', - profile=hostProfile - ) - - # actual resource bundle - bundle = ResourceBundle.objects.create(template=genericBundle) - - self.host1 = Host.objects.create( - template=self.gHost1, - booked=True, - name='host1', - bundle=bundle, - profile=hostProfile, - lab=self.lab - ) - - self.host2 = Host.objects.create( - template=self.gHost2, - booked=True, - name='host2', - bundle=bundle, - profile=hostProfile, - lab=self.lab - ) - - vlan1 = Vlan.objects.create(vlan_id=300, tagged=False) - vlan2 = Vlan.objects.create(vlan_id=300, tagged=False) - - Interface.objects.create( - mac_address='00:11:22:33:44:55', - bus_address='some bus address', - switch_name='switch1', - port_name='port10', - config=vlan1, - host=self.host1 - ) - Interface.objects.create( - mac_address='00:11:22:33:44:56', - bus_address='some bus address', - switch_name='switch1', - port_name='port12', - config=vlan2, - host=self.host2 - ) - - def test_convert_bundle(self): - ResourceManager.getInstance().convertResoureBundle(self.genericBundle, self.lab.name) - # verify bundle configuration - - -class HostNameValidatorTestCase(TestCase): - - def test_valid_hostnames(self): - self.assertTrue(HostNameValidator.is_valid_hostname("localhost")) - self.assertTrue(HostNameValidator.is_valid_hostname("Localhost")) - self.assertTrue(HostNameValidator.is_valid_hostname("localHost")) - self.assertTrue(HostNameValidator.is_valid_hostname("LOCALHOST")) - self.assertTrue(HostNameValidator.is_valid_hostname("f")) - self.assertTrue(HostNameValidator.is_valid_hostname("abc123doreyme")) - self.assertTrue(HostNameValidator.is_valid_hostname("F9999999")) - self.assertTrue(HostNameValidator.is_valid_hostname("my-host")) - self.assertTrue(HostNameValidator.is_valid_hostname("My-Host")) - self.assertTrue(HostNameValidator.is_valid_hostname("MY-HOST")) - self.assertTrue(HostNameValidator.is_valid_hostname("a-long-name-for-my-host")) - - def test_invalid_hostnames(self): - self.assertFalse(HostNameValidator.is_valid_hostname("-long-name-for-my-host")) - self.assertFalse(HostNameValidator.is_valid_hostname("546")) - self.assertFalse(HostNameValidator.is_valid_hostname("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")) - - def test_invalid_chars(self): - self.assertFalse(HostNameValidator.is_valid_hostname("contains!char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains@char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains#char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains$char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains%char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains^char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains&char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains*char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains(char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains)char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains_char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains=char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains+char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains|char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains\\char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains[char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains]char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains;char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains:char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains'char")) - self.assertFalse(HostNameValidator.is_valid_hostname('contains"char')) - self.assertFalse(HostNameValidator.is_valid_hostname("contains'char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains<char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains>char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains,char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains?char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains/char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains`char")) - self.assertFalse(HostNameValidator.is_valid_hostname("contains~char")) diff --git a/src/resource_inventory/tests/test_models.py b/src/resource_inventory/tests/test_models.py deleted file mode 100644 index 3f2d1d8..0000000 --- a/src/resource_inventory/tests/test_models.py +++ /dev/null @@ -1,173 +0,0 @@ -############################################################################## -# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## -from django.test import TestCase -from django.contrib.auth.models import User -from account.models import Lab -from resource_inventory.models import ( - Scenario, - Installer, - Opsys, - ConfigBundle, - OPNFVConfig, - OPNFVRole, - Image, - HostProfile, - GenericResourceBundle, - GenericResource, - GenericHost, - HostConfiguration -) - - -class ConfigUtil(): - count = 0 - - @staticmethod - def makeScenario(): - return Scenario.objects.create(name="testScenario") - - @staticmethod - def makeInstaller(): - inst = Installer.objects.create(name="testInstaller") - inst.sup_scenarios = [ConfigUtil.makeScenario()] - return inst - - @staticmethod - def makeOpsys(): - os = Opsys.objects.create(name="test Operating System") - os.sup_installers = [ConfigUtil.makeInstaller()] - return os - - @staticmethod - def makeConfigBundle(): - user = User.objects.create(username="test_user" + str(ConfigUtil.count)) - ConfigUtil.count += 1 - return ConfigBundle.objects.create(owner=user) - - @staticmethod - def makeOPNFVConfig(): - installer = ConfigUtil.makeInstaller() - scenario = ConfigUtil.makeScenario() - bundle = ConfigUtil.makeConfigBundle() - return OPNFVConfig.objects.create( - installer=installer, - scenario=scenario, - bundle=bundle - ) - - @staticmethod - def makeOPNFVRole(): - return OPNFVRole.objects.create( - name="Test role", - description="This is a test role" - ) - - @staticmethod - def makeImage(): - owner = User.objects.create(username="another test user") - lab_user = User.objects.create(username="labUserForTests") - lab = Lab.objects.create( - lab_user=lab_user, - name="this is lab for testing", - contact_email="email@mail.com", - contact_phone="123-4567" - ) - - return Image.objects.create( - cobbler_id="profile1", - from_lab=lab, - name="an image for testing", - owner=owner - ) - - @staticmethod - def makeGenericHost(): - profile = HostProfile.objects.create( - host_type=0, - name="test lab for config bundle", - description="this is a test profile" - ) - user = User.objects.create(username="test sample user 12") - bundle = GenericResourceBundle.objects.create( - name="Generic bundle for config tests", - xml="", - owner=user, - description="" - ) - - resource = GenericResource.objects.create( - bundle=bundle, - name="a test generic resource" - ) - - return GenericHost.objects.create( - profile=profile, - resource=resource - ) - - @staticmethod - def makeHostConfiguration(): - host = ConfigUtil.makeGenericHost() - image = ConfigUtil.makeImage() - bundle = ConfigUtil.makeConfigBundle() - opnfvRole = ConfigUtil.makeOPNFVRole() - return HostConfiguration.objects.create( - host=host, - image=image, - bundle=bundle, - opnfvRole=opnfvRole - ) - - -class ScenarioTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeScenario()) - - -class InstallerTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeInstaller()) - - -class OperatingSystemTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeOpsys()) - - -class ConfigBundleTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeConfigBundle()) - - -class OPNFVConfigTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeOPNFVConfig()) - - -class OPNFVRoleTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeOPNFVRole()) - - -class HostConfigurationTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeHostConfiguration()) - - -class ImageTestCase(TestCase): - - def test_save(self): - self.assertTrue(ConfigUtil.makeImage()) diff --git a/src/resource_inventory/urls.py b/src/resource_inventory/urls.py index a9a4d43..f9bd07e 100644 --- a/src/resource_inventory/urls.py +++ b/src/resource_inventory/urls.py @@ -7,30 +7,3 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - - -""" -laas_dashboard URL Configuration. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from resource_inventory.views import HostView, hostprofile_detail_view - - -app_name = 'resource' -urlpatterns = [ - url(r'^hosts$', HostView.as_view(), name='hosts'), - url(r'^profiles/(?P<hostprofile_id>.+)/$', hostprofile_detail_view, name='host_detail'), -] diff --git a/src/resource_inventory/views.py b/src/resource_inventory/views.py index 52f8c75..f903394 100644 --- a/src/resource_inventory/views.py +++ b/src/resource_inventory/views.py @@ -6,33 +6,3 @@ # which accompanies this distribution, and is available at # http://www.apache.org/licenses/LICENSE-2.0 ############################################################################## - - -from django.views.generic import TemplateView -from django.shortcuts import get_object_or_404 -from django.shortcuts import render - -from resource_inventory.models import ResourceProfile, ResourceQuery - - -class HostView(TemplateView): - template_name = "resource/hosts.html" - - def get_context_data(self, **kwargs): - context = super(HostView, self).get_context_data(**kwargs) - hosts = ResourceQuery.filter(working=True) - context.update({'hosts': hosts, 'title': "Hardware Resources"}) - return context - - -def hostprofile_detail_view(request, hostprofile_id): - hostprofile = get_object_or_404(ResourceProfile, id=hostprofile_id) - - return render( - request, - "resource/hostprofile_detail.html", - { - 'title': "Host Type: " + str(hostprofile.name), - 'hostprofile': hostprofile - } - ) |