Adds pdf and idf into api 11/67611/4
authorParker Berberian <pberberian@iol.unh.edu>
Wed, 17 Apr 2019 16:44:04 +0000 (12:44 -0400)
committerParker Berberian <pberberian@iol.unh.edu>
Wed, 8 May 2019 14:12:58 +0000 (10:12 -0400)
The Pod Descriptor File (pdf) and Installer descriptor file (idf)
are now hosted in the api. The url endpoint where the lab
can retrieve them are now part of a software task.

An OPNFV task also contains a new dictionary that describes how bridges
should be configured on the jumphost. This information is not
contained in the pdf/idf but is needed by the lab.

Change-Id: I6971279979ba180725926035bd9db481aafb1073
Signed-off-by: Parker Berberian <pberberian@iol.unh.edu>
dashboard/src/api/migrations/0007_auto_20190417_1511.py [new file with mode: 0644]
dashboard/src/api/migrations/0008_auto_20190419_1414.py [new file with mode: 0644]
dashboard/src/api/migrations/0009_merge_20190508_1317.py [new file with mode: 0644]
dashboard/src/api/models.py
dashboard/src/api/urls.py
dashboard/src/api/views.py
dashboard/src/resource_inventory/idf_templater.py

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 (file)
index 0000000..e7d2c59
--- /dev/null
@@ -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/0008_auto_20190419_1414.py b/dashboard/src/api/migrations/0008_auto_20190419_1414.py
new file mode 100644 (file)
index 0000000..03c3865
--- /dev/null
@@ -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 (file)
index 0000000..1a34380
--- /dev/null
@@ -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 = [
+    ]
index c165454..1f708ae 100644 (file)
@@ -12,6 +12,7 @@ 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
@@ -23,6 +24,7 @@ from resource_inventory.models import (
     Host,
     Image,
     Interface,
+    HostOPNFVConfig,
     RemoteInfo,
     OPNFVConfig
 )
@@ -98,6 +100,14 @@ class LabManager(object):
         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
@@ -351,11 +361,44 @@ class TaskConfig(models.Model):
         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)
 
@@ -367,6 +410,12 @@ class OpnfvApiConfig(models.Model):
             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():
@@ -395,6 +444,16 @@ class OpnfvApiConfig(models.Model):
         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)
@@ -632,6 +691,7 @@ class SnapshotConfig(TaskConfig):
         if not self.delta:
             self.delta = self.to_json()
             self.save()
+
         d = json.loads(self.delta)
         return d
 
@@ -823,7 +883,6 @@ class JobFactory(object):
             job=job
         )
         cls.makeSoftware(
-            hosts=hosts,
             booking=booking,
             job=job
         )
@@ -915,18 +974,41 @@ class JobFactory(object):
             network_config.save()
 
     @classmethod
-    def makeSoftware(cls, hosts=[], booking=None, job=Job()):
+    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,
-            scenario=booking.opnfv_config.scenario,
+            installer=booking.opnfv_config.installer.name,
+            scenario=booking.opnfv_config.scenario.name,
+            bridge_config=cls.make_bridge_config(booking)
         )
 
-        for host in hosts:
+        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)
index d18a04d..d1f772a 100644 (file)
@@ -41,6 +41,8 @@ from api.views import (
     done_jobs,
     update_host_bmc,
     lab_host,
+    get_pdf,
+    get_idf,
     GenerateTokenView
 )
 
@@ -55,6 +57,8 @@ urlpatterns = [
     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/<slug:booking_id>/pdf', get_pdf, name="get-pdf"),
+    path('labs/<slug:lab_name>/booking/<slug: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),
index 2ae1ac5..fb28958 100644 (file)
@@ -13,7 +13,7 @@ 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
+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
@@ -64,6 +64,18 @@ def lab_host(request, lab_name="", host_id=""):
         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)
index 26307e3..bf6eda0 100644 (file)
@@ -19,8 +19,15 @@ class IDFTemplater:
     """
     Utility class to create a full IDF yaml file
     """
+    net_names = ["admin", "mgmt", "private", "public"]
+    bridge_names = {
+        "admin": "br-admin",
+        "mgmt": "br-mgmt",
+        "private": "br-private",
+        "public": "br-public"
+    }
+
     def __init__(self):
-        self.net_names = ["admin", "mgmt", "private", "public"]
         self.networks = {}
         for i, name in enumerate(self.net_names):
             self.networks[name] = {
@@ -117,11 +124,7 @@ class IDFTemplater:
         return fuel
 
     def get_fuel_bridges(self):
-        bridges = {}
-        for net in self.net_names:
-            bridges[net] = "br-" + net
-
-        return bridges
+        return self.bridge_names
 
     def get_fuel_nodes(self, booking):
         jumphost = booking.opnfv_config.host_opnfv_config.get(