summaryrefslogtreecommitdiffstats
path: root/dashboard/src/api
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src/api')
-rw-r--r--dashboard/src/api/__init__.py2
-rw-r--r--dashboard/src/api/admin.py41
-rw-r--r--dashboard/src/api/migrations/0001_initial.py185
-rw-r--r--dashboard/src/api/migrations/0002_remove_job_delta.py17
-rw-r--r--dashboard/src/api/migrations/0003_auto_20190102_1956.py18
-rw-r--r--dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py42
-rw-r--r--dashboard/src/api/migrations/0005_snapshotconfig_delta.py18
-rw-r--r--dashboard/src/api/migrations/0006_auto_20190313_1729.py23
-rw-r--r--dashboard/src/api/migrations/0007_auto_20190417_1511.py25
-rw-r--r--dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py20
-rw-r--r--dashboard/src/api/migrations/0008_auto_20190419_1414.py28
-rw-r--r--dashboard/src/api/migrations/0009_merge_20190508_1317.py14
-rw-r--r--dashboard/src/api/migrations/__init__.py4
-rw-r--r--dashboard/src/api/models.py1015
-rw-r--r--dashboard/src/api/serializers.py53
-rw-r--r--dashboard/src/api/serializers/__init__.py8
-rw-r--r--dashboard/src/api/serializers/booking_serializer.py175
-rw-r--r--dashboard/src/api/serializers/old_serializers.py21
-rw-r--r--dashboard/src/api/tests/__init__.py8
-rw-r--r--dashboard/src/api/tests/test_models_unittest.py269
-rw-r--r--dashboard/src/api/urls.py36
-rw-r--r--dashboard/src/api/views.py139
22 files changed, 2076 insertions, 85 deletions
diff --git a/dashboard/src/api/__init__.py b/dashboard/src/api/__init__.py
index b5914ce..b6fef6c 100644
--- a/dashboard/src/api/__init__.py
+++ b/dashboard/src/api/__init__.py
@@ -6,5 +6,3 @@
# which accompanies this distribution, and is available at
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-
-
diff --git a/dashboard/src/api/admin.py b/dashboard/src/api/admin.py
new file mode 100644
index 0000000..8b2fcb3
--- /dev/null
+++ b/dashboard/src/api/admin.py
@@ -0,0 +1,41 @@
+##############################################################################
+# 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
+from django.contrib import admin
+
+from api.models import (
+ Job,
+ OpnfvApiConfig,
+ HardwareConfig,
+ NetworkConfig,
+ SoftwareConfig,
+ AccessConfig,
+ AccessRelation,
+ SoftwareRelation,
+ HostHardwareRelation,
+ HostNetworkRelation,
+)
+
+
+class ApiConfig(AppConfig):
+ name = 'apiJobs'
+
+
+admin.site.register(Job)
+admin.site.register(OpnfvApiConfig)
+admin.site.register(HardwareConfig)
+admin.site.register(NetworkConfig)
+admin.site.register(SoftwareConfig)
+admin.site.register(AccessConfig)
+admin.site.register(AccessRelation)
+admin.site.register(SoftwareRelation)
+admin.site.register(HostHardwareRelation)
+admin.site.register(HostNetworkRelation)
diff --git a/dashboard/src/api/migrations/0001_initial.py b/dashboard/src/api/migrations/0001_initial.py
new file mode 100644
index 0000000..abe6f5e
--- /dev/null
+++ b/dashboard/src/api/migrations/0001_initial.py
@@ -0,0 +1,185 @@
+##############################################################################
+# 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
+##############################################################################
+# Generated by Django 2.1 on 2018-09-14 14:48
+
+import api.models
+from django.conf import settings
+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),
+ ('booking', '__first__'),
+ ('resource_inventory', '__first__'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='AccessRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='HostHardwareRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Host')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='HostNetworkRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ('host', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Host')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='Job',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('delta', models.TextField()),
+ ('complete', models.BooleanField(default=False)),
+ ('booking', models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='OpnfvApiConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('installer', models.CharField(max_length=100)),
+ ('scenario', models.CharField(max_length=100)),
+ ('delta', models.TextField()),
+ ('roles', models.ManyToManyField(to='resource_inventory.Host')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='SoftwareRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ migrations.CreateModel(
+ name='TaskConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ],
+ ),
+ migrations.CreateModel(
+ name='AccessConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('access_type', models.CharField(max_length=50)),
+ ('revoke', models.BooleanField(default=False)),
+ ('context', models.TextField(default='')),
+ ('delta', models.TextField()),
+ ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.CreateModel(
+ name='HardwareConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('image', models.CharField(default='defimage', max_length=100)),
+ ('power', models.CharField(default='off', max_length=100)),
+ ('hostname', models.CharField(default='hostname', max_length=100)),
+ ('ipmi_create', models.BooleanField(default=False)),
+ ('delta', models.TextField()),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.CreateModel(
+ name='NetworkConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('delta', models.TextField()),
+ ('interfaces', models.ManyToManyField(to='resource_inventory.Interface')),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.CreateModel(
+ name='SoftwareConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('opnfv', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.OpnfvApiConfig')),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.AddField(
+ model_name='hostnetworkrelation',
+ name='job',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'),
+ ),
+ migrations.AddField(
+ model_name='hosthardwarerelation',
+ name='job',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'),
+ ),
+ migrations.AddField(
+ model_name='accessrelation',
+ name='job',
+ field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job'),
+ ),
+ migrations.AddField(
+ model_name='softwarerelation',
+ name='config',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.SoftwareConfig'),
+ ),
+ migrations.AddField(
+ model_name='hostnetworkrelation',
+ name='config',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.NetworkConfig'),
+ ),
+ migrations.AddField(
+ model_name='hosthardwarerelation',
+ name='config',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.HardwareConfig'),
+ ),
+ migrations.AddField(
+ model_name='accessrelation',
+ name='config',
+ field=models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.AccessConfig'),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0002_remove_job_delta.py b/dashboard/src/api/migrations/0002_remove_job_delta.py
new file mode 100644
index 0000000..157a40f
--- /dev/null
+++ b/dashboard/src/api/migrations/0002_remove_job_delta.py
@@ -0,0 +1,17 @@
+# Generated by Django 2.1 on 2018-10-17 15:32
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='job',
+ name='delta',
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0003_auto_20190102_1956.py b/dashboard/src/api/migrations/0003_auto_20190102_1956.py
new file mode 100644
index 0000000..2ea5d70
--- /dev/null
+++ b/dashboard/src/api/migrations/0003_auto_20190102_1956.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1 on 2019-01-02 19:56
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0002_remove_job_delta'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='accessconfig',
+ name='delta',
+ field=models.TextField(default='{}'),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py b/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py
new file mode 100644
index 0000000..62bc7af
--- /dev/null
+++ b/dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py
@@ -0,0 +1,42 @@
+# Generated by Django 2.1 on 2019-01-17 15:54
+
+import api.models
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0004_auto_20181017_1532'),
+ ('api', '0003_auto_20190102_1956'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='SnapshotConfig',
+ fields=[
+ ('taskconfig_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='api.TaskConfig')),
+ ('image', models.IntegerField(null=True)),
+ ('dashboard_id', models.IntegerField()),
+ ('host', models.ForeignKey(null=True, on_delete=django.db.models.deletion.DO_NOTHING, to='resource_inventory.Host')),
+ ],
+ bases=('api.taskconfig',),
+ ),
+ migrations.CreateModel(
+ name='SnapshotRelation',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('status', models.IntegerField(default=0)),
+ ('task_id', models.CharField(default=api.models.get_task_uuid, max_length=37)),
+ ('lab_token', models.CharField(default='null', max_length=50)),
+ ('message', models.TextField(default='')),
+ ('config', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='api.SnapshotConfig')),
+ ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')),
+ ('snapshot', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.Image')),
+ ],
+ options={
+ 'abstract': False,
+ },
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0005_snapshotconfig_delta.py b/dashboard/src/api/migrations/0005_snapshotconfig_delta.py
new file mode 100644
index 0000000..559af90
--- /dev/null
+++ b/dashboard/src/api/migrations/0005_snapshotconfig_delta.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.1 on 2019-01-17 16:07
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0004_snapshotconfig_snapshotrelation'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='snapshotconfig',
+ name='delta',
+ field=models.TextField(default='{}'),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0006_auto_20190313_1729.py b/dashboard/src/api/migrations/0006_auto_20190313_1729.py
new file mode 100644
index 0000000..ec148bd
--- /dev/null
+++ b/dashboard/src/api/migrations/0006_auto_20190313_1729.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.1 on 2019-03-13 17:29
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0005_snapshotconfig_delta'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='opnfvapiconfig',
+ name='installer',
+ field=models.CharField(max_length=200),
+ ),
+ migrations.AlterField(
+ model_name='opnfvapiconfig',
+ name='scenario',
+ field=models.CharField(max_length=300),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0007_auto_20190417_1511.py b/dashboard/src/api/migrations/0007_auto_20190417_1511.py
new file mode 100644
index 0000000..e7d2c59
--- /dev/null
+++ b/dashboard/src/api/migrations/0007_auto_20190417_1511.py
@@ -0,0 +1,25 @@
+# Generated by Django 2.1 on 2019-04-17 15:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0006_auto_20190313_1729'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='opnfvapiconfig',
+ name='idf',
+ field=models.CharField(default='', max_length=100),
+ preserve_default=False,
+ ),
+ migrations.AddField(
+ model_name='opnfvapiconfig',
+ name='pdf',
+ field=models.CharField(default='', max_length=100),
+ preserve_default=False,
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py b/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py
new file mode 100644
index 0000000..46f3631
--- /dev/null
+++ b/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py
@@ -0,0 +1,20 @@
+# Generated by Django 2.1 on 2019-05-01 18:53
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0010_auto_20190430_1405'),
+ ('api', '0006_auto_20190313_1729'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='opnfvapiconfig',
+ name='opnfv_config',
+ field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0008_auto_20190419_1414.py b/dashboard/src/api/migrations/0008_auto_20190419_1414.py
new file mode 100644
index 0000000..03c3865
--- /dev/null
+++ b/dashboard/src/api/migrations/0008_auto_20190419_1414.py
@@ -0,0 +1,28 @@
+# Generated by Django 2.1 on 2019-04-19 14:14
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0009_auto_20190315_1757'),
+ ('api', '0007_auto_20190417_1511'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='BridgeConfig',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('interfaces', models.ManyToManyField(to='resource_inventory.Interface')),
+ ('opnfv_config', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.OPNFVConfig')),
+ ],
+ ),
+ migrations.AddField(
+ model_name='opnfvapiconfig',
+ name='bridge_config',
+ field=models.OneToOneField(null=True, on_delete=django.db.models.deletion.CASCADE, to='api.BridgeConfig'),
+ ),
+ ]
diff --git a/dashboard/src/api/migrations/0009_merge_20190508_1317.py b/dashboard/src/api/migrations/0009_merge_20190508_1317.py
new file mode 100644
index 0000000..1a34380
--- /dev/null
+++ b/dashboard/src/api/migrations/0009_merge_20190508_1317.py
@@ -0,0 +1,14 @@
+# Generated by Django 2.1 on 2019-05-08 13:17
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('api', '0008_auto_20190419_1414'),
+ ('api', '0007_opnfvapiconfig_opnfv_config'),
+ ]
+
+ operations = [
+ ]
diff --git a/dashboard/src/api/migrations/__init__.py b/dashboard/src/api/migrations/__init__.py
index b5914ce..e0408fa 100644
--- a/dashboard/src/api/migrations/__init__.py
+++ b/dashboard/src/api/migrations/__init__.py
@@ -1,10 +1,8 @@
##############################################################################
-# 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
##############################################################################
-
-
diff --git a/dashboard/src/api/models.py b/dashboard/src/api/models.py
new file mode 100644
index 0000000..1f708ae
--- /dev/null
+++ b/dashboard/src/api/models.py
@@ -0,0 +1,1015 @@
+##############################################################################
+# 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.exceptions import PermissionDenied
+from django.shortcuts import get_object_or_404
+from django.urls import reverse
+
+import json
+import uuid
+
+from booking.models import Booking
+from resource_inventory.models import (
+ Lab,
+ HostProfile,
+ Host,
+ Image,
+ Interface,
+ HostOPNFVConfig,
+ RemoteInfo,
+ OPNFVConfig
+)
+from resource_inventory.idf_templater import IDFTemplater
+from resource_inventory.pdf_templater import PDFTemplater
+
+
+class JobStatus(object):
+ NEW = 0
+ CURRENT = 100
+ DONE = 200
+ ERROR = 300
+
+
+class LabManagerTracker(object):
+
+ @classmethod
+ def get(cls, lab_name, token):
+ """
+ Takes in a lab name (from a url path)
+ returns a lab manager instance for that lab, if it exists
+ """
+ try:
+ lab = Lab.objects.get(name=lab_name)
+ except Exception:
+ raise PermissionDenied("Lab not found")
+ if lab.api_token == token:
+ return LabManager(lab)
+ raise PermissionDenied("Lab not authorized")
+
+
+class LabManager(object):
+ """
+ This is the class that will ultimately handle all REST calls to
+ lab endpoints.
+ handles jobs, inventory, status, etc
+ may need to create helper classes
+ """
+
+ def __init__(self, lab):
+ self.lab = lab
+
+ def update_host_remote_info(self, data, host_id):
+ host = get_object_or_404(Host, labid=host_id, lab=self.lab)
+ info = {}
+ try:
+ info['address'] = data['address']
+ info['mac_address'] = data['mac_address']
+ info['password'] = data['password']
+ info['user'] = data['user']
+ info['type'] = data['type']
+ info['versions'] = json.dumps(data['versions'])
+ except Exception as e:
+ return {"error": "invalid arguement: " + str(e)}
+ remote_info = host.remote_management
+ if "default" in remote_info.mac_address:
+ remote_info = RemoteInfo()
+ remote_info.address = info['address']
+ remote_info.mac_address = info['mac_address']
+ remote_info.password = info['password']
+ remote_info.user = info['user']
+ remote_info.type = info['type']
+ remote_info.versions = info['versions']
+ remote_info.save()
+ host.remote_management = remote_info
+ host.save()
+ booking = Booking.objects.get(resource=host.bundle)
+ self.update_xdf(booking)
+ return {"status": "success"}
+
+ def update_xdf(self, booking):
+ booking.pdf = PDFTemplater.makePDF(booking)
+ booking.idf = IDFTemplater().makeIDF(booking)
+ booking.save()
+
+ def get_pdf(self, booking_id):
+ booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
+ return booking.pdf
+
+ def get_idf(self, booking_id):
+ booking = get_object_or_404(Booking, pk=booking_id, lab=self.lab)
+ return booking.idf
+
+ def get_profile(self):
+ prof = {}
+ prof['name'] = self.lab.name
+ prof['contact'] = {
+ "phone": self.lab.contact_phone,
+ "email": self.lab.contact_email
+ }
+ prof['host_count'] = []
+ for host in HostProfile.objects.filter(labs=self.lab):
+ count = Host.objects.filter(profile=host, lab=self.lab).count()
+ prof['host_count'].append(
+ {
+ "type": host.name,
+ "count": count
+ }
+ )
+ return prof
+
+ def get_inventory(self):
+ inventory = {}
+ hosts = Host.objects.filter(lab=self.lab)
+ images = Image.objects.filter(from_lab=self.lab)
+ profiles = HostProfile.objects.filter(labs=self.lab)
+ inventory['hosts'] = self.serialize_hosts(hosts)
+ inventory['images'] = self.serialize_images(images)
+ inventory['host_types'] = self.serialize_host_profiles(profiles)
+ return inventory
+
+ def get_host(self, hostname):
+ host = get_object_or_404(Host, labid=hostname, lab=self.lab)
+ return {
+ "booked": host.booked,
+ "working": host.working,
+ "type": host.profile.name
+ }
+
+ def update_host(self, hostname, data):
+ host = get_object_or_404(Host, labid=hostname, lab=self.lab)
+ if "working" in data:
+ working = data['working'] == "true"
+ host.working = working
+ host.save()
+ return self.get_host(hostname)
+
+ def get_status(self):
+ return {"status": self.lab.status}
+
+ def set_status(self, payload):
+ {}
+
+ def get_current_jobs(self):
+ jobs = Job.objects.filter(booking__lab=self.lab)
+
+ return self.serialize_jobs(jobs, status=JobStatus.CURRENT)
+
+ def get_new_jobs(self):
+ jobs = Job.objects.filter(booking__lab=self.lab)
+
+ return self.serialize_jobs(jobs, status=JobStatus.NEW)
+
+ def get_done_jobs(self):
+ jobs = Job.objects.filter(booking__lab=self.lab)
+
+ return self.serialize_jobs(jobs, status=JobStatus.DONE)
+
+ def get_job(self, jobid):
+ return Job.objects.get(pk=jobid).to_dict()
+
+ def update_job(self, jobid, data):
+ {}
+
+ def serialize_jobs(self, jobs, status=JobStatus.NEW):
+ job_ser = []
+ for job in jobs:
+ jsonized_job = job.get_delta(status)
+ if len(jsonized_job['payload']) < 1:
+ continue
+ job_ser.append(jsonized_job)
+
+ return job_ser
+
+ def serialize_hosts(self, hosts):
+ host_ser = []
+ for host in hosts:
+ h = {}
+ h['interfaces'] = []
+ h['hostname'] = host.name
+ h['host_type'] = host.profile.name
+ for iface in host.interfaces.all():
+ eth = {}
+ eth['mac'] = iface.mac_address
+ eth['busaddr'] = iface.bus_address
+ eth['name'] = iface.name
+ eth['switchport'] = {"switch_name": iface.switch_name, "port_name": iface.port_name}
+ h['interfaces'].append(eth)
+ return host_ser
+
+ def serialize_images(self, images):
+ images_ser = []
+ for image in images:
+ images_ser.append(
+ {
+ "name": image.name,
+ "lab_id": image.lab_id,
+ "dashboard_id": image.id
+ }
+ )
+ return images_ser
+
+ def serialize_host_profiles(self, profiles):
+ profile_ser = []
+ for profile in profiles:
+ p = {}
+ p['cpu'] = {
+ "cores": profile.cpuprofile.first().cores,
+ "arch": profile.cpuprofile.first().architecture,
+ "cpus": profile.cpuprofile.first().cpus,
+ }
+ p['disks'] = []
+ for disk in profile.storageprofile.all():
+ d = {
+ "size": disk.size,
+ "type": disk.media_type,
+ "name": disk.name
+ }
+ p['disks'].append(d)
+ p['description'] = profile.description
+ p['interfaces'] = []
+ for iface in profile.interfaceprofile.all():
+ p['interfaces'].append(
+ {
+ "speed": iface.speed,
+ "name": iface.name
+ }
+ )
+
+ p['ram'] = {"amount": profile.ramprofile.first().amount}
+ p['name'] = profile.name
+ profile_ser.append(p)
+ return profile_ser
+
+
+class Job(models.Model):
+ """
+ This is the class that is serialized and put into the api
+ """
+ booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True)
+ status = models.IntegerField(default=JobStatus.NEW)
+ complete = models.BooleanField(default=False)
+
+ def to_dict(self):
+ d = {}
+ j = {}
+ j['id'] = self.id
+ for relation in AccessRelation.objects.filter(job=self):
+ if 'access' not in d:
+ d['access'] = {}
+ d['access'][relation.task_id] = relation.config.to_dict()
+ for relation in SoftwareRelation.objects.filter(job=self):
+ if 'software' not in d:
+ d['software'] = {}
+ d['software'][relation.task_id] = relation.config.to_dict()
+ for relation in HostHardwareRelation.objects.filter(job=self):
+ if 'hardware' not in d:
+ d['hardware'] = {}
+ d['hardware'][relation.task_id] = relation.config.to_dict()
+ for relation in HostNetworkRelation.objects.filter(job=self):
+ if 'network' not in d:
+ d['network'] = {}
+ d['network'][relation.task_id] = relation.config.to_dict()
+ for relation in SnapshotRelation.objects.filter(job=self):
+ if 'snapshot' not in d:
+ d['snapshot'] = {}
+ d['snapshot'][relation.task_id] = relation.config.to_dict()
+
+ j['payload'] = d
+
+ return j
+
+ def get_tasklist(self, status="all"):
+ tasklist = []
+ clist = [
+ HostHardwareRelation,
+ AccessRelation,
+ HostNetworkRelation,
+ SoftwareRelation,
+ SnapshotRelation
+ ]
+ if status == "all":
+ for cls in clist:
+ tasklist += list(cls.objects.filter(job=self))
+ else:
+ for cls in clist:
+ tasklist += list(cls.objects.filter(job=self).filter(status=status))
+ return tasklist
+
+ def is_fulfilled(self):
+ """
+ This method should return true if all of the job's tasks are done,
+ and false otherwise
+ """
+ my_tasks = self.get_tasklist()
+ for task in my_tasks:
+ if task.status != JobStatus.DONE:
+ return False
+ return True
+
+ def get_delta(self, status):
+ d = {}
+ j = {}
+ j['id'] = self.id
+ for relation in AccessRelation.objects.filter(job=self).filter(status=status):
+ if 'access' not in d:
+ d['access'] = {}
+ d['access'][relation.task_id] = relation.config.get_delta()
+ for relation in SoftwareRelation.objects.filter(job=self).filter(status=status):
+ if 'software' not in d:
+ d['software'] = {}
+ d['software'][relation.task_id] = relation.config.get_delta()
+ for relation in HostHardwareRelation.objects.filter(job=self).filter(status=status):
+ if 'hardware' not in d:
+ d['hardware'] = {}
+ d['hardware'][relation.task_id] = relation.config.get_delta()
+ for relation in HostNetworkRelation.objects.filter(job=self).filter(status=status):
+ if 'network' not in d:
+ d['network'] = {}
+ d['network'][relation.task_id] = relation.config.get_delta()
+ for relation in SnapshotRelation.objects.filter(job=self).filter(status=status):
+ if 'snapshot' not in d:
+ d['snapshot'] = {}
+ d['snapshot'][relation.task_id] = relation.config.get_delta()
+
+ j['payload'] = d
+ return j
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+
+class TaskConfig(models.Model):
+ def to_dict(self):
+ pass
+
+ def get_delta(self):
+ pass
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def clear_delta(self):
+ self.delta = '{}'
+
+
+class BridgeConfig(models.Model):
+ """
+ Displays mapping between jumphost interfaces and
+ bridges
+ """
+ interfaces = models.ManyToManyField(Interface)
+ opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.CASCADE)
+
+ def to_dict(self):
+ d = {}
+ hid = self.interfaces.first().host.labid
+ d[hid] = {}
+ for interface in self.interfaces.all():
+ d[hid][interface.mac_address] = []
+ for vlan in interface.config.all():
+ network_role = self.opnfv_model.networks().filter(network=vlan.network)
+ bridge = IDFTemplater.bridge_names[network_role.name]
+ br_config = {
+ "vlan_id": vlan.vlan_id,
+ "tagged": vlan.tagged,
+ "bridge": bridge
+ }
+ d[hid][interface.mac_address].append(br_config)
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+
+class OpnfvApiConfig(models.Model):
+
+ installer = models.CharField(max_length=200)
+ scenario = models.CharField(max_length=300)
+ roles = models.ManyToManyField(Host)
+ # pdf and idf are url endpoints, not the actual file
+ pdf = models.CharField(max_length=100)
+ idf = models.CharField(max_length=100)
+ bridge_config = models.OneToOneField(BridgeConfig, on_delete=models.CASCADE, null=True)
+ delta = models.TextField()
+ opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL)
+
+ def to_dict(self):
+ d = {}
+ if not self.opnfv_config:
+ return d
+ if self.installer:
+ d['installer'] = self.installer
+ if self.scenario:
+ d['scenario'] = self.scenario
+ if self.pdf:
+ d['pdf'] = self.pdf
+ if self.idf:
+ d['idf'] = self.idf
+ if self.bridge_config:
+ d['bridged_interfaces'] = self.bridge_config.to_dict()
+
+ hosts = self.roles.all()
+ if hosts.exists():
+ d['roles'] = []
+ for host in hosts:
+ d['roles'].append({
+ host.labid: self.opnfv_config.host_opnfv_config.get(
+ host_config__pk=host.config.pk
+ ).role.name
+ })
+
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def set_installer(self, installer):
+ self.installer = installer
+ d = json.loads(self.delta)
+ d['installer'] = installer
+ self.delta = json.dumps(d)
+
+ def set_scenario(self, scenario):
+ self.scenario = scenario
+ d = json.loads(self.delta)
+ d['scenario'] = scenario
+ self.delta = json.dumps(d)
+
+ def set_xdf(self, booking, update_delta=True):
+ kwargs = {'lab_name': booking.lab.name, 'booking_id': booking.id}
+ self.pdf = reverse('get-pdf', kwargs=kwargs)
+ self.idf = reverse('get-idf', kwargs=kwargs)
+ if update_delta:
+ d = json.loads(self.delta)
+ d['pdf'] = self.pdf
+ d['idf'] = self.idf
+ self.delta = json.dumps(d)
+
+ def add_role(self, host):
+ self.roles.add(host)
+ d = json.loads(self.delta)
+ if 'role' not in d:
+ d['role'] = []
+ d['roles'].append({host.labid: host.config.opnfvRole.name})
+ self.delta = json.dumps(d)
+
+ def clear_delta(self):
+ self.delta = '{}'
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+ return json.loads(self.delta)
+
+
+class AccessConfig(TaskConfig):
+ access_type = models.CharField(max_length=50)
+ user = models.ForeignKey(User, on_delete=models.CASCADE)
+ revoke = models.BooleanField(default=False)
+ context = models.TextField(default="")
+ delta = models.TextField(default="{}")
+
+ def to_dict(self):
+ d = {}
+ d['access_type'] = self.access_type
+ d['user'] = self.user.id
+ d['revoke'] = self.revoke
+ try:
+ d['context'] = json.loads(self.context)
+ except Exception:
+ pass
+ return d
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+ d = json.loads(self.delta)
+ d["lab_token"] = self.accessrelation.lab_token
+
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def clear_delta(self):
+ d = {}
+ d["lab_token"] = self.accessrelation.lab_token
+ self.delta = json.dumps(d)
+
+ def set_access_type(self, access_type):
+ self.access_type = access_type
+ d = json.loads(self.delta)
+ d['access_type'] = access_type
+ self.delta = json.dumps(d)
+
+ def set_user(self, user):
+ self.user = user
+ d = json.loads(self.delta)
+ d['user'] = self.user.id
+ self.delta = json.dumps(d)
+
+ def set_revoke(self, revoke):
+ self.revoke = revoke
+ d = json.loads(self.delta)
+ d['revoke'] = revoke
+ self.delta = json.dumps(d)
+
+ def set_context(self, context):
+ self.context = json.dumps(context)
+ d = json.loads(self.delta)
+ d['context'] = context
+ self.delta = json.dumps(d)
+
+
+class SoftwareConfig(TaskConfig):
+ """
+ handled opnfv installations, etc
+ """
+ opnfv = models.ForeignKey(OpnfvApiConfig, on_delete=models.CASCADE)
+
+ def to_dict(self):
+ d = {}
+ if self.opnfv:
+ d['opnfv'] = self.opnfv.to_dict()
+
+ d["lab_token"] = self.softwarerelation.lab_token
+ self.delta = json.dumps(d)
+
+ return d
+
+ def get_delta(self):
+ d = {}
+ d['opnfv'] = self.opnfv.get_delta()
+ d['lab_token'] = self.softwarerelation.lab_token
+
+ return d
+
+ def clear_delta(self):
+ self.opnfv.clear_delta()
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+
+class HardwareConfig(TaskConfig):
+ """
+ handles imaging, user accounts, etc
+ """
+ image = models.CharField(max_length=100, default="defimage")
+ power = models.CharField(max_length=100, default="off")
+ hostname = models.CharField(max_length=100, default="hostname")
+ ipmi_create = models.BooleanField(default=False)
+ delta = models.TextField()
+
+ def to_dict(self):
+ d = {}
+ d['image'] = self.image
+ d['power'] = self.power
+ d['hostname'] = self.hostname
+ d['ipmi_create'] = str(self.ipmi_create)
+ d['id'] = self.hosthardwarerelation.host.labid
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+ d = json.loads(self.delta)
+ d['lab_token'] = self.hosthardwarerelation.lab_token
+ return d
+
+ def clear_delta(self):
+ d = {}
+ d["id"] = self.hosthardwarerelation.host.labid
+ d["lab_token"] = self.hosthardwarerelation.lab_token
+ self.delta = json.dumps(d)
+
+ def set_image(self, image):
+ self.image = image
+ d = json.loads(self.delta)
+ d['image'] = self.image
+ self.delta = json.dumps(d)
+
+ def set_power(self, power):
+ self.power = power
+ d = json.loads(self.delta)
+ d['power'] = power
+ self.delta = json.dumps(d)
+
+ def set_hostname(self, hostname):
+ self.hostname = hostname
+ d = json.loads(self.delta)
+ d['hostname'] = hostname
+ self.delta = json.dumps(d)
+
+ def set_ipmi_create(self, ipmi_create):
+ self.ipmi_create = ipmi_create
+ d = json.loads(self.delta)
+ d['ipmi_create'] = ipmi_create
+ self.delta = json.dumps(d)
+
+
+class NetworkConfig(TaskConfig):
+ """
+ handles network configuration
+ """
+ interfaces = models.ManyToManyField(Interface)
+ delta = models.TextField()
+
+ def to_dict(self):
+ d = {}
+ hid = self.hostnetworkrelation.host.labid
+ d[hid] = {}
+ for interface in self.interfaces.all():
+ d[hid][interface.mac_address] = []
+ for vlan in interface.config.all():
+ d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
+
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+ d = json.loads(self.delta)
+ d['lab_token'] = self.hostnetworkrelation.lab_token
+ return d
+
+ def clear_delta(self):
+ self.delta = json.dumps(self.to_dict())
+ self.save()
+
+ def add_interface(self, interface):
+ self.interfaces.add(interface)
+ d = json.loads(self.delta)
+ hid = self.hostnetworkrelation.host.labid
+ if hid not in d:
+ d[hid] = {}
+ d[hid][interface.mac_address] = []
+ for vlan in interface.config.all():
+ d[hid][interface.mac_address].append({"vlan_id": vlan.vlan_id, "tagged": vlan.tagged})
+ self.delta = json.dumps(d)
+
+
+class SnapshotConfig(TaskConfig):
+
+ host = models.ForeignKey(Host, null=True, on_delete=models.DO_NOTHING)
+ image = models.IntegerField(null=True)
+ dashboard_id = models.IntegerField()
+ delta = models.TextField(default="{}")
+
+ def to_dict(self):
+ d = {}
+ if self.host:
+ d['host'] = self.host.labid
+ if self.image:
+ d['image'] = self.image
+ d['dashboard_id'] = self.dashboard_id
+ return d
+
+ def to_json(self):
+ return json.dumps(self.to_dict())
+
+ def get_delta(self):
+ if not self.delta:
+ self.delta = self.to_json()
+ self.save()
+
+ d = json.loads(self.delta)
+ return d
+
+ def clear_delta(self):
+ self.delta = json.dumps(self.to_dict())
+ self.save()
+
+ def set_host(self, host):
+ self.host = host
+ d = json.loads(self.delta)
+ d['host'] = host.labid
+ self.delta = json.dumps(d)
+
+ def set_image(self, image):
+ self.image = image
+ d = json.loads(self.delta)
+ d['image'] = self.image
+ self.delta = json.dumps(d)
+
+ def clear_image(self):
+ self.image = None
+ d = json.loads(self.delta)
+ d.pop("image", None)
+ self.delta = json.dumps(d)
+
+ def set_dashboard_id(self, dash):
+ self.dashboard_id = dash
+ d = json.loads(self.delta)
+ d['dashboard_id'] = self.dashboard_id
+ self.delta = json.dumps(d)
+
+
+def get_task(task_id):
+ for taskclass in [AccessRelation, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, SnapshotRelation]:
+ try:
+ ret = taskclass.objects.get(task_id=task_id)
+ return ret
+ except taskclass.DoesNotExist:
+ pass
+ from django.core.exceptions import ObjectDoesNotExist
+ raise ObjectDoesNotExist("Could not find matching TaskRelation instance")
+
+
+def get_task_uuid():
+ return str(uuid.uuid4())
+
+
+class TaskRelation(models.Model):
+ status = models.IntegerField(default=JobStatus.NEW)
+ job = models.ForeignKey(Job, on_delete=models.CASCADE)
+ config = models.OneToOneField(TaskConfig, on_delete=models.CASCADE)
+ task_id = models.CharField(default=get_task_uuid, max_length=37)
+ lab_token = models.CharField(default="null", max_length=50)
+ message = models.TextField(default="")
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+ def type_str(self):
+ return "Generic Task"
+
+ class Meta:
+ abstract = True
+
+
+class AccessRelation(TaskRelation):
+ config = models.OneToOneField(AccessConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Access Task"
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
+class SoftwareRelation(TaskRelation):
+ config = models.OneToOneField(SoftwareConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Software Configuration Task"
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
+class HostHardwareRelation(TaskRelation):
+ host = models.ForeignKey(Host, on_delete=models.CASCADE)
+ config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Hardware Configuration Task"
+
+ def get_delta(self):
+ return self.config.to_dict()
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
+class HostNetworkRelation(TaskRelation):
+ host = models.ForeignKey(Host, on_delete=models.CASCADE)
+ config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Network Configuration Task"
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
+class SnapshotRelation(TaskRelation):
+ snapshot = models.ForeignKey(Image, on_delete=models.CASCADE)
+ config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE)
+
+ def type_str(self):
+ return "Snapshot Task"
+
+ def get_delta(self):
+ return self.config.to_dict()
+
+ def delete(self, *args, **kwargs):
+ self.config.delete()
+ return super(self.__class__, self).delete(*args, **kwargs)
+
+
+class JobFactory(object):
+
+ @classmethod
+ def reimageHost(cls, new_image, booking, host):
+ """
+ This method will make all necessary changes to make a lab
+ reimage a host.
+ """
+ job = Job.objects.get(booking=booking)
+ # make hardware task new
+ hardware_relation = HostHardwareRelation.objects.get(host=host, job=job)
+ hardware_relation.config.set_image(new_image.lab_id)
+ hardware_relation.config.save()
+ hardware_relation.status = JobStatus.NEW
+
+ # re-apply networking after host is reset
+ net_relation = HostNetworkRelation.objects.get(host=host, job=job)
+ net_relation.status = JobStatus.NEW
+
+ # re-apply ssh access after host is reset
+ for relation in AccessRelation.objects.filter(job=job, config__access_type="ssh"):
+ relation.status = JobStatus.NEW
+ relation.save()
+
+ hardware_relation.save()
+ net_relation.save()
+
+ @classmethod
+ def makeSnapshotTask(cls, image, booking, host):
+ relation = SnapshotRelation()
+ job = Job.objects.get(booking=booking)
+ config = SnapshotConfig.objects.create(dashboard_id=image.id)
+
+ relation.job = job
+ relation.config = config
+ relation.config.save()
+ relation.config = relation.config
+ relation.snapshot = image
+ relation.save()
+
+ config.clear_delta()
+ config.set_host(host)
+ config.save()
+
+ @classmethod
+ def makeCompleteJob(cls, booking):
+ hosts = Host.objects.filter(bundle=booking.resource)
+ job = None
+ try:
+ job = Job.objects.get(booking=booking)
+ except Exception:
+ job = Job.objects.create(status=JobStatus.NEW, booking=booking)
+ cls.makeHardwareConfigs(
+ hosts=hosts,
+ job=job
+ )
+ cls.makeNetworkConfigs(
+ hosts=hosts,
+ job=job
+ )
+ cls.makeSoftware(
+ booking=booking,
+ job=job
+ )
+ all_users = list(booking.collaborators.all())
+ all_users.append(booking.owner)
+ cls.makeAccessConfig(
+ users=all_users,
+ access_type="vpn",
+ revoke=False,
+ job=job
+ )
+ for user in all_users:
+ try:
+ cls.makeAccessConfig(
+ users=[user],
+ access_type="ssh",
+ revoke=False,
+ job=job,
+ context={
+ "key": user.userprofile.ssh_public_key.open().read().decode(encoding="UTF-8"),
+ "hosts": [host.labid for host in hosts]
+ }
+ )
+ except Exception:
+ continue
+
+ @classmethod
+ def makeHardwareConfigs(cls, hosts=[], job=Job()):
+ for host in hosts:
+ hardware_config = None
+ try:
+ hardware_config = HardwareConfig.objects.get(relation__host=host)
+ except Exception:
+ hardware_config = HardwareConfig()
+
+ relation = HostHardwareRelation()
+ relation.host = host
+ relation.job = job
+ relation.config = hardware_config
+ relation.config.save()
+ relation.config = relation.config
+ relation.save()
+
+ hardware_config.clear_delta()
+ hardware_config.set_image(host.config.image.lab_id)
+ hardware_config.set_hostname(host.template.resource.name)
+ hardware_config.set_power("on")
+ hardware_config.set_ipmi_create(True)
+ hardware_config.save()
+
+ @classmethod
+ def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
+ for user in users:
+ relation = AccessRelation()
+ relation.job = job
+ config = AccessConfig()
+ config.access_type = access_type
+ config.user = user
+ config.save()
+ relation.config = config
+ relation.save()
+ config.clear_delta()
+ if context:
+ config.set_context(context)
+ config.set_access_type(access_type)
+ config.set_revoke(revoke)
+ config.set_user(user)
+ config.save()
+
+ @classmethod
+ def makeNetworkConfigs(cls, hosts=[], job=Job()):
+ for host in hosts:
+ network_config = None
+ try:
+ network_config = NetworkConfig.objects.get(relation__host=host)
+ except Exception:
+ network_config = NetworkConfig.objects.create()
+
+ relation = HostNetworkRelation()
+ relation.host = host
+ relation.job = job
+ network_config.save()
+ relation.config = network_config
+ relation.save()
+ network_config.clear_delta()
+
+ for interface in host.interfaces.all():
+ network_config.add_interface(interface)
+ network_config.save()
+
+ @classmethod
+ def make_bridge_config(cls, booking):
+ if booking.resource.hosts.count() < 2:
+ return None
+ try:
+ jumphost_config = HostOPNFVConfig.objects.filter(
+ role__name__iexact="jumphost"
+ )
+ jumphost = Host.objects.get(
+ bundle=booking.resource,
+ config=jumphost_config.host_config
+ )
+ except Exception:
+ return None
+ br_config = BridgeConfig.objects.create(opnfv_config=booking.opnfv_config)
+ for iface in jumphost.interfaces.all():
+ br_config.interfaces.add(iface)
+ return br_config
+
+ @classmethod
+ def makeSoftware(cls, booking=None, job=Job()):
+
+ if not booking.opnfv_config:
+ return None
+
+ opnfv_api_config = OpnfvApiConfig.objects.create(
+ opnfv_config=booking.opnfv_config,
+ installer=booking.opnfv_config.installer.name,
+ scenario=booking.opnfv_config.scenario.name,
+ bridge_config=cls.make_bridge_config(booking)
+ )
+
+ opnfv_api_config.set_xdf(booking, False)
+ opnfv_api_config.save()
+
+ for host in booking.resource.hosts.all():
+ opnfv_api_config.roles.add(host)
+ software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config)
+ software_relation = SoftwareRelation.objects.create(job=job, config=software_config)
+ return software_relation
diff --git a/dashboard/src/api/serializers.py b/dashboard/src/api/serializers.py
deleted file mode 100644
index 10e1975..0000000
--- a/dashboard/src/api/serializers.py
+++ /dev/null
@@ -1,53 +0,0 @@
-##############################################################################
-# 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 rest_framework import serializers
-
-from account.models import UserProfile
-from notifier.models import Notifier
-from booking.models import Booking
-from dashboard.models import Server, Resource, ResourceStatus
-
-class BookingSerializer(serializers.ModelSerializer):
- installer_name = serializers.CharField(source='installer.name')
- scenario_name = serializers.CharField(source='scenario.name')
- opsys_name = serializers.CharField(source='opsys.name')
-
- class Meta:
- model = Booking
- fields = ('id', 'changeid', 'reset', 'user', 'resource_id', 'opsys_name', 'start', 'end', 'installer_name', 'scenario_name', 'purpose')
-
-
-class ServerSerializer(serializers.ModelSerializer):
- class Meta:
- model = Server
- fields = ('id', 'resource_id', 'name', 'model', 'cpu', 'ram', 'storage')
-
-
-class ResourceSerializer(serializers.ModelSerializer):
- class Meta:
- model = Resource
- fields = ('id', 'name', 'description', 'resource_lab', 'url', 'server_set', 'dev_pod')
-
-class ResourceStatusSerializer(serializers.ModelSerializer):
- class Meta:
- model = ResourceStatus
- fields = ('id', 'resource', 'timestamp','type', 'title', 'content')
-
-class NotifierSerializer(serializers.ModelSerializer):
- class Meta:
- model = Notifier
- fields = ('id', 'title', 'content', 'user', 'sender', 'message_type', 'msg_sent')
-
-class UserSerializer(serializers.ModelSerializer):
- username = serializers.CharField(source='user.username')
- class Meta:
- model = UserProfile
- fields = ('user', 'username', 'ssh_public_key', 'pgp_public_key', 'email_addr')
diff --git a/dashboard/src/api/serializers/__init__.py b/dashboard/src/api/serializers/__init__.py
new file mode 100644
index 0000000..e0408fa
--- /dev/null
+++ b/dashboard/src/api/serializers/__init__.py
@@ -0,0 +1,8 @@
+##############################################################################
+# 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
+##############################################################################
diff --git a/dashboard/src/api/serializers/booking_serializer.py b/dashboard/src/api/serializers/booking_serializer.py
new file mode 100644
index 0000000..9b5c059
--- /dev/null
+++ b/dashboard/src/api/serializers/booking_serializer.py
@@ -0,0 +1,175 @@
+##############################################################################
+# 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 rest_framework import serializers
+
+from resource_inventory.models import (
+ HostConfiguration,
+ CpuProfile,
+ DiskProfile,
+ InterfaceProfile,
+ RamProfile,
+ Image,
+ Interface
+)
+
+
+class BookingField(serializers.Field):
+
+ def to_representation(self, booking):
+ """
+ Takes in a booking object.
+ Returns a dictionary of primitives representing that booking
+ """
+ ser = {}
+ ser['id'] = booking.id
+ # main loop to grab relevant info out of booking
+ host_configs = {} # mapping hostname -> config
+ networks = {} # mapping vlan id -> network_hosts
+ for host in booking.resource.hosts.all():
+ host_configs[host.name] = HostConfiguration.objects.get(host=host.template)
+ if "jumphost" not in ser and host_configs[host.name].opnfvRole.name.lower() == "jumphost":
+ ser['jumphost'] = host.name
+ # host is a Host model
+ for i in range(len(host.interfaces.all())):
+ interface = host.interfaces.all()[i]
+ # interface is an Interface model
+ for vlan in interface.config.all():
+ # vlan is Vlan model
+ if vlan.id not in networks:
+ networks[vlan.id] = []
+ net_host = {"hostname": host.name, "tagged": vlan.tagged, "interface": i}
+ networks[vlan.id].append(net_host)
+ # creates networking object of proper form
+ networking = []
+ for vlanid in networks:
+ network = {}
+ network['vlan_id'] = vlanid
+ network['hosts'] = networks[vlanid]
+
+ ser['networking'] = networking
+
+ # creates hosts object of correct form
+ hosts = []
+ for hostname in host_configs:
+ host = {"hostname": hostname}
+ host['deploy_image'] = True # TODO?
+ image = host_configs[hostname].image
+ host['image'] = {
+ "name": image.name,
+ "lab_id": image.lab_id,
+ "dashboard_id": image.id
+ }
+ hosts.append(host)
+
+ ser['hosts'] = hosts
+
+ return ser
+
+ def to_internal_value(self, data):
+ """
+ Takes in a dictionary of primitives
+ Returns a booking object
+
+ This is not going to be implemented or allowed.
+ If someone needs to create a booking through the api,
+ they will send a different booking object
+ """
+ return None
+
+
+class BookingSerializer(serializers.Serializer):
+
+ booking = BookingField()
+
+
+# Host Type stuff, for inventory
+class CPUSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = CpuProfile
+ fields = ('cores', 'architecture', 'cpus')
+
+
+class DiskSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = DiskProfile
+ fields = ('size', 'media_type', 'name')
+
+
+class InterfaceProfileSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = InterfaceProfile
+ fields = ('speed', 'name')
+
+
+class RamSerializer(serializers.ModelSerializer):
+ class Meta:
+ model = RamProfile
+ fields = ('amount', 'channels')
+
+
+class HostTypeSerializer(serializers.Serializer):
+ name = serializers.CharField(max_length=200)
+ ram = RamSerializer()
+ interface = InterfaceProfileSerializer()
+ description = serializers.CharField(max_length=1000)
+ disks = DiskSerializer()
+ cpu = CPUSerializer()
+
+
+# the rest of the inventory stuff
+class NetworkSerializer(serializers.Serializer):
+ cidr = serializers.CharField(max_length=200)
+ gateway = serializers.IPAddressField(max_length=200)
+ vlan = serializers.IntegerField()
+
+
+class ImageSerializer(serializers.ModelSerializer):
+ lab_id = serializers.IntegerField()
+ id = serializers.IntegerField(source="dashboard_id")
+ name = serializers.CharField(max_length=50)
+ description = serializers.CharField(max_length=200)
+
+ class Meta:
+ model = Image
+
+
+class InterfaceField(serializers.Field):
+ def to_representation(self, interface):
+ pass
+
+ def to_internal_value(self, data):
+ """
+ takes in a serialized interface and creates an Interface model
+ """
+ mac = data['mac']
+ bus_address = data['busaddr']
+ switch_name = data['switchport']['switch_name']
+ port_name = data['switchport']['port_name']
+ # TODO config??
+ return Interface.objects.create(
+ mac_address=mac,
+ bus_address=bus_address,
+ switch_name=switch_name,
+ port_name=port_name
+ )
+
+
+class InventoryHostSerializer(serializers.Serializer):
+ hostname = serializers.CharField(max_length=100)
+ host_type = serializers.CharField(max_length=100)
+ interfaces = InterfaceField()
+
+
+class InventorySerializer(serializers.Serializer):
+ hosts = InventoryHostSerializer()
+ networks = NetworkSerializer()
+ images = ImageSerializer()
+ host_types = HostTypeSerializer()
diff --git a/dashboard/src/api/serializers/old_serializers.py b/dashboard/src/api/serializers/old_serializers.py
new file mode 100644
index 0000000..0944881
--- /dev/null
+++ b/dashboard/src/api/serializers/old_serializers.py
@@ -0,0 +1,21 @@
+##############################################################################
+# 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 rest_framework import serializers
+
+from account.models import UserProfile
+
+
+class UserSerializer(serializers.ModelSerializer):
+ username = serializers.CharField(source='user.username')
+
+ class Meta:
+ model = UserProfile
+ fields = ('user', 'username', 'ssh_public_key', 'pgp_public_key', 'email_addr')
diff --git a/dashboard/src/api/tests/__init__.py b/dashboard/src/api/tests/__init__.py
new file mode 100644
index 0000000..2435a9f
--- /dev/null
+++ b/dashboard/src/api/tests/__init__.py
@@ -0,0 +1,8 @@
+##############################################################################
+# Copyright (c) 2016 Parker Berberian and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
diff --git a/dashboard/src/api/tests/test_models_unittest.py b/dashboard/src/api/tests/test_models_unittest.py
new file mode 100644
index 0000000..e6f97a6
--- /dev/null
+++ b/dashboard/src/api/tests/test_models_unittest.py
@@ -0,0 +1,269 @@
+##############################################################################
+# Copyright (c) 2019 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 api.models import (
+ Job,
+ JobStatus,
+ JobFactory,
+ HostNetworkRelation,
+ HostHardwareRelation,
+ SoftwareRelation,
+ AccessConfig,
+ SnapshotRelation
+)
+
+from resource_inventory.models import (
+ OPNFVRole,
+ HostProfile,
+)
+
+from django.test import TestCase, Client
+
+from dashboard.testing_utils import (
+ make_host,
+ make_user,
+ make_user_profile,
+ make_lab,
+ make_installer,
+ make_image,
+ make_scenario,
+ make_os,
+ make_complete_host_profile,
+ make_booking,
+)
+
+
+class ValidBookingCreatesValidJob(TestCase):
+ @classmethod
+ def setUpTestData(cls):
+ cls.user = make_user(False, username="newtestuser", password="testpassword")
+ cls.userprofile = make_user_profile(cls.user)
+ cls.lab = make_lab()
+
+ cls.host_profile = make_complete_host_profile(cls.lab)
+ cls.scenario = make_scenario()
+ cls.installer = make_installer([cls.scenario])
+ os = make_os([cls.installer])
+ cls.image = make_image(cls.lab, 1, cls.user, os, cls.host_profile)
+ for i in range(30):
+ make_host(cls.host_profile, cls.lab, name="host" + str(i), labid="host" + str(i))
+ cls.client = Client()
+
+ def setUp(self):
+ self.booking, self.compute_hostnames, self.jump_hostname = self.create_multinode_generic_booking()
+
+ def create_multinode_generic_booking(self):
+ topology = {}
+
+ compute_hostnames = ["cmp01", "cmp02", "cmp03"]
+
+ host_type = HostProfile.objects.first()
+
+ universal_networks = [
+ {"name": "public", "tagged": False, "public": True},
+ {"name": "admin", "tagged": True, "public": False}]
+ compute_networks = [{"name": "private", "tagged": True, "public": False}]
+ jumphost_networks = [{"name": "external", "tagged": True, "public": True}]
+
+ # generate a bunch of extra networks
+ for i in range(10):
+ net = {"tagged": False, "public": False}
+ net["name"] = "net" + str(i)
+ universal_networks.append(net)
+
+ jumphost_info = {
+ "type": host_type,
+ "role": OPNFVRole.objects.get_or_create(name="Jumphost")[0],
+ "nets": self.make_networks(host_type, jumphost_networks + universal_networks),
+ "image": self.image
+ }
+ topology["jump"] = jumphost_info
+
+ for hostname in compute_hostnames:
+ host_info = {
+ "type": host_type,
+ "role": OPNFVRole.objects.get_or_create(name="Compute")[0],
+ "nets": self.make_networks(host_type, compute_networks + universal_networks),
+ "image": self.image
+ }
+ topology[hostname] = host_info
+
+ booking = make_booking(
+ owner=self.user,
+ lab=self.lab,
+ topology=topology,
+ installer=self.installer,
+ scenario=self.scenario
+ )
+
+ if not booking.resource:
+ raise Exception("Booking does not have a resource when trying to pass to makeCompleteJob")
+ return booking, compute_hostnames, "jump"
+
+ def make_networks(self, hostprofile, nets):
+ """
+ distributes nets accross hostprofile's interfaces
+ returns a 2D array
+ """
+ network_struct = []
+ count = hostprofile.interfaceprofile.all().count()
+ for i in range(count):
+ network_struct.append([])
+ while(nets):
+ index = len(nets) % count
+ network_struct[index].append(nets.pop())
+
+ return network_struct
+
+ #################################################################
+ # Complete Job Tests
+ #################################################################
+
+ def test_complete_job_makes_access_configs(self):
+ JobFactory.makeCompleteJob(self.booking)
+ job = Job.objects.get(booking=self.booking)
+ self.assertIsNotNone(job)
+
+ access_configs = AccessConfig.objects.filter(accessrelation__job=job)
+
+ vpn_configs = access_configs.filter(access_type="vpn")
+ ssh_configs = access_configs.filter(access_type="ssh")
+
+ self.assertFalse(AccessConfig.objects.exclude(access_type__in=["vpn", "ssh"]).exists())
+
+ all_users = list(self.booking.collaborators.all())
+ all_users.append(self.booking.owner)
+
+ for user in all_users:
+ self.assertTrue(vpn_configs.filter(user=user).exists())
+ self.assertTrue(ssh_configs.filter(user=user).exists())
+
+ def test_complete_job_makes_network_configs(self):
+ JobFactory.makeCompleteJob(self.booking)
+ job = Job.objects.get(booking=self.booking)
+ self.assertIsNotNone(job)
+
+ booking_hosts = self.booking.resource.hosts.all()
+
+ netrelations = HostNetworkRelation.objects.filter(job=job)
+ netconfigs = [r.config for r in netrelations]
+
+ netrelation_hosts = [r.host for r in netrelations]
+
+ for config in netconfigs:
+ for interface in config.interfaces.all():
+ self.assertTrue(interface.host in booking_hosts)
+
+ # if no interfaces are referenced that shouldn't have vlans,
+ # and no vlans exist outside those accounted for in netconfigs,
+ # then the api is faithfully representing networks
+ # as netconfigs reference resource_inventory models directly
+
+ # this test relies on the assumption that
+ # every interface is configured, whether it does or does not have vlans
+ # if this is not true, the test fails
+
+ for host in booking_hosts:
+ self.assertTrue(host in netrelation_hosts)
+ relation = HostNetworkRelation.objects.filter(job=job).get(host=host)
+
+ # do 2 direction matching that interfaces are one to one
+ config = relation.config
+ for interface in config.interfaces.all():
+ self.assertTrue(interface in host.interfaces)
+ for interface in host.interfaces.all():
+ self.assertTrue(interface in config.interfaces)
+
+ for host in netrelation_hosts:
+ self.assertTrue(host in booking_hosts)
+
+ def test_complete_job_makes_hardware_configs(self):
+ JobFactory.makeCompleteJob(self.booking)
+ job = Job.objects.get(booking=self.booking)
+ self.assertIsNotNone(job)
+
+ hardware_relations = HostHardwareRelation.objects.filter(job=job)
+
+ job_hosts = [r.host for r in hardware_relations]
+
+ booking_hosts = self.booking.resource.hosts.all()
+
+ self.assertEqual(len(booking_hosts), len(job_hosts))
+
+ for relation in hardware_relations:
+ self.assertTrue(relation.host in booking_hosts)
+ self.assertEqual(relation.status, JobStatus.NEW)
+ config = relation.config
+ host = relation.host
+ self.assertEqual(config.hostname, host.template.resource.name)
+
+ def test_complete_job_makes_software_configs(self):
+ JobFactory.makeCompleteJob(self.booking)
+ job = Job.objects.get(booking=self.booking)
+ self.assertIsNotNone(job)
+
+ srelation = SoftwareRelation.objects.filter(job=job).first()
+ self.assertIsNotNone(srelation)
+
+ sconfig = srelation.config
+ self.assertIsNotNone(sconfig)
+
+ oconfig = sconfig.opnfv
+ self.assertIsNotNone(oconfig)
+
+ # not onetoone in models, but first() is safe here based on how ConfigBundle and a matching OPNFVConfig are created
+ # this should, however, be made explicit
+ self.assertEqual(oconfig.installer, self.booking.config_bundle.opnfv_config.first().installer.name)
+ self.assertEqual(oconfig.scenario, self.booking.config_bundle.opnfv_config.first().scenario.name)
+
+ for host in oconfig.roles.all():
+ role_name = host.config.host_opnfv_config.first().role.name
+ if str(role_name).lower() == "jumphost":
+ self.assertEqual(host.template.resource.name, self.jump_hostname)
+ elif str(role_name).lower() == "compute":
+ self.assertTrue(host.template.resource.name in self.compute_hostnames)
+ else:
+ self.fail(msg="Host with non-configured role name related to job: " + str(role_name))
+
+ def test_make_snapshot_task(self):
+ host = self.booking.resource.hosts.first()
+ image = make_image(self.lab, -1, None, None, host.profile)
+
+ Job.objects.create(booking=self.booking)
+
+ JobFactory.makeSnapshotTask(image, self.booking, host)
+
+ snap_relation = SnapshotRelation.objects.get(job=self.booking.job)
+ config = snap_relation.config
+ self.assertEqual(host.id, config.host.id)
+ self.assertEqual(config.dashboard_id, image.id)
+ self.assertEqual(snap_relation.snapshot.id, image.id)
+
+ def test_make_hardware_configs(self):
+ hosts = self.booking.resource.hosts.all()
+ job = Job.objects.create(booking=self.booking)
+ JobFactory.makeHardwareConfigs(hosts=hosts, job=job)
+
+ hardware_relations = HostHardwareRelation.objects.filter(job=job)
+
+ self.assertEqual(hardware_relations.count(), hosts.count())
+
+ host_set = set([h.id for h in hosts])
+
+ for relation in hardware_relations:
+ try:
+ host_set.remove(relation.host.id)
+ except KeyError:
+ self.fail("Hardware Relation/Config not created for host " + str(relation.host))
+
+ self.assertEqual(relation.config.power, "on")
+ self.assertTrue(relation.config.ipmi_create)
+ # TODO: the rest of hwconf attrs
+
+ self.assertEqual(len(host_set), 0)
diff --git a/dashboard/src/api/urls.py b/dashboard/src/api/urls.py
index c2cd510..7a48425 100644
--- a/dashboard/src/api/urls.py
+++ b/dashboard/src/api/urls.py
@@ -1,5 +1,6 @@
##############################################################################
# Copyright (c) 2016 Max Breitenfeldt and others.
+# 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
@@ -24,19 +25,44 @@ Including another URLconf
2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls'))
"""
from django.conf.urls import url, include
+from django.urls import path
from rest_framework import routers
-from api.views import *
+from api.views import (
+ BookingViewSet,
+ UserViewSet,
+ lab_profile,
+ lab_status,
+ lab_inventory,
+ specific_job,
+ specific_task,
+ new_jobs,
+ current_jobs,
+ done_jobs,
+ update_host_bmc,
+ lab_host,
+ get_pdf,
+ get_idf,
+ GenerateTokenView
+)
router = routers.DefaultRouter()
-router.register(r'resources', ResourceViewSet)
-router.register(r'servers', ServerViewSet)
router.register(r'bookings', BookingViewSet)
-router.register(r'resource_status', ResourceStatusViewSet)
-router.register(r'notifier', NotifierViewSet)
router.register(r'user', UserViewSet)
urlpatterns = [
url(r'^', include(router.urls)),
+ path('labs/<slug:lab_name>/profile', lab_profile),
+ path('labs/<slug:lab_name>/status', lab_status),
+ path('labs/<slug:lab_name>/inventory', lab_inventory),
+ path('labs/<slug:lab_name>/hosts/<slug:host_id>', lab_host),
+ path('labs/<slug:lab_name>/hosts/<slug:host_id>/bmc', update_host_bmc),
+ path('labs/<slug:lab_name>/booking/<int:booking_id>/pdf', get_pdf, name="get-pdf"),
+ path('labs/<slug:lab_name>/booking/<int:booking_id>/idf', get_idf, name="get-idf"),
+ path('labs/<slug:lab_name>/jobs/<int:job_id>', specific_job),
+ path('labs/<slug:lab_name>/jobs/<int:job_id>/<slug:task_id>', specific_task),
+ path('labs/<slug:lab_name>/jobs/new', new_jobs),
+ path('labs/<slug:lab_name>/jobs/current', current_jobs),
+ path('labs/<slug:lab_name>/jobs/done', done_jobs),
url(r'^token$', GenerateTokenView.as_view(), name='generate_token'),
]
diff --git a/dashboard/src/api/views.py b/dashboard/src/api/views.py
index b873ef1..fb28958 100644
--- a/dashboard/src/api/views.py
+++ b/dashboard/src/api/views.py
@@ -1,5 +1,6 @@
##############################################################################
# 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
@@ -8,17 +9,21 @@
##############################################################################
-from django.contrib.auth.decorators import login_required, user_passes_test
+from django.contrib.auth.decorators import login_required
from django.shortcuts import redirect
from django.utils.decorators import method_decorator
from django.views import View
+from django.http.response import JsonResponse, HttpResponse
from rest_framework import viewsets
from rest_framework.authtoken.models import Token
+from django.views.decorators.csrf import csrf_exempt
-from api.serializers import *
+from api.serializers.booking_serializer import BookingSerializer
+from api.serializers.old_serializers import UserSerializer
from account.models import UserProfile
from booking.models import Booking
-from dashboard.models import Resource, Server, ResourceStatus
+from api.models import LabManagerTracker, get_task
+from notifier.manager import NotificationHandler
class BookingViewSet(viewsets.ModelViewSet):
@@ -27,29 +32,11 @@ class BookingViewSet(viewsets.ModelViewSet):
filter_fields = ('resource', 'id')
-class ServerViewSet(viewsets.ModelViewSet):
- queryset = Server.objects.all()
- serializer_class = ServerSerializer
- filter_fields = ('resource', 'name')
-
-
-class ResourceViewSet(viewsets.ModelViewSet):
- queryset = Resource.objects.all()
- serializer_class = ResourceSerializer
- filter_fields = ('name', 'id')
-
-class ResourceStatusViewSet(viewsets.ModelViewSet):
- queryset = ResourceStatus.objects.all()
- serializer_class = ResourceStatusSerializer
-
-class NotifierViewSet(viewsets.ModelViewSet):
- queryset = Notifier.objects.none()
- serializer_class = NotifierSerializer
-
class UserViewSet(viewsets.ModelViewSet):
queryset = UserProfile.objects.all()
serializer_class = UserSerializer
+
@method_decorator(login_required, name='dispatch')
class GenerateTokenView(View):
def get(self, request, *args, **kwargs):
@@ -59,3 +46,111 @@ class GenerateTokenView(View):
token.delete()
Token.objects.create(user=user)
return redirect('account:settings')
+
+
+def lab_inventory(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return JsonResponse(lab_manager.get_inventory(), safe=False)
+
+
+@csrf_exempt
+def lab_host(request, lab_name="", host_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ if request.method == "GET":
+ return JsonResponse(lab_manager.get_host(host_id), safe=False)
+ if request.method == "POST":
+ return JsonResponse(lab_manager.update_host(host_id, request.POST), safe=False)
+
+
+def get_pdf(request, lab_name="", booking_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return HttpResponse(lab_manager.get_pdf(booking_id), content_type="text/plain")
+
+
+def get_idf(request, lab_name="", booking_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return HttpResponse(lab_manager.get_idf(booking_id), content_type="text/plain")
+
+
+def lab_status(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ if request.method == "POST":
+ return JsonResponse(lab_manager.set_status(request.POST), safe=False)
+ return JsonResponse(lab_manager.get_status(), safe=False)
+
+
+@csrf_exempt
+def update_host_bmc(request, lab_name="", host_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ if request.method == "POST":
+ # update / create RemoteInfo for host
+ return JsonResponse(
+ lab_manager.update_host_remote_info(request.POST, host_id),
+ safe=False
+ )
+
+
+def lab_profile(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return JsonResponse(lab_manager.get_profile(), safe=False)
+
+
+@csrf_exempt
+def specific_task(request, lab_name="", job_id="", task_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ LabManagerTracker.get(lab_name, lab_token) # Authorize caller, but we dont need the result
+
+ if request.method == "POST":
+ task = get_task(task_id)
+ if 'status' in request.POST:
+ task.status = request.POST.get('status')
+ if 'message' in request.POST:
+ task.message = request.POST.get('message')
+ if 'lab_token' in request.POST:
+ task.lab_token = request.POST.get('lab_token')
+ task.save()
+ NotificationHandler.task_updated(task)
+ d = {}
+ d['task'] = task.config.get_delta()
+ m = {}
+ m['status'] = task.status
+ m['job'] = str(task.job)
+ m['message'] = task.message
+ d['meta'] = m
+ return JsonResponse(d, safe=False)
+ elif request.method == "GET":
+ return JsonResponse(get_task(task_id).config.get_delta())
+
+
+@csrf_exempt
+def specific_job(request, lab_name="", job_id=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ if request.method == "POST":
+ return JsonResponse(lab_manager.update_job(job_id, request.POST), safe=False)
+ return JsonResponse(lab_manager.get_job(job_id), safe=False)
+
+
+def new_jobs(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return JsonResponse(lab_manager.get_new_jobs(), safe=False)
+
+
+def current_jobs(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return JsonResponse(lab_manager.get_current_jobs(), safe=False)
+
+
+def done_jobs(request, lab_name=""):
+ lab_token = request.META.get('HTTP_AUTH_TOKEN')
+ lab_manager = LabManagerTracker.get(lab_name, lab_token)
+ return JsonResponse(lab_manager.get_done_jobs(), safe=False)