OverHaul the Snapshot Workflow
[pharos-tools.git] / dashboard / src / api / models.py
index 7448ac4..e4016aa 100644 (file)
@@ -8,14 +8,21 @@
 ##############################################################################
 
 
+from django.contrib.auth.models import User
 from django.db import models
 from django.core.exceptions import PermissionDenied
 
 import json
 import uuid
 
-from resource_inventory.models import *
 from booking.models import Booking
+from resource_inventory.models import (
+    Lab,
+    HostProfile,
+    Host,
+    Image,
+    Interface
+)
 
 
 class JobStatus(object):
@@ -57,16 +64,18 @@ class LabManager(object):
         prof = {}
         prof['name'] = self.lab.name
         prof['contact'] = {
-                "phone": self.lab.contact_phone,
-                "email": self.lab.contact_email
-                }
+            "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
-                })
+            prof['host_count'].append(
+                {
+                    "type": host.name,
+                    "count": count
+                }
+            )
         return prof
 
     def get_inventory(self):
@@ -135,11 +144,13 @@ class LabManager(object):
     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
-                })
+            images_ser.append(
+                {
+                    "name": image.name,
+                    "lab_id": image.lab_id,
+                    "dashboard_id": image.id
+                }
+            )
         return images_ser
 
     def serialize_host_profiles(self, profiles):
@@ -147,25 +158,27 @@ class LabManager(object):
         for profile in profiles:
             p = {}
             p['cpu'] = {
-                    "cores": profile.cpuprofile.first().cores,
-                    "arch": profile.cpuprofile.first().architecture,
-                    "cpus": profile.cpuprofile.first().cpus,
-                    }
+                "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
-                    }
+                    "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['interfaces'].append(
+                    {
+                        "speed": iface.speed,
+                        "name": iface.name
+                    }
+                )
 
             p['ram'] = {"amount": profile.ramprofile.first().amount}
             p['name'] = profile.name
@@ -201,6 +214,10 @@ class Job(models.Model):
             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
 
@@ -208,7 +225,13 @@ class Job(models.Model):
 
     def get_tasklist(self, status="all"):
         tasklist = []
-        clist = [HostHardwareRelation, AccessRelation, HostNetworkRelation, SoftwareRelation]
+        clist = [
+            HostHardwareRelation,
+            AccessRelation,
+            HostNetworkRelation,
+            SoftwareRelation,
+            SnapshotRelation
+        ]
         if status == "all":
             for cls in clist:
                 tasklist += list(cls.objects.filter(job=self))
@@ -217,6 +240,17 @@ class Job(models.Model):
                 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 = {}
@@ -237,6 +271,10 @@ class Job(models.Model):
             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
@@ -258,6 +296,7 @@ class TaskConfig(models.Model):
     def clear_delta(self):
         self.delta = '{}'
 
+
 class OpnfvApiConfig(models.Model):
 
     installer = models.CharField(max_length=100)
@@ -312,19 +351,23 @@ class OpnfvApiConfig(models.Model):
             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()
+    delta = models.TextField(default="{}")
 
     def to_dict(self):
         d = {}
-        d['access_type'] =  self.access_type
+        d['access_type'] = self.access_type
         d['user'] = self.user.id
         d['revoke'] = self.revoke
-        d['context'] = self.context
+        try:
+            d['context'] = json.loads(self.context)
+        except:
+            pass
         return d
 
     def get_delta(self):
@@ -363,11 +406,12 @@ class AccessConfig(TaskConfig):
         self.delta = json.dumps(d)
 
     def set_context(self, context):
-        self.context = 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
@@ -397,6 +441,7 @@ class SoftwareConfig(TaskConfig):
     def to_json(self):
         return json.dumps(self.to_dict())
 
+
 class HardwareConfig(TaskConfig):
     """
     handles imaging, user accounts, etc
@@ -503,6 +548,61 @@ class NetworkConfig(TaskConfig):
         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]:
         try:
@@ -586,8 +686,40 @@ class HostNetworkRelation(TaskRelation):
         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 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)
@@ -597,29 +729,39 @@ class JobFactory(object):
         except:
             job = Job.objects.create(status=JobStatus.NEW, booking=booking)
         cls.makeHardwareConfigs(
-                hosts=hosts,
-                job=job
-                )
+            hosts=hosts,
+            job=job
+        )
         cls.makeNetworkConfigs(
-                hosts=hosts,
-                job=job
-                )
+            hosts=hosts,
+            job=job
+        )
         cls.makeSoftware(
-                hosts=hosts,
-                job=job
-                )
+            hosts=hosts,
+            job=job
+        )
+        all_users = list(booking.collaborators.all())
+        all_users.append(booking.owner)
         cls.makeAccessConfig(
-                users=booking.collaborators.all(),
-                access_type="vpn",
-                revoke=False,
-                job=job
-                )
-        cls.makeAccessConfig(
-                users=[booking.owner],
-                access_type="vpn",
-                revoke=False,
-                job=job
+            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()):
@@ -646,7 +788,7 @@ class JobFactory(object):
             hardware_config.save()
 
     @classmethod
-    def makeAccessConfig(cls, users, access_type, revoke=False, job=Job()):
+    def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False):
         for user in users:
             relation = AccessRelation()
             relation.job = job
@@ -657,6 +799,8 @@ class JobFactory(object):
             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)
@@ -709,6 +853,3 @@ class JobFactory(object):
             return software_relation
         except:
             return None
-
-    def makeAccess(cls, user, access_type, revoke):
-        pass