diff options
Diffstat (limited to 'src/resource_inventory')
22 files changed, 2123 insertions, 0 deletions
diff --git a/src/resource_inventory/__init__.py b/src/resource_inventory/__init__.py new file mode 100644 index 0000000..f903394 --- /dev/null +++ b/src/resource_inventory/__init__.py @@ -0,0 +1,8 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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 +############################################################################## diff --git a/src/resource_inventory/admin.py b/src/resource_inventory/admin.py new file mode 100644 index 0000000..7ff510b --- /dev/null +++ b/src/resource_inventory/admin.py @@ -0,0 +1,53 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt 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.contrib import admin + +from resource_inventory.models import ( + HostProfile, + InterfaceProfile, + DiskProfile, + CpuProfile, + RamProfile, + GenericResourceBundle, + GenericResource, + GenericHost, + GenericInterface, + Host, + Interface, + Network, + Vlan, + ResourceBundle, + Scenario, + Installer, + Opsys, + ConfigBundle, + OPNFVConfig, + OPNFVRole, + Image, + HostConfiguration, + RemoteInfo +) + +profiles = [HostProfile, InterfaceProfile, DiskProfile, CpuProfile, RamProfile] + +admin.site.register(profiles) + +generics = [GenericResourceBundle, GenericResource, GenericHost, GenericInterface] + +admin.site.register(generics) + +physical = [Host, Interface, Network, Vlan, ResourceBundle] + +admin.site.register(physical) + +config = [Scenario, Installer, Opsys, ConfigBundle, OPNFVConfig, OPNFVRole, Image, HostConfiguration, RemoteInfo] + +admin.site.register(config) diff --git a/src/resource_inventory/apps.py b/src/resource_inventory/apps.py new file mode 100644 index 0000000..79768a7 --- /dev/null +++ b/src/resource_inventory/apps.py @@ -0,0 +1,14 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt 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.apps import AppConfig + + +class ResourcesConfig(AppConfig): + name = 'hwresource' diff --git a/src/resource_inventory/idf_templater.py b/src/resource_inventory/idf_templater.py new file mode 100644 index 0000000..bf6eda0 --- /dev/null +++ b/src/resource_inventory/idf_templater.py @@ -0,0 +1,151 @@ +############################################################################## +# 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 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): + """ + fills the installer descriptor file 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/0001_initial.py b/src/resource_inventory/migrations/0001_initial.py new file mode 100644 index 0000000..d01e8e7 --- /dev/null +++ b/src/resource_inventory/migrations/0001_initial.py @@ -0,0 +1,328 @@ +# Generated by Django 2.1 on 2018-09-14 14:48 + +from django.conf import settings +import django.core.validators +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('account', '0001_initial'), + ] + + operations = [ + migrations.CreateModel( + name='ConfigBundle', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=200, unique=True)), + ('description', models.CharField(default='', max_length=1000)), + ], + ), + migrations.CreateModel( + name='CpuProfile', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('cores', models.IntegerField()), + ('architecture', models.CharField(choices=[('x86_64', 'x86_64'), ('aarch64', 'aarch64')], max_length=50)), + ('cpus', models.IntegerField()), + ('cflags', models.TextField(null=True)), + ], + ), + migrations.CreateModel( + name='DiskProfile', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('size', models.IntegerField()), + ('media_type', models.CharField(choices=[('SSD', 'SSD'), ('HDD', 'HDD')], max_length=50)), + ('name', models.CharField(max_length=50)), + ('rotation', models.IntegerField(default=0)), + ('interface', models.CharField(choices=[('sata', 'sata'), ('sas', 'sas'), ('ssd', 'ssd'), ('nvme', 'nvme'), ('scsi', 'scsi'), ('iscsi', 'iscsi')], default='sata', max_length=50)), + ], + ), + migrations.CreateModel( + name='GenericHost', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ], + ), + migrations.CreateModel( + name='GenericInterface', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='generic_interfaces', to='resource_inventory.GenericHost')), + ], + ), + migrations.CreateModel( + name='GenericResource', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=200, validators=[django.core.validators.RegexValidator(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 (_)', regex='(?=^.{1,253}$)(?=(^([A-Za-z0-9\\-\\_]{1,62}\\.)*[A-Za-z0-9\\-\\_]{1,63}$))')])), + ], + ), + migrations.CreateModel( + name='GenericResourceBundle', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=300, unique=True)), + ('xml', models.TextField()), + ('description', models.CharField(default='', max_length=1000)), + ('lab', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='account.Lab')), + ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Host', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('booked', models.BooleanField(default=False)), + ('name', models.CharField(max_length=200, unique=True)), + ('labid', models.CharField(default='default_id', max_length=200)), + ('working', models.BooleanField(default=True)), + ('vendor', models.CharField(default='unknown', max_length=100)), + ('model', models.CharField(default='unknown', max_length=150)), + ], + ), + migrations.CreateModel( + name='HostConfiguration', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('bundle', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='hostConfigurations', to='resource_inventory.ConfigBundle')), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='configuration', to='resource_inventory.GenericHost')), + ], + ), + migrations.CreateModel( + name='HostProfile', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('host_type', models.PositiveSmallIntegerField()), + ('name', models.CharField(max_length=200, unique=True)), + ('description', models.TextField()), + ('labs', models.ManyToManyField(related_name='hostprofiles', to='account.Lab')), + ], + ), + migrations.CreateModel( + name='Image', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('lab_id', models.IntegerField()), + ('name', models.CharField(max_length=200)), + ('public', models.BooleanField(default=True)), + ('description', models.TextField()), + ('from_lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), + ('host_type', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.HostProfile')), + ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), + ], + ), + migrations.CreateModel( + name='Installer', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=200)), + ], + ), + migrations.CreateModel( + name='Interface', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('mac_address', models.CharField(max_length=17)), + ('bus_address', models.CharField(max_length=50)), + ('name', models.CharField(default='eth0', max_length=100)), + ], + ), + migrations.CreateModel( + name='InterfaceProfile', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('speed', models.IntegerField()), + ('name', models.CharField(max_length=100)), + ('nic_type', models.CharField(choices=[('onboard', 'onboard'), ('pcie', 'pcie')], default='onboard', max_length=50)), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='interfaceprofile', to='resource_inventory.HostProfile')), + ], + ), + migrations.CreateModel( + name='Network', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('vlan_id', models.IntegerField()), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.CreateModel( + name='OPNFVConfig', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('bundle', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='opnfv_config', to='resource_inventory.ConfigBundle')), + ('installer', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Installer')), + ], + ), + migrations.CreateModel( + name='OPNFVRole', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=200)), + ('description', models.TextField()), + ], + ), + migrations.CreateModel( + name='Opsys', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=100)), + ('sup_installers', models.ManyToManyField(blank=True, to='resource_inventory.Installer')), + ], + ), + migrations.CreateModel( + name='RamProfile', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('amount', models.IntegerField()), + ('channels', models.IntegerField()), + ('host', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='ramprofile', to='resource_inventory.HostProfile')), + ], + ), + migrations.CreateModel( + name='ResourceBundle', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('template', models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.GenericResourceBundle')), + ], + ), + migrations.CreateModel( + name='Scenario', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('name', models.CharField(max_length=300)), + ], + ), + migrations.CreateModel( + name='Vlan', + fields=[ + ('id', models.AutoField(primary_key=True, serialize=False)), + ('vlan_id', models.IntegerField()), + ('tagged', models.BooleanField()), + ], + ), + migrations.CreateModel( + name='GenericPod', + fields=[ + ('genericresource_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='resource_inventory.GenericResource')), + ], + bases=('resource_inventory.genericresource',), + ), + migrations.AddField( + model_name='opnfvconfig', + name='scenario', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Scenario'), + ), + migrations.AddField( + model_name='interface', + name='config', + field=models.ManyToManyField(to='resource_inventory.Vlan'), + ), + migrations.AddField( + model_name='interface', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaces', to='resource_inventory.Host'), + ), + migrations.AddField( + model_name='installer', + name='sup_scenarios', + field=models.ManyToManyField(blank=True, to='resource_inventory.Scenario'), + ), + migrations.AddField( + model_name='hostconfiguration', + name='image', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='resource_inventory.Image'), + ), + migrations.AddField( + model_name='hostconfiguration', + name='opnfvRole', + field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='resource_inventory.OPNFVRole'), + ), + migrations.AddField( + model_name='host', + name='bundle', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='hosts', to='resource_inventory.ResourceBundle'), + ), + migrations.AddField( + model_name='host', + name='config', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='configuration', to='resource_inventory.HostConfiguration'), + ), + migrations.AddField( + model_name='host', + name='lab', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab'), + ), + migrations.AddField( + model_name='host', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.HostProfile'), + ), + migrations.AddField( + model_name='host', + name='template', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.GenericHost'), + ), + migrations.AddField( + model_name='genericresource', + name='bundle', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='generic_resources', to='resource_inventory.GenericResourceBundle'), + ), + migrations.AddField( + model_name='genericinterface', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.InterfaceProfile'), + ), + migrations.AddField( + model_name='genericinterface', + name='vlans', + field=models.ManyToManyField(to='resource_inventory.Vlan'), + ), + migrations.AddField( + model_name='generichost', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.HostProfile'), + ), + migrations.AddField( + model_name='generichost', + name='resource', + field=models.OneToOneField(on_delete=django.db.models.deletion.DO_NOTHING, related_name='generic_host', to='resource_inventory.GenericResource'), + ), + migrations.AddField( + model_name='diskprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='storageprofile', to='resource_inventory.HostProfile'), + ), + migrations.AddField( + model_name='cpuprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING, related_name='cpuprofile', to='resource_inventory.HostProfile'), + ), + migrations.AddField( + model_name='configbundle', + name='bundle', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.GenericResourceBundle'), + ), + migrations.AddField( + model_name='configbundle', + name='owner', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL), + ), + migrations.AddField( + model_name='genericpod', + name='hosts', + field=models.ManyToManyField(to='resource_inventory.GenericHost'), + ), + migrations.AddField( + model_name='genericpod', + name='networks', + field=models.ManyToManyField(to='resource_inventory.Network'), + ), + ] diff --git a/src/resource_inventory/migrations/0002_auto_20180919_1459.py b/src/resource_inventory/migrations/0002_auto_20180919_1459.py new file mode 100644 index 0000000..80c9e6f --- /dev/null +++ b/src/resource_inventory/migrations/0002_auto_20180919_1459.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1 on 2018-09-19 14:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0001_initial'), + ] + + operations = [ + migrations.AlterField( + model_name='hostprofile', + name='host_type', + field=models.PositiveSmallIntegerField(default=0), + ), + ] diff --git a/src/resource_inventory/migrations/0003_vlan_public.py b/src/resource_inventory/migrations/0003_vlan_public.py new file mode 100644 index 0000000..07dc647 --- /dev/null +++ b/src/resource_inventory/migrations/0003_vlan_public.py @@ -0,0 +1,18 @@ +# Generated by Django 2.1 on 2018-09-26 14:41 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0002_auto_20180919_1459'), + ] + + operations = [ + migrations.AddField( + model_name='vlan', + name='public', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/resource_inventory/migrations/0004_auto_20181017_1532.py b/src/resource_inventory/migrations/0004_auto_20181017_1532.py new file mode 100644 index 0000000..3a7475c --- /dev/null +++ b/src/resource_inventory/migrations/0004_auto_20181017_1532.py @@ -0,0 +1,28 @@ +# Generated by Django 2.1 on 2018-10-17 15:32 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0003_vlan_public'), + ] + + operations = [ + migrations.RemoveField( + model_name='genericpod', + name='genericresource_ptr', + ), + migrations.RemoveField( + model_name='genericpod', + name='hosts', + ), + migrations.RemoveField( + model_name='genericpod', + name='networks', + ), + migrations.DeleteModel( + name='GenericPod', + ), + ] diff --git a/src/resource_inventory/migrations/0005_image_os.py b/src/resource_inventory/migrations/0005_image_os.py new file mode 100644 index 0000000..ede008e --- /dev/null +++ b/src/resource_inventory/migrations/0005_image_os.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1 on 2019-01-10 16:18 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0004_auto_20181017_1532'), + ] + + operations = [ + migrations.AddField( + model_name='image', + name='os', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Opsys'), + ), + ] diff --git a/src/resource_inventory/migrations/0006_auto_20190124_1700.py b/src/resource_inventory/migrations/0006_auto_20190124_1700.py new file mode 100644 index 0000000..a5a972f --- /dev/null +++ b/src/resource_inventory/migrations/0006_auto_20190124_1700.py @@ -0,0 +1,76 @@ +# Generated by Django 2.1 on 2019-01-24 17:00 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion +import resource_inventory.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0005_image_os'), + ] + + operations = [ + migrations.AlterField( + model_name='cpuprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cpuprofile', to='resource_inventory.HostProfile'), + ), + migrations.AlterField( + model_name='diskprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='storageprofile', to='resource_inventory.HostProfile'), + ), + migrations.AlterField( + model_name='generichost', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.HostProfile'), + ), + migrations.AlterField( + model_name='generichost', + name='resource', + field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, related_name='generic_host', to='resource_inventory.GenericResource'), + ), + migrations.AlterField( + model_name='genericinterface', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='generic_interfaces', to='resource_inventory.GenericHost'), + ), + migrations.AlterField( + model_name='genericresource', + name='bundle', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='generic_resources', to='resource_inventory.GenericResourceBundle'), + ), + migrations.AlterField( + model_name='genericresourcebundle', + name='lab', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='account.Lab'), + ), + migrations.AlterField( + model_name='genericresourcebundle', + name='owner', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL), + ), + migrations.AlterField( + model_name='hostconfiguration', + name='opnfvRole', + field=models.ForeignKey(on_delete=models.SET(resource_inventory.models.get_sentinal_opnfv_role), to='resource_inventory.OPNFVRole'), + ), + migrations.AlterField( + model_name='interfaceprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaceprofile', to='resource_inventory.HostProfile'), + ), + migrations.AlterField( + model_name='ramprofile', + name='host', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ramprofile', to='resource_inventory.HostProfile'), + ), + migrations.AlterField( + model_name='resourcebundle', + name='template', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.GenericResourceBundle'), + ), + ] diff --git a/src/resource_inventory/migrations/0007_auto_20190306_1616.py b/src/resource_inventory/migrations/0007_auto_20190306_1616.py new file mode 100644 index 0000000..19a49c5 --- /dev/null +++ b/src/resource_inventory/migrations/0007_auto_20190306_1616.py @@ -0,0 +1,31 @@ +# Generated by Django 2.1 on 2019-03-06 16:16 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0006_auto_20190124_1700'), + ] + + operations = [ + migrations.CreateModel( + name='RemoteInfo', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('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(default='ipmi', max_length=50)), + ('versions', models.CharField(max_length=100)), + ], + ), + migrations.AlterField( + model_name='genericinterface', + name='profile', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.InterfaceProfile'), + ), + ] diff --git a/src/resource_inventory/migrations/0008_host_remote_management.py b/src/resource_inventory/migrations/0008_host_remote_management.py new file mode 100644 index 0000000..f74a535 --- /dev/null +++ b/src/resource_inventory/migrations/0008_host_remote_management.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1 on 2019-03-06 16:42 + +from django.db import migrations, models +import resource_inventory.models + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0007_auto_20190306_1616'), + ] + + operations = [ + migrations.AddField( + model_name='host', + name='remote_management', + field=models.ForeignKey(default=resource_inventory.models.get_default_remote_info, on_delete=models.SET(resource_inventory.models.get_default_remote_info), to='resource_inventory.RemoteInfo'), + ), + ] diff --git a/src/resource_inventory/migrations/0009_auto_20190315_1757.py b/src/resource_inventory/migrations/0009_auto_20190315_1757.py new file mode 100644 index 0000000..92ed0e9 --- /dev/null +++ b/src/resource_inventory/migrations/0009_auto_20190315_1757.py @@ -0,0 +1,73 @@ +# Generated by Django 2.1 on 2019-03-15 17:57 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0008_host_remote_management'), + ] + + operations = [ + migrations.CreateModel( + name='NetworkConnection', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlan_is_tagged', models.BooleanField()), + ], + ), + migrations.CreateModel( + name='NetworkRole', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100)), + ], + ), + migrations.RemoveField( + model_name='genericinterface', + name='vlans', + ), + migrations.RemoveField( + model_name='network', + name='vlan_id', + ), + migrations.AddField( + model_name='network', + name='bundle', + field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='networks', to='resource_inventory.GenericResourceBundle'), + preserve_default=False, + ), + migrations.AddField( + model_name='network', + name='is_public', + field=models.BooleanField(default=False), + preserve_default=False, + ), + migrations.AddField( + model_name='vlan', + name='network', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='networkrole', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='networkconnection', + name='network', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Network'), + ), + migrations.AddField( + model_name='genericinterface', + name='connections', + field=models.ManyToManyField(to='resource_inventory.NetworkConnection'), + ), + migrations.AddField( + model_name='opnfvconfig', + name='networks', + field=models.ManyToManyField(to='resource_inventory.NetworkRole'), + ), + ] diff --git a/src/resource_inventory/migrations/0010_auto_20190430_1405.py b/src/resource_inventory/migrations/0010_auto_20190430_1405.py new file mode 100644 index 0000000..3823eaf --- /dev/null +++ b/src/resource_inventory/migrations/0010_auto_20190430_1405.py @@ -0,0 +1,54 @@ +# Generated by Django 2.1 on 2019-04-30 14:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0009_auto_20190315_1757'), + ] + + operations = [ + migrations.CreateModel( + name='HostOPNFVConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.RemoveField( + model_name='hostconfiguration', + name='opnfvRole', + ), + migrations.AddField( + model_name='hostconfiguration', + name='is_head_node', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='opnfvconfig', + name='description', + field=models.CharField(blank=True, default='', max_length=600), + ), + migrations.AddField( + model_name='opnfvconfig', + name='name', + field=models.CharField(blank=True, default='', max_length=300), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='host_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.HostConfiguration'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='opnfv_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.OPNFVConfig'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_configs', to='resource_inventory.OPNFVRole'), + ), + ] diff --git a/src/resource_inventory/migrations/__init__.py b/src/resource_inventory/migrations/__init__.py new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/src/resource_inventory/migrations/__init__.py diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py new file mode 100644 index 0000000..b9f2c44 --- /dev/null +++ b/src/resource_inventory/models.py @@ -0,0 +1,371 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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.contrib.auth.models import User +from django.db import models +from django.core.validators import RegexValidator + +import re + +from account.models import Lab + + +# profile of resources hosted by labs +class HostProfile(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") + + 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 + + +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') + nic_type = models.CharField( + max_length=50, + choices=[ + ("onboard", "onboard"), + ("pcie", "pcie") + ], + default="onboard" + ) + + 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(HostProfile, 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(HostProfile, on_delete=models.CASCADE, related_name='cpuprofile') + cflags = models.TextField(null=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(HostProfile, on_delete=models.CASCADE, related_name='ramprofile') + + def __str__(self): + return str(self.amount) + "G for " + str(self.host) + + +# Generic resource templates +class GenericResourceBundle(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="") + + def getHosts(self): + return_hosts = [] + for genericResource in self.generic_resources.all(): + return_hosts.append(genericResource.getHost()) + + return return_hosts + + def __str__(self): + return self.name + + +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") + is_public = models.BooleanField() + + def __str__(self): + return self.name + + +class NetworkConnection(models.Model): + network = models.ForeignKey(Network, on_delete=models.CASCADE) + vlan_is_tagged = models.BooleanField() + + +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(Network, on_delete=models.DO_NOTHING, null=True) + + def __str__(self): + return str(self.vlan_id) + ("_T" if self.tagged else "") + + +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 getHost(self): + 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(models.Model): + id = models.AutoField(primary_key=True) + 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): + 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') + connections = models.ManyToManyField(NetworkConnection) + + def __str__(self): + return "type " + str(self.profile) + " on host " + str(self.host) + + +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 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) + + 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) + 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 + + +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 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(models.Model): + id = models.AutoField(primary_key=True) + 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 + + +class Interface(models.Model): + id = models.AutoField(primary_key=True) + mac_address = models.CharField(max_length=17) + bus_address = models.CharField(max_length=50) + name = models.CharField(max_length=100, default="eth0") + config = models.ManyToManyField(Vlan) + host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='interfaces') + + def __str__(self): + return self.mac_address + " on host " + str(self.host) + + +class OPNFV_SETTINGS(): + """ + This is a static configuration class + """ + # all the required network types in PDF/IDF spec + NETWORK_ROLES = ["public", "private", "admin", "mgmt"] diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py new file mode 100644 index 0000000..2302530 --- /dev/null +++ b/src/resource_inventory/pdf_templater.py @@ -0,0 +1,193 @@ +############################################################################## +# 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 Host, InterfaceProfile + + +class PDFTemplater: + """ + Utility class to create a full PDF yaml file + """ + + @classmethod + def makePDF(cls, booking): + """ + fills the pod descriptor file template with info about the resource + """ + template = "dashboard/pdf.yaml" + info = {} + info['details'] = cls.get_pdf_details(booking.resource) + info['jumphost'] = cls.get_pdf_jumphost(booking) + 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 = "https://wiki.opnfv.org/display/INF/Pharos+Laas" + + 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): + 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 = Host.objects.filter( + bundle=booking.resource, + config__is_head_node=True + ).first() + + return jumphost + + @classmethod + def get_pdf_jumphost(cls, booking): + """ + returns 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): + """ + returns a list of all the "nodes" (every host except jumphost) + """ + pdf_nodes = [] + nodes = set(Host.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): + """ + method to gather all needed info about a host + returns a dict + """ + host_info = {} + host_info['name'] = host.template.resource.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): + """ + returns "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): + """ + returns 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): + """ + returns a dict describing given interface + """ + iface_info = {} + iface_info['features'] = "none" + iface_info['mac_address'] = interface.mac_address + iface_info['name'] = interface.name + speed = "unknown" + try: + profile = InterfaceProfile.objects.get(host=interface.host.profile, name=interface.name) + speed = str(int(profile.speed / 1000)) + "gb" + except Exception: + pass + iface_info['speed'] = speed + return iface_info + + @classmethod + def get_pdf_host_remote_management(cls, host): + """ + gives 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 new file mode 100644 index 0000000..652e4e3 --- /dev/null +++ b/src/resource_inventory/resource_manager.py @@ -0,0 +1,174 @@ +############################################################################## +# 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 dashboard.exceptions import ( + ResourceExistenceException, + ResourceAvailabilityException, + ResourceProvisioningException, + ModelValidationException, +) +from resource_inventory.models import ( + Host, + HostConfiguration, + ResourceBundle, + HostProfile, + Network, + Vlan +) + + +class ResourceManager: + + instance = None + + def __init__(self): + pass + + @staticmethod + def getInstance(): + if ResourceManager.instance is None: + 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): + """ + This method will check if the given GenericResourceBundle + is available. No changes to the database + """ + + # count up hosts + profile_count = {} + for host in grb.getHosts(): + if host.profile not in profile_count: + profile_count[host.profile] = 0 + profile_count[host.profile] += 1 + + # check that all required hosts are available + for profile in profile_count.keys(): + available = Host.objects.filter( + booked=False, + lab=grb.lab, + profile=profile + ).count() + needed = profile_count[profile] + if available < needed: + return False + return True + + # public interface + def deleteResourceBundle(self, resourceBundle): + for host in Host.objects.filter(bundle=resourceBundle): + self.releaseHost(host) + resourceBundle.delete() + + def get_vlans(self, genericResourceBundle): + networks = {} + vlan_manager = genericResourceBundle.lab.vlan_manager + for network in genericResourceBundle.networks.all(): + if network.is_public: + public_net = vlan_manager.get_public_vlan() + vlan_manager.reserve_public_vlan(public_net.vlan) + networks[network.name] = public_net.vlan + else: + vlan = vlan_manager.get_vlan() + vlan_manager.reserve_vlans(vlan) + networks[network.name] = vlan + return networks + + def convertResourceBundle(self, genericResourceBundle, config=None): + """ + Takes in a GenericResourceBundle and 'converts' it into a ResourceBundle + """ + resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle) + generic_hosts = genericResourceBundle.getHosts() + physical_hosts = [] + + vlan_map = self.get_vlans(genericResourceBundle) + + for generic_host in generic_hosts: + host_config = None + if config: + host_config = HostConfiguration.objects.get(bundle=config, host=generic_host) + 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") + + return resource_bundle + + def configureNetworking(self, host, vlan_map): + generic_interfaces = list(host.template.generic_interfaces.all()) + for int_num, physical_interface in enumerate(host.interfaces.all()): + generic_interface = generic_interfaces[int_num] + physical_interface.config.clear() + for connection in generic_interface.connections.all(): + 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=connection.network + ) + ) + + # 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) + 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 releaseHost(self, host): + host.template = None + host.bundle = None + host.booked = False + host.save() + + def releaseNetworks(self, grb, vlan_manager, vlans): + for net_name, vlan_id in vlans.items(): + net = Network.objects.get(name=net_name, bundle=grb) + 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) + for host in hosts: + self.releaseHost(host) diff --git a/src/resource_inventory/tests/test_managers.py b/src/resource_inventory/tests/test_managers.py new file mode 100644 index 0000000..0e7c673 --- /dev/null +++ b/src/resource_inventory/tests/test_managers.py @@ -0,0 +1,249 @@ +############################################################################## +# 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 +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 diff --git a/src/resource_inventory/tests/test_models.py b/src/resource_inventory/tests/test_models.py new file mode 100644 index 0000000..e1b2106 --- /dev/null +++ b/src/resource_inventory/tests/test_models.py @@ -0,0 +1,173 @@ +############################################################################## +# 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( + lab_id=0, + 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 new file mode 100644 index 0000000..a72871b --- /dev/null +++ b/src/resource_inventory/urls.py @@ -0,0 +1,35 @@ +############################################################################## +# Copyright (c) 2016 Max Breitenfeldt and others. +# 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 +############################################################################## + + +"""pharos_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 new file mode 100644 index 0000000..8c3d899 --- /dev/null +++ b/src/resource_inventory/views.py @@ -0,0 +1,38 @@ +############################################################################## +# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, 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.views.generic import TemplateView +from django.shortcuts import get_object_or_404 +from django.shortcuts import render + +from resource_inventory.models import HostProfile, Host + + +class HostView(TemplateView): + template_name = "resource/hosts.html" + + def get_context_data(self, **kwargs): + context = super(HostView, self).get_context_data(**kwargs) + hosts = Host.objects.filter(working=True) + context.update({'hosts': hosts, 'title': "Hardware Resources"}) + return context + + +def hostprofile_detail_view(request, hostprofile_id): + hostprofile = get_object_or_404(HostProfile, id=hostprofile_id) + + return render( + request, + "resource/hostprofile_detail.html", + { + 'title': "Host Type: " + str(hostprofile.name), + 'hostprofile': hostprofile + } + ) |