From 1f3a770d2547848590f39e9d9b9bdffeb94eec14 Mon Sep 17 00:00:00 2001 From: Parker Berberian Date: Wed, 10 Oct 2018 16:06:47 -0400 Subject: Lab as a Service 2.0 See changes here: https://wiki.opnfv.org/display/INF/Pharos+Laas Change-Id: I59ada5f98e70a28d7f8c14eab3239597e236ca26 Signed-off-by: Sawyer Bergeron Signed-off-by: Parker Berberian --- src/account/admin.py | 5 +- src/account/migrations/0001_initial.py | 33 +++++- src/account/migrations/0002_auto_20180110_1636.py | 33 ------ src/account/migrations/0002_lab_description.py | 19 ++++ src/account/migrations/0003_auto_20180110_1639.py | 24 ---- src/account/migrations/0003_publicnetwork.py | 25 +++++ src/account/migrations/__init__.py | 10 -- src/account/models.py | 127 ++++++++++++++++++++++ src/account/tasks.py | 4 +- src/account/urls.py | 7 ++ src/account/views.py | 48 ++++++++ 11 files changed, 262 insertions(+), 73 deletions(-) delete mode 100644 src/account/migrations/0002_auto_20180110_1636.py create mode 100644 src/account/migrations/0002_lab_description.py delete mode 100644 src/account/migrations/0003_auto_20180110_1639.py create mode 100644 src/account/migrations/0003_publicnetwork.py (limited to 'src/account') diff --git a/src/account/admin.py b/src/account/admin.py index 6f77122..b4c142c 100644 --- a/src/account/admin.py +++ b/src/account/admin.py @@ -1,5 +1,6 @@ ############################################################################## # 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 @@ -10,7 +11,9 @@ from django.contrib import admin -from account.models import UserProfile, Lab +from account.models import UserProfile, Lab, VlanManager, PublicNetwork admin.site.register(UserProfile) admin.site.register(Lab) +admin.site.register(VlanManager) +admin.site.register(PublicNetwork) diff --git a/src/account/migrations/0001_initial.py b/src/account/migrations/0001_initial.py index 591f702..c8b5bdc 100644 --- a/src/account/migrations/0001_initial.py +++ b/src/account/migrations/0001_initial.py @@ -1,6 +1,4 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2016-11-03 13:33 -from __future__ import unicode_literals +# Generated by Django 2.1 on 2018-09-14 14:48 import account.models from django.conf import settings @@ -17,6 +15,18 @@ class Migration(migrations.Migration): ] operations = [ + migrations.CreateModel( + name='Lab', + fields=[ + ('name', models.CharField(max_length=200, primary_key=True, serialize=False, unique=True)), + ('contact_email', models.EmailField(blank=True, max_length=200, null=True)), + ('contact_phone', models.CharField(blank=True, max_length=20, null=True)), + ('status', models.IntegerField(default=0)), + ('location', models.TextField(default='unknown')), + ('api_token', models.CharField(max_length=50)), + ('lab_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), + ], + ), migrations.CreateModel( name='UserProfile', fields=[ @@ -24,15 +34,32 @@ class Migration(migrations.Migration): ('timezone', models.CharField(default='UTC', max_length=100)), ('ssh_public_key', models.FileField(blank=True, null=True, upload_to=account.models.upload_to)), ('pgp_public_key', models.FileField(blank=True, null=True, upload_to=account.models.upload_to)), + ('email_addr', models.CharField(default='email@mail.com', max_length=300)), ('company', models.CharField(max_length=200)), ('oauth_token', models.CharField(max_length=1024)), ('oauth_secret', models.CharField(max_length=1024)), ('jira_url', models.CharField(default='', max_length=100)), ('full_name', models.CharField(default='', max_length=100)), + ('booking_privledge', models.BooleanField(default=False)), ('user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), ], options={ 'db_table': 'user_profile', }, ), + migrations.CreateModel( + name='VlanManager', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlans', models.TextField()), + ('block_size', models.IntegerField()), + ('allow_overlapping', models.BooleanField()), + ('reserved_vlans', models.TextField()), + ], + ), + migrations.AddField( + model_name='lab', + name='vlan_manager', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.CASCADE, to='account.VlanManager'), + ), ] diff --git a/src/account/migrations/0002_auto_20180110_1636.py b/src/account/migrations/0002_auto_20180110_1636.py deleted file mode 100644 index 6170acb..0000000 --- a/src/account/migrations/0002_auto_20180110_1636.py +++ /dev/null @@ -1,33 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2018-01-10 16:36 -from __future__ import unicode_literals - -from django.conf import settings -from django.db import migrations, models -import django.db.models.deletion - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('account', '0001_initial'), - ] - - operations = [ - migrations.CreateModel( - name='Lab', - fields=[ - ('id', models.CharField(max_length=200, primary_key=True, serialize=False)), - ('name', models.CharField(max_length=200, unique=True)), - ('contact_email', models.EmailField(blank=True, max_length=200, null=True)), - ('contact_phone', models.CharField(blank=True, max_length=20, null=True)), - ('lab_user', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), - ], - ), - migrations.AddField( - model_name='userprofile', - name='email_addr', - field=models.CharField(default='email@mail.com', max_length=300), - ), - ] diff --git a/src/account/migrations/0002_lab_description.py b/src/account/migrations/0002_lab_description.py new file mode 100644 index 0000000..445501a --- /dev/null +++ b/src/account/migrations/0002_lab_description.py @@ -0,0 +1,19 @@ +# Generated by Django 2.1 on 2018-09-14 20:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0001_initial'), + ] + + operations = [ + migrations.AddField( + model_name='lab', + name='description', + field=models.CharField(default='Lab description default', max_length=240), + preserve_default=False, + ), + ] diff --git a/src/account/migrations/0003_auto_20180110_1639.py b/src/account/migrations/0003_auto_20180110_1639.py deleted file mode 100644 index d0bc4d6..0000000 --- a/src/account/migrations/0003_auto_20180110_1639.py +++ /dev/null @@ -1,24 +0,0 @@ -# -*- coding: utf-8 -*- -# Generated by Django 1.10 on 2018-01-10 16:39 -from __future__ import unicode_literals - -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - ('account', '0002_auto_20180110_1636'), - ] - - operations = [ - migrations.RemoveField( - model_name='lab', - name='id', - ), - migrations.AlterField( - model_name='lab', - name='name', - field=models.CharField(max_length=200, primary_key=True, serialize=False, unique=True), - ), - ] diff --git a/src/account/migrations/0003_publicnetwork.py b/src/account/migrations/0003_publicnetwork.py new file mode 100644 index 0000000..71e5caa --- /dev/null +++ b/src/account/migrations/0003_publicnetwork.py @@ -0,0 +1,25 @@ +# Generated by Django 2.1 on 2018-09-26 14:41 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0002_lab_description'), + ] + + operations = [ + migrations.CreateModel( + name='PublicNetwork', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('vlan', models.IntegerField()), + ('in_use', models.BooleanField(default=False)), + ('cidr', models.CharField(default='0.0.0.0/0', max_length=50)), + ('gateway', models.CharField(default='0.0.0.0', max_length=50)), + ('lab', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='account.Lab')), + ], + ), + ] diff --git a/src/account/migrations/__init__.py b/src/account/migrations/__init__.py index b5914ce..e69de29 100644 --- a/src/account/migrations/__init__.py +++ b/src/account/migrations/__init__.py @@ -1,10 +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/account/models.py b/src/account/models.py index aad4c50..18a8cbb 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -10,6 +10,14 @@ from django.contrib.auth.models import User from django.db import models +import json +import random + + +class LabStatus(object): + UP = 0 + TEMP_DOWN = 100 + DOWN = 200 def upload_to(object, filename): @@ -28,6 +36,7 @@ class UserProfile(models.Model): jira_url = models.CharField(max_length=100, default='') full_name = models.CharField(max_length=100, default='') + booking_privledge = models.BooleanField(default=False) class Meta: db_table = 'user_profile' @@ -35,11 +44,129 @@ class UserProfile(models.Model): def __str__(self): return self.user.username + +class VlanManager(models.Model): + # list of length 4096 containing either 0 (not available) or 1 (available) + vlans = models.TextField() + block_size = models.IntegerField() + allow_overlapping = models.BooleanField() + # list of length 4096 containing either 0 (not rexerved) or 1 (reserved) + reserved_vlans = models.TextField() + + def get_vlan(self, count=1): + allocated = [] + vlans = json.loads(self.vlans) + for i in range(count): + new_vlan = vlans.index(1) # will throw if none available + vlans[new_vlan] = 0 + allocated.append(new_vlan) + if count is 1: + return allocated[0] + return allocated + + def get_public_vlan(self): + return PublicNetwork.objects.filter(lab=self.lab_set.first(), in_use=False).first() + + def reserve_public_vlan(self, vlan): + net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan, in_use=False) + net.in_use = True + net.save() + + def release_public_vlan(self, vlan): + net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan, in_use=True) + net.in_use = False + net.save() + + def public_vlan_is_available(self, vlan): + net = PublicNetwork.objects.get(lab=self.lab_set.first(), vlan=vlan) + return not net.in_use + + + def is_available(self, vlans): + """ + 'vlans' is either a single vlan id integer or a list of integers + will return true (available) or false + """ + if self.allow_overlapping: + return True + + reserved = json.loads(self.reserved_vlans) + vlan_master_list = json.loads(self.vlans) + try: + iter(vlans) + except: + vlans = [vlans] + + for vlan in vlans: + if not vlan_master_list[vlan] or reserved[vlan]: + return False + return True + + def release_vlans(self, vlans): + """ + 'vlans' is either a single vlan id integer or a list of integers + will make the vlans available + doesnt return a value + """ + my_vlans = json.loads(self.vlans) + + try: + iter(vlans) + except: + vlans = [vlans] + + for vlan in vlans: + my_vlans[vlan] = 1 + self.vlans = json.dumps(my_vlans) + self.save() + + def reserve_vlans(self, vlans): + my_vlans = json.loads(self.vlans) + + try: + iter(vlans) + except: + vlans = [vlans] + + vlans = set(vlans) + + for vlan in vlans: + if my_vlans[vlan] is 0: + raise ValueError("vlan " + str(vlan) + " is not available") + + my_vlans[vlan] = 0 + self.vlans = json.dumps(my_vlans) + self.save() + + + class Lab(models.Model): lab_user = models.OneToOneField(User, on_delete=models.CASCADE) name = models.CharField(max_length=200, primary_key=True, unique=True, null=False, blank=False) contact_email = models.EmailField(max_length=200, null=True, blank=True) contact_phone = models.CharField(max_length=20, null=True, blank=True) + status = models.IntegerField(default=LabStatus.UP) + vlan_manager = models.ForeignKey(VlanManager, on_delete=models.CASCADE, null=True) + location = models.TextField(default="unknown") + api_token = models.CharField(max_length=50) + description = models.CharField(max_length=240) + + @staticmethod + def make_api_token(): + alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789" + key = "" + for i in range(45): + key += random.choice(alphabet) + return key + def __str__(self): return self.name + + +class PublicNetwork(models.Model): + vlan = models.IntegerField() + lab = models.ForeignKey(Lab, on_delete=models.CASCADE) + in_use = models.BooleanField(default=False) + cidr = models.CharField(max_length=50, default="0.0.0.0/0") + gateway = models.CharField(max_length=50, default="0.0.0.0") diff --git a/src/account/tasks.py b/src/account/tasks.py index bfb865d..fe51974 100644 --- a/src/account/tasks.py +++ b/src/account/tasks.py @@ -1,5 +1,6 @@ ############################################################################## # 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 @@ -28,7 +29,6 @@ def sync_jira_accounts(): user.email = user_dict['emailAddress'] user.userprofile.url = user_dict['self'] user.userprofile.full_name = user_dict['displayName'] - print(user_dict) user.userprofile.save() - user.save() \ No newline at end of file + user.save() diff --git a/src/account/urls.py b/src/account/urls.py index 3962a0c..6ce2115 100644 --- a/src/account/urls.py +++ b/src/account/urls.py @@ -1,5 +1,6 @@ ############################################################################## # 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 @@ -27,10 +28,16 @@ from django.conf.urls import url from account.views import * +app_name = "account" urlpatterns = [ url(r'^settings/', AccountSettingsView.as_view(), name='settings'), url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'), url(r'^login/$', JiraLoginView.as_view(), name='login'), url(r'^logout/$', JiraLogoutView.as_view(), name='logout'), url(r'^users/$', UserListView.as_view(), name='users'), + url(r'^my/resources', account_resource_view, name="my-resources"), + url(r'^my/bookings', account_booking_view, name="my-bookings"), + url(r'^my/images', account_images_view, name="my-images"), + url(r'^my/configurations', account_configuration_view, name="my-configurations"), + url(r'^my/$', account_detail_view, name="my-account"), ] diff --git a/src/account/views.py b/src/account/views.py index e6a0e5d..04d21b8 100644 --- a/src/account/views.py +++ b/src/account/views.py @@ -1,5 +1,6 @@ ############################################################################## # 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 @@ -21,12 +22,15 @@ from django.contrib.auth.models import User from django.urls import reverse from django.utils.decorators import method_decorator from django.views.generic import RedirectView, TemplateView, UpdateView +from django.shortcuts import render from jira import JIRA from rest_framework.authtoken.models import Token from account.forms import AccountSettingsForm from account.jira_util import SignatureMethod_RSA_SHA1 from account.models import UserProfile +from booking.models import Booking +from resource_inventory.models import GenericResourceBundle, ConfigBundle, Image @method_decorator(login_required, name='dispatch') @@ -153,3 +157,47 @@ class UserListView(TemplateView): context = super(UserListView, self).get_context_data(**kwargs) context.update({'title': "Dashboard Users", 'users': users}) return context + + +def account_detail_view(request): + template = "account/details.html" + return render(request, template) + +def account_resource_view(request): + """ + gathers a users genericResoureBundles and + turns them into displayable objects + """ + if not request.user.is_authenticated: + return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) + template = "account/resource_list.html" + resources = list(GenericResourceBundle.objects.filter(owner=request.user)) + context = {"resources": resources, "title": "My Resources"} + return render(request, template, context=context) + +def account_booking_view(request): + if not request.user.is_authenticated: + return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) + template = "account/booking_list.html" + bookings = list(Booking.objects.filter(owner=request.user)) + collab_bookings = list(request.user.collaborators.all()) + context = {"title": "My Bookings", "bookings": bookings, "collab_bookings": collab_bookings} + return render(request, template, context=context) + +def account_configuration_view(request): + if not request.user.is_authenticated: + return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) + template = "account/configuration_list.html" + configs = list(ConfigBundle.objects.filter(owner=request.user)) + context = {"title": "Configuration List", "configurations": configs} + return render(request, template, context=context) + +def account_images_view(request): + if not request.user.is_authenticated: + return render(request, "dashboard/login.html", {'title': 'Authentication Required'}) + template = "account/image_list.html" + my_images = Image.objects.filter(owner=request.user) + public_images = Image.objects.filter(public=True) + context = {"title": "Images", "images": my_images, "public_images": public_images } + return render(request, template, context=context) + -- cgit 1.2.3-korg