summaryrefslogtreecommitdiffstats
path: root/dashboard
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard')
-rw-r--r--dashboard/src/api/tests/test_models_unittest.py255
-rw-r--r--dashboard/src/booking/forms.py19
-rw-r--r--dashboard/src/booking/lib.py36
-rw-r--r--dashboard/src/booking/quick_deployer.py9
-rw-r--r--dashboard/src/booking/tests/test_quick_booking.py105
-rw-r--r--dashboard/src/booking/views.py2
-rw-r--r--dashboard/src/dashboard/testing_utils.py647
-rw-r--r--dashboard/src/templates/booking/steps/booking_meta.html1
-rw-r--r--dashboard/src/templates/dashboard/genericselect.html71
-rw-r--r--dashboard/src/templates/dashboard/searchable_select_multiple.html70
-rw-r--r--dashboard/src/workflow/booking_workflow.py288
-rw-r--r--dashboard/src/workflow/forms.py333
-rw-r--r--dashboard/src/workflow/models.py83
-rw-r--r--dashboard/src/workflow/opnfv_workflow.py84
-rw-r--r--dashboard/src/workflow/sw_bundle_workflow.py19
-rw-r--r--dashboard/src/workflow/urls.py4
-rw-r--r--dashboard/src/workflow/workflow_factory.py3
17 files changed, 945 insertions, 1084 deletions
diff --git a/dashboard/src/api/tests/test_models_unittest.py b/dashboard/src/api/tests/test_models_unittest.py
index 971f757..cabffc9 100644
--- a/dashboard/src/api/tests/test_models_unittest.py
+++ b/dashboard/src/api/tests/test_models_unittest.py
@@ -7,19 +7,14 @@
# http://www.apache.org/licenses/LICENSE-2.0
##############################################################################
-
-from datetime import timedelta
-from django.utils import timezone
-
-from booking.models import Booking
from api.models import (
Job,
JobStatus,
JobFactory,
- AccessRelation,
HostNetworkRelation,
HostHardwareRelation,
SoftwareRelation,
+ AccessConfig,
)
from resource_inventory.models import (
@@ -30,105 +25,122 @@ from resource_inventory.models import (
from django.test import TestCase, Client
from dashboard.testing_utils import (
- instantiate_host,
- instantiate_user,
- instantiate_userprofile,
- instantiate_lab,
- instantiate_installer,
- instantiate_image,
- instantiate_scenario,
- instantiate_os,
- make_hostprofile_set,
- instantiate_opnfvrole,
- instantiate_publicnet,
- instantiate_booking,
+ 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.loginuser = instantiate_user(False, username="newtestuser", password="testpassword")
- cls.userprofile = instantiate_userprofile(cls.loginuser)
+ 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()
- lab_user = instantiate_user(True)
- cls.lab = instantiate_lab(lab_user)
+ def setUp(self):
+ self.booking, self.compute_hostnames, self.jump_hostname = self.create_multinode_generic_booking()
- cls.host_profile = make_hostprofile_set(cls.lab)
- cls.scenario = instantiate_scenario()
- cls.installer = instantiate_installer([cls.scenario])
- os = instantiate_os([cls.installer])
- cls.image = instantiate_image(cls.lab, 1, cls.loginuser, os, cls.host_profile)
- for i in range(30):
- instantiate_host(cls.host_profile, cls.lab, name="host" + str(i), labid="host" + str(i))
- cls.role = instantiate_opnfvrole("Jumphost")
- cls.computerole = instantiate_opnfvrole("Compute")
- instantiate_publicnet(10, cls.lab)
- instantiate_publicnet(12, cls.lab)
- instantiate_publicnet(14, cls.lab)
+ def create_multinode_generic_booking(self):
+ topology = {}
- cls.lab_selected = 'lab_' + str(cls.lab.lab_user.id) + '_selected'
- cls.host_selected = 'host_' + str(cls.host_profile.id) + '_selected'
+ compute_hostnames = ["cmp01", "cmp02", "cmp03"]
- cls.post_data = cls.build_post_data()
+ host_type = HostProfile.objects.first()
- cls.client = Client()
+ 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}]
- def setUp(self):
- self.client.login(
- username=self.loginuser.username, password="testpassword")
- self.booking, self.compute_hostnames, self.jump_hostname = self.create_multinode_generic_booking()
+ # 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)
- @classmethod
- def build_post_data(cls):
- post_data = {}
- post_data['filter_field'] = '{"hosts":[{"host_' + str(cls.host_profile.id) + '":"true"}], "labs": [{"lab_' + str(cls.lab.lab_user.id) + '":"true"}]}'
- post_data['purpose'] = 'purposefieldcontentstring'
- post_data['project'] = 'projectfieldcontentstring'
- post_data['length'] = '3'
- post_data['ignore_this'] = 1
- post_data['users'] = ''
- post_data['hostname'] = 'hostnamefieldcontentstring'
- post_data['image'] = str(cls.image.id)
- post_data['installer'] = str(cls.installer.id)
- post_data['scenario'] = str(cls.scenario.id)
- return post_data
-
- def post(self, changed_fields={}):
- payload = self.post_data.copy()
- payload.update(changed_fields)
- response = self.client.post('/booking/quick/', payload)
- return response
-
- def generate_booking(self):
- self.post()
- return Booking.objects.first()
+ 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")
+ JobFactory.makeCompleteJob(booking)
+
+ return booking, compute_hostnames, "jump"
+
+ def make_networks(self, hostprofile, nets):
+ """
+ distributes 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
+
+ # begin tests
def test_valid_access_configs(self):
job = Job.objects.get(booking=self.booking)
self.assertIsNotNone(job)
- access_configs = [r.config for r in AccessRelation.objects.filter(job=job).all()]
+ access_configs = AccessConfig.objects.filter(accessrelation__job=job)
- vpnconfigs = []
- sshconfigs = []
+ vpn_configs = access_configs.filter(access_type="vpn")
+ ssh_configs = access_configs.filter(access_type="ssh")
- for config in access_configs:
- if config.access_type == "vpn":
- vpnconfigs.append(config)
- elif config.access_type == "ssh":
- sshconfigs.append(config)
- else:
- self.fail(msg="Undefined accessconfig: " + config.access_type + " found")
+ self.assertFalse(AccessConfig.objects.exclude(access_type__in=["vpn", "ssh"]).exists())
- user_set = []
- user_set.append(self.booking.owner)
- user_set += self.booking.collaborators.all()
+ all_users = list(self.booking.collaborators.all())
+ all_users.append(self.booking.owner)
- for configs in [vpnconfigs, sshconfigs]:
- for user in user_set:
- configusers = [c.user for c in configs]
- self.assertTrue(user in configusers)
+ for user in all_users:
+ self.assertTrue(vpn_configs.filter(user=user).exists())
+ self.assertTrue(ssh_configs.filter(user=user).exists())
def test_valid_network_configs(self):
job = Job.objects.get(booking=self.booking)
@@ -136,12 +148,12 @@ class ValidBookingCreatesValidJob(TestCase):
booking_hosts = self.booking.resource.hosts.all()
- netrelation_set = HostNetworkRelation.objects.filter(job=job)
- netconfig_set = [r.config for r in netrelation_set]
+ netrelations = HostNetworkRelation.objects.filter(job=job)
+ netconfigs = [r.config for r in netrelations]
- netrelation_hosts = [r.host for r in netrelation_set]
+ netrelation_hosts = [r.host for r in netrelations]
- for config in netconfig_set:
+ for config in netconfigs:
for interface in config.interfaces.all():
self.assertTrue(interface.host in booking_hosts)
@@ -172,15 +184,15 @@ class ValidBookingCreatesValidJob(TestCase):
job = Job.objects.get(booking=self.booking)
self.assertIsNotNone(job)
- hrelations = HostHardwareRelation.objects.filter(job=job).all()
+ hardware_relations = HostHardwareRelation.objects.filter(job=job)
- job_hosts = [r.host for r in hrelations]
+ 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 hrelations:
+ for relation in hardware_relations:
self.assertTrue(relation.host in booking_hosts)
self.assertEqual(relation.status, JobStatus.NEW)
config = relation.config
@@ -213,66 +225,3 @@ class ValidBookingCreatesValidJob(TestCase):
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 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}]
- just_compute_networks = [{"name": "private", "tagged": True, "public": False}]
- just_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"] = "u_net" + str(i)
- universal_networks.append(net)
-
- jhost_info = {}
- jhost_info["type"] = host_type
- jhost_info["role"] = OPNFVRole.objects.get(name="Jumphost")
- jhost_info["nets"] = self.make_networks(host_type, list(just_jumphost_networks + universal_networks))
- jhost_info["image"] = self.image
- topology["jump"] = jhost_info
-
- for hostname in compute_hostnames:
- host_info = {}
- host_info["type"] = host_type
- host_info["role"] = OPNFVRole.objects.get(name="Compute")
- host_info["nets"] = self.make_networks(host_type, list(just_compute_networks + universal_networks))
- host_info["image"] = self.image
- topology[hostname] = host_info
-
- booking = instantiate_booking(self.loginuser,
- timezone.now(),
- timezone.now() + timedelta(days=1),
- "demobooking",
- 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")
- JobFactory.makeCompleteJob(booking)
-
- return booking, compute_hostnames, "jump"
-
- """
- evenly distributes networks given across a given profile's interfaces
- """
- def make_networks(self, hostprofile, nets):
- 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
diff --git a/dashboard/src/booking/forms.py b/dashboard/src/booking/forms.py
index de427ab..e48b293 100644
--- a/dashboard/src/booking/forms.py
+++ b/dashboard/src/booking/forms.py
@@ -10,12 +10,13 @@ import django.forms as forms
from django.forms.widgets import NumberInput
from workflow.forms import (
- SearchableSelectMultipleWidget,
MultipleSelectFilterField,
MultipleSelectFilterWidget,
FormUtils)
from account.models import UserProfile
from resource_inventory.models import Image, Installer, Scenario
+from workflow.forms import SearchableSelectMultipleField
+from booking.lib import get_user_items, get_user_field_opts
class QuickBookingForm(forms.Form):
@@ -27,16 +28,11 @@ class QuickBookingForm(forms.Form):
scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False)
def __init__(self, data=None, user=None, *args, **kwargs):
- chosen_users = []
if "default_user" in kwargs:
default_user = kwargs.pop("default_user")
else:
default_user = "you"
self.default_user = default_user
- if "chosen_users" in kwargs:
- chosen_users = kwargs.pop("chosen_users")
- elif data and "users" in data:
- chosen_users = data.getlist("users")
super(QuickBookingForm, self).__init__(data=data, **kwargs)
@@ -44,12 +40,13 @@ class QuickBookingForm(forms.Form):
Image.objects.filter(public=True) | Image.objects.filter(owner=user)
)
- self.fields['users'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(
- attrs=self.build_search_widget_attrs(chosen_users, default_user=default_user)
- ),
- required=False
+ self.fields['users'] = SearchableSelectMultipleField(
+ queryset=UserProfile.objects.select_related('user').exclude(user=user),
+ items=get_user_items(exclude=user),
+ required=False,
+ **get_user_field_opts()
)
+
attrs = FormUtils.getLabData(0)
attrs['selection_data'] = 'false'
self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(attrs=attrs))
diff --git a/dashboard/src/booking/lib.py b/dashboard/src/booking/lib.py
new file mode 100644
index 0000000..8132c75
--- /dev/null
+++ b/dashboard/src/booking/lib.py
@@ -0,0 +1,36 @@
+##############################################################################
+# Copyright (c) 2019 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 account.models import UserProfile
+
+
+def get_user_field_opts():
+ return {
+ 'show_from_noentry': False,
+ 'show_x_results': 5,
+ 'results_scrollable': True,
+ 'selectable_limit': -1,
+ 'placeholder': 'Search for other users',
+ 'name': 'users',
+ 'disabled': False
+ }
+
+
+def get_user_items(exclude=None):
+ qs = UserProfile.objects.select_related('user').exclude(user=exclude)
+ items = {}
+ for up in qs:
+ item = {
+ 'id': up.id,
+ 'expanded_name': up.full_name,
+ 'small_name': up.user.username,
+ 'string': up.email_addr
+ }
+ items[up.id] = item
+ return items
diff --git a/dashboard/src/booking/quick_deployer.py b/dashboard/src/booking/quick_deployer.py
index 763c8a0..ac69c8c 100644
--- a/dashboard/src/booking/quick_deployer.py
+++ b/dashboard/src/booking/quick_deployer.py
@@ -12,7 +12,6 @@ import json
import uuid
import re
from django.db.models import Q
-from django.contrib.auth.models import User
from datetime import timedelta
from django.utils import timezone
from account.models import Lab
@@ -321,12 +320,8 @@ def create_from_form(form, request):
)
booking.pdf = PDFTemplater.makePDF(booking)
- users_field = users_field[2:-2]
- if users_field: # may be empty after split, if no collaborators entered
- users_field = json.loads(users_field)
- for collaborator in users_field:
- user = User.objects.get(id=collaborator['id'])
- booking.collaborators.add(user)
+ for collaborator in users_field: # list of UserProfiles
+ booking.collaborators.add(collaborator.user)
booking.save()
diff --git a/dashboard/src/booking/tests/test_quick_booking.py b/dashboard/src/booking/tests/test_quick_booking.py
index 936a9a5..e445860 100644
--- a/dashboard/src/booking/tests/test_quick_booking.py
+++ b/dashboard/src/booking/tests/test_quick_booking.py
@@ -13,60 +13,55 @@ from django.test import TestCase, Client
from booking.models import Booking
from dashboard.testing_utils import (
- instantiate_host,
- instantiate_user,
- instantiate_userprofile,
- instantiate_lab,
- instantiate_installer,
- instantiate_image,
- instantiate_scenario,
- instantiate_os,
- make_hostprofile_set,
- instantiate_opnfvrole,
- instantiate_publicnet,
+ make_host,
+ make_user,
+ make_user_profile,
+ make_lab,
+ make_installer,
+ make_image,
+ make_scenario,
+ make_os,
+ make_complete_host_profile,
+ make_opnfv_role,
+ make_public_net,
)
-# from dashboard import test_utils
class QuickBookingValidFormTestCase(TestCase):
@classmethod
def setUpTestData(cls):
- cls.loginuser = instantiate_user(False, username="newtestuser", password="testpassword")
- instantiate_userprofile(cls.loginuser, True)
+ cls.user = make_user(False, username="newtestuser", password="testpassword")
+ make_user_profile(cls.user, True)
- lab_user = instantiate_user(True)
- cls.lab = instantiate_lab(lab_user)
+ lab_user = make_user(True)
+ cls.lab = make_lab(lab_user)
- cls.host_profile = make_hostprofile_set(cls.lab)
- cls.scenario = instantiate_scenario()
- cls.installer = instantiate_installer([cls.scenario])
- os = instantiate_os([cls.installer])
- cls.image = instantiate_image(cls.lab, 1, cls.loginuser, os, cls.host_profile)
- cls.host = instantiate_host(cls.host_profile, cls.lab)
- cls.role = instantiate_opnfvrole()
- cls.pubnet = instantiate_publicnet(10, cls.lab)
-
- cls.lab_selected = 'lab_' + str(cls.lab.lab_user.id) + '_selected'
- cls.host_selected = 'host_' + str(cls.host_profile.id) + '_selected'
+ 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)
+ cls.host = make_host(cls.host_profile, cls.lab)
+ cls.role = make_opnfv_role()
+ cls.pubnet = make_public_net(10, cls.lab)
cls.post_data = cls.build_post_data()
-
cls.client = Client()
@classmethod
def build_post_data(cls):
- post_data = {}
- post_data['filter_field'] = '{"hosts":[{"host_' + str(cls.host_profile.id) + '":"true"}], "labs": [{"lab_' + str(cls.lab.lab_user.id) + '":"true"}]}'
- post_data['purpose'] = 'purposefieldcontentstring'
- post_data['project'] = 'projectfieldcontentstring'
- post_data['length'] = '3'
- post_data['ignore_this'] = 1
- post_data['users'] = ''
- post_data['hostname'] = 'hostnamefieldcontentstring'
- post_data['image'] = str(cls.image.id)
- post_data['installer'] = str(cls.installer.id)
- post_data['scenario'] = str(cls.scenario.id)
- return post_data
+ return {
+ 'filter_field': '{"hosts":[{"host_' + str(cls.host_profile.id) + '":"true"}], "labs": [{"lab_' + str(cls.lab.lab_user.id) + '":"true"}]}',
+ 'purpose': 'my_purpose',
+ 'project': 'my_project',
+ 'length': '3',
+ 'ignore_this': 1,
+ 'users': '',
+ 'hostname': 'my_host',
+ 'image': str(cls.image.id),
+ 'installer': str(cls.installer.id),
+ 'scenario': str(cls.scenario.id)
+ }
def post(self, changed_fields={}):
payload = self.post_data.copy()
@@ -75,26 +70,26 @@ class QuickBookingValidFormTestCase(TestCase):
return response
def setUp(self):
- self.client.login(
- username=self.loginuser.username, password="testpassword")
+ self.client.login(username=self.user.username, password="testpassword")
- def is_valid_booking(self, booking):
- self.assertEqual(booking.owner, self.loginuser)
- self.assertEqual(booking.purpose, 'purposefieldcontentstring')
- self.assertEqual(booking.project, 'projectfieldcontentstring')
+ def assertValidBooking(self, booking):
+ self.assertEqual(booking.owner, self.user)
+ self.assertEqual(booking.purpose, 'my_purpose')
+ self.assertEqual(booking.project, 'my_project')
delta = booking.end - booking.start
delta -= datetime.timedelta(days=3)
self.assertLess(delta, datetime.timedelta(minutes=1))
- resourcebundle = booking.resource
- configbundle = booking.config_bundle
+ resource_bundle = booking.resource
+ config_bundle = booking.config_bundle
- self.assertEqual(self.installer, configbundle.opnfv_config.first().installer)
- self.assertEqual(self.scenario, configbundle.opnfv_config.first().scenario)
- self.assertEqual(resourcebundle.template.getHosts()[0].profile, self.host_profile)
- self.assertEqual(resourcebundle.template.getHosts()[0].resource.name, 'hostnamefieldcontentstring')
+ opnfv_config = config_bundle.opnfv_config.first()
+ self.assertEqual(self.installer, opnfv_config.installer)
+ self.assertEqual(self.scenario, opnfv_config.scenario)
- return True
+ host = resource_bundle.hosts.first()
+ self.assertEqual(host.profile, self.host_profile)
+ self.assertEqual(host.template.resource.name, 'my_host')
def test_with_too_long_length(self):
response = self.post({'length': '22'})
@@ -144,7 +139,7 @@ class QuickBookingValidFormTestCase(TestCase):
self.assertEqual(response.status_code, 200)
booking = Booking.objects.first()
self.assertIsNotNone(booking)
- self.assertTrue(self.is_valid_booking(booking))
+ self.assertValidBooking(booking)
def test_with_valid_form(self):
response = self.post()
@@ -152,4 +147,4 @@ class QuickBookingValidFormTestCase(TestCase):
self.assertEqual(response.status_code, 200)
booking = Booking.objects.first()
self.assertIsNotNone(booking)
- self.assertTrue(self.is_valid_booking(booking))
+ self.assertValidBooking(booking)
diff --git a/dashboard/src/booking/views.py b/dashboard/src/booking/views.py
index 8211a0c..13e9d01 100644
--- a/dashboard/src/booking/views.py
+++ b/dashboard/src/booking/views.py
@@ -47,7 +47,7 @@ def quick_create(request):
context['lab_profile_map'] = profiles
- context['form'] = QuickBookingForm(initial={}, chosen_users=[], default_user=request.user.username, user=request.user)
+ context['form'] = QuickBookingForm(default_user=request.user.username, user=request.user)
context.update(drop_filter(request.user))
diff --git a/dashboard/src/dashboard/testing_utils.py b/dashboard/src/dashboard/testing_utils.py
index 558031d..1cca3e6 100644
--- a/dashboard/src/dashboard/testing_utils.py
+++ b/dashboard/src/dashboard/testing_utils.py
@@ -9,13 +9,13 @@
from django.contrib.auth.models import User
from django.core.files.base import ContentFile
+from django.utils import timezone
import json
import re
+from datetime import timedelta
-from dashboard.exceptions import (
- InvalidHostnameException
-)
+from dashboard.exceptions import InvalidHostnameException
from booking.models import Booking
from account.models import UserProfile, Lab, LabStatus, VlanManager, PublicNetwork
from resource_inventory.models import (
@@ -31,7 +31,6 @@ from resource_inventory.models import (
OPNFVRole,
RamProfile,
Network,
- Vlan,
GenericResourceBundle,
GenericResource,
GenericHost,
@@ -39,30 +38,13 @@ from resource_inventory.models import (
GenericInterface,
HostConfiguration,
OPNFVConfig,
+ NetworkConnection,
+ HostOPNFVConfig
)
from resource_inventory.resource_manager import ResourceManager
-
-class BookingContextData(object):
- def prepopulate(self, *args, **kwargs):
- self.loginuser = instantiate_user(False, username=kwargs.get("login_username", "newtestuser"), password="testpassword")
- instantiate_userprofile(self.loginuser)
-
- lab_user = kwargs.get("lab_user", instantiate_user(True))
- self.lab = instantiate_lab(lab_user)
-
- self.host_profile = make_hostprofile_set(self.lab)
- self.scenario = instantiate_scenario()
- self.installer = instantiate_installer([self.scenario])
- os = instantiate_os([self.installer])
- self.image = instantiate_image(self.lab, 1, self.loginuser, os, self.host_profile)
- self.host = instantiate_host(self.host_profile, self.lab)
- self.role = instantiate_opnfvrole()
- self.pubnet = instantiate_publicnet(10, self.lab)
-
-
"""
-Info for instantiate_booking() function:
+Info for make_booking() function:
[topology] argument structure:
the [topology] argument should describe the structure of the pod
the top level should be a dictionary, with each key being a hostname
@@ -91,424 +73,323 @@ Info for instantiate_booking() function:
"""
-def instantiate_booking(owner,
- start,
- end,
- booking_identifier,
- lab=Lab.objects.first(),
- purpose="purposetext",
- project="projecttext",
- collaborators=[],
- topology={},
- installer=None,
- scenario=None):
- (grb, host_set) = instantiate_grb(topology, owner, lab, booking_identifier)
- cb = instantiate_cb(grb, owner, booking_identifier, topology, host_set, installer, scenario)
-
- resource = ResourceManager.getInstance().convertResourceBundle(grb, lab, cb)
+def make_booking(owner=None, start=timezone.now(),
+ end=timezone.now() + timedelta(days=1),
+ lab=None, purpose="my_purpose",
+ project="my_project", collaborators=[],
+ topology={}, installer=None, scenario=None):
- booking = Booking()
-
- booking.resource = resource
+ grb, host_set = make_grb(topology, owner, lab)
+ config_bundle = make_config_bundle(grb, owner, topology, host_set, installer, scenario)
+ resource = ResourceManager.getInstance().convertResourceBundle(grb, config=config_bundle)
if not resource:
raise Exception("Resource not created")
- booking.config_bundle = cb
- booking.start = start
- booking.end = end
- booking.owner = owner
- booking.purpose = purpose
- booking.project = project
- booking.lab = lab
- booking.save()
-
- return booking
-
-
-def instantiate_cb(grb,
- owner,
- booking_identifier,
- topology={},
- host_set={},
- installer=None,
- scenario=None):
- cb = ConfigBundle()
- cb.owner = owner
- cb.name = str(booking_identifier) + "_cb"
- cb.description = "cb generated by instantiate_cb() method"
- cb.save()
-
- opnfvconfig = OPNFVConfig()
- opnfvconfig.installer = installer
- opnfvconfig.scenario = scenario
- opnfvconfig.bundle = cb
- opnfvconfig.save()
+
+ return Booking.objects.create(
+ resource=resource,
+ config_bundle=config_bundle,
+ start=start,
+ end=end,
+ owner=owner,
+ purpose=purpose,
+ project=project,
+ lab=lab
+ )
+
+
+def make_config_bundle(grb, owner, topology={}, host_set={},
+ installer=None, scenario=None):
+ cb = ConfigBundle.objects.create(
+ owner=owner,
+ name="config bundle " + str(ConfigBundle.objects.count()),
+ description="cb generated by make_config_bundle() method"
+ )
+
+ opnfv_config = OPNFVConfig.objects.create(
+ installer=installer,
+ scenario=scenario,
+ bundle=cb
+ )
# generate host configurations based on topology and host set
for hostname, host_info in topology.items():
- hconf = HostConfiguration()
- hconf.bundle = cb
- hconf.host = host_set[hostname]
- hconf.image = host_info["image"]
- hconf.opnfvRole = host_info["role"]
- hconf.save()
+ host_config = HostConfiguration.objects.create(
+ host=host_set[hostname],
+ image=host_info["image"],
+ bundle=cb,
+ is_head_node=host_info['role'].name.lower() == "jumphost"
+ )
+ HostOPNFVConfig.objects.create(
+ role=host_info["role"],
+ host_config=host_config,
+ opnfv_config=opnfv_config
+ )
return cb
-def instantiate_grb(topology,
- owner,
- lab,
- booking_identifier):
+def make_network(name, lab, grb, public):
+ network = Network(name=name, bundle=grb, is_public=public)
+ if public:
+ public_net = lab.vlan_manager.get_public_vlan()
+ if not public_net:
+ raise Exception("No more public networks available")
+ 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:
+ raise Exception("No more generic vlans are available")
+ lab.vlan_manager.reserve_vlans([private_net])
+ network.vlan_id = private_net
+
+ network.save()
+ return network
+
- grb = GenericResourceBundle(owner=owner, lab=lab)
- grb.name = str(booking_identifier) + "_grb"
- grb.description = "grb generated by instantiate_grb() method"
- grb.save()
+def make_grb(topology, owner, lab):
+
+ grb = GenericResourceBundle.objects.create(
+ owner=owner,
+ lab=lab,
+ name="Generic ResourceBundle " + str(GenericResourceBundle.objects.count()),
+ description="grb generated by make_grb() method"
+ )
networks = {}
host_set = {}
- for hostname in topology.keys():
- info = topology[hostname]
+ for hostname, info in topology.items():
host_profile = info["type"]
# need to construct host from hostname and type
- ghost = instantiate_ghost(grb, host_profile, hostname)
- host_set[hostname] = ghost
-
- ghost.save()
+ generic_host = make_generic_host(grb, host_profile, hostname)
+ host_set[hostname] = generic_host
# set up networks
nets = info["nets"]
for interface_index, interface_profile in enumerate(host_profile.interfaceprofile.all()):
- generic_interface = GenericInterface()
- generic_interface.host = ghost
- generic_interface.profile = interface_profile
- generic_interface.save()
-
+ generic_interface = GenericInterface.objects.create(host=generic_host, profile=interface_profile)
netconfig = nets[interface_index]
- for network_index, network_info in enumerate(netconfig):
+ for network_info in netconfig:
network_name = network_info["name"]
- network = None
- if network_name in networks:
- network = networks[network_name]
- else:
- network = Network()
- network.name = network_name
- network.vlan_id = lab.vlan_manager.get_vlan()
- network.save()
- networks[network_name] = network
- if network_info["public"]:
- public_net = lab.vlan_manager.get_public_vlan()
- if not public_net:
- raise Exception("No more public networks available")
- 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:
- raise Exception("No more generic vlans are available")
- lab.vlan_manager.reserve_vlans([private_net])
- network.vlan_id = private_net
-
- vlan = Vlan()
- vlan.vlan_id = network.vlan_id
- vlan.public = network_info["public"]
- vlan.tagged = network_info["tagged"]
- vlan.save()
- generic_interface.vlans.add(vlan)
-
- return (grb, host_set)
-
-
-def instantiate_ghost(grb, host_profile, hostname):
- if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
- raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
- gresource = GenericResource(bundle=grb, name=hostname)
- gresource.save()
+ if network_name not in networks:
+ networks[network_name] = make_network(network_name, lab, grb, network_info['public'])
+
+ generic_interface.connections.add(NetworkConnection.objects.create(
+ network=networks[network_name],
+ vlan_is_tagged=network_info["tagged"]
+ ))
- ghost = GenericHost()
- ghost.resource = gresource
- ghost.profile = host_profile
- ghost.save()
+ return grb, host_set
- return ghost
+def make_generic_host(grb, host_profile, hostname):
+ if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})$", hostname):
+ raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions")
+ gresource = GenericResource.objects.create(bundle=grb, name=hostname)
+
+ return GenericHost.objects.create(resource=gresource, profile=host_profile)
-def instantiate_user(is_superuser,
- username="testuser",
- password="testpassword",
- email="default_email@user.com"
- ):
+
+def make_user(is_superuser=False, username="testuser",
+ password="testpassword", email="default_email@user.com"):
user = User.objects.create_user(username=username, email=email, password=password)
user.is_superuser = is_superuser
-
user.save()
return user
-def instantiate_userprofile(user, email_addr="email@email.com", company="company", full_name="John Doe", booking_privledge=True, ssh_file=None):
- up = UserProfile()
- up.email_address = email_addr
- up.company = company
- up.full_name = full_name
- up.booking_privledge = booking_privledge
- up.user = user
- up.save()
- up.ssh_public_key.save("user_ssh_key", ssh_file if ssh_file else ContentFile("public key content string"))
+def make_user_profile(user=None, email_addr="email@email.com",
+ company="company", full_name="John Doe",
+ booking_privledge=True, ssh_file=None):
+ user = user or User.objects.first() or make_user()
+ profile = UserProfile.objects.create(
+ email_addr=email_addr,
+ company=company,
+ full_name=full_name,
+ booking_privledge=booking_privledge,
+ user=user
+ )
+ profile.ssh_public_key.save("user_ssh_key", ssh_file if ssh_file else ContentFile("public key content string"))
- return up
+ return profile
-def instantiate_vlanmanager(vlans=None,
- block_size=20,
- allow_overlapping=False,
- reserved_vlans=None
- ):
- vlanmanager = VlanManager()
+def make_vlan_manager(vlans=None, block_size=20, allow_overlapping=False, reserved_vlans=None):
if not vlans:
- vlans = []
- for vlan in range(0, 4095):
- vlans.append(vlan % 2)
- vlanmanager.vlans = json.dumps(vlans)
+ vlans = [vlan % 2 for vlan in range(4095)]
if not reserved_vlans:
- reserved_vlans = []
- for vlan in range(0, 4095):
- reserved_vlans.append(0)
- vlanmanager.reserved_vlans = json.dumps(vlans)
- vlanmanager.block_size = block_size
- vlanmanager.allow_overlapping = allow_overlapping
-
- vlanmanager.save()
+ reserved_vlans = [0 for i in range(4095)]
- return vlanmanager
+ return VlanManager.objects.create(
+ vlans=json.dumps(vlans),
+ reserved_vlans=json.dumps(vlans),
+ block_size=block_size,
+ allow_overlapping=allow_overlapping
+ )
-def instantiate_lab(user=None,
- name="Test Lab Instance",
- status=LabStatus.UP,
- vlan_manager=None
- ):
+def make_lab(user=None, name="Test Lab Instance",
+ status=LabStatus.UP, vlan_manager=None,
+ pub_net_count=5):
if not vlan_manager:
- vlan_manager = instantiate_vlanmanager()
+ vlan_manager = make_vlan_manager()
if not user:
- user = instantiate_user(True, 'test_user', 'test_pass', 'test_user@test_site.org')
-
- lab = Lab()
- lab.lab_user = user
- lab.name = name
- lab.contact_email = 'test_lab@test_site.org'
- lab.contact_phone = '603 123 4567'
- lab.status = status
- lab.vlan_manager = vlan_manager
- lab.description = 'test lab instantiation'
- lab.api_token = '12345678'
-
- lab.save()
+ user = make_user()
+
+ lab = Lab.objects.create(
+ lab_user=user,
+ name=name,
+ contact_email='test_lab@test_site.org',
+ contact_phone='603 123 4567',
+ status=status,
+ vlan_manager=vlan_manager,
+ description='test lab instantiation',
+ api_token='12345678'
+ )
+
+ for i in range(pub_net_count):
+ make_public_net(vlan=i * 2 + 1, lab=lab)
return lab
"""
-resource_inventory instantiation section for permenant resources
+resource_inventory instantiation section for permanent resources
"""
-def make_hostprofile_set(lab, name="test_hostprofile"):
- hostprof = instantiate_hostprofile(lab, name=name)
- instantiate_diskprofile(hostprof, 500, name=name)
- instantiate_cpuprofile(hostprof)
- instantiate_interfaceprofile(hostprof, name=name)
- instantiate_ramprofile(hostprof)
-
- return hostprof
-
-
-def instantiate_hostprofile(lab,
- host_type=0,
- name="test hostprofile instance"
- ):
- hostprof = HostProfile()
- hostprof.host_type = host_type
- hostprof.name = name
- hostprof.description = 'test hostprofile instance'
- hostprof.save()
- hostprof.labs.add(lab)
-
- hostprof.save()
-
- return hostprof
-
-
-def instantiate_ramprofile(host,
- channels=4,
- amount=256):
- ramprof = RamProfile()
- ramprof.host = host
- ramprof.amount = amount
- ramprof.channels = channels
- ramprof.save()
-
- return ramprof
-
-
-def instantiate_diskprofile(hostprofile,
- size=0,
- media_type="SSD",
- name="test diskprofile",
- rotation=0,
- interface="sata"):
-
- diskprof = DiskProfile()
- diskprof.name = name
- diskprof.size = size
- diskprof.media_type = media_type
- diskprof.host = hostprofile
- diskprof.rotation = rotation
- diskprof.interface = interface
-
- diskprof.save()
-
- return diskprof
-
-
-def instantiate_cpuprofile(hostprofile,
- cores=4,
- architecture="x86_64",
- cpus=4,
- ):
- cpuprof = CpuProfile()
- cpuprof.cores = cores
- cpuprof.architecture = architecture
- cpuprof.cpus = cpus
- cpuprof.host = hostprofile
- cpuprof.cflags = ''
-
- cpuprof.save()
-
- return cpuprof
-
-
-def instantiate_interfaceprofile(hostprofile,
- speed=1000,
- name="test interface profile",
- nic_type="pcie"
- ):
- intprof = InterfaceProfile()
- intprof.host = hostprofile
- intprof.name = name
- intprof.speed = speed
- intprof.nic_type = nic_type
-
- intprof.save()
-
- return intprof
-
-
-def instantiate_image(lab,
- lab_id,
- owner,
- os,
- host_profile,
- public=True,
- name="default image",
- description="default image"
- ):
- image = Image()
- image.from_lab = lab
- image.lab_id = lab_id
- image.os = os
- image.host_type = host_profile
- image.public = public
- image.name = name
- image.description = description
-
- image.save()
-
- return image
-
-
-def instantiate_scenario(name="test scenario"):
- scenario = Scenario()
- scenario.name = name
- scenario.save()
- return scenario
-
-
-def instantiate_installer(supported_scenarios,
- name="test installer"
- ):
- installer = Installer()
- installer.name = name
- installer.save()
- for scenario in supported_scenarios:
+def make_complete_host_profile(lab, name="test_hostprofile"):
+ host_profile = make_host_profile(lab, name=name)
+ make_disk_profile(host_profile, 500, name=name)
+ make_cpu_profile(host_profile)
+ make_interface_profile(host_profile, name=name)
+ make_ram_profile(host_profile)
+
+ return host_profile
+
+
+def make_host_profile(lab, host_type=0, name="test hostprofile"):
+ host_profile = HostProfile.objects.create(
+ host_type=host_type,
+ name=name,
+ description='test hostprofile instance'
+ )
+ host_profile.labs.add(lab)
+
+ return host_profile
+
+
+def make_ram_profile(host, channels=4, amount=256):
+ return RamProfile.objects.create(
+ host=host,
+ amount=amount,
+ channels=channels
+ )
+
+
+def make_disk_profile(hostprofile, size=0, media_type="SSD",
+ name="test diskprofile", rotation=0,
+ interface="sata"):
+ return DiskProfile.objects.create(
+ name=name,
+ size=size,
+ media_type=media_type,
+ host=hostprofile,
+ rotation=rotation,
+ interface=interface
+ )
+
+
+def make_cpu_profile(hostprofile,
+ cores=4,
+ architecture="x86_64",
+ cpus=4,):
+ return CpuProfile.objects.create(
+ cores=cores,
+ architecture=architecture,
+ cpus=cpus,
+ host=hostprofile,
+ cflags=''
+ )
+
+
+def make_interface_profile(hostprofile,
+ speed=1000,
+ name="test interface profile",
+ nic_type="pcie"):
+ return InterfaceProfile.objects.create(
+ host=hostprofile,
+ name=name,
+ speed=speed,
+ nic_type=nic_type
+ )
+
+
+def make_image(lab, lab_id, owner, os, host_profile,
+ public=True, name="default image", description="default image"):
+ return Image.objects.create(
+ from_lab=lab,
+ lab_id=lab_id,
+ os=os,
+ host_type=host_profile,
+ public=public,
+ name=name,
+ description=description
+ )
+
+
+def make_scenario(name="test scenario"):
+ return Scenario.objects.create(name=name)
+
+
+def make_installer(scenarios, name="test installer"):
+ installer = Installer.objects.create(name=name)
+ for scenario in scenarios:
installer.sup_scenarios.add(scenario)
- installer.save()
return installer
-def instantiate_os(supported_installers,
- name="test operating system",
- ):
- os = Opsys()
- os.name = name
- os.save()
- for installer in supported_installers:
+def make_os(installers, name="test OS"):
+ os = Opsys.objects.create(name=name)
+ for installer in installers:
os.sup_installers.add(installer)
- os.save()
+
return os
-def instantiate_host(host_profile,
- lab,
- labid="test_host",
- name="test_host",
- booked=False,
- working=True,
- config=None,
- template=None,
- bundle=None,
- model="Model 1",
- vendor="ACME"):
- host = Host()
- host.lab = lab
- host.profile = host_profile
- host.name = name
- host.booked = booked
- host.working = working
- host.config = config
- host.template = template
- host.bundle = bundle
- host.model = model
- host.vendor = vendor
-
- host.save()
-
- return host
-
-
-def instantiate_opnfvrole(name="Jumphost",
- description="test opnfvrole"):
- role = OPNFVRole()
- role.name = name
- role.description = description
- role.save()
-
- return role
-
-
-def instantiate_publicnet(vlan,
- lab,
- in_use=False,
- cidr="0.0.0.0/0",
- gateway="0.0.0.0"):
- pubnet = PublicNetwork()
- pubnet.lab = lab
- pubnet.vlan = vlan
- pubnet.cidr = cidr
- pubnet.gateway = gateway
- pubnet.save()
-
- return pubnet
+def make_host(host_profile, lab, labid="test_host", name="test_host",
+ booked=False, working=True, config=None, template=None,
+ bundle=None, model="Model 1", vendor="ACME"):
+ return Host.objects.create(
+ lab=lab,
+ profile=host_profile,
+ name=name,
+ booked=booked,
+ working=working,
+ config=config,
+ template=template,
+ bundle=bundle,
+ model=model,
+ vendor=vendor
+ )
+
+
+def make_opnfv_role(name="Jumphost", description="test opnfvrole"):
+ return OPNFVRole.objects.create(
+ name=name,
+ description=description
+ )
+
+
+def make_public_net(vlan, lab, in_use=False,
+ cidr="0.0.0.0/0", gateway="0.0.0.0"):
+ return PublicNetwork.objects.create(
+ lab=lab,
+ vlan=vlan,
+ cidr=cidr,
+ gateway=gateway
+ )
diff --git a/dashboard/src/templates/booking/steps/booking_meta.html b/dashboard/src/templates/booking/steps/booking_meta.html
index fe43f53..cdd7834 100644
--- a/dashboard/src/templates/booking/steps/booking_meta.html
+++ b/dashboard/src/templates/booking/steps/booking_meta.html
@@ -48,6 +48,7 @@
</script>
{% bootstrap_field form.info_file %}
<p>You must provide a url to your project's INFO.yaml file if you are a PTL and you are trying to book a POD with multiple servers in it.</p>
+ {% bootstrap_field form.deploy_opnfv %}
</div>
<div class="panel panel_center">
</div>
diff --git a/dashboard/src/templates/dashboard/genericselect.html b/dashboard/src/templates/dashboard/genericselect.html
new file mode 100644
index 0000000..fc29ee6
--- /dev/null
+++ b/dashboard/src/templates/dashboard/genericselect.html
@@ -0,0 +1,71 @@
+{% extends "workflow/viewport-element.html" %}
+{% load staticfiles %}
+
+{% load bootstrap3 %}
+
+{% block content %}
+
+<style>
+#{{select_type}}_form_div {
+ width: 100%;
+ padding: 5%;
+ }
+
+ .panel {
+ border: none;
+ }
+ .select_panels {
+ width: 100%;
+ display: grid;
+ grid-template-columns: 45% 10% 45%;
+
+ }
+
+ .panel_center {
+ text-align: center;
+ border: none;
+
+ }
+ .panel_center p{
+ font-size: 20pt;
+ }
+</style>
+
+<div id="{{select_type}}_form_div">
+ <div class="select_panels">
+ <div class="panel_chooser panel">
+ <form id="{{select_type}}_select_form" method="post" action="" class="form" id="{{select_type}}selectorform">
+ {% csrf_token %}
+ {{ form|default:"<p>no form loaded</p>" }}
+ {% buttons %}
+
+ {% endbuttons %}
+ </form>
+ </div>
+ <div class="panel_center panel"><p>OR</p></div>
+ <div class="panel_add panel">
+ <button class="btn {% if disabled %} disabled {% endif %}"
+ style="width: 100%; height: 100%;"
+ {% if not disabled %}onclick="parent.add_wf({{addable_type_num}})"
+ {% endif %}>Create {{select_type_title}}
+ </button>
+ </div>
+ </div>
+</div>
+<script>
+ {% if disabled %}
+ disable();
+ {% endif %}
+</script>
+
+{% endblock content %}
+{% block onleave %}
+var form = $("#{{select_type}}_select_form");
+var formData = form.serialize();
+var req = new XMLHttpRequest();
+req.open("POST", "/wf/workflow/", false);
+req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
+req.onerror = function() { alert("problem with form submission"); }
+req.send(formData);
+{% endblock %}
+
diff --git a/dashboard/src/templates/dashboard/searchable_select_multiple.html b/dashboard/src/templates/dashboard/searchable_select_multiple.html
index c08fbe5..69394ab 100644
--- a/dashboard/src/templates/dashboard/searchable_select_multiple.html
+++ b/dashboard/src/templates/dashboard/searchable_select_multiple.html
@@ -7,9 +7,10 @@
<p>Please make a different selection, as the current config conflicts with the selected pod</p>
{% endif %}
</div>
- <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="{{initial.name}}" oninput="search(this.value)"
+ <input id="user_field" name="ignore_this" class="form-control" autocomplete="off" type="text" placeholder="{{placeholder}}" value="" oninput="search(this.value)"
{% if disabled %} disabled {% endif %}
>
+ </input>
<input type="hidden" id="selector" name="{{ name }}" class="form-control" style="display: none;"
{% if disabled %} disabled {% endif %}
@@ -18,14 +19,6 @@
<ul id="drop_results"></ul>
-
- <div id="default_entry_wrap" style="display: none;">
- <div class="list_entry unremovable_list_entry">
- <p id="default_text" class="full_name"></p>
- <button class="btn-remove btn disabled">remove</button>
- </div>
- </div>
-
<div id="added_list">
</div>
@@ -96,16 +89,32 @@
<script type="text/javascript">
//flags
- var show_from_noentry = {{show_from_noentry|default:"false"}};
- var show_x_results = {{show_x_results|default:-1}};
- var results_scrollable = {{results_scrollable|default:"false"}};
- var selectable_limit = {{selectable_limit|default:-1}};
- var field_name = "{{name|default:"users"}}";
- var placeholder = "{{placeholder|default:"begin typing"}}";
- var default_entry = "{{default_entry}}";
+ var show_from_noentry = {{show_from_noentry|yesno:"true,false"}}; // whether to show any results before user starts typing
+ var show_x_results = {{show_x_results|default:-1}}; // how many results to show at a time, -1 shows all results
+ var results_scrollable = {{results_scrollable|yesno:"true,false"}}; // whether list should be scrollable
+ var selectable_limit = {{selectable_limit|default:-1}}; // how many selections can be made, -1 allows infinitely many
+ var placeholder = "{{placeholder|default:"begin typing"}}"; // placeholder that goes in text box
//needed info
- var items = {{items|safe}}
+ var items = {{items|safe}} // items to add to trie. Type is a dictionary of dictionaries with structure:
+ /*
+ {
+ id# : {
+ "id": any, identifiable on backend
+ "small_name": string, displayed first (before separator), searchable (use for e.g. username)
+ "expanded_name": string, displayed second (after separator), searchable (use for e.g. email address)
+ "string": string, not displayed, still searchable
+ }
+ }
+ */
+
+ /* used later:
+ {{ selectable_limit }}: changes what number displays for field
+ {{ name }}: form identifiable name, relevant for backend
+ // when submitted, form will contain field data in post with name as the key
+ {{ placeholder }}: "greyed out" contents put into search field initially to guide user as to what they're searching for
+ {{ initial }}: in search_field_init(), marked safe, an array of id's each referring to an id from items
+ */
//tries
var expanded_name_trie = {}
@@ -116,18 +125,6 @@
string_trie.isComplete = false;
var added_items = [];
- var initial_log = {{ initial|safe }};
-
- var added_template = {{ added_list|default:"{}" }};
-
- if( default_entry )
- {
- var default_entry_div = document.getElementById("default_entry_wrap");
- default_entry_div.style.display = "inherit";
-
- var entry_p = document.getElementById("default_text");
- entry_p.innerText = default_entry;
- }
search_field_init();
@@ -335,7 +332,7 @@
function select_item(item_id)
{
//TODO make faster
- var item = items[item_id];
+ var item = items[item_id]['id'];
if( (selectable_limit > -1 && added_items.length < selectable_limit) || selectable_limit < 0 )
{
if( added_items.indexOf(item) == -1 )
@@ -357,11 +354,6 @@
document.getElementById("user_field").focus();
}
- function edit_item(item_id){
- var wf_type = "{{wf_type}}";
- parent.add_edit_wf(wf_type, item_id);
- }
-
function update_selected_list()
{
document.getElementById("added_number").innerText = added_items.length;
@@ -382,7 +374,8 @@
for( var key in added_items )
{
- item = added_items[key];
+ item_id = added_items[key];
+ item = items[item_id];
list_html += '<div class="list_entry"><p class="full_name">'
+ item["expanded_name"]
@@ -391,11 +384,6 @@
+ '</p><button onclick="remove_item('
+ Object.values(items).indexOf(item)
+ ')" class="btn-remove btn">remove</button>';
- {% if edit %}
- list_html += '<button onclick="edit_item('
- + item['id']
- + ')" class="btn-remove btn">edit</button>';
- {% endif %}
list_html += '</div>';
}
diff --git a/dashboard/src/workflow/booking_workflow.py b/dashboard/src/workflow/booking_workflow.py
index eb87728..42372ce 100644
--- a/dashboard/src/workflow/booking_workflow.py
+++ b/dashboard/src/workflow/booking_workflow.py
@@ -8,188 +8,136 @@
##############################################################################
from django.contrib import messages
-from django.shortcuts import render
-from django.contrib.auth.models import User
from django.utils import timezone
-import json
from datetime import timedelta
from booking.models import Booking
-from workflow.models import WorkflowStep
-from workflow.forms import ResourceSelectorForm, SWConfigSelectorForm, BookingMetaForm
-from resource_inventory.models import GenericResourceBundle, ResourceBundle, ConfigBundle
+from workflow.models import WorkflowStep, AbstractSelectOrCreate
+from workflow.forms import ResourceSelectorForm, SWConfigSelectorForm, BookingMetaForm, OPNFVSelectForm
+from resource_inventory.models import GenericResourceBundle, ConfigBundle, OPNFVConfig
-class Resource_Select(WorkflowStep):
- template = 'booking/steps/resource_select.html'
+"""
+subclassing notes:
+ subclasses have to define the following class attributes:
+ self.repo_key: main output of step, where the selected/created single selector
+ result is placed at the end
+ self.confirm_key:
+"""
+
+
+class Abstract_Resource_Select(AbstractSelectOrCreate):
+ form = ResourceSelectorForm
+ template = 'dashboard/genericselect.html'
title = "Select Resource"
description = "Select a resource template to use for your deployment"
short_title = "pod select"
def __init__(self, *args, **kwargs):
- super(Resource_Select, self).__init__(*args, **kwargs)
- self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
- self.repo_check_key = False
- self.confirm_key = "booking"
+ super().__init__(*args, **kwargs)
+ self.select_repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
+ self.confirm_key = self.workflow_type
- def get_context(self):
- context = super(Resource_Select, self).get_context()
- default = []
-
- chosen_bundle = self.repo_get(self.repo_key, False)
- if chosen_bundle:
- default.append(chosen_bundle.id)
+ def alert_bundle_missing(self):
+ self.set_invalid("Please select a valid resource bundle")
- bundle = chosen_bundle
- edit = self.repo_get(self.repo.EDIT, False)
+ def get_form_queryset(self):
user = self.repo_get(self.repo.SESSION_USER)
- context['form'] = ResourceSelectorForm(
- data={"user": user},
- chosen_resource=default,
- bundle=bundle,
- edit=edit
- )
- return context
+ qs = GenericResourceBundle.objects.filter(owner=user)
+ return qs
- def post_render(self, request):
- form = ResourceSelectorForm(request.POST)
- context = self.get_context()
- if form.is_valid():
- data = form.cleaned_data['generic_resource_bundle']
- data = data[2:-2]
- if not data:
- self.set_invalid("Please select a valid bundle")
- return render(request, self.template, context)
- selected_bundle = json.loads(data)
- if len(selected_bundle) < 1:
- self.set_invalid("Please select a valid bundle")
- return render(request, self.template, context)
- selected_id = selected_bundle[0]['id']
- gresource_bundle = None
- try:
- selected_id = int(selected_id)
- gresource_bundle = GenericResourceBundle.objects.get(id=selected_id)
- except ValueError:
- # we want the bundle in the repo
- gresource_bundle = self.repo_get(
- self.repo.GRESOURCE_BUNDLE_MODELS,
- {}
- ).get("bundle", GenericResourceBundle())
- self.repo_put(
- self.repo_key,
- gresource_bundle
- )
- confirm = self.repo_get(self.repo.CONFIRMATION)
- if self.confirm_key not in confirm:
- confirm[self.confirm_key] = {}
- confirm[self.confirm_key]["resource name"] = gresource_bundle.name
- self.repo_put(self.repo.CONFIRMATION, confirm)
- messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
- self.set_valid("Step Completed")
- return render(request, self.template, context)
- else:
- messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
- self.set_invalid("Please complete the fields highlighted in red to continue")
- return render(request, self.template, context)
+ def get_page_context(self):
+ return {
+ 'select_type': 'resource',
+ 'select_type_title': 'Resource Bundle',
+ 'addable_type_num': 1
+ }
+ def put_confirm_info(self, bundle):
+ confirm_dict = self.repo_get(self.repo.CONFIRMATION)
+ if self.confirm_key not in confirm_dict:
+ confirm_dict[self.confirm_key] = {}
+ confirm_dict[self.confirm_key]["Resource Template"] = bundle.name
+ self.repo_put(self.repo.CONFIRMATION, confirm_dict)
-class Booking_Resource_Select(Resource_Select):
- def __init__(self, *args, **kwargs):
- super(Booking_Resource_Select, self).__init__(*args, **kwargs)
- self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
- self.confirm_key = "booking"
+class Booking_Resource_Select(Abstract_Resource_Select):
+ workflow_type = "booking"
- def get_context(self):
- context = super(Booking_Resource_Select, self).get_context()
- return context
- def post_render(self, request):
- response = super(Booking_Resource_Select, self).post_render(request)
- models = self.repo_get(self.repo.BOOKING_MODELS, {})
- if "booking" not in models:
- models['booking'] = Booking()
- booking = models['booking']
- resource = self.repo_get(self.repo_key, False)
- if resource:
- try:
- booking.resource.template = resource
- except Exception:
- booking.resource = ResourceBundle(template=resource)
- models['booking'] = booking
- self.repo_put(self.repo.BOOKING_MODELS, models)
- return response
-
-
-class SWConfig_Select(WorkflowStep):
- template = 'booking/steps/swconfig_select.html'
+class SWConfig_Select(AbstractSelectOrCreate):
title = "Select Software Configuration"
description = "Choose the software and related configurations you want to have used for your deployment"
short_title = "pod config"
+ form = SWConfigSelectorForm
- def post_render(self, request):
- form = SWConfigSelectorForm(request.POST)
- if form.is_valid():
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE
+ self.confirm_key = "booking"
- bundle_json = form.cleaned_data['software_bundle']
- bundle_json = bundle_json[2:-2] # Stupid django string bug
- if not bundle_json:
- self.set_invalid("Please select a valid config")
- return self.render(request)
- bundle_json = json.loads(bundle_json)
- if len(bundle_json) < 1:
- self.set_invalid("Please select a valid config")
- return self.render(request)
- bundle = None
- id = int(bundle_json[0]['id'])
- bundle = ConfigBundle.objects.get(id=id)
-
- grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
-
- if grb and bundle.bundle != grb:
- self.set_invalid("Incompatible config selected for resource bundle")
- return self.render(request)
- if not grb:
- self.repo_set(self.repo.SELECTED_GRESOURCE_BUNDLE, bundle.bundle)
+ def alert_bundle_missing(self):
+ self.set_invalid("Please select a valid pod config")
- models = self.repo_get(self.repo.BOOKING_MODELS, {})
- if "booking" not in models:
- models['booking'] = Booking()
- models['booking'].config_bundle = bundle
- self.repo_put(self.repo.BOOKING_MODELS, models)
- confirm = self.repo_get(self.repo.CONFIRMATION)
- if "booking" not in confirm:
- confirm['booking'] = {}
- confirm['booking']["configuration name"] = bundle.name
- self.repo_put(self.repo.CONFIRMATION, confirm)
- self.set_valid("Step Completed")
- messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
- else:
- self.set_invalid("Please select or create a valid config")
- messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
+ def get_form_queryset(self):
+ user = self.repo_get(self.repo.SESSION_USER)
+ grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
+ qs = ConfigBundle.objects.filter(owner=user).filter(bundle=grb)
+ return qs
- return self.render(request)
+ def put_confirm_info(self, bundle):
+ confirm_dict = self.repo_get(self.repo.CONFIRMATION)
+ if self.confirm_key not in confirm_dict:
+ confirm_dict[self.confirm_key] = {}
+ confirm_dict[self.confirm_key]["Software Configuration"] = bundle.name
+ self.repo_put(self.repo.CONFIRMATION, confirm_dict)
- def get_context(self):
- context = super(SWConfig_Select, self).get_context()
- default = []
- bundle = None
- chosen_bundle = None
- created_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
- booking = self.repo_get(self.repo.BOOKING_MODELS, {}).get("booking", False)
- try:
- chosen_bundle = booking.config_bundle
- default.append(chosen_bundle.id)
- bundle = chosen_bundle
- except Exception:
- if created_bundle:
- default.append(created_bundle.id)
- bundle = created_bundle
- edit = self.repo_get(self.repo.EDIT, False)
- grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE)
- context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=bundle, edit=edit, resource=grb)
- return context
+ def get_page_context(self):
+ return {
+ 'select_type': 'swconfig',
+ 'select_type_title': 'Software Config',
+ 'addable_type_num': 2
+ }
+
+
+class OPNFV_EnablePicker(object):
+ pass
+
+
+class OPNFV_Select(AbstractSelectOrCreate, OPNFV_EnablePicker):
+ title = "Choose an OPNFV Config"
+ description = "Choose or create a description of how you want to deploy OPNFV"
+ short_title = "opnfv config"
+ form = OPNFVSelectForm
+ enabled = False
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.select_repo_key = self.repo.SELECTED_OPNFV_CONFIG
+ self.confirm_key = "booking"
+
+ def alert_bundle_missing(self):
+ self.set_invalid("Please select a valid OPNFV config")
+
+ def get_form_queryset(self):
+ cb = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
+ qs = OPNFVConfig.objects.filter(bundle=cb)
+ return qs
+
+ def put_confirm_info(self, config):
+ confirm_dict = self.repo_get(self.repo.CONFIRMATION)
+ if self.confirm_key not in confirm_dict:
+ confirm_dict[self.confirm_key] = {}
+ confirm_dict[self.confirm_key]["OPNFV Configuration"] = config.name
+ self.repo_put(self.repo.CONFIRMATION, confirm_dict)
+
+ def get_page_context(self):
+ return {
+ 'select_type': 'opnfv',
+ 'select_type_title': 'OPNFV Config',
+ 'addable_type_num': 4
+ }
class Booking_Meta(WorkflowStep):
@@ -214,23 +162,17 @@ class Booking_Meta(WorkflowStep):
initial['info_file'] = info
users = models.get("collaborators", [])
for user in users:
- default.append(user.id)
+ default.append(user.userprofile)
except Exception:
pass
- default_user = self.repo_get(self.repo.SESSION_USER)
- if default_user is None:
- # TODO: error
- default_user = "you"
- else:
- default_user = default_user.username
+ owner = self.repo_get(self.repo.SESSION_USER)
- context['form'] = BookingMetaForm(initial=initial, chosen_users=default, default_user=default_user)
+ context['form'] = BookingMetaForm(initial=initial, user_initial=default, owner=owner)
return context
def post_render(self, request):
- form = BookingMetaForm(data=request.POST)
- context = self.get_context()
+ form = BookingMetaForm(data=request.POST, owner=request.user)
forms = self.repo_get(self.repo.BOOKING_FORMS, {})
@@ -253,15 +195,16 @@ class Booking_Meta(WorkflowStep):
for key in ['length', 'project', 'purpose']:
confirm['booking'][key] = form.cleaned_data[key]
- user_data = form.cleaned_data['users']
+ if form.cleaned_data["deploy_opnfv"]:
+ self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=True)
+ else:
+ self.repo_get(self.repo.SESSION_MANAGER).set_step_statuses(OPNFV_EnablePicker, desired_enabled=False)
+
+ userprofile_list = form.cleaned_data['users']
confirm['booking']['collaborators'] = []
- user_data = user_data[2:-2] # fixes malformed string from querydict
- if user_data:
- form_users = json.loads(user_data)
- for user_json in form_users:
- user = User.objects.get(pk=user_json['id'])
- models['collaborators'].append(user)
- confirm['booking']['collaborators'].append(user.username)
+ for userprofile in userprofile_list:
+ models['collaborators'].append(userprofile.user)
+ confirm['booking']['collaborators'].append(userprofile.user.username)
info_file = form.cleaned_data.get("info_file", False)
if info_file:
@@ -274,5 +217,4 @@ class Booking_Meta(WorkflowStep):
else:
messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True)
self.set_invalid("Please complete the fields highlighted in red to continue")
- context['form'] = form # TODO: store this form
- return render(request, self.template, context)
+ return self.render(request)
diff --git a/dashboard/src/workflow/forms.py b/dashboard/src/workflow/forms.py
index 6d26b5c..bd2d14a 100644
--- a/dashboard/src/workflow/forms.py
+++ b/dashboard/src/workflow/forms.py
@@ -9,40 +9,37 @@
import django.forms as forms
-from django.forms import widgets
+from django.forms import widgets, ValidationError
from django.utils.safestring import mark_safe
from django.template.loader import render_to_string
from django.forms.widgets import NumberInput
+import json
+
from account.models import Lab
from account.models import UserProfile
from resource_inventory.models import (
- GenericResourceBundle,
- ConfigBundle,
OPNFVRole,
Installer,
Scenario,
)
+from booking.lib import get_user_items, get_user_field_opts
class SearchableSelectMultipleWidget(widgets.SelectMultiple):
template_name = 'dashboard/searchable_select_multiple.html'
def __init__(self, attrs=None):
- self.items = attrs['set']
+ self.items = attrs['items']
self.show_from_noentry = attrs['show_from_noentry']
self.show_x_results = attrs['show_x_results']
- self.results_scrollable = attrs['scrollable']
+ self.results_scrollable = attrs['results_scrollable']
self.selectable_limit = attrs['selectable_limit']
self.placeholder = attrs['placeholder']
self.name = attrs['name']
- self.initial = attrs.get("initial", "")
- self.default_entry = attrs.get("default_entry", "")
- self.edit = attrs.get("edit", False)
- self.wf_type = attrs.get("wf_type")
- self.incompatible = attrs.get("incompatible", "false")
+ self.initial = attrs.get("initial", [])
- super(SearchableSelectMultipleWidget, self).__init__(attrs)
+ super(SearchableSelectMultipleWidget, self).__init__()
def render(self, name, value, attrs=None, renderer=None):
@@ -59,132 +56,160 @@ class SearchableSelectMultipleWidget(widgets.SelectMultiple):
'selectable_limit': self.selectable_limit,
'placeholder': self.placeholder,
'initial': self.initial,
- 'default_entry': self.default_entry,
- 'edit': self.edit,
- 'wf_type': self.wf_type,
- 'incompatible': self.incompatible
}
-class ResourceSelectorForm(forms.Form):
+class SearchableSelectMultipleField(forms.Field):
+ def __init__(self, *args, required=True, widget=None, label=None, disabled=False,
+ items=None, queryset=None, show_from_noentry=True, show_x_results=-1,
+ results_scrollable=False, selectable_limit=-1, placeholder="search here",
+ name="searchable_select", initial=[], **kwargs):
+ """from the documentation:
+ # required -- Boolean that specifies whether the field is required.
+ # True by default.
+ # widget -- A Widget class, or instance of a Widget class, that should
+ # be used for this Field when displaying it. Each Field has a
+ # default Widget that it'll use if you don't specify this. In
+ # most cases, the default widget is TextInput.
+ # label -- A verbose name for this field, for use in displaying this
+ # field in a form. By default, Django will use a "pretty"
+ # version of the form field name, if the Field is part of a
+ # Form.
+ # initial -- A value to use in this Field's initial display. This value
+ # is *not* used as a fallback if data isn't given.
+ # help_text -- An optional string to use as "help text" for this Field.
+ # error_messages -- An optional dictionary to override the default
+ # messages that the field will raise.
+ # show_hidden_initial -- Boolean that specifies if it is needed to render a
+ # hidden widget with initial value after widget.
+ # validators -- List of additional validators to use
+ # localize -- Boolean that specifies if the field should be localized.
+ # disabled -- Boolean that specifies whether the field is disabled, that
+ # is its widget is shown in the form but not editable.
+ # label_suffix -- Suffix to be added to the label. Overrides
+ # form's label_suffix.
+ """
+
+ self.widget = widget
+ if self.widget is None:
+ self.widget = SearchableSelectMultipleWidget(
+ attrs={
+ 'items': items,
+ 'initial': [obj.id for obj in initial],
+ 'show_from_noentry': show_from_noentry,
+ 'show_x_results': show_x_results,
+ 'results_scrollable': results_scrollable,
+ 'selectable_limit': selectable_limit,
+ 'placeholder': placeholder,
+ 'name': name,
+ 'disabled': disabled
+ }
+ )
+ self.disabled = disabled
+ self.queryset = queryset
+ self.selectable_limit = selectable_limit
- def __init__(self, data=None, **kwargs):
- chosen_resource = ""
- bundle = None
- edit = False
- if "chosen_resource" in kwargs:
- chosen_resource = kwargs.pop("chosen_resource")
- if "bundle" in kwargs:
- bundle = kwargs.pop("bundle")
- if "edit" in kwargs:
- edit = kwargs.pop("edit")
- super(ResourceSelectorForm, self).__init__(data=data, **kwargs)
- queryset = GenericResourceBundle.objects.select_related("owner").all()
- if data and 'user' in data:
- queryset = queryset.filter(owner=data['user'])
+ super().__init__(disabled=disabled, **kwargs)
- attrs = self.build_search_widget_attrs(chosen_resource, bundle, edit, queryset)
+ self.required = required
- self.fields['generic_resource_bundle'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(attrs=attrs)
+ def clean(self, data):
+ data = data[0]
+ if not data:
+ if self.required:
+ raise ValidationError("Nothing was selected")
+ else:
+ return []
+ data_as_list = json.loads(data)
+ if self.selectable_limit != -1:
+ if len(data_as_list) > self.selectable_limit:
+ raise ValidationError("Too many items were selected")
+
+ items = []
+ for elem in data_as_list:
+ items.append(self.queryset.get(id=elem))
+
+ return items
+
+
+class SearchableSelectAbstractForm(forms.Form):
+ def __init__(self, *args, queryset=None, initial=[], **kwargs):
+ self.queryset = queryset
+ items = self.generate_items(self.queryset)
+ options = self.generate_options()
+
+ super(SearchableSelectAbstractForm, self).__init__(*args, **kwargs)
+ self.fields['searchable_select'] = SearchableSelectMultipleField(
+ initial=initial,
+ items=items,
+ queryset=self.queryset,
+ **options
)
- def build_search_widget_attrs(self, chosen_resource, bundle, edit, queryset):
- resources = {}
- for res in queryset:
- displayable = {}
- displayable['small_name'] = res.name
- if res.owner:
- displayable['expanded_name'] = res.owner.username
- else:
- displayable['expanded_name'] = ""
- displayable['string'] = res.description
- displayable['id'] = res.id
- resources[res.id] = displayable
-
- attrs = {
- 'set': resources,
- 'show_from_noentry': "true",
+ def get_validated_bundle(self):
+ bundles = self.cleaned_data['searchable_select']
+ if len(bundles) < 1: # don't need to check for >1, as field does that for us
+ raise ValidationError("No bundle was selected")
+ return bundles[0]
+
+ def generate_items(self, queryset):
+ raise Exception("SearchableSelectAbstractForm does not implement concrete generate_items()")
+
+ def generate_options(self, disabled=False):
+ return {
+ 'show_from_noentry': True,
'show_x_results': -1,
- 'scrollable': "true",
+ 'results_scrollable': True,
'selectable_limit': 1,
- 'name': "generic_resource_bundle",
- 'placeholder': "resource",
- 'initial': chosen_resource,
- 'edit': edit,
- 'wf_type': 1
+ 'placeholder': 'Search for a Bundle',
+ 'name': 'searchable_select',
+ 'disabled': False
}
- return attrs
-class SWConfigSelectorForm(forms.Form):
+class SWConfigSelectorForm(SearchableSelectAbstractForm):
+ def generate_items(self, queryset):
+ items = {}
- def __init__(self, *args, **kwargs):
- chosen_software = ""
- bundle = None
- edit = False
- resource = None
- user = None
- if "chosen_software" in kwargs:
- chosen_software = kwargs.pop("chosen_software")
-
- if "bundle" in kwargs:
- bundle = kwargs.pop("bundle")
- if "edit" in kwargs:
- edit = kwargs.pop("edit")
- if "resource" in kwargs:
- resource = kwargs.pop("resource")
- if "user" in kwargs:
- user = kwargs.pop("user")
- super(SWConfigSelectorForm, self).__init__(*args, **kwargs)
- attrs = self.build_search_widget_attrs(chosen_software, bundle, edit, resource, user)
- self.fields['software_bundle'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(attrs=attrs)
- )
+ for bundle in queryset:
+ items[bundle.id] = {
+ 'small_name': bundle.name,
+ 'expanded_name': bundle.owner.username,
+ 'string': bundle.description,
+ 'id': bundle.id
+ }
- def build_search_widget_attrs(self, chosen, bundle, edit, resource, user):
- configs = {}
- queryset = ConfigBundle.objects.select_related('owner').all()
- if resource:
- if user is None:
- user = resource.owner
- queryset = queryset.filter(bundle=resource)
+ return items
- if user:
- queryset = queryset.filter(owner=user)
+
+class OPNFVSelectForm(SearchableSelectAbstractForm):
+ def generate_items(self, queryset):
+ items = {}
for config in queryset:
- displayable = {}
- displayable['small_name'] = config.name
- displayable['expanded_name'] = config.owner.username
- displayable['string'] = config.description
- displayable['id'] = config.id
- configs[config.id] = displayable
-
- incompatible_choice = "false"
- if bundle and bundle.id not in configs:
- displayable = {}
- displayable['small_name'] = bundle.name
- displayable['expanded_name'] = bundle.owner.username
- displayable['string'] = bundle.description
- displayable['id'] = bundle.id
- configs[bundle.id] = displayable
- incompatible_choice = "true"
-
- attrs = {
- 'set': configs,
- 'show_from_noentry': "true",
- 'show_x_results': -1,
- 'scrollable': "true",
- 'selectable_limit': 1,
- 'name': "software_bundle",
- 'placeholder': "config",
- 'initial': chosen,
- 'edit': edit,
- 'wf_type': 2,
- 'incompatible': incompatible_choice
- }
- return attrs
+ items[config.id] = {
+ 'small_name': config.name,
+ 'expanded_name': config.bundle.owner.username,
+ 'string': config.description,
+ 'id': config.id
+ }
+
+ return items
+
+
+class ResourceSelectorForm(SearchableSelectAbstractForm):
+ def generate_items(self, queryset):
+ items = {}
+
+ for bundle in queryset:
+ items[bundle.id] = {
+ 'small_name': bundle.name,
+ 'expanded_name': bundle.owner.username,
+ 'string': bundle.description,
+ 'id': bundle.id
+ }
+
+ return items
class BookingMetaForm(forms.Form):
@@ -202,66 +227,18 @@ class BookingMetaForm(forms.Form):
purpose = forms.CharField(max_length=1000)
project = forms.CharField(max_length=400)
info_file = forms.CharField(max_length=1000, required=False)
+ deploy_opnfv = forms.BooleanField(required=False)
- def __init__(self, data=None, *args, **kwargs):
- chosen_users = []
- if "default_user" in kwargs:
- default_user = kwargs.pop("default_user")
- else:
- default_user = "you"
- self.default_user = default_user
- if "chosen_users" in kwargs:
- chosen_users = kwargs.pop("chosen_users")
- elif data and "users" in data:
- chosen_users = data.getlist("users")
- else:
- pass
-
- super(BookingMetaForm, self).__init__(data=data, **kwargs)
-
- self.fields['users'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(
- attrs=self.build_search_widget_attrs(chosen_users, default_user=default_user)
- ),
- required=False
- )
-
- def build_user_list(self):
- """
- returns a mapping of UserProfile ids to displayable objects expected by
- searchable multiple select widget
- """
- try:
- users = {}
- d_qset = UserProfile.objects.select_related('user').all().exclude(user__username=self.default_user)
- for userprofile in d_qset:
- user = {
- 'id': userprofile.user.id,
- 'expanded_name': userprofile.full_name,
- 'small_name': userprofile.user.username,
- 'string': userprofile.email_addr
- }
-
- users[userprofile.user.id] = user
-
- return users
- except Exception:
- pass
-
- def build_search_widget_attrs(self, chosen_users, default_user="you"):
+ def __init__(self, *args, user_initial=[], owner=None, **kwargs):
+ super(BookingMetaForm, self).__init__(**kwargs)
- attrs = {
- 'set': self.build_user_list(),
- 'show_from_noentry': "false",
- 'show_x_results': 10,
- 'scrollable': "false",
- 'selectable_limit': -1,
- 'name': "users",
- 'placeholder': "username",
- 'initial': chosen_users,
- 'edit': False
- }
- return attrs
+ self.fields['users'] = SearchableSelectMultipleField(
+ queryset=UserProfile.objects.select_related('user').exclude(user=owner),
+ initial=user_initial,
+ items=get_user_items(exclude=owner),
+ required=False,
+ **get_user_field_opts()
+ )
class MultipleSelectFilterWidget(forms.Widget):
@@ -298,7 +275,7 @@ class MultipleSelectFilterField(forms.Field):
# Form.
# initial -- A value to use in this Field's initial display. This value
# is *not* used as a fallback if data isn't given.
- # help_text -- An optional string to use as "help text" for this Field.
+ # help_text -- An optional string to use as "help; text" for this Field.
# error_messages -- An optional dictionary to override the default
# messages that the field will raise.
# show_hidden_initial -- Boolean that specifies if it is needed to render a
diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py
index 25d7e84..43a9bf2 100644
--- a/dashboard/src/workflow/models.py
+++ b/dashboard/src/workflow/models.py
@@ -240,6 +240,67 @@ class WorkflowStep(object):
return self.repo.put(key, value, self.id)
+"""
+subclassing notes:
+ subclasses have to define the following class attributes:
+ self.select_repo_key: where the selected "object" or "bundle" is to be placed in the repo
+ self.form: the form to be used
+ alert_bundle_missing(): what message to display if a user does not select/selects an invalid object
+ get_form_queryset(): generate a queryset to be used to filter available items for the field
+ get_page_context(): return simple context such as page header and other info
+"""
+
+
+class AbstractSelectOrCreate(WorkflowStep):
+ template = 'dashboard/genericselect.html'
+ title = "Select a Bundle"
+ short_title = "select"
+ description = "Generic bundle selector step"
+
+ select_repo_key = None
+ form = None # subclasses are expected to use a form that is a subclass of SearchableSelectGenericForm
+
+ def alert_bundle_missing(self): # override in subclasses to change message if field isn't filled out
+ self.set_invalid("Please select a valid bundle")
+
+ def post_render(self, request):
+ context = self.get_context()
+ form = self.form(request.POST, queryset=self.get_form_queryset())
+ if form.is_valid():
+ bundle = form.get_validated_bundle()
+ if not bundle:
+ self.alert_bundle_missing()
+ return render(request, self.template, context)
+ self.repo_put(self.select_repo_key, bundle)
+ self.put_confirm_info(bundle)
+ self.set_valid("Step Completed")
+ else:
+ self.alert_bundle_missing()
+ messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
+
+ return self.render(request)
+
+ def get_context(self):
+ default = []
+
+ bundle = self.repo_get(self.select_repo_key, False)
+ if bundle:
+ default.append(bundle)
+
+ form = self.form(queryset=self.get_form_queryset(), initial=default)
+
+ context = {'form': form, **self.get_page_context()}
+ context.update(super().get_context())
+
+ return context
+
+ def get_page_context():
+ return {
+ 'select_type': 'generic',
+ 'select_type_title': 'Generic Bundle'
+ }
+
+
class Confirmation_Step(WorkflowStep):
template = 'workflow/confirm.html'
title = "Confirm Changes"
@@ -335,6 +396,7 @@ class Repository():
self.el[key] = value
def get(self, key, default, id):
+
self.add_get_history(key, id)
return self.el.get(key, default)
@@ -359,6 +421,7 @@ class Repository():
errors = self.make_snapshot()
if errors:
return errors
+
# if GRB WF, create it
if self.GRESOURCE_BUNDLE_MODELS in self.el:
errors = self.make_generic_resource_bundle()
@@ -499,7 +562,7 @@ class Repository():
models = self.el[self.CONFIG_MODELS]
if 'bundle' in models:
bundle = models['bundle']
- bundle.bundle = bundle.bundle
+ bundle.bundle = self.el[self.SELECTED_GRESOURCE_BUNDLE]
try:
bundle.save()
except Exception as e:
@@ -537,15 +600,22 @@ class Repository():
models = self.el[self.BOOKING_MODELS]
owner = self.el[self.SESSION_USER]
+ if 'booking' in models:
+ booking = models['booking']
+ else:
+ return "BOOK, no booking model exists. CODE:0x000f"
+
+ selected_grb = None
+
if self.SELECTED_GRESOURCE_BUNDLE in self.el:
selected_grb = self.el[self.SELECTED_GRESOURCE_BUNDLE]
else:
return "BOOK, no selected resource. CODE:0x000e"
- if 'booking' in models:
- booking = models['booking']
- else:
- return "BOOK, no booking model exists. CODE:0x000f"
+ if self.SELECTED_CONFIG_BUNDLE not in self.el:
+ return "BOOK, no selected config bundle. CODE:0x001f"
+
+ booking.config_bundle = self.el[self.SELECTED_CONFIG_BUNDLE]
if not booking.start:
return "BOOK, booking has no start. CODE:0x0010"
@@ -567,7 +637,6 @@ class Repository():
booking.resource = resource_bundle
booking.owner = owner
- booking.config_bundle = booking.config_bundle
booking.lab = selected_grb.lab
is_allowed = BookingAuthManager().booking_allowed(booking, self)
@@ -600,7 +669,7 @@ class Repository():
def make_opnfv_config(self):
opnfv_models = self.el[self.OPNFV_MODELS]
- config_bundle = opnfv_models['configbundle']
+ config_bundle = self.el[self.SELECTED_CONFIG_BUNDLE]
if not config_bundle:
return "No Configuration bundle selected"
info = opnfv_models.get("meta", {})
diff --git a/dashboard/src/workflow/opnfv_workflow.py b/dashboard/src/workflow/opnfv_workflow.py
index 490d2f0..7d499ec 100644
--- a/dashboard/src/workflow/opnfv_workflow.py
+++ b/dashboard/src/workflow/opnfv_workflow.py
@@ -9,66 +9,39 @@
from django.forms import formset_factory
-from django.contrib import messages
-import json
-
-from workflow.models import WorkflowStep
+from workflow.models import WorkflowStep, AbstractSelectOrCreate
from resource_inventory.models import ConfigBundle, OPNFV_SETTINGS
from workflow.forms import OPNFVSelectionForm, OPNFVNetworkRoleForm, OPNFVHostRoleForm, SWConfigSelectorForm, BasicMetaForm
-class OPNFV_Resource_Select(WorkflowStep):
- template = 'booking/steps/swconfig_select.html'
+class OPNFV_Resource_Select(AbstractSelectOrCreate):
title = "Select Software Configuration"
- description = "Choose the software and related configurations you want to use to configure OPNFV"
- short_title = "software configuration"
- modified_key = "configbundle_step"
+ description = "Choose the software bundle you wish to use as a base for your OPNFV configuration"
+ short_title = "software config"
+ form = SWConfigSelectorForm
- def update_confirmation(self):
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- config_bundle = self.repo_get(self.repo.OPNFV_MODELS, {}).get("configbundle")
- if not config_bundle:
- return
- confirm['software bundle'] = config_bundle.name
- confirm['hardware POD'] = config_bundle.bundle.name
- self.repo_put(self.repo.CONFIRMATION, confirm)
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE
- def post_render(self, request):
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- form = SWConfigSelectorForm(request.POST)
- if form.is_valid():
- bundle_json = form.cleaned_data['software_bundle']
- bundle_json = bundle_json[2:-2] # Stupid django string bug
- if not bundle_json:
- self.set_invalid("Please select a valid config")
- return self.render(request)
- bundle_json = json.loads(bundle_json)
- if len(bundle_json) < 1:
- self.set_invalid("Please select a valid config")
- return self.render(request)
- bundle = None
- id = int(bundle_json[0]['id'])
- bundle = ConfigBundle.objects.get(id=id)
-
- models['configbundle'] = bundle
- self.repo_put(self.repo.OPNFV_MODELS, models)
- self.set_valid("Step Completed")
- messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
- self.update_confirmation()
- else:
- self.set_invalid("Please select or create a valid config")
- messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True)
-
- return self.render(request)
-
- def get_context(self):
- context = super(OPNFV_Resource_Select, self).get_context()
- default = []
+ def get_form_queryset(self):
user = self.repo_get(self.repo.SESSION_USER)
-
- context['form'] = SWConfigSelectorForm(chosen_software=default, bundle=None, edit=True, resource=None, user=user)
- return context
+ qs = ConfigBundle.objects.filter(owner=user)
+ return qs
+
+ def put_confirm_info(self, bundle):
+ confirm_dict = self.repo_get(self.repo.CONFIRMATION)
+ confirm_dict['software bundle'] = bundle.name
+ confirm_dict['hardware POD'] = bundle.bundle.name
+ self.repo_put(self.repo.CONFIRMATION, confirm_dict)
+
+ def get_page_context(self):
+ return {
+ 'select_type': 'swconfig',
+ 'select_type_title': 'Software Config',
+ 'addable_type_num': 2
+ }
class Pick_Installer(WorkflowStep):
@@ -92,7 +65,7 @@ class Pick_Installer(WorkflowStep):
def get_context(self):
context = super(Pick_Installer, self).get_context()
- models = self.repo_get(self.repo.OPNFV_MODELS, None)
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
initial = {
"installer": models.get("installer_chosen"),
"scenario": models.get("scenario_chosen")
@@ -155,7 +128,7 @@ class Assign_Network_Roles(WorkflowStep):
def get_context(self):
context = super(Assign_Network_Roles, self).get_context()
- config_bundle = self.repo_get(self.repo.OPNFV_MODELS, {}).get("configbundle")
+ config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
if config_bundle is None:
context["unavailable"] = True
return context
@@ -179,7 +152,7 @@ class Assign_Network_Roles(WorkflowStep):
def post_render(self, request):
models = self.repo_get(self.repo.OPNFV_MODELS, {})
- config_bundle = models.get("configbundle")
+ config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
roles = OPNFV_SETTINGS.NETWORK_ROLES
net_role_formset = self.create_netformset(roles, config_bundle, data=request.POST)
if net_role_formset.is_valid():
@@ -228,8 +201,7 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in
def get_context(self):
context = super(Assign_Host_Roles, self).get_context()
- models = self.repo_get(self.repo.OPNFV_MODELS, {})
- config = models.get("configbundle")
+ config = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
if config is None:
context['error'] = "Please select a Configuration on the first step"
diff --git a/dashboard/src/workflow/sw_bundle_workflow.py b/dashboard/src/workflow/sw_bundle_workflow.py
index 329b716..0c558fc 100644
--- a/dashboard/src/workflow/sw_bundle_workflow.py
+++ b/dashboard/src/workflow/sw_bundle_workflow.py
@@ -12,25 +12,12 @@ from django.forms import formset_factory
from workflow.models import WorkflowStep
from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
-from workflow.booking_workflow import Resource_Select
+from workflow.booking_workflow import Abstract_Resource_Select
from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration
-# resource selection step is reused from Booking workflow
-class SWConf_Resource_Select(Resource_Select):
- def __init__(self, *args, **kwargs):
- super(SWConf_Resource_Select, self).__init__(*args, **kwargs)
- self.repo_key = self.repo.SELECTED_GRESOURCE_BUNDLE
- self.confirm_key = "configuration"
-
- def post_render(self, request):
- response = super(SWConf_Resource_Select, self).post_render(request)
- models = self.repo_get(self.repo.CONFIG_MODELS, {})
- bundle = models.get("bundle", ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER)))
- bundle.bundle = self.repo_get(self.repo_key) # super put grb here
- models['bundle'] = bundle
- self.repo_put(self.repo.CONFIG_MODELS, models)
- return response
+class SWConf_Resource_Select(Abstract_Resource_Select):
+ workflow_type = "configuration"
class Define_Software(WorkflowStep):
diff --git a/dashboard/src/workflow/urls.py b/dashboard/src/workflow/urls.py
index b131d84..5a97904 100644
--- a/dashboard/src/workflow/urls.py
+++ b/dashboard/src/workflow/urls.py
@@ -14,7 +14,7 @@ from django.conf import settings
from workflow.views import step_view, delete_session, manager_view, viewport_view
from workflow.models import Repository
from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
-from workflow.booking_workflow import SWConfig_Select, Resource_Select, Booking_Meta
+from workflow.booking_workflow import SWConfig_Select, Booking_Resource_Select, Booking_Meta
app_name = 'workflow'
urlpatterns = [
@@ -31,4 +31,4 @@ if settings.TESTING:
urlpatterns.append(url(r'^workflow/step/resource_meta$', Resource_Meta_Info("", Repository()).test_render))
urlpatterns.append(url(r'^workflow/step/booking_meta$', Booking_Meta("", Repository()).test_render))
urlpatterns.append(url(r'^workflow/step/software_select$', SWConfig_Select("", Repository()).test_render))
- urlpatterns.append(url(r'^workflow/step/resource_select$', Resource_Select("", Repository()).test_render))
+ urlpatterns.append(url(r'^workflow/step/resource_select$', Booking_Resource_Select("", Repository()).test_render))
diff --git a/dashboard/src/workflow/workflow_factory.py b/dashboard/src/workflow/workflow_factory.py
index 08cf296..03c8126 100644
--- a/dashboard/src/workflow/workflow_factory.py
+++ b/dashboard/src/workflow/workflow_factory.py
@@ -8,7 +8,7 @@
##############################################################################
-from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, Booking_Meta
+from workflow.booking_workflow import Booking_Resource_Select, SWConfig_Select, Booking_Meta, OPNFV_Select
from workflow.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info
from workflow.sw_bundle_workflow import Config_Software, Define_Software, SWConf_Resource_Select
from workflow.snapshot_workflow import Select_Host_Step, Image_Meta_Step
@@ -76,6 +76,7 @@ class WorkflowFactory():
Booking_Resource_Select,
SWConfig_Select,
Booking_Meta,
+ OPNFV_Select,
]
resource_steps = [