summaryrefslogtreecommitdiffstats
path: root/src/resource_inventory
diff options
context:
space:
mode:
Diffstat (limited to 'src/resource_inventory')
-rw-r--r--src/resource_inventory/__init__.py8
-rw-r--r--src/resource_inventory/admin.py29
-rw-r--r--src/resource_inventory/apps.py14
-rw-r--r--src/resource_inventory/migrations/0001_initial.py328
-rw-r--r--src/resource_inventory/migrations/0002_auto_20180919_1459.py18
-rw-r--r--src/resource_inventory/migrations/0003_vlan_public.py18
-rw-r--r--src/resource_inventory/migrations/__init__.py0
-rw-r--r--src/resource_inventory/models.py299
-rw-r--r--src/resource_inventory/resource_manager.py197
-rw-r--r--src/resource_inventory/tests/test_managers.py236
-rw-r--r--src/resource_inventory/tests/test_models.py162
-rw-r--r--src/resource_inventory/urls.py34
-rw-r--r--src/resource_inventory/views.py24
13 files changed, 1367 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..222877a
--- /dev/null
+++ b/src/resource_inventory/admin.py
@@ -0,0 +1,29 @@
+##############################################################################
+# 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 *
+
+profiles = [HostProfile, InterfaceProfile, DiskProfile, CpuProfile, RamProfile]
+
+admin.site.register(profiles)
+
+generics = [GenericResourceBundle, GenericResource, GenericHost, GenericPod, GenericInterface]
+
+admin.site.register(generics)
+
+physical = [Host, Interface, Network, Vlan, ResourceBundle]
+
+admin.site.register(physical)
+
+config = [Scenario, Installer, Opsys, ConfigBundle, OPNFVConfig, OPNFVRole, Image, HostConfiguration]
+
+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/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/__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..b71748e
--- /dev/null
+++ b/src/resource_inventory/models.py
@@ -0,0 +1,299 @@
+##############################################################################
+# 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("^[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.DO_NOTHING, 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.DO_NOTHING, 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.DO_NOTHING, 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.DO_NOTHING, related_name='ramprofile')
+
+ def __str__(self):
+ return str(self.amount) + "G for " + str(self.host)
+
+
+##Networking -- located here due to import order requirements
+class Network(models.Model):
+ id = models.AutoField(primary_key=True)
+ vlan_id = models.IntegerField()
+ name = models.CharField(max_length=100)
+
+ def __str__(self):
+ return self.name
+
+class Vlan(models.Model):
+ id = models.AutoField(primary_key=True)
+ vlan_id = models.IntegerField()
+ tagged = models.BooleanField()
+ public = models.BooleanField(default=False)
+
+ def __str__(self):
+ return str(self.vlan_id) + ("_T" if self.tagged else "")
+
+
+# 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.DO_NOTHING)
+ lab = models.ForeignKey(Lab, null=True, on_delete=models.DO_NOTHING)
+ 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 GenericResource(models.Model):
+ bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.DO_NOTHING)
+ hostname_validchars = RegexValidator(regex='(?=^.{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('(?=^.{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.DO_NOTHING)
+ resource = models.OneToOneField(GenericResource, related_name='generic_host', on_delete=models.DO_NOTHING)
+
+ 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.DO_NOTHING)
+
+ def __str__(self):
+ return "instance of " + str(self.template)
+
+
+# Networking
+
+
+class GenericInterface(models.Model):
+ id = models.AutoField(primary_key=True)
+ vlans = models.ManyToManyField(Vlan)
+ profile = models.ForeignKey(InterfaceProfile, on_delete=models.DO_NOTHING)
+ host = models.ForeignKey(GenericHost, on_delete=models.DO_NOTHING, related_name='generic_interfaces')
+
+ 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 ConfigBundle(models.Model):
+ id = models.AutoField(primary_key=True)
+ owner = models.ForeignKey(User, on_delete=models.CASCADE) #consider setting to root user?
+ 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)
+
+ 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) #may need to change to models.SET() once images are transferrable between compatible host types
+ description = models.TextField()
+
+ def __str__(self):
+ return self.name
+
+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)
+ opnfvRole = models.ForeignKey(OPNFVRole, on_delete=models.PROTECT) #need protocol for phasing out a role if we are going to allow that to happen
+
+ def __str__(self):
+ return "config with " + str(self.host) + " and image " + str(self.image)
+
+
+# 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")
+
+ 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)
diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py
new file mode 100644
index 0000000..cd70867
--- /dev/null
+++ b/src/resource_inventory/resource_manager.py
@@ -0,0 +1,197 @@
+##############################################################################
+# 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.core.exceptions import *
+from django.template.loader import render_to_string
+
+import booking
+from dashboard.exceptions import *
+from resource_inventory.models import *
+
+class ResourceManager:
+
+ instance = None
+
+ def __init__(self):
+ pass
+
+ @staticmethod
+ def getInstance():
+ if ResourceManager.instance is None:
+ ResourceManager.instance = ResourceManager()
+ return ResourceManager.instance
+
+ #public interface
+ def deleteResourceBundle(self, resourceBundle):
+ for host in Host.objects.filter(bundle=resourceBundle):
+ self.releaseHost(host)
+ resourceBundle.delete()
+
+ def convertResourceBundle(self, genericResourceBundle, lab=None, config=None):
+ """
+ Takes in a GenericResourceBundle and 'converts' it into a ResourceBundle
+ """
+ resource_bundle = ResourceBundle()
+ resource_bundle.template = genericResourceBundle
+ resource_bundle.save()
+
+ hosts = genericResourceBundle.getHosts()
+
+ #current supported case: user creating new booking
+ #currently unsupported: editing existing booking
+
+ physical_hosts = []
+
+ for host in hosts:
+ host_config=None
+ if config:
+ host_config = HostConfiguration.objects.get(bundle=config, host=host)
+ try:
+ physical_host = self.acquireHost(host, genericResourceBundle.lab.name)
+ except ResourceAvailabilityException:
+ self.fail_acquire(physical_hosts)
+ raise ResourceAvailabilityException("Could not provision hosts, not enough available")
+ try:
+ physical_host.bundle = resource_bundle
+ physical_host.template = host
+ physical_host.config = host_config
+ physical_hosts.append(physical_host)
+
+ self.configureNetworking(physical_host)
+ except:
+ self.fail_acquire(physical_hosts)
+ raise ResourceProvisioningException("Network configuration failed.")
+ try:
+ physical_host.save()
+ except:
+ self.fail_acquire(physical_hosts)
+ raise ModelValidationException("Saving hosts failed")
+
+ return resource_bundle
+
+ def configureNetworking(self, host):
+ 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 vlan in generic_interface.vlans.all():
+ physical_interface.config.add(vlan)
+
+ #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 fail_acquire(self, hosts):
+ for host in hosts:
+ self.releaseHost(host)
+
+ def makePDF(self, resource):
+ """
+ fills the pod descriptor file template with info about the resource
+ """
+ template = "dashboard/pdf.yaml"
+ info = {}
+ info['details'] = self.get_pdf_details(resource)
+ info['jumphost'] = self.get_pdf_jumphost(resource)
+ info['nodes'] = self.get_pdf_nodes(resource)
+
+ return render_to_string(template, context=info)
+
+ def get_pdf_details(self, resource):
+ 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 as e:
+ pass
+
+ details['owner'] = owner
+ details['email'] = email
+ details['lab'] = lab
+ details['location'] = location
+ details['type'] = pod_type
+ details['link'] = link
+
+ return details
+
+ def get_pdf_jumphost(self, resource):
+ jumphost = Host.objects.get(bundle=resource, config__opnfvRole__name__iexact="jumphost")
+ return self.get_pdf_host(jumphost)
+
+ def get_pdf_nodes(self, resource):
+ pdf_nodes = []
+ nodes = Host.objects.filter(bundle=resource).exclude(config__opnfvRole__name__iexact="jumphost")
+ for node in nodes:
+ pdf_nodes.append(self.get_pdf_host(node))
+
+ return pdf_nodes
+
+
+ def get_pdf_host(self, host):
+ host_info = {}
+ host_info['name'] = host.template.resource.name
+ host_info['node'] = {}
+ host_info['node']['type'] = "baremetal"
+ host_info['node']['vendor'] = host.vendor
+ host_info['node']['model'] = host.model
+ host_info['node']['arch'] = host.profile.cpuprofile.first().architecture
+ host_info['node']['cpus'] = host.profile.cpuprofile.first().cpus
+ host_info['node']['cores'] = host.profile.cpuprofile.first().cores
+ cflags = host.profile.cpuprofile.first().cflags
+ if cflags and cflags.strip():
+ host_info['node']['cpu_cflags'] = cflags
+ host_info['node']['memory'] = str(host.profile.ramprofile.first().amount) + "G"
+ host_info['disks'] = []
+ for disk in host.profile.storageprofile.all():
+ 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
+ host_info['disks'].append(disk_info)
+
+ host_info['interfaces'] = []
+ for interface in host.interfaces.all():
+ iface_info = {}
+ iface_info['name'] = interface.name
+ iface_info['address'] = "unknown"
+ iface_info['mac_address'] = interface.mac_address
+ vlans = "|".join([str(vlan.vlan_id) for vlan in interface.config.all()])
+ iface_info['vlans'] = vlans
+ host_info['interfaces'].append(iface_info)
+
+ return host_info
diff --git a/src/resource_inventory/tests/test_managers.py b/src/resource_inventory/tests/test_managers.py
new file mode 100644
index 0000000..5a13b2e
--- /dev/null
+++ b/src/resource_inventory/tests/test_managers.py
@@ -0,0 +1,236 @@
+##############################################################################
+# 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 resource.models import *
+
+
+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 = InterfaceProfile.objects.create(
+ speed=1000,
+ name='eno3',
+ host=hostProfile
+ )
+ diskProfile = DiskProfile.objects.create(
+ size=1000,
+ media_type="SSD",
+ name='/dev/sda',
+ host=hostProfile
+ )
+ cpuProfile = CpuProfile.objects.create(
+ cores=96,
+ architecture="x86_64",
+ cpus=2,
+ host=hostProfile
+ )
+ ramProfile = 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)
+
+ iface1 = 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
+ )
+ iface2 = 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 = InterfaceProfile.objects.create(
+ speed=1000,
+ name='eno3',
+ host=hostProfile
+ )
+ diskProfile = DiskProfile.objects.create(
+ size=1000,
+ media_type="SSD",
+ name='/dev/sda',
+ host=hostProfile
+ )
+ cpuProfile = CpuProfile.objects.create(
+ cores=96,
+ architecture="x86_64",
+ cpus=2,
+ host=hostProfile
+ )
+ ramProfile = 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)
+
+ iface1 = 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
+ )
+ iface2 = 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):
+ bundle = 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..4ddedf2
--- /dev/null
+++ b/src/resource_inventory/tests/test_models.py
@@ -0,0 +1,162 @@
+##############################################################################
+# 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 *
+
+
+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..4e159ba
--- /dev/null
+++ b/src/resource_inventory/urls.py
@@ -0,0 +1,34 @@
+##############################################################################
+# 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
+
+
+app_name = "resource"
+urlpatterns = [
+ url(r'^hosts$', HostView.as_view(), name='hosts')
+]
diff --git a/src/resource_inventory/views.py b/src/resource_inventory/views.py
new file mode 100644
index 0000000..7e73006
--- /dev/null
+++ b/src/resource_inventory/views.py
@@ -0,0 +1,24 @@
+##############################################################################
+# 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.shortcuts import render
+from django.views import View
+from django.views.generic import TemplateView
+
+from resource_inventory.models import 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