aboutsummaryrefslogtreecommitdiffstats
path: root/src/resource_inventory
diff options
context:
space:
mode:
authorParker Berberian <pberberian@iol.unh.edu>2020-03-16 17:54:09 +0000
committerGerrit Code Review <gerrit@opnfv.org>2020-03-16 17:54:09 +0000
commit0db3a84d9d9ed213983a517efd35c339537ef472 (patch)
treea6d7ac7ba2f2d70e18cb984bda4020c736082c62 /src/resource_inventory
parent176ec9aacbc87e6077e8807c60f95a1ccbbc26e3 (diff)
parent064f145f218385a6401fa6be2ccbbc462e915c26 (diff)
Merge "Test resource templates now use the same lab as the image generated alongside it."
Diffstat (limited to 'src/resource_inventory')
-rw-r--r--src/resource_inventory/admin.py49
-rw-r--r--src/resource_inventory/migrations/0012_auto_20200103_1850.py37
-rw-r--r--src/resource_inventory/migrations/0012_manual_20200218_1536.py25
-rw-r--r--src/resource_inventory/migrations/0013_auto_20200218_1536.py404
-rw-r--r--src/resource_inventory/models.py468
-rw-r--r--src/resource_inventory/pdf_templater.py6
-rw-r--r--src/resource_inventory/resource_manager.py126
-rw-r--r--src/resource_inventory/views.py6
8 files changed, 794 insertions, 327 deletions
diff --git a/src/resource_inventory/admin.py b/src/resource_inventory/admin.py
index 7ff510b..13afd99 100644
--- a/src/resource_inventory/admin.py
+++ b/src/resource_inventory/admin.py
@@ -11,16 +11,15 @@
from django.contrib import admin
from resource_inventory.models import (
- HostProfile,
+ ResourceProfile,
InterfaceProfile,
DiskProfile,
CpuProfile,
RamProfile,
- GenericResourceBundle,
- GenericResource,
- GenericHost,
- GenericInterface,
- Host,
+ ResourceTemplate,
+ ResourceConfiguration,
+ InterfaceConfiguration,
+ Server,
Interface,
Network,
Vlan,
@@ -28,26 +27,30 @@ from resource_inventory.models import (
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)
+admin.site.register([
+ ResourceProfile,
+ InterfaceProfile,
+ DiskProfile,
+ CpuProfile,
+ RamProfile,
+ ResourceTemplate,
+ ResourceConfiguration,
+ InterfaceConfiguration,
+ Server,
+ Interface,
+ Network,
+ Vlan,
+ ResourceBundle,
+ Scenario,
+ Installer,
+ Opsys,
+ OPNFVConfig,
+ OPNFVRole,
+ Image,
+ RemoteInfo])
diff --git a/src/resource_inventory/migrations/0012_auto_20200103_1850.py b/src/resource_inventory/migrations/0012_auto_20200103_1850.py
index 2bb203e..569e433 100644
--- a/src/resource_inventory/migrations/0012_auto_20200103_1850.py
+++ b/src/resource_inventory/migrations/0012_auto_20200103_1850.py
@@ -4,30 +4,23 @@ from django.db import migrations, models
import django.db.models.deletion
-def genTempVlanNetwork(apps, editor):
+def pairVlanPhysicalNetworks(apps, editor):
+ PhysicalNetwork = apps.get_model("resource_inventory", "PhysicalNetwork")
Vlan = apps.get_model("resource_inventory", "Vlan")
- Network = apps.get_model("resource_inventory", "Network")
- tempVlanNetwork = apps.get_model("resource_inventory", "tempVlanNetwork")
for vlan in Vlan.objects.filter(network__isnull=False):
- tempVlanNetwork.objects.create(network=vlan.network, vlan=vlan)
+ if PhysicalNetwork.objects.filter(id=vlan.network.id).exists():
+ continue
+ PhysicalNetwork.objects.create(id=vlan.network.id, vlan_id=vlan.vlan_id, generic_network=vlan.network)
-def deleteTempVlanNetworks(apps, editor):
- tempVlanNetwork = apps.get_model("resource_inventory", "tempVlanNetwork")
- tempVlanNetwork.objects.all().delete()
-
-
-def pairVlanPhysicalNetworks(apps, editor):
- PhysicalNetwork = apps.get_model("resource_inventory", "PhysicalNetwork")
- tempVlanPair = apps.get_model("resource_inventory", "tempVlanNetwork")
- for pair in tempVlanPair.objects.all():
- physicalNetwork = PhysicalNetwork.objects.create(vlan_id=vlan.vlan_id,
- generic_network=pair.network)
- pair.vlan.network = physicalNetwork
def deletePhysicalNetworks(apps, editor):
+ Vlan = apps.get_model("resource_inventory", "Vlan")
+ for vlan in Vlan.objects.all():
+ vlan.network = None
PhysicalNetwork = apps.get_model("resource_inventory", "PhysicalNetwork")
PhysicalNetwork.objects.all().delete()
+
class Migration(migrations.Migration):
dependencies = [
@@ -56,21 +49,11 @@ class Migration(migrations.Migration):
name='id',
field=models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID'),
),
- migrations.CreateModel(
- name='tempVlanNetwork',
- fields=[
- ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
- ('vlan', models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.vlan')),
- ('network', models.ForeignKey(null=True, to='resource_inventory.network', on_delete=django.db.models.deletion.CASCADE)),
- ]
- ),
- migrations.RunPython(genTempVlanNetwork, deleteTempVlanNetworks),
+ migrations.RunPython(pairVlanPhysicalNetworks, deletePhysicalNetworks),
migrations.AlterField(
model_name='vlan',
name='network',
field=models.ForeignKey(on_delete=django.db.models.deletion.DO_NOTHING,
to='resource_inventory.PhysicalNetwork', null=True),
),
- migrations.RunPython(pairVlanPhysicalNetworks, deletePhysicalNetworks),
- migrations.DeleteModel("tempVlanNetwork")
]
diff --git a/src/resource_inventory/migrations/0012_manual_20200218_1536.py b/src/resource_inventory/migrations/0012_manual_20200218_1536.py
new file mode 100644
index 0000000..378bdc3
--- /dev/null
+++ b/src/resource_inventory/migrations/0012_manual_20200218_1536.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.2 on 2020-02-18 15:36
+
+from django.conf import settings
+from django.db import migrations
+
+
+def clear_networks(apps, schema_editor):
+ Network = apps.get_model('resource_inventory', 'Network')
+ Vlan = apps.get_model('resource_inventory', 'Vlan')
+ for vlan in Vlan.objects.all():
+ vlan.delete()
+ for net in Network.objects.all():
+ net.delete()
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('resource_inventory', '0012_auto_20200103_1850'),
+ ]
+
+ operations = [
+ migrations.RunPython(clear_networks)
+ ]
diff --git a/src/resource_inventory/migrations/0013_auto_20200218_1536.py b/src/resource_inventory/migrations/0013_auto_20200218_1536.py
new file mode 100644
index 0000000..014cb2f
--- /dev/null
+++ b/src/resource_inventory/migrations/0013_auto_20200218_1536.py
@@ -0,0 +1,404 @@
+# Generated by Django 2.2 on 2020-02-18 15:36
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+import resource_inventory.models
+
+
+def clear_resource_bundles(apps, schema_editor):
+ ResourceBundle = apps.get_model('resource_inventory', 'ResourceBundle')
+ for rb in ResourceBundle.objects.all():
+ rb.template = None
+ rb.save()
+
+
+def create_default_template(apps, schema_editor):
+ ResourceTemplate = apps.get_model('resource_inventory', 'ResourceTemplate')
+ ResourceTemplate.objects.create(id=1, name="Default Template")
+
+
+def populate_servers(apps, schema_editor):
+ """Convert old Host models to Server Resources."""
+ Host = apps.get_model('resource_inventory', 'Host')
+ Server = apps.get_model('resource_inventory', 'Server')
+ ResourceProfile = apps.get_model('resource_inventory', 'ResourceProfile')
+ for h in Host.objects.all():
+ rp = ResourceProfile.objects.get(id=h.profile.id)
+ Server.objects.create(
+ working=h.working,
+ vendor=h.vendor,
+ labid=h.labid,
+ booked=h.booked,
+ name=h.labid,
+ lab=h.lab,
+ profile=rp
+ )
+
+
+def populate_resource_templates(apps, schema_editor):
+ """
+ Convert old GenericResourceBundles to ResourceTemplate.
+
+ This will be kept blank for now. If, during testing, we realize
+ we want to implement this, we will. For now, it seems
+ fine to let the old models just die and create
+ new ones as needed.
+ """
+ pass
+
+
+def populate_resource_profiles(apps, schema_editor):
+ """
+ Convert old HostProfile models to ResourceProfiles.
+
+ Also updates all the foreign keys pointed to the old
+ host profile. This change was basically only a name change.
+ """
+ HostProfile = apps.get_model('resource_inventory', 'HostProfile')
+ ResourceProfile = apps.get_model('resource_inventory', 'ResourceProfile')
+ for hp in HostProfile.objects.all():
+ rp = ResourceProfile.objects.create(id=hp.id, name=hp.name, description=hp.description)
+ rp.labs.add(*list(hp.labs.all()))
+ """
+ TODO: link these models together
+ rp.interfaceprofile = hp.interfaceprofile
+ rp.storageprofile = hp.storageprofile
+ rp.cpuprofile = hp.cpuprofile
+ rp.ramprofile = hp.ramprofile
+ rp.save()
+ hp.interfaceprofile.host = rp
+ rp.storageprofile.host = rp
+ rp.cpuprofile.host = rp
+ rp.ramprofile.host = rp
+ rp.interfaceprofile.save()
+ rp.storageprofile.save()
+ rp.cpuprofile.save()
+ rp.ramprofile.save()
+ """
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('booking', '0007_remove_booking_config_bundle'),
+ ('account', '0004_downtime'),
+ ('api', '0013_manual_20200218_1536'),
+ ('resource_inventory', '0012_manual_20200218_1536'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='InterfaceConfiguration',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('connections', models.ManyToManyField(to='resource_inventory.NetworkConnection')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ResourceConfiguration',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('is_head_node', models.BooleanField(default=False)),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ResourceOPNFVConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='ResourceProfile',
+ fields=[
+ ('id', models.AutoField(primary_key=True, serialize=False)),
+ ('name', models.CharField(max_length=200, unique=True)),
+ ('description', models.TextField()),
+ ('labs', models.ManyToManyField(related_name='resourceprofiles', to='account.Lab')),
+ ],
+ ),
+ migrations.RunPython(populate_resource_profiles),
+ migrations.CreateModel(
+ name='ResourceTemplate',
+ 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)),
+ ('public', models.BooleanField(default=False)),
+ ('hidden', models.BooleanField(default=False)),
+ ('lab', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='resourcetemplates', to='account.Lab')),
+ ('owner', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ migrations.RunPython(populate_resource_templates),
+ migrations.CreateModel(
+ name='Server',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('working', models.BooleanField(default=True)),
+ ('vendor', models.CharField(default='unknown', max_length=100)),
+ ('model', models.CharField(default='unknown', max_length=150)),
+ ('labid', models.CharField(default='default_id', max_length=200, unique=True)),
+ ('booked', models.BooleanField(default=False)),
+ ('name', models.CharField(max_length=200, unique=True)),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='bundle',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='config',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceConfiguration'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='interfaces',
+ field=models.ManyToManyField(to='resource_inventory.Interface'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='lab',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ name='profile',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AddField(
+ model_name='server',
+ 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'),
+ ),
+ migrations.RunPython(populate_servers),
+ migrations.RemoveField(
+ model_name='generichost',
+ name='profile',
+ ),
+ migrations.RemoveField(
+ model_name='generichost',
+ name='resource',
+ ),
+ migrations.RemoveField(
+ model_name='genericinterface',
+ name='connections',
+ ),
+ migrations.RemoveField(
+ model_name='genericinterface',
+ name='host',
+ ),
+ migrations.RemoveField(
+ model_name='genericinterface',
+ name='profile',
+ ),
+ migrations.RemoveField(
+ model_name='genericresource',
+ name='bundle',
+ ),
+ migrations.RemoveField(
+ model_name='genericresourcebundle',
+ name='lab',
+ ),
+ migrations.RemoveField(
+ model_name='genericresourcebundle',
+ name='owner',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='bundle',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='config',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='lab',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='profile',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='remote_management',
+ ),
+ migrations.RemoveField(
+ model_name='host',
+ name='template',
+ ),
+ migrations.RemoveField(
+ model_name='hostconfiguration',
+ name='bundle',
+ ),
+ migrations.RemoveField(
+ model_name='hostconfiguration',
+ name='host',
+ ),
+ migrations.RemoveField(
+ model_name='hostconfiguration',
+ name='image',
+ ),
+ migrations.RemoveField(
+ model_name='hostopnfvconfig',
+ name='host_config',
+ ),
+ migrations.RemoveField(
+ model_name='hostopnfvconfig',
+ name='opnfv_config',
+ ),
+ migrations.RemoveField(
+ model_name='hostopnfvconfig',
+ name='role',
+ ),
+ migrations.RemoveField(
+ model_name='hostprofile',
+ name='labs',
+ ),
+ migrations.RemoveField(
+ model_name='interface',
+ name='host',
+ ),
+ migrations.RemoveField(
+ model_name='interface',
+ name='name',
+ ),
+ migrations.RemoveField(
+ model_name='opnfvconfig',
+ name='bundle',
+ ),
+ migrations.AddField(
+ model_name='interface',
+ name='profile',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.InterfaceProfile'),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='interfaceprofile',
+ name='order',
+ field=models.IntegerField(default=-1),
+ ),
+ migrations.AlterField(
+ model_name='cpuprofile',
+ name='host',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='cpuprofile', to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AlterField(
+ model_name='diskprofile',
+ name='host',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='storageprofile', to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AlterField(
+ model_name='image',
+ name='host_type',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AlterField(
+ model_name='interfaceprofile',
+ name='host',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interfaceprofile', to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AlterField(
+ model_name='network',
+ name='bundle',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='networks', to='resource_inventory.ResourceTemplate'),
+ ),
+ migrations.AlterField(
+ model_name='ramprofile',
+ name='host',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ramprofile', to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.RunPython(clear_resource_bundles),
+ migrations.AlterField(
+ model_name='resourcebundle',
+ name='template',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceTemplate'),
+ ),
+ migrations.DeleteModel(
+ name='ConfigBundle',
+ ),
+ migrations.DeleteModel(
+ name='GenericHost',
+ ),
+ migrations.DeleteModel(
+ name='GenericInterface',
+ ),
+ migrations.DeleteModel(
+ name='GenericResource',
+ ),
+ migrations.DeleteModel(
+ name='GenericResourceBundle',
+ ),
+ migrations.DeleteModel(
+ name='HostConfiguration',
+ ),
+ migrations.DeleteModel(
+ name='HostOPNFVConfig',
+ ),
+ migrations.DeleteModel(
+ name='HostProfile',
+ ),
+ migrations.DeleteModel(
+ name='Host',
+ ),
+ migrations.AddField(
+ model_name='resourceopnfvconfig',
+ name='opnfv_config',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resource_opnfv_config', to='resource_inventory.OPNFVConfig'),
+ ),
+ migrations.AddField(
+ model_name='resourceopnfvconfig',
+ name='resource_config',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resource_opnfv_config', to='resource_inventory.ResourceConfiguration'),
+ ),
+ migrations.AddField(
+ model_name='resourceopnfvconfig',
+ name='role',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='resource_opnfv_configs', to='resource_inventory.OPNFVRole'),
+ ),
+ migrations.AddField(
+ model_name='resourceconfiguration',
+ name='image',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to='resource_inventory.Image'),
+ ),
+ migrations.AddField(
+ model_name='resourceconfiguration',
+ name='profile',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceProfile'),
+ ),
+ migrations.AddField(
+ model_name='resourceconfiguration',
+ name='template',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, related_name='resourceConfigurations', to='resource_inventory.ResourceTemplate'),
+ ),
+ migrations.AddField(
+ model_name='interfaceconfiguration',
+ name='profile',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.InterfaceProfile'),
+ ),
+ migrations.AddField(
+ model_name='interfaceconfiguration',
+ name='resource_config',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='interface_configs', to='resource_inventory.ResourceConfiguration'),
+ ),
+ migrations.AddField(
+ model_name='interface',
+ name='acts_as',
+ field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.InterfaceConfiguration'),
+ ),
+ migrations.RunPython(create_default_template),
+ migrations.AddField(
+ model_name='opnfvconfig',
+ name='template',
+ field=models.ForeignKey(default=1, on_delete=django.db.models.deletion.CASCADE, related_name='opnfv_config', to='resource_inventory.ResourceTemplate'),
+ preserve_default=False,
+ ),
+ ]
diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py
index a8b75d9..d11f71b 100644
--- a/src/resource_inventory/models.py
+++ b/src/resource_inventory/models.py
@@ -8,21 +8,30 @@
##############################################################################
from django.contrib.auth.models import User
+from django.core.exceptions import ValidationError
from django.db import models
-from django.core.validators import RegexValidator
+from django.db.models import Q
import re
from account.models import Lab
+from dashboard.utils import AbstractModelQuery
-# profile of resources hosted by labs
-class HostProfile(models.Model):
+"""
+Profiles of resources hosted by labs.
+
+These describe hardware attributes of the different Resources a lab hosts.
+A single Resource subclass (e.g. Server) may have instances that point to different
+Profile models (e.g. an x86 server profile and armv8 server profile.
+"""
+
+
+class ResourceProfile(models.Model):
id = models.AutoField(primary_key=True)
- host_type = models.PositiveSmallIntegerField(default=0)
name = models.CharField(max_length=200, unique=True)
description = models.TextField()
- labs = models.ManyToManyField(Lab, related_name="hostprofiles")
+ labs = models.ManyToManyField(Lab, related_name="resourceprofiles")
def validate(self):
validname = re.compile(r"^[A-Za-z0-9\-\_\.\/\, ]+$")
@@ -34,12 +43,33 @@ class HostProfile(models.Model):
def __str__(self):
return self.name
+ def get_resources(self, lab=None, working=True, unreserved=False):
+ """
+ Return a list of Resource objects which have this profile.
+
+ If lab is provided, only resources at that lab will be returned.
+ If working=True, will only return working hosts
+ """
+ resources = []
+ query = Q(profile=self)
+ if lab:
+ query = query & Q(lab=lab)
+ if working:
+ query = query & Q(working=True)
+
+ resources = ResourceQuery.filter(query)
+
+ if unreserved:
+ resources = [r for r in resources if not r.is_reserved()]
+
+ return resources
+
class InterfaceProfile(models.Model):
id = models.AutoField(primary_key=True)
speed = models.IntegerField()
name = models.CharField(max_length=100)
- host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='interfaceprofile')
+ host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='interfaceprofile')
nic_type = models.CharField(
max_length=50,
choices=[
@@ -48,6 +78,7 @@ class InterfaceProfile(models.Model):
],
default="onboard"
)
+ order = models.IntegerField(default=-1)
def __str__(self):
return self.name + " for " + str(self.host)
@@ -61,7 +92,7 @@ class DiskProfile(models.Model):
("HDD", "HDD")
])
name = models.CharField(max_length=50)
- host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='storageprofile')
+ host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='storageprofile')
rotation = models.IntegerField(default=0)
interface = models.CharField(
max_length=50,
@@ -88,7 +119,7 @@ class CpuProfile(models.Model):
("aarch64", "aarch64")
])
cpus = models.IntegerField()
- host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='cpuprofile')
+ host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='cpuprofile')
cflags = models.TextField(null=True)
def __str__(self):
@@ -99,16 +130,116 @@ class RamProfile(models.Model):
id = models.AutoField(primary_key=True)
amount = models.IntegerField()
channels = models.IntegerField()
- host = models.ForeignKey(HostProfile, on_delete=models.CASCADE, related_name='ramprofile')
+ host = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE, related_name='ramprofile')
def __str__(self):
return str(self.amount) + "G for " + str(self.host)
+"""
+Resource Models
+
+These models represent actual hardware resources
+with varying degrees of abstraction.
+"""
+
+
+class ResourceTemplate(models.Model):
+ """
+ Models a "template" of a complete, configured collection of resources that can be booked.
+
+ For example, this may represent a Pharos POD. This model is a template of the actual
+ resources that will be booked. This model can be "instantiated" into real resource models
+ across multiple different bookings.
+ """
+
+ # TODO: template might not be a good name because this is a collection of lots of configured resources
+ id = models.AutoField(primary_key=True)
+ name = models.CharField(max_length=300, unique=True)
+ xml = models.TextField()
+ owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
+ lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL, related_name="resourcetemplates")
+ description = models.CharField(max_length=1000, default="")
+ public = models.BooleanField(default=False)
+ hidden = models.BooleanField(default=False)
+
+ def getConfigs(self):
+ return list(self.resourceConfigurations.all())
+
+ def __str__(self):
+ return self.name
+
+
+class ResourceBundle(models.Model):
+ """
+ Collection of Resource objects.
+
+ This is just a way of aggregating all the resources in a booking into a single model.
+ """
+
+ template = models.ForeignKey(ResourceTemplate, on_delete=models.SET_NULL, null=True)
+
+ def __str__(self):
+ if self.template is None:
+ return "Resource bundle " + str(self.id) + " with no template"
+ return "instance of " + str(self.template)
+
+ def get_resources(self):
+ return ResourceQuery.filter(bundle=self)
+
+ def get_resource_with_role(self, role):
+ # TODO
+ pass
+
+
+class ResourceConfiguration(models.Model):
+ """Model to represent a complete configuration for a single physical Resource."""
+
+ id = models.AutoField(primary_key=True)
+ profile = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE)
+ image = models.ForeignKey("Image", on_delete=models.PROTECT)
+ template = models.ForeignKey(ResourceTemplate, related_name="resourceConfigurations", null=True, on_delete=models.CASCADE)
+ is_head_node = models.BooleanField(default=False)
+ # name?
+
+ def __str__(self):
+ return "config with " + str(self.template) + " and image " + str(self.image)
+
+
+def get_default_remote_info():
+ return RemoteInfo.objects.get_or_create(
+ address="default",
+ mac_address="default",
+ password="default",
+ user="default",
+ management_type="default",
+ versions="[default]"
+ )[0].pk
+
+
class Resource(models.Model):
+ """
+ Super class for all hardware resource models.
+
+ Defines methods that must be implemented and common database fields.
+ Any new kind of Resource a lab wants to host (White box switch, traffic generator, etc)
+ should inherit from this class and fulfill the functional interface
+ """
+
class Meta:
abstract = True
+ bundle = models.ForeignKey(ResourceBundle, on_delete=models.SET_NULL, null=True)
+ profile = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE)
+ config = models.ForeignKey(ResourceConfiguration, on_delete=models.SET_NULL, null=True)
+ working = models.BooleanField(default=True)
+ vendor = models.CharField(max_length=100, default="unknown")
+ model = models.CharField(max_length=150, default="unknown")
+ interfaces = models.ManyToManyField("Interface")
+ remote_management = models.ForeignKey("RemoteInfo", default=get_default_remote_info, on_delete=models.SET(get_default_remote_info))
+ labid = models.CharField(max_length=200, default="default_id", unique=True)
+ lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
+
def get_configuration(self, state):
"""
Get configuration of Resource.
@@ -129,45 +260,129 @@ class Resource(models.Model):
def get_interfaces(self):
"""
- Returns a list of interfaces on this resource.
+ Return a list of interfaces on this resource.
+
The ordering of interfaces should be consistent.
"""
raise NotImplementedError("Must implement in concrete Resource classes")
+ def is_reserved(self):
+ """Return True if this Resource is reserved."""
+ raise NotImplementedError("Must implement in concrete Resource classes")
+
+ def same_instance(self, other):
+ """Return True if this Resource is the same instance as other."""
+ raise NotImplementedError("Must implement in concrete Resource classes")
+
+ def save(self, *args, **kwargs):
+ """Assert that labid is unique across all Resource models."""
+ res = ResourceQuery.filter(labid=self.labid)
+ if len(res) > 1:
+ raise ValidationError("Too many resources with labid " + str(self.labid))
+
+ if len(res) == 1:
+ if not self.same_instance(res[0]):
+ raise ValidationError("Too many resources with labid " + str(self.labid))
+ super().save(*args, **kwargs)
-# Generic resource templates
-class GenericResourceBundle(models.Model):
+
+class RemoteInfo(models.Model):
+ address = models.CharField(max_length=15)
+ mac_address = models.CharField(max_length=17)
+ password = models.CharField(max_length=100)
+ user = models.CharField(max_length=100)
+ management_type = models.CharField(max_length=50, default="ipmi")
+ versions = models.CharField(max_length=100) # json serialized list of floats
+
+
+class Server(Resource):
+ """Resource subclass - a basic baremetal server."""
+
+ booked = models.BooleanField(default=False)
+ name = models.CharField(max_length=200, unique=True)
+
+ def __str__(self):
+ return self.name
+
+ def get_configuration(self, state):
+ ipmi = state == ConfigState.NEW
+ power = "off" if state == ConfigState.CLEAN else "on"
+
+ return {
+ "id": self.labid,
+ "image": self.config.image.lab_id,
+ "hostname": self.template.resource.name,
+ "power": power,
+ "ipmi_create": str(ipmi)
+ }
+
+ def get_interfaces(self):
+ return list(self.interfaces.all().order_by('bus_address'))
+
+ def release(self):
+ self.booked = False
+ self.save()
+
+ def reserve(self):
+ self.booked = True
+ self.save()
+
+ def is_reserved(self):
+ return self.booked
+
+ def same_instance(self, other):
+ return isinstance(other, Server) and other.name == self.name
+
+
+class Opsys(models.Model):
id = models.AutoField(primary_key=True)
- name = models.CharField(max_length=300, unique=True)
- xml = models.TextField()
- owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
- lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL)
- description = models.CharField(max_length=1000, default="")
- public = models.BooleanField(default=False)
- hidden = models.BooleanField(default=False)
+ name = models.CharField(max_length=100)
+ sup_installers = models.ManyToManyField("Installer", blank=True)
+
+ def __str__(self):
+ return self.name
- def getResources(self):
- my_resources = []
- for genericResource in self.generic_resources.all():
- my_resources.append(genericResource.getResource())
- return my_resources
+class Image(models.Model):
+ """Model for representing OS images / snapshots of hosts."""
+
+ id = models.AutoField(primary_key=True)
+ lab_id = models.IntegerField() # ID the lab who holds this image knows
+ from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
+ name = models.CharField(max_length=200)
+ owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
+ public = models.BooleanField(default=True)
+ host_type = models.ForeignKey(ResourceProfile, on_delete=models.CASCADE)
+ description = models.TextField()
+ os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE)
def __str__(self):
return self.name
+ def in_use(self):
+ for resource in ResourceQuery.filter(config__image=self):
+ if resource.is_reserved():
+ return True
+
+ return False
+
+
+"""
+Networking configuration models
+"""
+
class Network(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=100)
- bundle = models.ForeignKey(GenericResourceBundle, on_delete=models.CASCADE, related_name="networks")
+ bundle = models.ForeignKey(ResourceTemplate, on_delete=models.CASCADE, related_name="networks")
is_public = models.BooleanField()
def __str__(self):
return self.name
-class PhysicalNetwork(Resource):
+class PhysicalNetwork(models.Model):
vlan_id = models.IntegerField()
generic_network = models.ForeignKey(Network, on_delete=models.CASCADE)
@@ -205,65 +420,21 @@ class Vlan(models.Model):
return str(self.vlan_id) + ("_T" if self.tagged else "")
-class ConfigState:
- NEW = 0
- RESET = 100
- CLEAN = 200
-
-
-class GenericResource(models.Model):
- bundle = models.ForeignKey(GenericResourceBundle, related_name='generic_resources', on_delete=models.CASCADE)
- hostname_validchars = RegexValidator(regex=r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))', message="Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)")
- name = models.CharField(max_length=200, validators=[hostname_validchars])
-
- def getResource(self):
- # TODO: This will have to be dealt with
- return self.generic_host
-
- def __str__(self):
- return self.name
-
- def validate(self):
- validname = re.compile(r'(?=^.{1,253}$)(?=(^([A-Za-z0-9\-\_]{1,62}\.)*[A-Za-z0-9\-\_]{1,63}$))')
- if not validname.match(self.name):
- return "Enter a valid hostname. Full domain name may be 1-253 characters, each hostname 1-63 characters (including suffixed dot), and valid characters for hostnames are A-Z, a-z, 0-9, hyphen (-), and underscore (_)"
- else:
- return None
-
-
-# Host template
-class GenericHost(models.Model):
- id = models.AutoField(primary_key=True)
- profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
- resource = models.OneToOneField(GenericResource, related_name='generic_host', on_delete=models.CASCADE)
-
- def __str__(self):
- return self.resource.name
-
-
-# Physical, actual resources
-class ResourceBundle(Resource):
- template = models.ForeignKey(GenericResourceBundle, on_delete=models.SET_NULL, null=True)
-
- def __str__(self):
- if self.template is None:
- return "Resource bundle " + str(self.id) + " with no template"
- return "instance of " + str(self.template)
-
- def get_host(self, role="Jumphost"):
- return Host.objects.filter(bundle=self, config__is_head_node=True).first() # should only ever be one, but it is not an invariant in the models
-
-
-class GenericInterface(models.Model):
+class InterfaceConfiguration(models.Model):
id = models.AutoField(primary_key=True)
profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE)
- host = models.ForeignKey(GenericHost, on_delete=models.CASCADE, related_name='generic_interfaces')
+ resource_config = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE, related_name='interface_configs')
connections = models.ManyToManyField(NetworkConnection)
def __str__(self):
return "type " + str(self.profile) + " on host " + str(self.host)
+"""
+OPNFV / Software configuration models
+"""
+
+
class Scenario(models.Model):
id = models.AutoField(primary_key=True)
name = models.CharField(max_length=300)
@@ -281,38 +452,16 @@ class Installer(models.Model):
return self.name
-class Opsys(models.Model):
- id = models.AutoField(primary_key=True)
- name = models.CharField(max_length=100)
- sup_installers = models.ManyToManyField(Installer, blank=True)
-
- def __str__(self):
- return self.name
-
-
class NetworkRole(models.Model):
name = models.CharField(max_length=100)
network = models.ForeignKey(Network, on_delete=models.CASCADE)
-class ConfigBundle(models.Model):
- id = models.AutoField(primary_key=True)
- owner = models.ForeignKey(User, on_delete=models.CASCADE)
- name = models.CharField(max_length=200, unique=True)
- description = models.CharField(max_length=1000, default="")
- bundle = models.ForeignKey(GenericResourceBundle, null=True, on_delete=models.CASCADE)
- public = models.BooleanField(default=False)
- hidden = models.BooleanField(default=False)
-
- def __str__(self):
- return self.name
-
-
class OPNFVConfig(models.Model):
id = models.AutoField(primary_key=True)
installer = models.ForeignKey(Installer, on_delete=models.CASCADE)
scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE)
- bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE)
+ template = models.ForeignKey(ResourceTemplate, related_name="opnfv_config", on_delete=models.CASCADE)
networks = models.ManyToManyField(NetworkRole)
name = models.CharField(max_length=300, blank=True, default="")
description = models.CharField(max_length=600, blank=True, default="")
@@ -330,105 +479,14 @@ class OPNFVRole(models.Model):
return self.name
-class Image(models.Model):
- """Model for representing OS images / snapshots of hosts."""
-
- id = models.AutoField(primary_key=True)
- lab_id = models.IntegerField() # ID the lab who holds this image knows
- from_lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
- name = models.CharField(max_length=200)
- owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
- public = models.BooleanField(default=True)
- host_type = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
- description = models.TextField()
- os = models.ForeignKey(Opsys, null=True, on_delete=models.CASCADE)
-
- def __str__(self):
- return self.name
-
- def in_use(self):
- return Host.objects.filter(booked=True, config__image=self).exists()
-
-
def get_sentinal_opnfv_role():
return OPNFVRole.objects.get_or_create(name="deleted", description="Role was deleted.")
-class HostConfiguration(models.Model):
- """Model to represent a complete configuration for a single physical host."""
-
- id = models.AutoField(primary_key=True)
- host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE)
- image = models.ForeignKey(Image, on_delete=models.PROTECT)
- bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE)
- is_head_node = models.BooleanField(default=False)
-
- def __str__(self):
- return "config with " + str(self.host) + " and image " + str(self.image)
-
-
-class HostOPNFVConfig(models.Model):
- role = models.ForeignKey(OPNFVRole, related_name="host_opnfv_configs", on_delete=models.CASCADE)
- host_config = models.ForeignKey(HostConfiguration, related_name="host_opnfv_config", on_delete=models.CASCADE)
- opnfv_config = models.ForeignKey(OPNFVConfig, related_name="host_opnfv_config", on_delete=models.CASCADE)
-
-
-class RemoteInfo(models.Model):
- address = models.CharField(max_length=15)
- mac_address = models.CharField(max_length=17)
- password = models.CharField(max_length=100)
- user = models.CharField(max_length=100)
- management_type = models.CharField(max_length=50, default="ipmi")
- versions = models.CharField(max_length=100) # json serialized list of floats
-
-
-def get_default_remote_info():
- return RemoteInfo.objects.get_or_create(
- address="default",
- mac_address="default",
- password="default",
- user="default",
- management_type="default",
- versions="[default]"
- )[0].pk
-
-
-# Concrete host, actual machine in a lab
-class Host(Resource):
- template = models.ForeignKey(GenericHost, on_delete=models.SET_NULL, null=True)
- booked = models.BooleanField(default=False)
- name = models.CharField(max_length=200, unique=True)
- bundle = models.ForeignKey(ResourceBundle, related_name='hosts', on_delete=models.SET_NULL, null=True)
- config = models.ForeignKey(HostConfiguration, null=True, related_name="configuration", on_delete=models.SET_NULL)
- labid = models.CharField(max_length=200, default="default_id")
- profile = models.ForeignKey(HostProfile, on_delete=models.CASCADE)
- lab = models.ForeignKey(Lab, on_delete=models.CASCADE)
- working = models.BooleanField(default=True)
- vendor = models.CharField(max_length=100, default="unknown")
- model = models.CharField(max_length=150, default="unknown")
- remote_management = models.ForeignKey(RemoteInfo, default=get_default_remote_info, on_delete=models.SET(get_default_remote_info))
-
- def __str__(self):
- return self.name
-
- def get_configuration(self, state):
- ipmi = state == ConfigState.NEW
- power = "off" if state == ConfigState.CLEAN else "on"
-
- return {
- "id": self.labid,
- "image": self.config.image.lab_id,
- "hostname": self.template.resource.name,
- "power": power,
- "ipmi_create": str(ipmi)
- }
-
- def release(self):
- self.booked = False
- self.save()
-
- def get_interfaces(self):
- return list(self.interfaces.all().order_by('bus_address'))
+class ResourceOPNFVConfig(models.Model):
+ role = models.ForeignKey(OPNFVRole, related_name="resource_opnfv_configs", on_delete=models.CASCADE)
+ resource_config = models.ForeignKey(ResourceConfiguration, related_name="resource_opnfv_config", on_delete=models.CASCADE)
+ opnfv_config = models.ForeignKey(OPNFVConfig, related_name="resource_opnfv_config", on_delete=models.CASCADE)
class Interface(models.Model):
@@ -436,15 +494,33 @@ class Interface(models.Model):
mac_address = models.CharField(max_length=17)
bus_address = models.CharField(max_length=50)
config = models.ManyToManyField(Vlan)
- host = models.ForeignKey(Host, on_delete=models.CASCADE, related_name='interfaces')
+ acts_as = models.OneToOneField(InterfaceConfiguration, null=True, on_delete=models.SET_NULL)
profile = models.ForeignKey(InterfaceProfile, on_delete=models.CASCADE)
def __str__(self):
return self.mac_address + " on host " + str(self.host)
+"""
+Some Enums for dealing with global constants.
+"""
+
+
class OPNFV_SETTINGS():
"""This is a static configuration class."""
# all the required network types in PDF/IDF spec
NETWORK_ROLES = ["public", "private", "admin", "mgmt"]
+
+
+class ConfigState:
+ NEW = 0
+ RESET = 100
+ CLEAN = 200
+
+
+RESOURCE_TYPES = [Server]
+
+
+class ResourceQuery(AbstractModelQuery):
+ model_list = [Server]
diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py
index 51e3746..6844b09 100644
--- a/src/resource_inventory/pdf_templater.py
+++ b/src/resource_inventory/pdf_templater.py
@@ -10,7 +10,7 @@
from django.template.loader import render_to_string
import booking
-from resource_inventory.models import Host, InterfaceProfile
+from resource_inventory.models import Server, InterfaceProfile
class PDFTemplater:
@@ -66,7 +66,7 @@ class PDFTemplater:
)
jumphost = booking.resource.hosts.get(config=jumphost_opnfv_config.host_config)
else: # if there is no opnfv config, use headnode
- jumphost = Host.objects.filter(
+ jumphost = Server.objects.filter(
bundle=booking.resource,
config__is_head_node=True
).first()
@@ -85,7 +85,7 @@ class PDFTemplater:
def get_pdf_nodes(cls, booking):
"""Return a list of all the "nodes" (every host except jumphost)."""
pdf_nodes = []
- nodes = set(Host.objects.filter(bundle=booking.resource))
+ nodes = set(Server.objects.filter(bundle=booking.resource))
nodes.discard(cls.get_jumphost(booking))
for node in nodes:
diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py
index 242d21a..c8b2b05 100644
--- a/src/resource_inventory/resource_manager.py
+++ b/src/resource_inventory/resource_manager.py
@@ -7,18 +7,13 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
import re
+from django.db.models import Q
+
+from dashboard.exceptions import ResourceAvailabilityException
-from dashboard.exceptions import (
- ResourceExistenceException,
- ResourceAvailabilityException,
- ResourceProvisioningException,
- ModelValidationException,
-)
from resource_inventory.models import (
- Host,
- HostConfiguration,
ResourceBundle,
- HostProfile,
+ ResourceTemplate,
Network,
Vlan,
PhysicalNetwork,
@@ -38,32 +33,27 @@ class ResourceManager:
ResourceManager.instance = ResourceManager()
return ResourceManager.instance
- def getAvailableHostTypes(self, lab):
- hostset = Host.objects.filter(lab=lab).filter(booked=False).filter(working=True)
- hostprofileset = HostProfile.objects.filter(host__in=hostset, labs=lab)
- return set(hostprofileset)
+ def getAvailableResourceTemplates(self, lab, user):
+ templates = ResourceTemplate.objects.filter(lab=lab)
+ templates.filter(Q(owner=user) | Q(public=True))
+ return templates
- def hostsAvailable(self, grb):
+ def templateIsReservable(self, resource_template):
"""
- Check if the given GenericResourceBundle is available.
+ Check if the required resources to reserve this template is available.
No changes to the database
"""
# count up hosts
profile_count = {}
- for host in grb.getResources():
- if host.profile not in profile_count:
- profile_count[host.profile] = 0
- profile_count[host.profile] += 1
+ for config in resource_template.getConfigs():
+ if config.profile not in profile_count:
+ profile_count[config.profile] = 0
+ profile_count[config.profile] += 1
# check that all required hosts are available
for profile in profile_count.keys():
- available = Host.objects.filter(
- booked=False,
- working=True,
- lab=grb.lab,
- profile=profile
- ).count()
+ available = len(profile.get_resources(lab=resource_template.lab, unreserved=True))
needed = profile_count[profile]
if available < needed:
return False
@@ -71,8 +61,8 @@ class ResourceManager:
# public interface
def deleteResourceBundle(self, resourceBundle):
- for host in Host.objects.filter(bundle=resourceBundle):
- host.release()
+ for resource in resourceBundle.get_resources():
+ resource.release()
resourceBundle.delete()
def get_vlans(self, genericResourceBundle):
@@ -89,43 +79,32 @@ class ResourceManager:
networks[network.name] = vlan
return networks
- def convertResourceBundle(self, genericResourceBundle, config=None):
+ def instantiateTemplate(self, resource_template, config=None):
"""
- Convert a GenericResourceBundle into a ResourceBundle.
+ Convert a ResourceTemplate into a ResourceBundle.
- Takes in a genericResourceBundle and reserves all the
+ Takes in a ResourceTemplate and reserves all the
Resources needed and returns a completed ResourceBundle.
"""
- resource_bundle = ResourceBundle.objects.create(template=genericResourceBundle)
- generic_hosts = genericResourceBundle.getResources()
- physical_hosts = []
+ resource_bundle = ResourceBundle.objects.create(template=resource_template)
+ res_configs = resource_template.getConfigs()
+ resources = []
- vlan_map = self.get_vlans(genericResourceBundle)
+ vlan_map = self.get_vlans(resource_template)
- for generic_host in generic_hosts:
- host_config = None
- if config:
- host_config = HostConfiguration.objects.get(bundle=config, host=generic_host)
- 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")
+ for config in res_configs:
try:
- physical_host.bundle = resource_bundle
- physical_host.template = generic_host
- physical_host.config = host_config
- physical_hosts.append(physical_host)
-
- self.configureNetworking(physical_host, vlan_map)
- except Exception:
- self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle)
- raise ResourceProvisioningException("Network configuration failed.")
- try:
- physical_host.save()
- except Exception:
- self.fail_acquire(physical_hosts, vlan_map, genericResourceBundle)
- raise ModelValidationException("Saving hosts failed")
+ phys_res = self.acquireHost(config)
+ phys_res.bundle = resource_bundle
+ phys_res.config = config
+ resources.append(phys_res)
+
+ self.configureNetworking(phys_res, vlan_map)
+ phys_res.save()
+
+ except Exception as e:
+ self.fail_acquire(resources, vlan_map, resource_template)
+ raise e
return resource_bundle
@@ -149,30 +128,27 @@ class ResourceManager:
)
# private interface
- def acquireHost(self, genericHost, labName):
- host_full_set = Host.objects.filter(lab__name__exact=labName, profile=genericHost.profile)
- if not host_full_set.first():
- raise ResourceExistenceException("No matching servers found")
- host_set = host_full_set.filter(booked=False, working=True)
- if not host_set.first():
- raise ResourceAvailabilityException("No unbooked hosts match requested hosts")
- host = host_set.first()
- host.booked = True
- host.template = genericHost
- host.save()
- return host
-
- def releaseNetworks(self, grb, vlan_manager, vlans):
+ def acquireHost(self, resource_config):
+ resources = resource_config.profile.get_resources(lab=resource_config.lab, unreserved=True)
+ try:
+ resource = resources[0] # TODO: should we randomize and 'load balance' the servers?
+ resource.config = resource_config
+ resource.reserve()
+ return resource
+ except IndexError:
+ raise ResourceAvailabilityException("No available resources of requested type")
+
+ def releaseNetworks(self, template, vlans):
+ vlan_manager = template.lab.vlan_manager
for net_name, vlan_id in vlans.items():
- net = Network.objects.get(name=net_name, bundle=grb)
+ net = Network.objects.get(name=net_name, bundle=template)
if(net.is_public):
vlan_manager.release_public_vlan(vlan_id)
else:
vlan_manager.release_vlans(vlan_id)
- def fail_acquire(self, hosts, vlans, grb):
- vlan_manager = grb.lab.vlan_manager
- self.releaseNetworks(grb, vlan_manager, vlans)
+ def fail_acquire(self, hosts, vlans, template):
+ self.releaseNetworks(template, vlans)
for host in hosts:
host.release()
diff --git a/src/resource_inventory/views.py b/src/resource_inventory/views.py
index 8c3d899..52f8c75 100644
--- a/src/resource_inventory/views.py
+++ b/src/resource_inventory/views.py
@@ -12,7 +12,7 @@ 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
+from resource_inventory.models import ResourceProfile, ResourceQuery
class HostView(TemplateView):
@@ -20,13 +20,13 @@ class HostView(TemplateView):
def get_context_data(self, **kwargs):
context = super(HostView, self).get_context_data(**kwargs)
- hosts = Host.objects.filter(working=True)
+ hosts = ResourceQuery.filter(working=True)
context.update({'hosts': hosts, 'title': "Hardware Resources"})
return context
def hostprofile_detail_view(request, hostprofile_id):
- hostprofile = get_object_or_404(HostProfile, id=hostprofile_id)
+ hostprofile = get_object_or_404(ResourceProfile, id=hostprofile_id)
return render(
request,