From 8483acb4c4e8c758855f2184483af834977b0c52 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Thu, 17 Jan 2019 12:51:04 -0500 Subject: OverHaul the Snapshot Workflow Makes the Snapshot workflow much prettier and more functional. Change-Id: Icdd66f64e6d336ad49ed3cf638a301d0ca92fda9 Signed-off-by: Parker Berberian --- .../0004_snapshotconfig_snapshotrelation.py | 42 +++++++++ .../api/migrations/0005_snapshotconfig_delta.py | 18 ++++ dashboard/src/api/models.py | 103 ++++++++++++++++++++- dashboard/src/notifier/manager.py | 4 +- .../src/templates/notifier/email_fulfilled.txt | 6 +- .../templates/snapshot_workflow/steps/meta.html | 19 ++-- .../snapshot_workflow/steps/select_host.html | 65 +++++++++---- dashboard/src/workflow/forms.py | 2 +- dashboard/src/workflow/models.py | 10 ++ dashboard/src/workflow/snapshot_workflow.py | 9 +- 10 files changed, 247 insertions(+), 31 deletions(-) create mode 100644 dashboard/src/api/migrations/0004_snapshotconfig_snapshotrelation.py create mode 100644 dashboard/src/api/migrations/0005_snapshotconfig_delta.py 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/models.py b/dashboard/src/api/models.py index a1fedfe..e4016aa 100644 --- a/dashboard/src/api/models.py +++ b/dashboard/src/api/models.py @@ -214,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 @@ -221,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)) @@ -261,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 @@ -534,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: @@ -617,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) diff --git a/dashboard/src/notifier/manager.py b/dashboard/src/notifier/manager.py index 3361074..f03c2cc 100644 --- a/dashboard/src/notifier/manager.py +++ b/dashboard/src/notifier/manager.py @@ -75,7 +75,7 @@ class NotificationHandler(object): if (not hasattr(task, "user")) or task.user == user: user_tasklist.append( { - "title": task.type_str + " Message: ", + "title": task.type_str() + " Message: ", "content": task.message } ) @@ -94,7 +94,7 @@ class NotificationHandler(object): "Your Booking is Ready", message, os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@pharos-dashboard"), - user.userprofile.email_addr, + [user.userprofile.email_addr], fail_silently=False ) diff --git a/dashboard/src/templates/notifier/email_fulfilled.txt b/dashboard/src/templates/notifier/email_fulfilled.txt index d473961..65593db 100644 --- a/dashboard/src/templates/notifier/email_fulfilled.txt +++ b/dashboard/src/templates/notifier/email_fulfilled.txt @@ -3,9 +3,9 @@ The booking you requested of the OPNFV Lab as a Service has finished deploying and is ready for you to use. The lab that fulfilled your booking request has sent you the following messages: - {% for message in messages %} - {% message.title %} - {% message.content %} + {% for email_message in messages %} + {{ email_message.title }} + {{ email_message.content }} -------------------- {% endfor %} diff --git a/dashboard/src/templates/snapshot_workflow/steps/meta.html b/dashboard/src/templates/snapshot_workflow/steps/meta.html index 2e767cc..cc49691 100644 --- a/dashboard/src/templates/snapshot_workflow/steps/meta.html +++ b/dashboard/src/templates/snapshot_workflow/steps/meta.html @@ -4,16 +4,21 @@ {% load bootstrap3 %} {% block content %} - - - {% bootstrap_form_errors form type='non_fields' %} -
-{% csrf_token %} -{{form}} -
+
+
+ {% csrf_token %} +
+ {% bootstrap_field form.name %} + {% bootstrap_field form.description %} +
+
+
{% endblock content %} {% block onleave %} diff --git a/dashboard/src/templates/snapshot_workflow/steps/select_host.html b/dashboard/src/templates/snapshot_workflow/steps/select_host.html index 16dd5d4..27a9238 100644 --- a/dashboard/src/templates/snapshot_workflow/steps/select_host.html +++ b/dashboard/src/templates/snapshot_workflow/steps/select_host.html @@ -5,43 +5,73 @@ {% block content %} - - {% bootstrap_form_errors form type='non_fields' %}
{% csrf_token %}
-
+