diff options
Diffstat (limited to 'src/api')
39 files changed, 0 insertions, 3649 deletions
diff --git a/src/api/__init__.py b/src/api/__init__.py deleted file mode 100644 index b6fef6c..0000000 --- a/src/api/__init__.py +++ /dev/null @@ -1,8 +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 -############################################################################## diff --git a/src/api/admin.py b/src/api/admin.py deleted file mode 100644 index 1e243a0..0000000 --- a/src/api/admin.py +++ /dev/null @@ -1,43 +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 django.apps import AppConfig -from django.contrib import admin - -from api.models import ( - Job, - OpnfvApiConfig, - HardwareConfig, - NetworkConfig, - SoftwareConfig, - AccessConfig, - AccessRelation, - SoftwareRelation, - HostHardwareRelation, - HostNetworkRelation, - APILog -) - - -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) -admin.site.register(APILog) diff --git a/src/api/forms.py b/src/api/forms.py deleted file mode 100644 index 1b74a9b..0000000 --- a/src/api/forms.py +++ /dev/null @@ -1,16 +0,0 @@ -############################################################################## -# Copyright (c) 2019 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 -############################################################################# - -import django.forms as forms - - -class DowntimeForm(forms.Form): - start = forms.DateTimeField() - end = forms.DateTimeField() - description = forms.CharField(max_length=1000, required=False) diff --git a/src/api/migrations/0001_initial.py b/src/api/migrations/0001_initial.py deleted file mode 100644 index abe6f5e..0000000 --- a/src/api/migrations/0001_initial.py +++ /dev/null @@ -1,185 +0,0 @@ -############################################################################## -# 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/src/api/migrations/0002_remove_job_delta.py b/src/api/migrations/0002_remove_job_delta.py deleted file mode 100644 index 157a40f..0000000 --- a/src/api/migrations/0002_remove_job_delta.py +++ /dev/null @@ -1,17 +0,0 @@ -# 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/src/api/migrations/0003_auto_20190102_1956.py b/src/api/migrations/0003_auto_20190102_1956.py deleted file mode 100644 index 2ea5d70..0000000 --- a/src/api/migrations/0003_auto_20190102_1956.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/src/api/migrations/0004_snapshotconfig_snapshotrelation.py b/src/api/migrations/0004_snapshotconfig_snapshotrelation.py deleted file mode 100644 index 62bc7af..0000000 --- a/src/api/migrations/0004_snapshotconfig_snapshotrelation.py +++ /dev/null @@ -1,42 +0,0 @@ -# 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/src/api/migrations/0005_snapshotconfig_delta.py b/src/api/migrations/0005_snapshotconfig_delta.py deleted file mode 100644 index 559af90..0000000 --- a/src/api/migrations/0005_snapshotconfig_delta.py +++ /dev/null @@ -1,18 +0,0 @@ -# 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/src/api/migrations/0006_auto_20190313_1729.py b/src/api/migrations/0006_auto_20190313_1729.py deleted file mode 100644 index ec148bd..0000000 --- a/src/api/migrations/0006_auto_20190313_1729.py +++ /dev/null @@ -1,23 +0,0 @@ -# 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/src/api/migrations/0007_auto_20190417_1511.py b/src/api/migrations/0007_auto_20190417_1511.py deleted file mode 100644 index e7d2c59..0000000 --- a/src/api/migrations/0007_auto_20190417_1511.py +++ /dev/null @@ -1,25 +0,0 @@ -# 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/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py b/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py deleted file mode 100644 index 46f3631..0000000 --- a/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py +++ /dev/null @@ -1,20 +0,0 @@ -# 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/src/api/migrations/0008_auto_20190419_1414.py b/src/api/migrations/0008_auto_20190419_1414.py deleted file mode 100644 index 03c3865..0000000 --- a/src/api/migrations/0008_auto_20190419_1414.py +++ /dev/null @@ -1,28 +0,0 @@ -# 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/src/api/migrations/0009_merge_20190508_1317.py b/src/api/migrations/0009_merge_20190508_1317.py deleted file mode 100644 index 1a34380..0000000 --- a/src/api/migrations/0009_merge_20190508_1317.py +++ /dev/null @@ -1,14 +0,0 @@ -# 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/src/api/migrations/0010_auto_20191219_2004.py b/src/api/migrations/0010_auto_20191219_2004.py deleted file mode 100644 index ec48584..0000000 --- a/src/api/migrations/0010_auto_20191219_2004.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2019-12-19 20:04 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0009_merge_20190508_1317'), - ] - - operations = [ - migrations.AddField( - model_name='taskconfig', - name='delta_keys_list', - field=models.CharField(default='[]', max_length=200), - ), - migrations.AddField( - model_name='taskconfig', - name='state', - field=models.IntegerField(default=200), - ), - ] diff --git a/src/api/migrations/0011_auto_20200218_1536.py b/src/api/migrations/0011_auto_20200218_1536.py deleted file mode 100644 index 0fd7029..0000000 --- a/src/api/migrations/0011_auto_20200218_1536.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0010_auto_20191219_2004'), - # ('resource_inventory', '0013_auto_20200218_1536') - ] - - operations = [ - migrations.AddField( - model_name='hosthardwarerelation', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - migrations.AddField( - model_name='hostnetworkrelation', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - migrations.AddField( - model_name='snapshotconfig', - name='resource_id', - field=models.CharField(default='default_id', max_length=200), - ), - ] diff --git a/src/api/migrations/0012_manual_20200218_1536.py b/src/api/migrations/0012_manual_20200218_1536.py deleted file mode 100644 index 55befbd..0000000 --- a/src/api/migrations/0012_manual_20200218_1536.py +++ /dev/null @@ -1,22 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations - - -def set_resource_id(apps, schema_editor): - for cls in ["HostHardwareRelation", "HostNetworkRelation", "SnapshotConfig"]: - model = apps.get_model('api', cls) - for m in model.objects.all(): - m.resource_id = m.host.labid - m.save() - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0011_auto_20200218_1536'), - ] - - operations = [ - migrations.RunPython(set_resource_id), - ] diff --git a/src/api/migrations/0013_manual_20200218_1536.py b/src/api/migrations/0013_manual_20200218_1536.py deleted file mode 100644 index 3ac427e..0000000 --- a/src/api/migrations/0013_manual_20200218_1536.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2020-02-18 15:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0012_manual_20200218_1536'), - ] - - operations = [ - migrations.RemoveField( - model_name='hosthardwarerelation', - name='host', - ), - migrations.RemoveField( - model_name='hostnetworkrelation', - name='host', - ), - migrations.RemoveField( - model_name='snapshotconfig', - name='host', - ), - migrations.RemoveField( - model_name='opnfvapiconfig', - name='roles', - ), - ] diff --git a/src/api/migrations/0014_manual_20200220.py b/src/api/migrations/0014_manual_20200220.py deleted file mode 100644 index 2e2cd58..0000000 --- a/src/api/migrations/0014_manual_20200220.py +++ /dev/null @@ -1,18 +0,0 @@ - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0013_manual_20200218_1536'), - ('resource_inventory', '0013_auto_20200218_1536') - ] - - operations = [ - migrations.AddField( - model_name='opnfvapiconfig', - name='roles', - field=models.ManyToManyField(to='resource_inventory.ResourceOPNFVConfig'), - ), - ] diff --git a/src/api/migrations/0015_auto_20201109_1947.py b/src/api/migrations/0015_auto_20201109_1947.py deleted file mode 100644 index bbd51e5..0000000 --- a/src/api/migrations/0015_auto_20201109_1947.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 19:47 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0014_manual_20200220'), - ] - - operations = [ - migrations.AlterField( - model_name='taskconfig', - name='state', - field=models.IntegerField(default=0), - ), - ] diff --git a/src/api/migrations/0016_auto_20201109_2149.py b/src/api/migrations/0016_auto_20201109_2149.py deleted file mode 100644 index a430659..0000000 --- a/src/api/migrations/0016_auto_20201109_2149.py +++ /dev/null @@ -1,41 +0,0 @@ -# Generated by Django 2.2 on 2020-11-09 21:49 - -import api.models -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0015_auto_20201109_1947'), - ] - - operations = [ - migrations.CreateModel( - name='ActiveUsersConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ], - ), - migrations.AddField( - model_name='job', - name='job_type', - field=models.CharField(choices=[('BOOK', 'Booking'), ('DATA', 'Analytics')], default='BOOK', max_length=4), - ), - migrations.CreateModel( - name='ActiveUsersRelation', - 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.ActiveUsersConfig')), - ('job', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='api.Job')), - ], - options={ - 'abstract': False, - }, - ), - ] diff --git a/src/api/migrations/0017_apilog.py b/src/api/migrations/0017_apilog.py deleted file mode 100644 index d209aef..0000000 --- a/src/api/migrations/0017_apilog.py +++ /dev/null @@ -1,27 +0,0 @@ -# Generated by Django 2.2 on 2021-03-19 20:45 - -from django.conf import settings -import django.contrib.postgres.fields.jsonb -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('api', '0016_auto_20201109_2149'), - ] - - operations = [ - migrations.CreateModel( - name='APILog', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('call_time', models.DateTimeField(auto_now=True)), - ('endpoint', models.CharField(max_length=300)), - ('body', django.contrib.postgres.fields.jsonb.JSONField()), - ('user', models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, to=settings.AUTH_USER_MODEL)), - ], - ), - ] diff --git a/src/api/migrations/0017_auto_20210630_1629.py b/src/api/migrations/0017_auto_20210630_1629.py deleted file mode 100644 index 643ff5f..0000000 --- a/src/api/migrations/0017_auto_20210630_1629.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-06-30 16:29 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0016_auto_20201109_2149'), - ] - - operations = [ - migrations.AlterField( - model_name='snapshotconfig', - name='image', - field=models.CharField(max_length=200, null=True), - ), - ] diff --git a/src/api/migrations/0018_apilog_ip_addr.py b/src/api/migrations/0018_apilog_ip_addr.py deleted file mode 100644 index 4b7ce39..0000000 --- a/src/api/migrations/0018_apilog_ip_addr.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 18:12 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0017_apilog'), - ] - - operations = [ - migrations.AddField( - model_name='apilog', - name='ip_addr', - field=models.GenericIPAddressField(null=True), - ), - ] diff --git a/src/api/migrations/0018_cloudinitfile.py b/src/api/migrations/0018_cloudinitfile.py deleted file mode 100644 index 4e41b39..0000000 --- a/src/api/migrations/0018_cloudinitfile.py +++ /dev/null @@ -1,25 +0,0 @@ -# Generated by Django 2.2 on 2021-07-01 20:45 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('resource_inventory', '0019_auto_20210701_1947'), - ('booking', '0008_auto_20201109_1947'), - ('api', '0017_auto_20210630_1629'), - ] - - operations = [ - migrations.CreateModel( - name='CloudInitFile', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('resource_id', models.CharField(max_length=200)), - ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')), - ('rconfig', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceConfiguration')), - ], - ), - ] diff --git a/src/api/migrations/0019_auto_20210322_1823.py b/src/api/migrations/0019_auto_20210322_1823.py deleted file mode 100644 index b3c4cdf..0000000 --- a/src/api/migrations/0019_auto_20210322_1823.py +++ /dev/null @@ -1,19 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 18:23 - -import django.contrib.postgres.fields.jsonb -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0018_apilog_ip_addr'), - ] - - operations = [ - migrations.AlterField( - model_name='apilog', - name='body', - field=django.contrib.postgres.fields.jsonb.JSONField(null=True), - ), - ] diff --git a/src/api/migrations/0019_auto_20210907_1448.py b/src/api/migrations/0019_auto_20210907_1448.py deleted file mode 100644 index 92140fb..0000000 --- a/src/api/migrations/0019_auto_20210907_1448.py +++ /dev/null @@ -1,29 +0,0 @@ -# Generated by Django 2.2 on 2021-09-07 14:48 - -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - ('booking', '0008_auto_20201109_1947'), - ('resource_inventory', '0020_cloudinitfile'), - ('api', '0018_cloudinitfile'), - ] - - operations = [ - migrations.CreateModel( - name='GeneratedCloudConfig', - fields=[ - ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('resource_id', models.CharField(max_length=200)), - ('text', models.TextField(blank=True, null=True)), - ('booking', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='booking.Booking')), - ('rconfig', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceConfiguration')), - ], - ), - migrations.DeleteModel( - name='CloudInitFile', - ), - ] diff --git a/src/api/migrations/0020_auto_20210322_2218.py b/src/api/migrations/0020_auto_20210322_2218.py deleted file mode 100644 index 0252c79..0000000 --- a/src/api/migrations/0020_auto_20210322_2218.py +++ /dev/null @@ -1,23 +0,0 @@ -# Generated by Django 2.2 on 2021-03-22 22:18 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0019_auto_20210322_1823'), - ] - - operations = [ - migrations.AddField( - model_name='apilog', - name='method', - field=models.CharField(max_length=4, null=True), - ), - migrations.AlterField( - model_name='apilog', - name='endpoint', - field=models.CharField(max_length=300, null=True), - ), - ] diff --git a/src/api/migrations/0021_auto_20210405_1943.py b/src/api/migrations/0021_auto_20210405_1943.py deleted file mode 100644 index ca6e741..0000000 --- a/src/api/migrations/0021_auto_20210405_1943.py +++ /dev/null @@ -1,18 +0,0 @@ -# Generated by Django 2.2 on 2021-04-05 19:43 - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0020_auto_20210322_2218'), - ] - - operations = [ - migrations.AlterField( - model_name='apilog', - name='method', - field=models.CharField(max_length=6, null=True), - ), - ] diff --git a/src/api/migrations/0022_add_cifile_generated_field.py b/src/api/migrations/0022_add_cifile_generated_field.py deleted file mode 100644 index f83a102..0000000 --- a/src/api/migrations/0022_add_cifile_generated_field.py +++ /dev/null @@ -1,15 +0,0 @@ -from django.db import migrations, models - - -class Migration(migrations.Migration): - dependencies = [ - ('api', '0018_cloudinitfile'), - ] - - operations = [ - migrations.AddField( - model_name="CloudInitFile", - name="generated", - field=models.BooleanField(default=False) - ), - ] diff --git a/src/api/migrations/0022_merge_20211102_2136.py b/src/api/migrations/0022_merge_20211102_2136.py deleted file mode 100644 index bb27ae4..0000000 --- a/src/api/migrations/0022_merge_20211102_2136.py +++ /dev/null @@ -1,14 +0,0 @@ -# Generated by Django 2.2 on 2021-11-02 21:36 - -from django.db import migrations - - -class Migration(migrations.Migration): - - dependencies = [ - ('api', '0019_auto_20210907_1448'), - ('api', '0021_auto_20210405_1943'), - ] - - operations = [ - ] diff --git a/src/api/migrations/__init__.py b/src/api/migrations/__init__.py deleted file mode 100644 index e0408fa..0000000 --- a/src/api/migrations/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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/src/api/models.py b/src/api/models.py deleted file mode 100644 index 93168f5..0000000 --- a/src/api/models.py +++ /dev/null @@ -1,1453 +0,0 @@ -############################################################################## -# 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, ValidationError -from django.shortcuts import get_object_or_404 -from django.contrib.postgres.fields import JSONField -from django.http import HttpResponseNotFound -from django.urls import reverse -from django.utils import timezone - -import json -import uuid -import yaml -import re - -from booking.models import Booking -from resource_inventory.models import ( - Lab, - ResourceProfile, - Image, - Opsys, - Interface, - ResourceOPNFVConfig, - RemoteInfo, - OPNFVConfig, - ConfigState, - ResourceQuery, - ResourceConfiguration, - CloudInitFile -) -from resource_inventory.idf_templater import IDFTemplater -from resource_inventory.pdf_templater import PDFTemplater -from account.models import Downtime, UserProfile -from dashboard.utils import AbstractModelQuery - - -class JobStatus: - """ - A poor man's enum for a job's status. - - A job is NEW if it has not been started or recognized by the Lab - A job is CURRENT if it has been started by the lab but it is not yet completed - a job is DONE if all the tasks are complete and the booking is ready to use - """ - - NEW = 0 - CURRENT = 100 - DONE = 200 - ERROR = 300 - - -class LabManagerTracker: - - @classmethod - def get(cls, lab_name, token): - """ - Get a LabManager. - - Takes in a lab name (from a url path) - returns a lab manager instance for that lab, if it exists - Also checks that the given API token is correct - """ - 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: - """ - Handles all lab REST calls. - - handles jobs, inventory, status, etc - may need to create helper classes - """ - - def __init__(self, lab): - self.lab = lab - - def get_opsyss(self): - return Opsys.objects.filter(from_lab=self.lab) - - def get_images(self): - return Image.objects.filter(from_lab=self.lab) - - def get_image(self, image_id): - return Image.objects.filter(from_lab=self.lab, lab_id=image_id) - - def get_opsys(self, opsys_id): - return Opsys.objects.filter(from_lab=self.lab, lab_id=opsys_id) - - def get_downtime(self): - return Downtime.objects.filter(start__lt=timezone.now(), end__gt=timezone.now(), lab=self.lab) - - def get_downtime_json(self): - downtime = self.get_downtime().first() # should only be one item in queryset - if downtime: - return { - "is_down": True, - "start": downtime.start, - "end": downtime.end, - "description": downtime.description - } - return {"is_down": False} - - def create_downtime(self, form): - """ - Create a downtime event. - - Takes in a dictionary that describes the model. - { - "start": utc timestamp - "end": utc timestamp - "description": human text (optional) - } - For timestamp structure, https://docs.djangoproject.com/en/2.2/ref/forms/fields/#datetimefield - """ - Downtime.objects.create( - start=form.cleaned_data['start'], - end=form.cleaned_data['end'], - description=form.cleaned_data['description'], - lab=self.lab - ) - return self.get_downtime_json() - - def update_host_remote_info(self, data, res_id): - resource = ResourceQuery.filter(labid=res_id, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(res_id)) - resource = resource[0] - 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 = resource.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() - resource.remote_management = remote_info - resource.save() - booking = Booking.objects.get(resource=resource.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'] = [{ - "type": profile.name, - "count": len(profile.get_resources(lab=self.lab))} - for profile in ResourceProfile.objects.filter(labs=self.lab)] - return prof - - def format_user(self, userprofile): - return { - "id": userprofile.user.id, - "username": userprofile.user.username, - "email": userprofile.email_addr, - "first_name": userprofile.user.first_name, - "last_name": userprofile.user.last_name, - "company": userprofile.company - } - - def get_users(self): - userlist = [self.format_user(profile) for profile in UserProfile.objects.select_related("user").all()] - - return json.dumps({"users": userlist}) - - def get_user(self, user_id): - user = User.objects.get(pk=user_id) - - profile = get_object_or_404(UserProfile, user=user) - - return json.dumps(self.format_user(profile)) - - def get_inventory(self): - inventory = {} - resources = ResourceQuery.filter(lab=self.lab) - images = Image.objects.filter(from_lab=self.lab) - profiles = ResourceProfile.objects.filter(labs=self.lab) - inventory['resources'] = self.serialize_resources(resources) - inventory['images'] = self.serialize_images(images) - inventory['host_types'] = self.serialize_host_profiles(profiles) - return inventory - - def get_host(self, hostname): - resource = ResourceQuery.filter(labid=hostname, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(hostname)) - resource = resource[0] - return { - "booked": resource.booked, - "working": resource.working, - "type": resource.profile.name - } - - def update_host(self, hostname, data): - resource = ResourceQuery.filter(labid=hostname, lab=self.lab) - if len(resource) != 1: - return HttpResponseNotFound("Could not find single host with id " + str(hostname)) - resource = resource[0] - if "working" in data: - working = data['working'] == "true" - resource.working = working - resource.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_analytics_job(self): - """ Get analytics job with status new """ - jobs = Job.objects.filter( - booking__lab=self.lab, - job_type='DATA' - ) - - return self.serialize_jobs(jobs, status=JobStatus.NEW) - - 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_resources(self, resources): - # TODO: rewrite for Resource model - host_ser = [] - for res in resources: - r = { - 'interfaces': [], - 'hostname': res.name, - 'host_type': res.profile.name - } - for iface in res.get_interfaces(): - r['interfaces'].append({ - 'mac': iface.mac_address, - 'busaddr': iface.bus_address, - 'name': iface.name, - 'switchport': {"switch_name": iface.switch_name, "port_name": iface.port_name} - }) - 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_resource_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 GeneratedCloudConfig(models.Model): - resource_id = models.CharField(max_length=200) - booking = models.ForeignKey(Booking, on_delete=models.CASCADE) - rconfig = models.ForeignKey(ResourceConfiguration, on_delete=models.CASCADE) - text = models.TextField(null=True, blank=True) - - def _normalize_username(self, username: str) -> str: - # TODO: make usernames posix compliant - s = re.sub(r'\W+', '', username) - return s - - def _get_ssh_string(self, username: str) -> str: - user = User.objects.get(username=username) - uprofile = user.userprofile - - ssh_file = uprofile.ssh_public_key - - escaped_file = ssh_file.open().read().decode(encoding="UTF-8").replace("\n", " ") - - return escaped_file - - def _serialize_users(self): - """ - returns the dictionary to be placed behind the `users` field of the toplevel c-i dict - """ - # conserves distro default user - user_array = ["default"] - - users = list(self.booking.collaborators.all()) - users.append(self.booking.owner) - for collaborator in users: - userdict = {} - - # TODO: validate if usernames are valid as linux usernames (and provide an override potentially) - userdict['name'] = self._normalize_username(collaborator.username) - - userdict['groups'] = "sudo" - userdict['sudo'] = "ALL=(ALL) NOPASSWD:ALL" - - userdict['ssh_authorized_keys'] = [self._get_ssh_string(collaborator.username)] - - user_array.append(userdict) - - # user_array.append({ - # "name": "opnfv", - # "passwd": "$6$k54L.vim1cLaEc4$5AyUIrufGlbtVBzuCWOlA1yV6QdD7Gr2MzwIs/WhuYR9ebSfh3Qlb7djkqzjwjxpnSAonK1YOabPP6NxUDccu.", - # "ssh_redirect_user": True, - # "sudo": "ALL=(ALL) NOPASSWD:ALL", - # "groups": "sudo", - # }) - - return user_array - - # TODO: make this configurable - def _serialize_sysinfo(self): - defuser = {} - defuser['name'] = 'opnfv' - defuser['plain_text_passwd'] = 'OPNFV_HOST' - defuser['home'] = '/home/opnfv' - defuser['shell'] = '/bin/bash' - defuser['lock_passwd'] = True - defuser['gecos'] = 'Lab Manager User' - defuser['groups'] = 'sudo' - - return {'default_user': defuser} - - # TODO: make this configurable - def _serialize_runcmds(self): - cmdlist = [] - - # have hosts run dhcp on boot - cmdlist.append(['sudo', 'dhclient', '-r']) - cmdlist.append(['sudo', 'dhclient']) - - return cmdlist - - def _serialize_netconf_v1(self): - # interfaces = {} # map from iface_name => dhcp_config - # vlans = {} # map from vlan_id => dhcp_config - - config_arr = [] - - for interface in self._resource().interfaces.all(): - interface_name = interface.profile.name - interface_mac = interface.mac_address - - iface_dict_entry = { - "type": "physical", - "name": interface_name, - "mac_address": interface_mac, - } - - for vlan in interface.config.all(): - if vlan.tagged: - vlan_dict_entry = {'type': 'vlan'} - vlan_dict_entry['name'] = str(interface_name) + "." + str(vlan.vlan_id) - vlan_dict_entry['vlan_link'] = str(interface_name) - vlan_dict_entry['vlan_id'] = int(vlan.vlan_id) - vlan_dict_entry['mac_address'] = str(interface_mac) - if vlan.public: - vlan_dict_entry["subnets"] = [{"type": "dhcp"}] - config_arr.append(vlan_dict_entry) - if (not vlan.tagged) and vlan.public: - iface_dict_entry["subnets"] = [{"type": "dhcp"}] - - # vlan_dict_entry['mtu'] = # TODO, determine override MTU if needed - - config_arr.append(iface_dict_entry) - - ns_dict = { - 'type': 'nameserver', - 'address': ['10.64.0.1', '8.8.8.8'] - } - - config_arr.append(ns_dict) - - full_dict = {'version': 1, 'config': config_arr} - - return full_dict - - @classmethod - def get(cls, booking_id: int, resource_lab_id: str, file_id: int): - return GeneratedCloudConfig.objects.get(resource_id=resource_lab_id, booking__id=booking_id, file_id=file_id) - - def _resource(self): - return ResourceQuery.get(labid=self.resource_id, lab=self.booking.lab) - - # def _get_facts(self): - # resource = self._resource() - - # hostname = self.rconfig.name - # iface_configs = for_config.interface_configs.all() - - def _to_dict(self): - main_dict = {} - - main_dict['users'] = self._serialize_users() - main_dict['network'] = self._serialize_netconf_v1() - main_dict['hostname'] = self.rconfig.name - - # add first startup commands - main_dict['runcmd'] = self._serialize_runcmds() - - # configure distro default user - main_dict['system_info'] = self._serialize_sysinfo() - - return main_dict - - def serialize(self) -> str: - return yaml.dump(self._to_dict(), width=float("inf")) - - -class APILog(models.Model): - user = models.ForeignKey(User, on_delete=models.PROTECT) - call_time = models.DateTimeField(auto_now=True) - method = models.CharField(null=True, max_length=6) - endpoint = models.CharField(null=True, max_length=300) - ip_addr = models.GenericIPAddressField(protocol="both", null=True, unpack_ipv4=False) - body = JSONField(null=True) - - def __str__(self): - return "Call to {} at {} by {}".format( - self.endpoint, - self.call_time, - self.user.username - ) - - -class AutomationAPIManager: - @staticmethod - def serialize_booking(booking): - sbook = {} - sbook['id'] = booking.pk - sbook['owner'] = booking.owner.username - sbook['collaborators'] = [user.username for user in booking.collaborators.all()] - sbook['start'] = booking.start - sbook['end'] = booking.end - sbook['lab'] = AutomationAPIManager.serialize_lab(booking.lab) - sbook['purpose'] = booking.purpose - sbook['resourceBundle'] = AutomationAPIManager.serialize_bundle(booking.resource) - return sbook - - @staticmethod - def serialize_lab(lab): - slab = {} - slab['id'] = lab.pk - slab['name'] = lab.name - return slab - - @staticmethod - def serialize_bundle(bundle): - sbundle = {} - sbundle['id'] = bundle.pk - sbundle['resources'] = [ - AutomationAPIManager.serialize_server(server) - for server in bundle.get_resources()] - return sbundle - - @staticmethod - def serialize_server(server): - sserver = {} - sserver['id'] = server.pk - sserver['name'] = server.name - return sserver - - @staticmethod - def serialize_resource_profile(profile): - sprofile = {} - sprofile['id'] = profile.pk - sprofile['name'] = profile.name - return sprofile - - @staticmethod - def serialize_template(rec_temp_and_count): - template = rec_temp_and_count[0] - count = rec_temp_and_count[1] - - stemplate = {} - stemplate['id'] = template.pk - stemplate['name'] = template.name - stemplate['count_available'] = count - stemplate['resourceProfiles'] = [ - AutomationAPIManager.serialize_resource_profile(config.profile) - for config in template.getConfigs() - ] - return stemplate - - @staticmethod - def serialize_image(image): - simage = {} - simage['id'] = image.pk - simage['name'] = image.name - return simage - - @staticmethod - def serialize_userprofile(up): - sup = {} - sup['id'] = up.pk - sup['username'] = up.user.username - return sup - - -class Job(models.Model): - """ - A Job to be performed by the Lab. - - The API uses Jobs and Tasks to communicate actions that need to be taken to the Lab - that is hosting a booking. A booking from a user has an associated Job which tells - the lab how to configure the hardware, networking, etc to fulfill the booking - for the user. - This is the class that is serialized and put into the api - """ - - JOB_TYPES = ( - ('BOOK', 'Booking'), - ('DATA', 'Analytics') - ) - - booking = models.OneToOneField(Booking, on_delete=models.CASCADE, null=True) - status = models.IntegerField(default=JobStatus.NEW) - complete = models.BooleanField(default=False) - job_type = models.CharField( - max_length=4, - choices=JOB_TYPES, - default='BOOK' - ) - - def to_dict(self): - d = {} - for relation in self.get_tasklist(): - if relation.job_key not in d: - d[relation.job_key] = {} - d[relation.job_key][relation.task_id] = relation.config.to_dict() - - return {"id": self.id, "payload": d} - - def get_tasklist(self, status="all"): - if status != "all": - return JobTaskQuery.filter(job=self, status=status) - return JobTaskQuery.filter(job=self) - - def is_fulfilled(self): - """ - If a job has been completed by the lab. - - 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 = {} - for relation in self.get_tasklist(status=status): - if relation.job_key not in d: - d[relation.job_key] = {} - d[relation.job_key][relation.task_id] = relation.config.get_delta() - - return {"id": self.id, "payload": d} - - def to_json(self): - return json.dumps(self.to_dict()) - - -class TaskConfig(models.Model): - state = models.IntegerField(default=ConfigState.NEW) - - keys = set() # TODO: This needs to be an instance variable, not a class variable - delta_keys_list = models.CharField(max_length=200, default="[]") - - @property - def delta_keys(self): - return list(set(json.loads(self.delta_keys_list))) - - @delta_keys.setter - def delta_keys(self, keylist): - self.delta_keys_list = json.dumps(keylist) - - def to_dict(self): - raise NotImplementedError - - def get_delta(self): - raise NotImplementedError - - def format_delta(self, config, token): - delta = {k: config[k] for k in self.delta_keys} - delta['lab_token'] = token - return delta - - def to_json(self): - return json.dumps(self.to_dict()) - - def clear_delta(self): - self.delta_keys = [] - - def set(self, *args): - dkeys = self.delta_keys - for arg in args: - if arg in self.keys: - dkeys.append(arg) - self.delta_keys = dkeys - - -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 = ResourceQuery.get(interface__pk=self.interfaces.first().pk).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 ActiveUsersConfig(models.Model): - """ - Task for getting active VPN users - - StackStorm needs no information to run this job - so this task is very bare, but neccessary to fit - job creation convention. - """ - - def clear_delta(self): - self.delta = '{}' - - def get_delta(self): - return json.loads(self.to_json()) - - def to_json(self): - return json.dumps(self.to_dict()) - - def to_dict(self): - return {} - - -class OpnfvApiConfig(models.Model): - - installer = models.CharField(max_length=200) - scenario = models.CharField(max_length=300) - roles = models.ManyToManyField(ResourceOPNFVConfig) - # 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): - return json.loads(self.to_json()) - - -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): - d = json.loads(self.to_json()) - 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): - """Handles software installations, such as OPNFV or ONAP.""" - - 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): - """Describes the desired configuration of the hardware.""" - - 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() - - keys = set(["id", "image", "power", "hostname", "ipmi_create"]) - - def to_dict(self): - return self.get_delta() - - def get_delta(self): - # TODO: grab the GeneratedCloudConfig urls from self.hosthardwarerelation.get_resource() - return self.format_delta( - self.hosthardwarerelation.get_resource().get_configuration(self.state), - self.hosthardwarerelation.lab_token) - - -class NetworkConfig(TaskConfig): - """Handles network configuration.""" - - interfaces = models.ManyToManyField(Interface) - delta = models.TextField() - - def to_dict(self): - d = {} - hid = self.hostnetworkrelation.resource_id - d[hid] = {} - for interface in self.interfaces.all(): - d[hid][interface.mac_address] = [] - if self.state != ConfigState.CLEAN: - for vlan in interface.config.all(): - # TODO: should this come from the interface? - # e.g. will different interfaces for different resources need different configs? - 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): - d = json.loads(self.to_json()) - 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.resource_id - 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): - - resource_id = models.CharField(max_length=200, default="default_id") - image = models.CharField(max_length=200, null=True) # cobbler ID - 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): - d = json.loads(self.to_json()) - 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 save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - -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): - """ - Relates a Job to a TaskConfig. - - superclass that relates a Job to tasks anc maintains information - like status and messages from the lab - """ - - 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="") - - job_key = None - - 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) - job_key = "access" - - 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) - job_key = "software" - - 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): - resource_id = models.CharField(max_length=200, default="default_id") - config = models.OneToOneField(HardwareConfig, on_delete=models.CASCADE) - job_key = "hardware" - - 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) - - def save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - def get_resource(self): - return ResourceQuery.get(labid=self.resource_id) - - -class HostNetworkRelation(TaskRelation): - resource_id = models.CharField(max_length=200, default="default_id") - config = models.OneToOneField(NetworkConfig, on_delete=models.CASCADE) - job_key = "network" - - def type_str(self): - return "Network Configuration Task" - - def delete(self, *args, **kwargs): - self.config.delete() - return super(self.__class__, self).delete(*args, **kwargs) - - def save(self, *args, **kwargs): - if len(ResourceQuery.filter(labid=self.resource_id)) != 1: - raise ValidationError("resource_id " + str(self.resource_id) + " does not refer to a single resource") - super().save(*args, **kwargs) - - def get_resource(self): - return ResourceQuery.get(labid=self.resource_id) - - -class SnapshotRelation(TaskRelation): - snapshot = models.ForeignKey(Image, on_delete=models.CASCADE) - config = models.OneToOneField(SnapshotConfig, on_delete=models.CASCADE) - job_key = "snapshot" - - 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 ActiveUsersRelation(TaskRelation): - config = models.OneToOneField(ActiveUsersConfig, on_delete=models.CASCADE) - job_key = "active users task" - - def type_str(self): - return "Active Users Task" - - -class JobFactory(object): - """This class creates all the API models (jobs, tasks, etc) needed to fulfill a booking.""" - - @classmethod - def reimageHost(cls, new_image, booking, host): - """Modify an existing job to reimage the given host.""" - job = Job.objects.get(booking=booking) - # make hardware task new - hardware_relation = HostHardwareRelation.objects.get(resource_id=host, job=job) - hardware_relation.config.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(resource_id=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 makeActiveUsersTask(cls): - """ Append active users task to analytics job """ - config = ActiveUsersConfig() - relation = ActiveUsersRelation() - job = Job.objects.get(job_type='DATA') - - job.status = JobStatus.NEW - - relation.job = job - relation.config = config - relation.config.save() - relation.config = relation.config - relation.save() - config.save() - - @classmethod - def makeAnalyticsJob(cls, booking): - """ - Create the analytics job - - This will only run once since there will only be one analytics job. - All analytics tasks get appended to analytics job. - """ - - if len(Job.objects.filter(job_type='DATA')) > 0: - raise Exception("Cannot have more than one analytics job") - - if booking.resource: - raise Exception("Booking is not marker for analytics job, has resoure") - - job = Job() - job.booking = booking - job.job_type = 'DATA' - job.save() - - cls.makeActiveUsersTask() - - @classmethod - def makeCompleteJob(cls, booking): - """Create everything that is needed to fulfill the given booking.""" - resources = booking.resource.get_resources() - job = None - try: - job = Job.objects.get(booking=booking) - except Exception: - job = Job.objects.create(status=JobStatus.NEW, booking=booking) - cls.makeHardwareConfigs( - resources=resources, - job=job - ) - cls.makeNetworkConfigs( - resources=resources, - job=job - ) - cls.makeSoftware( - booking=booking, - job=job - ) - cls.makeGeneratedCloudConfigs( - resources=resources, - 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": [r.labid for r in resources] - } - ) - except Exception: - continue - - @classmethod - def makeGeneratedCloudConfigs(cls, resources=[], job=Job()): - for res in resources: - cif = GeneratedCloudConfig.objects.create(resource_id=res.labid, booking=job.booking, rconfig=res.config) - cif.save() - - cif = CloudInitFile.create(priority=0, text=cif.serialize()) - cif.save() - - res.config.cloud_init_files.add(cif) - res.config.save() - - @classmethod - def makeHardwareConfigs(cls, resources=[], job=Job()): - """ - Create and save HardwareConfig. - - Helper function to create the tasks related to - configuring the hardware - """ - for res in resources: - hardware_config = None - try: - hardware_config = HardwareConfig.objects.get(relation__resource_id=res.labid) - except Exception: - hardware_config = HardwareConfig() - - relation = HostHardwareRelation() - relation.resource_id = res.labid - relation.job = job - relation.config = hardware_config - relation.config.save() - relation.config = relation.config - relation.save() - - hardware_config.set("id", "image", "hostname", "power", "ipmi_create") - hardware_config.save() - - @classmethod - def makeAccessConfig(cls, users, access_type, revoke=False, job=Job(), context=False): - """ - Create and save AccessConfig. - - Helper function to create the tasks related to - configuring the VPN, SSH, etc access for users - """ - 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, resources=[], job=Job()): - """ - Create and save NetworkConfig. - - Helper function to create the tasks related to - configuring the networking - """ - for res in resources: - network_config = None - try: - network_config = NetworkConfig.objects.get(relation__host=res) - except Exception: - network_config = NetworkConfig.objects.create() - - relation = HostNetworkRelation() - relation.resource_id = res.labid - relation.job = job - network_config.save() - relation.config = network_config - relation.save() - network_config.clear_delta() - - # TODO: use get_interfaces() on resource - for interface in res.interfaces.all(): - network_config.add_interface(interface) - network_config.save() - - @classmethod - def make_bridge_config(cls, booking): - if len(booking.resource.get_resources()) < 2: - return None - try: - jumphost_config = ResourceOPNFVConfig.objects.filter( - role__name__iexact="jumphost" - ) - jumphost = ResourceQuery.filter( - bundle=booking.resource, - config=jumphost_config.resource_config - )[0] - 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()): - """ - Create and save SoftwareConfig. - - Helper function to create the tasks related to - configuring the desired software, e.g. an OPNFV deployment - """ - 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.get_resources(): - 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 - - -JOB_TASK_CLASSLIST = [ - HostHardwareRelation, - AccessRelation, - HostNetworkRelation, - SoftwareRelation, - SnapshotRelation, - ActiveUsersRelation -] - - -class JobTaskQuery(AbstractModelQuery): - model_list = JOB_TASK_CLASSLIST diff --git a/src/api/serializers/__init__.py b/src/api/serializers/__init__.py deleted file mode 100644 index e0408fa..0000000 --- a/src/api/serializers/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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/src/api/serializers/booking_serializer.py b/src/api/serializers/booking_serializer.py deleted file mode 100644 index 993eb22..0000000 --- a/src/api/serializers/booking_serializer.py +++ /dev/null @@ -1,173 +0,0 @@ -############################################################################## -# 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 ( - ResourceConfiguration, - CpuProfile, - DiskProfile, - InterfaceProfile, - RamProfile, - Image, - Interface -) - - -class BookingField(serializers.Field): - - def to_representation(self, booking): - """ - Take 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] = ResourceConfiguration.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): - """ - Take in a dictionary of primitives, and return 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): - """Take 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/src/api/serializers/old_serializers.py b/src/api/serializers/old_serializers.py deleted file mode 100644 index 0944881..0000000 --- a/src/api/serializers/old_serializers.py +++ /dev/null @@ -1,21 +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 - - -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/src/api/tests/__init__.py b/src/api/tests/__init__.py deleted file mode 100644 index 2435a9f..0000000 --- a/src/api/tests/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -############################################################################## -# 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/src/api/tests/test_models_unittest.py b/src/api/tests/test_models_unittest.py deleted file mode 100644 index 2dee29b..0000000 --- a/src/api/tests/test_models_unittest.py +++ /dev/null @@ -1,271 +0,0 @@ -# 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, - ConfigState, -) - -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): - """ - Distribute 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.get_delta()["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)) - # TODO: ConfigState needs to be fixed in factory methods - relation.config.state = ConfigState.NEW - self.assertEqual(relation.config.get_delta()["power"], "on") - self.assertTrue(relation.config.get_delta()["ipmi_create"]) - # TODO: the rest of hwconf attrs - - self.assertEqual(len(host_set), 0) diff --git a/src/api/urls.py b/src/api/urls.py deleted file mode 100644 index cbb453c..0000000 --- a/src/api/urls.py +++ /dev/null @@ -1,108 +0,0 @@ -############################################################################## -# 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 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - - -""" -laas_dashboard URL Configuration. - -The `urlpatterns` list routes URLs to views. For more information please see: - https://docs.djangoproject.com/en/1.10/topics/http/urls/ -Examples: -Function views - 1. Add an import: from my_app import views - 2. Add a URL to urlpatterns: url(r'^$', views.home, name='home') -Class-based views - 1. Add an import: from other_app.views import Home - 2. Add a URL to urlpatterns: url(r'^$', Home.as_view(), name='home') -Including another URLconf - 1. Import the include() function: from django.conf.urls import url, include - 2. Add a URL to urlpatterns: url(r'^blog/', include('blog.urls')) -""" -from django.conf.urls import url -from django.urls import path - -from api.views import ( - lab_profile, - lab_status, - lab_inventory, - lab_downtime, - specific_job, - specific_task, - new_jobs, - current_jobs, - done_jobs, - update_host_bmc, - lab_host, - get_pdf, - get_idf, - lab_users, - lab_user, - GenerateTokenView, - analytics_job, - user_bookings, - specific_booking, - extend_booking, - make_booking, - list_labs, - all_users, - images_for_template, - available_templates, - resource_ci_metadata, - resource_ci_userdata, - resource_ci_userdata_directory, - all_images, - all_opsyss, - single_image, - single_opsys, - create_ci_file, - booking_details, -) - -urlpatterns = [ - path('labs/<slug:lab_name>/opsys/<slug:opsys_id>', single_opsys), - path('labs/<slug:lab_name>/image/<slug:image_id>', single_image), - path('labs/<slug:lab_name>/opsys', all_opsyss), - path('labs/<slug:lab_name>/image', all_images), - 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>/downtime', lab_downtime), - 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/<int:job_id>/cidata/<slug:resource_id>/user-data', resource_ci_userdata_directory, name="specific-user-data"), - path('labs/<slug:lab_name>/jobs/<int:job_id>/cidata/<slug:resource_id>/meta-data', resource_ci_metadata, name="specific-meta-data"), - path('labs/<slug:lab_name>/jobs/<int:job_id>/cidata/<slug:resource_id>/<int:file_id>/user-data', resource_ci_userdata, name="user-data-dir"), - 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), - path('labs/<slug:lab_name>/jobs/getByType/DATA', analytics_job), - path('labs/<slug:lab_name>/users', lab_users), - path('labs/<slug:lab_name>/users/<int:user_id>', lab_user), - - path('booking', user_bookings), - path('booking/<int:booking_id>', specific_booking), - path('booking/<int:booking_id>/extendBooking/<int:days>', extend_booking), - path('booking/makeBooking', make_booking), - path('booking/<int:booking_id>/details', booking_details), - - path('resource_inventory/availableTemplates', available_templates), - path('resource_inventory/<int:template_id>/images', images_for_template), - - path('resource_inventory/cloud/create', create_ci_file), - - path('users', all_users), - path('labs', list_labs), - - url(r'^token$', GenerateTokenView.as_view(), name='generate_token'), -] diff --git a/src/api/views.py b/src/api/views.py deleted file mode 100644 index d5966ed..0000000 --- a/src/api/views.py +++ /dev/null @@ -1,756 +0,0 @@ -############################################################################## -# 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 -############################################################################## - -import json -import math -import traceback -import sys -from datetime import timedelta - -from django.contrib.auth.decorators import login_required -from django.shortcuts import redirect, get_object_or_404 -from django.utils.decorators import method_decorator -from django.utils import timezone -from django.views import View -from django.http import HttpResponseNotFound -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 django.core.exceptions import ObjectDoesNotExist -from django.db.models import Q - -from api.serializers.booking_serializer import BookingSerializer -from api.serializers.old_serializers import UserSerializer -from api.forms import DowntimeForm -from account.models import UserProfile, Lab -from booking.models import Booking -from booking.quick_deployer import create_from_API -from api.models import LabManagerTracker, get_task, Job, AutomationAPIManager, APILog, GeneratedCloudConfig -from notifier.manager import NotificationHandler -from analytics.models import ActiveVPNUser -from resource_inventory.models import ( - Image, - Opsys, - CloudInitFile, - ResourceQuery, - ResourceTemplate, -) - -import yaml -import uuid -from deepmerge import Merger - -""" -API views. - -All functions return a Json blob -Most functions that deal with info from a specific lab (tasks, host info) -requires the Lab auth token. - for example, curl -H auth-token:mylabsauthtoken url - -Most functions let you GET or POST to the same endpoint, and -the correct thing will happen -""" - - -class BookingViewSet(viewsets.ModelViewSet): - queryset = Booking.objects.all() - serializer_class = BookingSerializer - filter_fields = ('resource', 'id') - - -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): - user = self.request.user - token, created = Token.objects.get_or_create(user=user) - if not created: - 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) - -# API extension for Cobbler integration - - -def all_images(request, lab_name=""): - a = [] - for i in Image.objects.all(): - a.append(i.serialize()) - return JsonResponse(a, safe=False) - - -def all_opsyss(request, lab_name=""): - a = [] - for opsys in Opsys.objects.all(): - a.append(opsys.serialize()) - - return JsonResponse(a, safe=False) - - -@csrf_exempt -def single_image(request, lab_name="", image_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - img = lab_manager.get_image(image_id).first() - - if request.method == "GET": - if not img: - return HttpResponse(status=404) - return JsonResponse(img.serialize(), safe=False) - - if request.method == "POST": - # get POST data - data = json.loads(request.body.decode('utf-8')) - if img: - img.update(data) - else: - # append lab name and the ID from the URL - data['from_lab_id'] = lab_name - data['lab_id'] = image_id - - # create and save a new Image object - img = Image.new_from_data(data) - - img.save() - - # indicate success in response - return HttpResponse(status=200) - return HttpResponse(status=405) - - -@csrf_exempt -def single_opsys(request, lab_name="", opsys_id=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - opsys = lab_manager.get_opsys(opsys_id).first() - - if request.method == "GET": - if not opsys: - return HttpResponse(status=404) - return JsonResponse(opsys.serialize(), safe=False) - - if request.method == "POST": - data = json.loads(request.body.decode('utf-8')) - if opsys: - opsys.update(data) - else: - # only name, available, and obsolete are needed to create an Opsys - # other fields are derived from the URL parameters - data['from_lab_id'] = lab_name - data['lab_id'] = opsys_id - opsys = Opsys.new_from_data(data) - - opsys.save() - return HttpResponse(status=200) - return HttpResponse(status=405) - -# end API extension - - -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) - - -def lab_users(request, lab_name=""): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_users(), content_type="text/plain") - - -def lab_user(request, lab_name="", user_id=-1): - lab_token = request.META.get('HTTP_AUTH_TOKEN') - lab_manager = LabManagerTracker.get(lab_name, lab_token) - return HttpResponse(lab_manager.get_user(user_id), content_type="text/plain") - - -@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) - - -@csrf_exempt -def resource_ci_userdata(request, lab_name="", job_id="", resource_id="", file_id=0): - # lab_token = request.META.get('HTTP_AUTH_TOKEN') - # lab_manager = LabManagerTracker.get(lab_name, lab_token) - - # job = lab_manager.get_job(job_id) - Job.objects.get(id=job_id) # verify a valid job was given, even if we don't use it - - cifile = None - try: - cifile = CloudInitFile.objects.get(id=file_id) - except ObjectDoesNotExist: - return HttpResponseNotFound("Could not find a matching resource by id " + str(resource_id)) - - text = cifile.text - - prepended_text = "#cloud-config\n" - # mstrat = CloudInitFile.merge_strategy() - # prepended_text = prepended_text + yaml.dump({"merge_strategy": mstrat}) + "\n" - # print("in cloudinitfile create") - text = prepended_text + text - cloud_dict = { - "datasource": { - "None": { - "metadata": { - "instance-id": str(uuid.uuid4()) - }, - "userdata_raw": text, - }, - }, - "datasource_list": ["None"], - } - - return HttpResponse(yaml.dump(cloud_dict, width=float("inf")), status=200) - - -@csrf_exempt -def resource_ci_metadata(request, lab_name="", job_id="", resource_id="", file_id=0): - return HttpResponse("#cloud-config", status=200) - - -@csrf_exempt -def resource_ci_userdata_directory(request, lab_name="", job_id="", resource_id=""): - # files = [{"id": file.file_id, "priority": file.priority} for file in CloudInitFile.objects.filter(job__id=job_id, resource_id=resource_id).order_by("priority").all()] - resource = ResourceQuery.get(labid=resource_id, lab=Lab.objects.get(name=lab_name)) - files = resource.config.cloud_init_files - files = [{"id": file.id, "priority": file.priority} for file in files.order_by("priority").all()] - - d = {} - - merge_failures = [] - - merger = Merger( - [ - (list, ["append"]), - (dict, ["merge"]), - ], - ["override"], # fallback - ["override"], # if types conflict (shouldn't happen in CI, but handle case) - ) - - for f in resource.config.cloud_init_files.order_by("priority").all(): - try: - other_dict = yaml.safe_load(f.text) - if not (type(d) is dict): - raise Exception("CI file was valid yaml but was not a dict") - - merger.merge(d, other_dict) - except Exception as e: - # if fail to merge, then just skip - print("Failed to merge file in, as it had invalid content:", f.id) - print("File text was:") - print(f.text) - merge_failures.append({f.id: str(e)}) - - if len(merge_failures) > 0: - d['merge_failures'] = merge_failures - - file = CloudInitFile.create(text=yaml.dump(d, width=float("inf")), priority=0) - - return HttpResponse(json.dumps([{"id": file.id, "priority": file.priority}]), status=200) - - -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) - - -@csrf_exempt -def analytics_job(request, lab_name=""): - """ returns all jobs with type booking""" - 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_analytics_job(), safe=False) - if request.method == "POST": - users = json.loads(request.body.decode('utf-8'))['active_users'] - try: - ActiveVPNUser.create(lab_name, users) - except ObjectDoesNotExist: - return JsonResponse('Lab does not exist!', safe=False) - return HttpResponse(status=200) - return HttpResponse(status=405) - - -def lab_downtime(request, lab_name=""): - 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_downtime_json()) - if request.method == "POST": - return post_lab_downtime(request, lab_manager) - if request.method == "DELETE": - return delete_lab_downtime(lab_manager) - return HttpResponse(status=405) - - -def post_lab_downtime(request, lab_manager): - current_downtime = lab_manager.get_downtime() - if current_downtime.exists(): - return JsonResponse({"error": "Lab is already in downtime"}, status=422) - form = DowntimeForm(request.POST) - if form.is_valid(): - return JsonResponse(lab_manager.create_downtime(form)) - else: - return JsonResponse(form.errors.get_json_data(), status=400) - - -def delete_lab_downtime(lab_manager): - current_downtime = lab_manager.get_downtime() - if current_downtime.exists(): - dt = current_downtime.first() - dt.end = timezone.now() - dt.save() - return JsonResponse(lab_manager.get_downtime_json(), safe=False) - else: - return JsonResponse({"error": "Lab is not in downtime"}, status=422) - - -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) - - -def auth_and_log(request, endpoint): - """ - Function to authenticate an API user and log info - in the API log model. This is to keep record of - all calls to the dashboard - """ - user_token = request.META.get('HTTP_AUTH_TOKEN') - response = None - - if user_token is None: - return HttpResponse('Unauthorized', status=401) - - try: - token = Token.objects.get(key=user_token) - except Token.DoesNotExist: - token = None - # Added logic to detect malformed token - if len(str(user_token)) != 40: - response = HttpResponse('Malformed Token', status=401) - else: - response = HttpResponse('Unauthorized', status=401) - - x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') - if x_forwarded_for: - ip = x_forwarded_for.split(',')[0] - else: - ip = request.META.get('REMOTE_ADDR') - - body = None - - if request.method in ['POST', 'PUT']: - try: - body = json.loads(request.body.decode('utf-8')), - except Exception: - response = HttpResponse('Invalid Request Body', status=400) - - APILog.objects.create( - user=token.user, - call_time=timezone.now(), - method=request.method, - endpoint=endpoint, - body=body, - ip_addr=ip - ) - - if response: - return response - else: - return token - - -""" -Booking API Views -""" - - -def user_bookings(request): - token = auth_and_log(request, 'booking') - - if isinstance(token, HttpResponse): - return token - - bookings = Booking.objects.filter(owner=token.user, end__gte=timezone.now()) - output = [AutomationAPIManager.serialize_booking(booking) - for booking in bookings] - return JsonResponse(output, safe=False) - - -@csrf_exempt -def specific_booking(request, booking_id=""): - token = auth_and_log(request, 'booking/{}'.format(booking_id)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - if request.method == "GET": - sbooking = AutomationAPIManager.serialize_booking(booking) - return JsonResponse(sbooking, safe=False) - - if request.method == "DELETE": - - if booking.end < timezone.now(): - return HttpResponse("Booking already over", status=400) - - booking.end = timezone.now() - booking.save() - return HttpResponse("Booking successfully cancelled") - - -@csrf_exempt -def extend_booking(request, booking_id="", days=""): - token = auth_and_log(request, 'booking/{}/extendBooking/{}'.format(booking_id, days)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - - if booking.end < timezone.now(): - return HttpResponse("This booking is already over, cannot extend") - - if days > 30: - return HttpResponse("Cannot extend a booking longer than 30 days") - - if booking.ext_count == 0: - return HttpResponse("Booking has already been extended 2 times, cannot extend again") - - booking.end += timedelta(days=days) - booking.ext_count -= 1 - booking.save() - - return HttpResponse("Booking successfully extended") - - -@csrf_exempt -def make_booking(request): - token = auth_and_log(request, 'booking/makeBooking') - - if isinstance(token, HttpResponse): - return token - - try: - booking = create_from_API(request.body, token.user) - - except Exception: - finalTrace = '' - exc_type, exc_value, exc_traceback = sys.exc_info() - for i in traceback.format_exception(exc_type, exc_value, exc_traceback): - finalTrace += '<br>' + i.strip() - return HttpResponse(finalTrace, status=400) - - sbooking = AutomationAPIManager.serialize_booking(booking) - return JsonResponse(sbooking, safe=False) - - -""" -Resource Inventory API Views -""" - - -def available_templates(request): - token = auth_and_log(request, 'resource_inventory/availableTemplates') - - if isinstance(token, HttpResponse): - return token - - # get available templates - # mirrors MultipleSelectFilter Widget - avt = [] - for lab in Lab.objects.all(): - for template in ResourceTemplate.objects.filter(Q(owner=token.user) | Q(public=True), lab=lab, temporary=False): - available_resources = lab.get_available_resources() - required_resources = template.get_required_resources() - least_available = 100 - - for resource, count_required in required_resources.items(): - try: - curr_count = math.floor(available_resources[str(resource)] / count_required) - if curr_count < least_available: - least_available = curr_count - except KeyError: - least_available = 0 - - if least_available > 0: - avt.append((template, least_available)) - - savt = [AutomationAPIManager.serialize_template(temp) - for temp in avt] - - return JsonResponse(savt, safe=False) - - -def images_for_template(request, template_id=""): - _ = auth_and_log(request, 'resource_inventory/{}/images'.format(template_id)) - - template = get_object_or_404(ResourceTemplate, pk=template_id) - images = [AutomationAPIManager.serialize_image(config.image) - for config in template.getConfigs()] - return JsonResponse(images, safe=False) - - -""" -User API Views -""" - - -def all_users(request): - token = auth_and_log(request, 'users') - - if token is None: - return HttpResponse('Unauthorized', status=401) - - users = [AutomationAPIManager.serialize_userprofile(up) - for up in UserProfile.objects.filter(public_user=True)] - - return JsonResponse(users, safe=False) - - -def create_ci_file(request): - token = auth_and_log(request, 'booking/makeCloudConfig') - - if isinstance(token, HttpResponse): - return token - - try: - cconf = request.body - d = yaml.load(cconf) - if not (type(d) is dict): - raise Exception() - - cconf = CloudInitFile.create(text=cconf, priority=CloudInitFile.objects.count()) - - return JsonResponse({"id": cconf.id}) - except Exception: - return JsonResponse({"error": "Provided config file was not valid yaml or was not a dict at the top level"}) - - -""" -Lab API Views -""" - - -def list_labs(request): - lab_list = [] - for lab in Lab.objects.all(): - lab_info = { - 'name': lab.name, - 'username': lab.lab_user.username, - 'status': lab.status, - 'project': lab.project, - 'description': lab.description, - 'location': lab.location, - 'info': lab.lab_info_link, - 'email': lab.contact_email, - 'phone': lab.contact_phone - } - lab_list.append(lab_info) - - return JsonResponse(lab_list, safe=False) - - -""" -Booking Details API Views -""" - - -def booking_details(request, booking_id=""): - token = auth_and_log(request, 'booking/{}/details'.format(booking_id)) - - if isinstance(token, HttpResponse): - return token - - booking = get_object_or_404(Booking, pk=booking_id, owner=token.user) - - # overview - overview = { - 'username': GeneratedCloudConfig._normalize_username(None, str(token.user)), - 'purpose': booking.purpose, - 'project': booking.project, - 'start_time': booking.start, - 'end_time': booking.end, - 'pod_definitions': booking.resource.template, - 'lab': booking.lab - } - - # deployment progress - task_list = [] - for task in booking.job.get_tasklist(): - task_info = { - 'name': str(task), - 'status': 'DONE', - 'lab_response': 'No response provided (yet)' - } - if task.status < 100: - task_info['status'] = 'PENDING' - elif task.status < 200: - task_info['status'] = 'IN PROGRESS' - - if task.message: - if task.type_str == "Access Task" and request.user.id != task.config.user.id: - task_info['lab_response'] = '--secret--' - else: - task_info['lab_response'] = str(task.message) - task_list.append(task_info) - - # pods - pod_list = [] - for host in booking.resource.get_resources(): - pod_info = { - 'hostname': host.config.name, - 'machine': host.name, - 'role': '', - 'is_headnode': host.config.is_head_node, - 'image': host.config.image, - 'ram': {'amount': str(host.profile.ramprofile.first().amount) + 'G', 'channels': host.profile.ramprofile.first().channels}, - 'cpu': {'arch': host.profile.cpuprofile.first().architecture, 'cores': host.profile.cpuprofile.first().cores, 'sockets': host.profile.cpuprofile.first().cpus}, - 'disk': {'size': str(host.profile.storageprofile.first().size) + 'GiB', 'type': host.profile.storageprofile.first().media_type, 'mount_point': host.profile.storageprofile.first().name}, - 'interfaces': [], - } - try: - pod_info['role'] = host.template.opnfvRole - except Exception: - pass - for intprof in host.profile.interfaceprofile.all(): - int_info = { - 'name': intprof.name, - 'speed': intprof.speed - } - pod_info['interfaces'].append(int_info) - pod_list.append(pod_info) - - # diagnostic info - diagnostic_info = { - 'job_id': booking.job.id, - 'ci_files': '', - 'pods': [] - } - for host in booking.resource.get_resources(): - pod = { - 'host': host.name, - 'configs': [], - - } - for ci_file in host.config.cloud_init_files.all(): - ci_info = { - 'id': ci_file.id, - 'text': ci_file.text - } - pod['configs'].append(ci_info) - diagnostic_info['pods'].append(pod) - - details = { - 'overview': overview, - 'deployment_progress': task_list, - 'pods': pod_list, - 'diagnostic_info': diagnostic_info, - 'pdf': booking.pdf - } - return JsonResponse(str(details), safe=False) |