summaryrefslogtreecommitdiffstats
path: root/dashboard/src/resource_inventory
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src/resource_inventory')
-rw-r--r--dashboard/src/resource_inventory/__init__.py8
-rw-r--r--dashboard/src/resource_inventory/admin.py53
-rw-r--r--dashboard/src/resource_inventory/apps.py14
-rw-r--r--dashboard/src/resource_inventory/idf_templater.py151
-rw-r--r--dashboard/src/resource_inventory/migrations/0001_initial.py328
-rw-r--r--dashboard/src/resource_inventory/migrations/0002_auto_20180919_1459.py18
-rw-r--r--dashboard/src/resource_inventory/migrations/0003_vlan_public.py18
-rw-r--r--dashboard/src/resource_inventory/migrations/0004_auto_20181017_1532.py28
-rw-r--r--dashboard/src/resource_inventory/migrations/0005_image_os.py19
-rw-r--r--dashboard/src/resource_inventory/migrations/0006_auto_20190124_1700.py76
-rw-r--r--dashboard/src/resource_inventory/migrations/0007_auto_20190306_1616.py31
-rw-r--r--dashboard/src/resource_inventory/migrations/0008_host_remote_management.py19
-rw-r--r--dashboard/src/resource_inventory/migrations/0009_auto_20190315_1757.py73
-rw-r--r--dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py54
-rw-r--r--dashboard/src/resource_inventory/migrations/__init__.py0
-rw-r--r--dashboard/src/resource_inventory/models.py371
-rw-r--r--dashboard/src/resource_inventory/pdf_templater.py193
-rw-r--r--dashboard/src/resource_inventory/resource_manager.py174
-rw-r--r--dashboard/src/resource_inventory/tests/test_managers.py249
-rw-r--r--dashboard/src/resource_inventory/tests/test_models.py173
-rw-r--r--dashboard/src/resource_inventory/urls.py35
-rw-r--r--dashboard/src/resource_inventory/views.py38
22 files changed, 2123 insertions, 0 deletions
diff --git a/dashboard/src/resource_inventory/__init__.py b/dashboard/src/resource_inventory/__init__.py
new file mode 100644
index 0000000..f903394
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/admin.py b/dashboard/src/resource_inventory/admin.py
new file mode 100644
index 0000000..7ff510b
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/apps.py b/dashboard/src/resource_inventory/apps.py
new file mode 100644
index 0000000..79768a7
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/idf_templater.py b/dashboard/src/resource_inventory/idf_templater.py
new file mode 100644
index 0000000..bf6eda0
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0001_initial.py b/dashboard/src/resource_inventory/migrations/0001_initial.py
new file mode 100644
index 0000000..d01e8e7
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0002_auto_20180919_1459.py b/dashboard/src/resource_inventory/migrations/0002_auto_20180919_1459.py
new file mode 100644
index 0000000..80c9e6f
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0003_vlan_public.py b/dashboard/src/resource_inventory/migrations/0003_vlan_public.py
new file mode 100644
index 0000000..07dc647
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0004_auto_20181017_1532.py b/dashboard/src/resource_inventory/migrations/0004_auto_20181017_1532.py
new file mode 100644
index 0000000..3a7475c
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0005_image_os.py b/dashboard/src/resource_inventory/migrations/0005_image_os.py
new file mode 100644
index 0000000..ede008e
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0006_auto_20190124_1700.py b/dashboard/src/resource_inventory/migrations/0006_auto_20190124_1700.py
new file mode 100644
index 0000000..a5a972f
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0007_auto_20190306_1616.py b/dashboard/src/resource_inventory/migrations/0007_auto_20190306_1616.py
new file mode 100644
index 0000000..19a49c5
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0008_host_remote_management.py b/dashboard/src/resource_inventory/migrations/0008_host_remote_management.py
new file mode 100644
index 0000000..f74a535
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0009_auto_20190315_1757.py b/dashboard/src/resource_inventory/migrations/0009_auto_20190315_1757.py
new file mode 100644
index 0000000..92ed0e9
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py b/dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py
new file mode 100644
index 0000000..3823eaf
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/migrations/__init__.py b/dashboard/src/resource_inventory/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dashboard/src/resource_inventory/migrations/__init__.py
diff --git a/dashboard/src/resource_inventory/models.py b/dashboard/src/resource_inventory/models.py
new file mode 100644
index 0000000..b9f2c44
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/pdf_templater.py b/dashboard/src/resource_inventory/pdf_templater.py
new file mode 100644
index 0000000..2302530
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/resource_manager.py b/dashboard/src/resource_inventory/resource_manager.py
new file mode 100644
index 0000000..652e4e3
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/tests/test_managers.py b/dashboard/src/resource_inventory/tests/test_managers.py
new file mode 100644
index 0000000..0e7c673
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/tests/test_models.py b/dashboard/src/resource_inventory/tests/test_models.py
new file mode 100644
index 0000000..e1b2106
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/urls.py b/dashboard/src/resource_inventory/urls.py
new file mode 100644
index 0000000..a72871b
--- /dev/null
+++ b/dashboard/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/dashboard/src/resource_inventory/views.py b/dashboard/src/resource_inventory/views.py
new file mode 100644
index 0000000..8c3d899
--- /dev/null
+++ b/dashboard/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
+ }
+ )