aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/account/migrations/0006_auto_20201109_1947.py23
-rw-r--r--src/account/models.py6
-rw-r--r--src/analytics/__init__.py0
-rw-r--r--src/analytics/admin.py13
-rw-r--r--src/analytics/apps.py14
-rw-r--r--src/analytics/migrations/0001_initial.py22
-rw-r--r--src/analytics/migrations/0002_auto_20201109_2149.py27
-rw-r--r--src/analytics/migrations/__init__.py0
-rw-r--r--src/analytics/models.py30
-rw-r--r--src/analytics/tests.py10
-rw-r--r--src/analytics/views.py10
-rw-r--r--src/api/migrations/0015_auto_20201109_1947.py18
-rw-r--r--src/api/migrations/0016_auto_20201109_2149.py41
-rw-r--r--src/api/models.py90
-rw-r--r--src/api/urls.py4
-rw-r--r--src/api/views.py20
-rw-r--r--src/booking/migrations/0008_auto_20201109_1947.py30
-rw-r--r--src/booking/views.py11
-rw-r--r--src/dashboard/admin_utils.py159
-rw-r--r--src/dashboard/tasks.py20
-rw-r--r--src/dashboard/testing_utils.py8
-rw-r--r--src/dashboard/views.py16
-rw-r--r--src/laas_dashboard/settings.py5
-rw-r--r--src/resource_inventory/migrations/0016_auto_20201109_1947.py59
-rw-r--r--src/resource_inventory/pdf_templater.py6
-rw-r--r--src/resource_inventory/resource_manager.py7
-rw-r--r--src/templates/akraino/booking/quick_deploy.html3
-rw-r--r--src/templates/base/booking/booking_detail.html3
-rw-r--r--src/templates/base/booking/quick_deploy.html5
-rw-r--r--src/templates/base/dashboard/landing.html17
-rw-r--r--src/templates/base/workflow/viewport-base.html5
-rw-r--r--src/templates/laas/dashboard/landing.html2
-rw-r--r--src/workflow/resource_bundle_workflow.py2
-rw-r--r--src/workflow/views.py8
34 files changed, 669 insertions, 25 deletions
diff --git a/src/account/migrations/0006_auto_20201109_1947.py b/src/account/migrations/0006_auto_20201109_1947.py
new file mode 100644
index 0000000..d08c426
--- /dev/null
+++ b/src/account/migrations/0006_auto_20201109_1947.py
@@ -0,0 +1,23 @@
+# Generated by Django 2.2 on 2020-11-09 19:47
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0005_auto_20200723_2100'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userprofile',
+ name='full_name',
+ field=models.CharField(blank=True, default='', max_length=100, null=True),
+ ),
+ migrations.AlterField(
+ model_name='userprofile',
+ name='jira_url',
+ field=models.CharField(blank=True, default='', max_length=100, null=True),
+ ),
+ ]
diff --git a/src/account/models.py b/src/account/models.py
index 2d0293f..40de4d8 100644
--- a/src/account/models.py
+++ b/src/account/models.py
@@ -80,12 +80,12 @@ class VlanManager(models.Model):
# if they use QinQ or a vxlan overlay, for example
allow_overlapping = models.BooleanField()
- def get_vlan(self, count=1):
+ def get_vlans(self, count=1):
"""
- Return the ID of available vlans, but does not reserve them.
+ Return the IDs of available vlans as a list[int], but does not reserve them.
Will throw index exception if not enough vlans are available.
- If count == 1, the return value is an int. Otherwise, it is a list of ints.
+ Always returns a list of ints
"""
allocated = []
vlans = json.loads(self.vlans)
diff --git a/src/analytics/__init__.py b/src/analytics/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/analytics/__init__.py
diff --git a/src/analytics/admin.py b/src/analytics/admin.py
new file mode 100644
index 0000000..63f139f
--- /dev/null
+++ b/src/analytics/admin.py
@@ -0,0 +1,13 @@
+##############################################################################
+# Copyright (c) 2020 Sean Smith 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 import admin
+from analytics.models import ActiveVPNUser
+
+admin.site.register(ActiveVPNUser)
diff --git a/src/analytics/apps.py b/src/analytics/apps.py
new file mode 100644
index 0000000..fe1b11f
--- /dev/null
+++ b/src/analytics/apps.py
@@ -0,0 +1,14 @@
+##############################################################################
+# Copyright (c) 2020 Sean Smith 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
+
+
+class AnalyticsConfig(AppConfig):
+ name = 'analytics'
diff --git a/src/analytics/migrations/0001_initial.py b/src/analytics/migrations/0001_initial.py
new file mode 100644
index 0000000..05a7ec8
--- /dev/null
+++ b/src/analytics/migrations/0001_initial.py
@@ -0,0 +1,22 @@
+# Generated by Django 2.2 on 2020-08-10 20:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ initial = True
+
+ dependencies = [
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ActiveVPNUsers',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('time_stamp', models.DateTimeField(auto_now_add=True)),
+ ('active_users', models.IntegerField()),
+ ],
+ ),
+ ]
diff --git a/src/analytics/migrations/0002_auto_20201109_2149.py b/src/analytics/migrations/0002_auto_20201109_2149.py
new file mode 100644
index 0000000..a845ff8
--- /dev/null
+++ b/src/analytics/migrations/0002_auto_20201109_2149.py
@@ -0,0 +1,27 @@
+# Generated by Django 2.2 on 2020-11-09 21:49
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0006_auto_20201109_1947'),
+ ('analytics', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='ActiveVPNUser',
+ fields=[
+ ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+ ('time_stamp', models.DateTimeField(auto_now_add=True)),
+ ('active_users', models.IntegerField()),
+ ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')),
+ ],
+ ),
+ migrations.DeleteModel(
+ name='ActiveVPNUsers',
+ ),
+ ]
diff --git a/src/analytics/migrations/__init__.py b/src/analytics/migrations/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/analytics/migrations/__init__.py
diff --git a/src/analytics/models.py b/src/analytics/models.py
new file mode 100644
index 0000000..10baa0c
--- /dev/null
+++ b/src/analytics/models.py
@@ -0,0 +1,30 @@
+##############################################################################
+# Copyright (c) 2020 Sean Smith 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.db import models
+from account.models import Lab
+
+
+class ActiveVPNUser(models.Model):
+ """ Keeps track of how many VPN Users are connected to Lab """
+ time_stamp = models.DateTimeField(auto_now_add=True)
+ lab = models.ForeignKey(Lab, on_delete=models.CASCADE, null=False)
+ active_users = models.IntegerField()
+
+ @classmethod
+ def create(cls, lab_name, active_users):
+ """
+ This creates an Active VPN Users entry from
+ from lab_name as a string
+ """
+
+ lab = Lab.objects.get(name=lab_name)
+ avu = cls(lab=lab, active_users=active_users)
+ avu.save()
+ return avu
diff --git a/src/analytics/tests.py b/src/analytics/tests.py
new file mode 100644
index 0000000..d234f48
--- /dev/null
+++ b/src/analytics/tests.py
@@ -0,0 +1,10 @@
+##############################################################################
+# Copyright (c) 2020 Sean Smith 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.test import TestCase
diff --git a/src/analytics/views.py b/src/analytics/views.py
new file mode 100644
index 0000000..160bc59
--- /dev/null
+++ b/src/analytics/views.py
@@ -0,0 +1,10 @@
+##############################################################################
+# Copyright (c) 2020 Sean Smith 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.shortcuts import render
diff --git a/src/api/migrations/0015_auto_20201109_1947.py b/src/api/migrations/0015_auto_20201109_1947.py
new file mode 100644
index 0000000..bbd51e5
--- /dev/null
+++ b/src/api/migrations/0015_auto_20201109_1947.py
@@ -0,0 +1,18 @@
+# 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
new file mode 100644
index 0000000..a430659
--- /dev/null
+++ b/src/api/migrations/0016_auto_20201109_2149.py
@@ -0,0 +1,41 @@
+# 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/models.py b/src/api/models.py
index 9b9b778..527e66b 100644
--- a/src/api/models.py
+++ b/src/api/models.py
@@ -249,6 +249,15 @@ class LabManager(object):
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()
@@ -339,9 +348,19 @@ class Job(models.Model):
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 = {}
@@ -449,6 +468,28 @@ class BridgeConfig(models.Model):
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)
@@ -860,6 +901,14 @@ class SnapshotRelation(TaskRelation):
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."""
@@ -903,6 +952,44 @@ class JobFactory(object):
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()
@@ -1077,7 +1164,8 @@ JOB_TASK_CLASSLIST = [
AccessRelation,
HostNetworkRelation,
SoftwareRelation,
- SnapshotRelation
+ SnapshotRelation,
+ ActiveUsersRelation
]
diff --git a/src/api/urls.py b/src/api/urls.py
index 0005d34..bae86ea 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -44,7 +44,8 @@ from api.views import (
get_idf,
lab_users,
lab_user,
- GenerateTokenView
+ GenerateTokenView,
+ analytics_job
)
urlpatterns = [
@@ -61,6 +62,7 @@ urlpatterns = [
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),
url(r'^token$', GenerateTokenView.as_view(), name='generate_token'),
diff --git a/src/api/views.py b/src/api/views.py
index 75a0db3..2e5f33f 100644
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -18,6 +18,7 @@ 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 api.serializers.booking_serializer import BookingSerializer
from api.serializers.old_serializers import UserSerializer
@@ -26,6 +27,8 @@ from account.models import UserProfile
from booking.models import Booking
from api.models import LabManagerTracker, get_task
from notifier.manager import NotificationHandler
+from analytics.models import ActiveVPNUser
+import json
"""
API views.
@@ -176,6 +179,23 @@ def current_jobs(request, lab_name=""):
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)
diff --git a/src/booking/migrations/0008_auto_20201109_1947.py b/src/booking/migrations/0008_auto_20201109_1947.py
new file mode 100644
index 0000000..289e476
--- /dev/null
+++ b/src/booking/migrations/0008_auto_20201109_1947.py
@@ -0,0 +1,30 @@
+# Generated by Django 2.2 on 2020-11-09 19:47
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('booking', '0007_remove_booking_config_bundle'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='booking',
+ name='collaborators',
+ field=models.ManyToManyField(blank=True, related_name='collaborators', to=settings.AUTH_USER_MODEL),
+ ),
+ migrations.AlterField(
+ model_name='booking',
+ name='opnfv_config',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'),
+ ),
+ migrations.AlterField(
+ model_name='booking',
+ name='resource',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle'),
+ ),
+ ]
diff --git a/src/booking/views.py b/src/booking/views.py
index bd57812..c41a7d6 100644
--- a/src/booking/views.py
+++ b/src/booking/views.py
@@ -19,7 +19,7 @@ from django.db.models import Q
from django.urls import reverse
from resource_inventory.models import ResourceBundle, ResourceProfile, Image, ResourceQuery
-from account.models import Downtime
+from account.models import Downtime, Lab
from booking.models import Booking
from booking.stats import StatisticsManager
from booking.forms import HostReImageForm
@@ -44,6 +44,7 @@ def quick_create(request):
context['form'] = QuickBookingForm(lab_data=attrs, default_user=request.user.username, user=request.user)
context['lab_profile_map'] = {}
context.update(drop_filter(request.user))
+ context['contact_email'] = Lab.objects.filter(name="UNH_IOL").first().contact_email
return render(request, 'booking/quick_deploy.html', context)
if request.method == 'POST':
@@ -74,9 +75,15 @@ class BookingView(TemplateView):
def get_context_data(self, **kwargs):
booking = get_object_or_404(Booking, id=self.kwargs['booking_id'])
title = 'Booking Details'
+ contact = Lab.objects.filter(name="UNH_IOL").first().contact_email
downtime = Downtime.objects.filter(lab=booking.lab, start__lt=timezone.now, end__gt=timezone.now()).first()
context = super(BookingView, self).get_context_data(**kwargs)
- context.update({'title': title, 'booking': booking, 'downtime': downtime})
+ context.update({
+ 'title': title,
+ 'booking': booking,
+ 'downtime': downtime,
+ 'contact_email': contact
+ })
return context
diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py
new file mode 100644
index 0000000..38e8955
--- /dev/null
+++ b/src/dashboard/admin_utils.py
@@ -0,0 +1,159 @@
+from resource_inventory.models import (
+ ResourceTemplate,
+ Image,
+ Server,
+ ResourceBundle,
+ ResourceProfile,
+ InterfaceProfile
+)
+
+from django.contrib.auth.models import User
+
+from account.models import Lab
+
+from resource_inventory.resource_manager import ResourceManager
+from resource_inventory.pdf_templater import PDFTemplater
+
+from booking.quick_deployer import update_template
+
+from datetime import timedelta
+
+from django.utils import timezone
+
+from booking.models import Booking
+from notifier.manager import NotificationHandler
+from api.models import JobFactory
+
+
+"""
+creates a quick booking using the given host
+"""
+
+
+def book_host(owner_username, host_labid, lab_username, hostname, image_id, template_name, length_days=21, collaborator_usernames=[], purpose="internal", project="LaaS"):
+ lab = Lab.objects.get(lab_user__username=lab_username)
+ host = Server.objects.filter(lab=lab).get(labid=host_labid)
+ if host.booked:
+ print("Can't book host, already marked as booked")
+ return
+ else:
+ host.booked = True
+ host.save()
+
+ template = ResourceTemplate.objects.filter(public=True).get(name=template_name)
+ image = Image.objects.get(id=image_id)
+
+ owner = User.objects.get(username=owner_username)
+
+ new_template = update_template(template, image, hostname, owner)
+
+ rmanager = ResourceManager.getInstance()
+
+ vlan_map = rmanager.get_vlans(new_template)
+
+ # only a single host so can reuse var for iter here
+ resource_bundle = ResourceBundle.objects.create(template=new_template)
+ res_configs = new_template.getConfigs()
+
+ for config in res_configs:
+ try:
+ host.bundle = resource_bundle
+ host.config = config
+ rmanager.configureNetworking(resource_bundle, host, vlan_map)
+ host.save()
+ except Exception:
+ host.booked = False
+ host.save()
+ print("Failed to book host due to error configuring it")
+ return
+
+ new_template.save()
+
+ booking = Booking.objects.create(
+ purpose=purpose,
+ project=project,
+ lab=lab,
+ owner=owner,
+ start=timezone.now(),
+ end=timezone.now() + timedelta(days=int(length_days)),
+ resource=resource_bundle,
+ opnfv_config=None
+ )
+
+ booking.pdf = PDFTemplater.makePDF(booking)
+
+ booking.save()
+
+ for collaborator_username in collaborator_usernames:
+ try:
+ user = User.objects.get(username=collaborator_username)
+ booking.collaborators.add(user)
+ except Exception:
+ print("couldn't add user with username ", collaborator_username)
+
+ booking.save()
+
+ JobFactory.makeCompleteJob(booking)
+ NotificationHandler.notify_new_booking(booking)
+
+
+def mark_working(host_labid, lab_username, working=True):
+ lab = Lab.objects.get(lab_user__username=lab_username)
+ server = Server.objects.filter(lab=lab).get(labid=host_labid)
+ print("changing server working status from ", server.working, "to", working)
+ server.working = working
+ server.save()
+
+
+def mark_booked(host_labid, lab_username, booked=True):
+ lab = Lab.objects.get(lab_user__username=lab_username)
+ server = Server.objects.filter(lab=lab).get(labid=host_labid)
+ print("changing server booked status from ", server.booked, "to", booked)
+ server.booked = booked
+ server.save()
+
+
+# returns host filtered by lab and then unique id within lab
+def get_host(host_labid, lab_username):
+ lab = Lab.objects.get(lab_user__username=lab_username)
+ return Server.objects.filter(lab=lab).get(labid=host_labid)
+
+
+def get_info(host_labid, lab_username):
+ info = {}
+ host = get_host(host_labid, lab_username)
+ info['host_labid'] = host_labid
+ info['booked'] = host.booked
+ info['working'] = host.working
+ info['profile'] = str(host.profile)
+ if host.bundle:
+ binfo = {}
+ info['bundle'] = binfo
+ if host.config:
+ cinfo = {}
+ info['config'] = cinfo
+
+ return info
+
+
+def map_cntt_interfaces(labid: str):
+ """
+ Use this during cntt migrations, call it with a host labid and it will change profiles for this host
+ as well as mapping its interfaces across. interface ens1f2 should have the mac address of interface eno50
+ as an invariant before calling this function
+ """
+ host = get_host(labid, "unh_iol")
+ host.profile = ResourceProfile.objects.get(name="HPE x86 CNTT")
+ host.save()
+ host = get_host(labid, "unh_iol")
+
+ for iface in host.interfaces.all():
+ new_ifprofile = None
+ if iface.profile.name == "ens1f2":
+ new_ifprofile = InterfaceProfile.objects.get(host=host.profile, name="eno50")
+ else:
+ new_ifprofile = InterfaceProfile.objects.get(host=host.profile, name=iface.profile.name)
+
+ iface.profile = new_ifprofile
+
+ iface.save()
diff --git a/src/dashboard/tasks.py b/src/dashboard/tasks.py
index 50e64c8..8554f6c 100644
--- a/src/dashboard/tasks.py
+++ b/src/dashboard/tasks.py
@@ -13,7 +13,17 @@ from celery import shared_task
from django.utils import timezone
from booking.models import Booking
from notifier.manager import NotificationHandler
-from api.models import Job, JobStatus, SoftwareRelation, HostHardwareRelation, HostNetworkRelation, AccessRelation
+from api.models import (
+ Job,
+ JobStatus,
+ SoftwareRelation,
+ HostHardwareRelation,
+ HostNetworkRelation,
+ AccessRelation,
+ JobFactory
+)
+
+from resource_inventory.resource_manager import ResourceManager
from resource_inventory.models import ConfigState
@@ -74,4 +84,10 @@ def free_hosts():
resource__isnull=False
)
for booking in bookings:
- booking.resource.release()
+ ResourceManager.getInstance().deleteResourceBundle(booking.resource)
+
+
+@shared_task
+def query_vpn_users():
+ """ get active vpn users """
+ JobFactory.makeActiveUsersTask()
diff --git a/src/dashboard/testing_utils.py b/src/dashboard/testing_utils.py
index d7a346e..5be6379 100644
--- a/src/dashboard/testing_utils.py
+++ b/src/dashboard/testing_utils.py
@@ -96,11 +96,11 @@ def make_network(name, lab, grb, public):
lab.vlan_manager.reserve_public_vlan(public_net.vlan)
network.vlan_id = public_net.vlan
else:
- private_net = lab.vlan_manager.get_vlan()
- if not private_net:
+ private_nets = lab.vlan_manager.get_vlans(count=1)
+ if not private_nets:
raise Exception("No more generic vlans are available")
- lab.vlan_manager.reserve_vlans([private_net])
- network.vlan_id = private_net
+ lab.vlan_manager.reserve_vlans(private_nets)
+ network.vlan_id = private_nets[0]
network.save()
return network
diff --git a/src/dashboard/views.py b/src/dashboard/views.py
index 2ace2d4..f9a908c 100644
--- a/src/dashboard/views.py
+++ b/src/dashboard/views.py
@@ -12,8 +12,12 @@
from django.shortcuts import get_object_or_404
from django.views.generic import TemplateView
from django.shortcuts import render
+from django.db.models import Q
+from datetime import datetime
+import pytz
from account.models import Lab
+from booking.models import Booking
from resource_inventory.models import Image, ResourceProfile, ResourceQuery
from workflow.workflow_manager import ManagerTracker
@@ -65,12 +69,22 @@ def host_profile_detail_view(request):
def landing_view(request):
manager = ManagerTracker.managers.get(request.session.get('manager_session'))
+ user = request.user
+ if not user.is_anonymous:
+ bookings = Booking.objects.filter(
+ Q(owner=user) | Q(collaborators=user),
+ end__gte=datetime.now(pytz.utc)
+ )
+ else:
+ bookings = None
+
return render(
request,
'dashboard/landing.html',
{
'manager': manager is not None,
- 'title': "Welcome to the Lab as a Service Dashboard"
+ 'title': "Welcome to the Lab as a Service Dashboard",
+ 'bookings': bookings
}
)
diff --git a/src/laas_dashboard/settings.py b/src/laas_dashboard/settings.py
index 62fc9ec..86778c1 100644
--- a/src/laas_dashboard/settings.py
+++ b/src/laas_dashboard/settings.py
@@ -28,6 +28,7 @@ INSTALLED_APPS = [
'notifier',
'workflow',
'api',
+ 'analytics',
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
@@ -209,6 +210,10 @@ CELERYBEAT_SCHEDULE = {
'task': 'notifier.tasks.notify_expiring',
'schedule': timedelta(hours=1)
},
+ 'query_vpn_users': {
+ 'task': 'dashboard.tasks.query_vpn_users',
+ 'schedule': timedelta(hours=1)
+ }
}
# Notifier Settings
diff --git a/src/resource_inventory/migrations/0016_auto_20201109_1947.py b/src/resource_inventory/migrations/0016_auto_20201109_1947.py
new file mode 100644
index 0000000..d145f06
--- /dev/null
+++ b/src/resource_inventory/migrations/0016_auto_20201109_1947.py
@@ -0,0 +1,59 @@
+# Generated by Django 2.2 on 2020-11-09 19:47
+
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('resource_inventory', '0015_resourcetemplate_copy_of'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='physicalnetwork',
+ name='bundle',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.ResourceBundle'),
+ ),
+ migrations.AddField(
+ model_name='resourceconfiguration',
+ name='name',
+ field=models.CharField(default='<Hostname>', max_length=3000),
+ ),
+ migrations.AlterField(
+ model_name='cpuprofile',
+ name='cflags',
+ field=models.TextField(blank=True, null=True),
+ ),
+ migrations.AlterField(
+ model_name='interface',
+ name='acts_as',
+ field=models.OneToOneField(blank=True, null=True, on_delete=django.db.models.deletion.CASCADE, to='resource_inventory.InterfaceConfiguration'),
+ ),
+ migrations.AlterField(
+ model_name='interfaceconfiguration',
+ name='connections',
+ field=models.ManyToManyField(blank=True, to='resource_inventory.NetworkConnection'),
+ ),
+ migrations.AlterField(
+ model_name='resourcetemplate',
+ name='copy_of',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceTemplate'),
+ ),
+ migrations.AlterField(
+ model_name='resourcetemplate',
+ name='name',
+ field=models.CharField(max_length=300),
+ ),
+ migrations.AlterField(
+ model_name='server',
+ name='bundle',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceBundle'),
+ ),
+ migrations.AlterField(
+ model_name='server',
+ name='config',
+ field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.ResourceConfiguration'),
+ ),
+ ]
diff --git a/src/resource_inventory/pdf_templater.py b/src/resource_inventory/pdf_templater.py
index 86d84aa..c4b22fe 100644
--- a/src/resource_inventory/pdf_templater.py
+++ b/src/resource_inventory/pdf_templater.py
@@ -22,7 +22,11 @@ class PDFTemplater:
template = "dashboard/pdf.yaml"
info = {}
info['details'] = cls.get_pdf_details(booking.resource)
- info['jumphost'] = cls.get_pdf_jumphost(booking)
+ try:
+ info['jumphost'] = cls.get_pdf_jumphost(booking)
+ except Exception:
+ # filling in jumphost info can be optional in some cases, this shouldn't be a hard error
+ info['jumphost'] = {}
info['nodes'] = cls.get_pdf_nodes(booking)
return render_to_string(template, context=info)
diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py
index 81f4747..140cc09 100644
--- a/src/resource_inventory/resource_manager.py
+++ b/src/resource_inventory/resource_manager.py
@@ -77,9 +77,10 @@ class ResourceManager:
vlan_manager.reserve_public_vlan(public_net.vlan)
networks[network.name] = public_net.vlan
else:
- vlan = vlan_manager.get_vlan()
- vlan_manager.reserve_vlans(vlan)
- networks[network.name] = vlan
+ # already throws if can't get requested count, so can always index in @ 0
+ vlans = vlan_manager.get_vlans(count=1)
+ vlan_manager.reserve_vlans(vlans[0])
+ networks[network.name] = vlans[0]
return networks
def instantiateTemplate(self, resource_template):
diff --git a/src/templates/akraino/booking/quick_deploy.html b/src/templates/akraino/booking/quick_deploy.html
index af9b3d3..c3e519f 100644
--- a/src/templates/akraino/booking/quick_deploy.html
+++ b/src/templates/akraino/booking/quick_deploy.html
@@ -6,7 +6,8 @@
Please select a host type you wish to book.
Only available types are shown.
More information can be found here:
- <a href="https://wiki.akraino.org/display/AK/Shared+Community+Lab">Akraino Wiki</a>
+ <a href="https://wiki.akraino.org/display/AK/Shared+Community+Lab">Akraino Wiki</a>.
+ If something isn't working right, let us know <a href="mailto:{{contact_email}}"> here! </a>
</p>
{% endblock form-text %}
{% block collab %}
diff --git a/src/templates/base/booking/booking_detail.html b/src/templates/base/booking/booking_detail.html
index 4b70f69..24a654c 100644
--- a/src/templates/base/booking/booking_detail.html
+++ b/src/templates/base/booking/booking_detail.html
@@ -159,7 +159,8 @@
<div class="card mb-3">
<div class="card-header d-flex">
<h4 class="d-inline">Deployment Progress</h4>
- <p>These are the different tasks that have to be completed before your deployment is ready</p>
+ <p>These are the different tasks that have to be completed before your deployment is ready.
+ If this is taking a really long time, let us know <a href="mailto:{{contact_email}}">here!</a></p>
<button data-toggle="collapse" data-target="#panel_tasks" class="btn btn-outline-secondary ml-auto">Expand</button>
</div>
<div class="collapse show" id="panel_tasks">
diff --git a/src/templates/base/booking/quick_deploy.html b/src/templates/base/booking/quick_deploy.html
index e4b9431..c954073 100644
--- a/src/templates/base/booking/quick_deploy.html
+++ b/src/templates/base/booking/quick_deploy.html
@@ -9,7 +9,10 @@
<div class="row mx-0 px-0">
<div class="col-12 mx-0 px-0 mt-2">
{% block form-text %}
- <p class="my-0">Please select a host type you wish to book. Only available types are shown.</p>
+ <p class="my-0">
+ Please select a host type you wish to book. Only available types are shown.
+ If something isn't working right, let us know <a href="mailto:{{contact_email}}"> here! </a>
+ </p>
{% endblock form-text %}
{% bootstrap_field form.filter_field show_label=False %}
</div>
diff --git a/src/templates/base/dashboard/landing.html b/src/templates/base/dashboard/landing.html
index ed50638..ecb12c6 100644
--- a/src/templates/base/dashboard/landing.html
+++ b/src/templates/base/dashboard/landing.html
@@ -16,12 +16,27 @@
{% csrf_token %}
<div class="row">
- <!-- About us -->
<div class="col-12 col-lg-6 mb-4">
+ <!-- About us -->
<h2 class="border-bottom">About Us</h2>
{% block about_us %}
<p>Here is some information about us!</p>
{% endblock about_us %}
+ {% block welcome_back %}
+ {% if user.is_authenticated %}
+ <h2 class="border-bottom">Welcome Back!</h2>
+ {% if bookings %}
+ <h5> These are your current bookings: </h5>
+ <ul style="list-style: none;">
+ {% for book in bookings %}
+ <li><a href="/booking/detail/{{ book.id }}/">{{ book.purpose }}</a></li>
+ {% endfor %}
+ </ul>
+ {% else %}
+ <h5> You have no current bookings <h5>
+ {% endif %}
+ {% endif %}
+ {% endblock welcome_back %}
</div>
<!-- Get started -->
diff --git a/src/templates/base/workflow/viewport-base.html b/src/templates/base/workflow/viewport-base.html
index d08145c..d9648c2 100644
--- a/src/templates/base/workflow/viewport-base.html
+++ b/src/templates/base/workflow/viewport-base.html
@@ -29,6 +29,11 @@
</nav>
</div>
</div>
+ <div class=”row”>
+ <div class=”col-xs-6 col-md-4”>
+ Is something not working right? Let us know <a href="mailto::{{contact_email}}"> here! </a>
+ </div>
+</div>
<!-- Top header -->
<div class="row">
<div class="col">
diff --git a/src/templates/laas/dashboard/landing.html b/src/templates/laas/dashboard/landing.html
index a8e0ff8..fc6b3e3 100644
--- a/src/templates/laas/dashboard/landing.html
+++ b/src/templates/laas/dashboard/landing.html
@@ -9,4 +9,4 @@
book a
whole block of servers with customized layer2 networks (e.g. a Pharos Pod). Read more here:
<a href="https://wiki.opnfv.org/x/HAE-Ag" target="_blank">LaaS Wiki</a></p>
-{% endblock about_us %}
+{% endblock %} \ No newline at end of file
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py
index 404224e..63a9519 100644
--- a/src/workflow/resource_bundle_workflow.py
+++ b/src/workflow/resource_bundle_workflow.py
@@ -294,7 +294,7 @@ class Define_Nets(WorkflowStep):
if lab is None or lab.vlan_manager is None:
return None
try:
- vlans = lab.vlan_manager.get_vlan(count=lab.vlan_manager.block_size)
+ vlans = lab.vlan_manager.get_vlans(count=lab.vlan_manager.block_size)
self.repo_put(self.repo.VLANS, vlans)
return vlans
except Exception:
diff --git a/src/workflow/views.py b/src/workflow/views.py
index 9666d72..fb311b7 100644
--- a/src/workflow/views.py
+++ b/src/workflow/views.py
@@ -10,6 +10,7 @@
from django.http import HttpResponse
from django.shortcuts import render
+from account.models import Lab
import uuid
@@ -73,7 +74,12 @@ def viewport_view(request):
if request.method != 'GET':
return HttpResponse(status=405)
- return render(request, 'workflow/viewport-base.html')
+
+ context = {
+ 'contact_email': Lab.objects.get(name="UNH_IOL").contact_email
+ }
+
+ return render(request, 'workflow/viewport-base.html', context)
def create_workflow(request):