summaryrefslogtreecommitdiffstats
path: root/dashboard/src/workflow
diff options
context:
space:
mode:
Diffstat (limited to 'dashboard/src/workflow')
-rw-r--r--dashboard/src/workflow/booking_workflow.py313
-rw-r--r--dashboard/src/workflow/forms.py541
-rw-r--r--dashboard/src/workflow/models.py457
-rw-r--r--dashboard/src/workflow/opnfv_workflow.py299
-rw-r--r--dashboard/src/workflow/resource_bundle_workflow.py387
-rw-r--r--dashboard/src/workflow/snapshot_workflow.py27
-rw-r--r--dashboard/src/workflow/sw_bundle_workflow.py224
-rw-r--r--dashboard/src/workflow/urls.py4
-rw-r--r--dashboard/src/workflow/views.py43
-rw-r--r--dashboard/src/workflow/workflow_factory.py78
-rw-r--r--dashboard/src/workflow/workflow_manager.py190
11 files changed, 1482 insertions, 1081 deletions
diff --git a/dashboard/src/workflow/booking_workflow.py b/dashboard/src/workflow/booking_workflow.py
index cd12ab6..42372ce 100644
--- a/dashboard/src/workflow/booking_workflow.py
+++ b/dashboard/src/workflow/booking_workflow.py
@@ -8,209 +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_default_entry(self):
- return None
+ def alert_bundle_missing(self):
+ self.set_invalid("Please select a valid resource bundle")
- def get_context(self):
- context = super(Resource_Select, self).get_context()
- default = []
- chosen_bundle = None
- default_bundle = self.get_default_entry()
- if default_bundle:
- context['disabled'] = True
- chosen_bundle = default_bundle
- if chosen_bundle.id:
- default.append(chosen_bundle.id)
- else:
- default.append("repo bundle")
- else:
- chosen_bundle = self.repo_get(self.repo_key, False)
- if chosen_bundle:
- if chosen_bundle.id:
- default.append(chosen_bundle.id)
- else:
- default.append("repo bundle")
-
- bundle = default_bundle
- if not 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
-
- 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.metastep.set_invalid("Please select a valid bundle")
- return render(request, self.template, context)
- selected_bundle = json.loads(data)
- 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.metastep.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.metastep.set_invalid("Please complete the fields highlighted in red to continue")
- return render(request, self.template, context)
+ qs = GenericResourceBundle.objects.filter(owner=user)
+ return qs
+ def get_page_context(self):
+ return {
+ 'select_type': 'resource',
+ 'select_type_title': 'Resource Bundle',
+ 'addable_type_num': 1
+ }
-class Booking_Resource_Select(Resource_Select):
+ 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)
- def __init__(self, *args, **kwargs):
- super(Booking_Resource_Select, self).__init__(*args, **kwargs)
- self.repo_key = self.repo.BOOKING_SELECTED_GRB
- self.confirm_key = "booking"
- def get_default_entry(self):
- default = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle")
- mine = self.repo_get(self.repo_key)
- if mine:
- return None
- try:
- config_bundle = self.repo_get(self.repo.BOOKING_MODELS)['booking'].config_bundle
- if default:
- return default # select created grb, even if preselected config bundle
- return config_bundle.bundle
- except:
- pass
- return default
+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:
- 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.metastep.set_invalid("Please select a valid config")
- return self.render(request)
- bundle_json = json.loads(bundle_json)
- bundle = None
- try:
- id = int(bundle_json[0]['id'])
- bundle = ConfigBundle.objects.get(id=id)
- except ValueError:
- bundle = self.repo_get(self.repo.CONFIG_MODELS).get("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.metastep.set_valid("Step Completed")
- messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True)
- else:
- self.metastep.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.CONFIG_MODELS, {}).get("bundle", False)
- 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:
- if created_bundle:
- default.append("repo bundle")
- bundle = created_bundle
- context['disabled'] = True
- edit = self.repo_get(self.repo.EDIT, False)
- grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB)
- 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):
@@ -235,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, {})
@@ -274,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:
@@ -291,9 +213,8 @@ class Booking_Meta(WorkflowStep):
self.repo_put(self.repo.BOOKING_MODELS, models)
self.repo_put(self.repo.CONFIRMATION, confirm)
messages.add_message(request, messages.SUCCESS, 'Form Validated', fail_silently=True)
- self.metastep.set_valid("Step Completed")
+ self.set_valid("Step Completed")
else:
messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True)
- self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
- context['form'] = form # TODO: store this form
- return render(request, self.template, context)
+ self.set_invalid("Please complete the fields highlighted in red to continue")
+ return self.render(request)
diff --git a/dashboard/src/workflow/forms.py b/dashboard/src/workflow/forms.py
index feb32f2..ee44ecd 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,
- Image,
Installer,
- Scenario
+ 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.initial = attrs.get("initial", [])
- super(SearchableSelectMultipleWidget, self).__init__(attrs)
+ super(SearchableSelectMultipleWidget, self).__init__()
def render(self, name, value, attrs=None, renderer=None):
@@ -59,127 +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
}
-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.
+ """
- 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'])
+ 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
- attrs = self.build_search_widget_attrs(chosen_resource, bundle, edit, queryset)
+ super().__init__(disabled=disabled, **kwargs)
- self.fields['generic_resource_bundle'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(attrs=attrs)
- )
+ self.required = required
- 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
+ def clean(self, data):
+ data = data[0]
+ if not data:
+ if self.required:
+ raise ValidationError("Nothing was selected")
else:
- displayable['expanded_name'] = ""
- displayable['string'] = res.description
- displayable['id'] = res.id
- resources[res.id] = displayable
-
- if bundle:
- displayable = {}
- displayable['small_name'] = bundle.name
- displayable['expanded_name'] = "Current bundle"
- displayable['string'] = bundle.description
- displayable['id'] = "repo bundle"
- resources["repo bundle"] = displayable
- attrs = {
- 'set': resources,
- 'show_from_noentry': "true",
+ 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 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
- 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")
- super(SWConfigSelectorForm, self).__init__(*args, **kwargs)
- attrs = self.build_search_widget_attrs(chosen_software, bundle, edit, resource)
- self.fields['software_bundle'] = forms.CharField(
- widget=SearchableSelectMultipleWidget(attrs=attrs)
- )
+ for bundle in queryset:
+ items[bundle.id] = {
+ 'expanded_name': bundle.name,
+ 'small_name': bundle.owner.username,
+ 'string': bundle.description,
+ 'id': bundle.id
+ }
- def build_search_widget_attrs(self, chosen, bundle, edit, resource):
- configs = {}
- queryset = ConfigBundle.objects.select_related('owner').all()
- if resource:
- queryset = queryset.filter(bundle=resource)
+ return items
+
+
+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
-
- if bundle:
- displayable = {}
- displayable['small_name'] = bundle.name
- displayable['expanded_name'] = "Current configuration"
- displayable['string'] = bundle.description
- displayable['id'] = "repo bundle"
- configs['repo bundle'] = displayable
-
- 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
- }
- return attrs
+ items[config.id] = {
+ 'expanded_name': config.name,
+ 'small_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] = {
+ 'expanded_name': bundle.name,
+ 'small_name': bundle.owner.username,
+ 'string': bundle.description,
+ 'id': bundle.id
+ }
+
+ return items
class BookingMetaForm(forms.Form):
@@ -188,195 +218,126 @@ class BookingMetaForm(forms.Form):
widget=NumberInput(
attrs={
"type": "range",
- 'min': "0",
+ 'min': "1",
"max": "21",
- "value": "0"
+ "value": "1"
}
)
)
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
+ def __init__(self, *args, user_initial=[], owner=None, **kwargs):
+ super(BookingMetaForm, self).__init__(**kwargs)
- 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
+ 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()
)
- 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"):
-
- 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
-
class MultipleSelectFilterWidget(forms.Widget):
- def __init__(self, attrs=None):
- super(MultipleSelectFilterWidget, self).__init__(attrs)
- self.attrs = attrs
+ def __init__(self, *args, display_objects=None, filter_items=None, neighbors=None, **kwargs):
+ super(MultipleSelectFilterWidget, self).__init__(*args, **kwargs)
+ self.display_objects = display_objects
+ self.filter_items = filter_items
+ self.neighbors = neighbors
self.template_name = "dashboard/multiple_select_filter_widget.html"
def render(self, name, value, attrs=None, renderer=None):
- attrs = self.attrs
- self.context = self.get_context(name, value, attrs)
- html = render_to_string(self.template_name, context=self.context)
+ context = self.get_context(name, value, attrs)
+ html = render_to_string(self.template_name, context=context)
return mark_safe(html)
def get_context(self, name, value, attrs):
- return attrs
+ return {
+ 'display_objects': self.display_objects,
+ 'neighbors': self.neighbors,
+ 'filter_items': self.filter_items,
+ 'initial_value': value
+ }
class MultipleSelectFilterField(forms.Field):
- def __init__(self, required=True, widget=None, label=None, initial=None,
- help_text='', error_messages=None, show_hidden_initial=False,
- validators=(), localize=False, disabled=False, label_suffix=None):
- """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.
- """
- # this is bad, but django forms are annoying
- self.widget = widget
- if self.widget is None:
- self.widget = MultipleSelectFilterWidget()
- super(MultipleSelectFilterField, self).__init__(
- required=required,
- widget=self.widget,
- label=label,
- initial=None,
- help_text=help_text,
- error_messages=error_messages,
- show_hidden_initial=show_hidden_initial,
- validators=validators,
- localize=localize,
- disabled=disabled,
- label_suffix=label_suffix
- )
+ def __init__(self, **kwargs):
+ self.initial = kwargs.get("initial")
+ super().__init__(**kwargs)
- def clean(data):
- """
- This method will raise a django.forms.ValidationError or return clean data
- """
- return data
+ def to_python(self, value):
+ return json.loads(value)
class FormUtils:
@staticmethod
- def getLabData():
+ def getLabData(multiple_hosts=False):
"""
Gets all labs and thier host profiles and returns a serialized version the form can understand.
Should be rewritten with a related query to make it faster
- Should be moved outside of global scope
"""
+ # javascript truthy variables
+ true = 1
+ false = 0
+ if multiple_hosts:
+ multiple_hosts = true
+ else:
+ multiple_hosts = false
labs = {}
hosts = {}
items = {}
- mapping = {}
+ neighbors = {}
for lab in Lab.objects.all():
- slab = {}
- slab['id'] = "lab_" + str(lab.lab_user.id)
- slab['name'] = lab.name
- slab['description'] = lab.description
- slab['selected'] = 0
- slab['selectable'] = 1
- slab['follow'] = 1
- slab['multiple'] = 0
- items[slab['id']] = slab
- mapping[slab['id']] = []
- labs[slab['id']] = slab
+ lab_node = {
+ 'id': "lab_" + str(lab.lab_user.id),
+ 'model_id': lab.lab_user.id,
+ 'name': lab.name,
+ 'description': lab.description,
+ 'selected': false,
+ 'selectable': true,
+ 'follow': false,
+ 'multiple': false,
+ 'class': 'lab'
+ }
+ if multiple_hosts:
+ # "follow" this lab node to discover more hosts if allowed
+ lab_node['follow'] = true
+ items[lab_node['id']] = lab_node
+ neighbors[lab_node['id']] = []
+ labs[lab_node['id']] = lab_node
+
for host in lab.hostprofiles.all():
- shost = {}
- shost['forms'] = [{"name": "host_name", "type": "text", "placeholder": "hostname"}]
- shost['id'] = "host_" + str(host.id)
- shost['name'] = host.name
- shost['description'] = host.description
- shost['selected'] = 0
- shost['selectable'] = 1
- shost['follow'] = 0
- shost['multiple'] = 1
- items[shost['id']] = shost
- mapping[slab['id']].append(shost['id'])
- if shost['id'] not in mapping:
- mapping[shost['id']] = []
- mapping[shost['id']].append(slab['id'])
- hosts[shost['id']] = shost
-
- filter_objects = [("labs", labs.values()), ("hosts", hosts.values())]
+ host_node = {
+ 'form': {"name": "host_name", "type": "text", "placeholder": "hostname"},
+ 'id': "host_" + str(host.id),
+ 'model_id': host.id,
+ 'name': host.name,
+ 'description': host.description,
+ 'selected': false,
+ 'selectable': true,
+ 'follow': false,
+ 'multiple': multiple_hosts,
+ 'class': 'host'
+ }
+ if multiple_hosts:
+ host_node['values'] = [] # place to store multiple values
+ items[host_node['id']] = host_node
+ neighbors[lab_node['id']].append(host_node['id'])
+ if host_node['id'] not in neighbors:
+ neighbors[host_node['id']] = []
+ neighbors[host_node['id']].append(lab_node['id'])
+ hosts[host_node['id']] = host_node
+
+ display_objects = [("lab", labs.values()), ("host", hosts.values())]
context = {
- 'filter_objects': filter_objects,
- 'mapping': mapping,
- 'items': items
+ 'display_objects': display_objects,
+ 'neighbors': neighbors,
+ 'filter_items': items
}
return context
@@ -384,14 +345,10 @@ class FormUtils:
class HardwareDefinitionForm(forms.Form):
def __init__(self, *args, **kwargs):
- selection_data = kwargs.pop("selection_data", False)
super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
- attrs = FormUtils.getLabData()
- attrs['selection_data'] = selection_data
+ attrs = FormUtils.getLabData(multiple_hosts=True)
self.fields['filter_field'] = MultipleSelectFilterField(
- widget=MultipleSelectFilterWidget(
- attrs=attrs
- )
+ widget=MultipleSelectFilterWidget(**attrs)
)
@@ -424,20 +381,14 @@ class NetworkConfigurationForm(forms.Form):
class HostSoftwareDefinitionForm(forms.Form):
- fields = ["host_name", "role", "image"]
host_name = forms.CharField(max_length=200, disabled=True, required=False)
- role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all())
- image = forms.ModelChoiceField(queryset=Image.objects.all())
+ headnode = forms.BooleanField(required=False, widget=forms.HiddenInput)
-
-class SoftwareConfigurationForm(forms.Form):
-
- name = forms.CharField(max_length=200)
- description = forms.CharField(widget=forms.Textarea)
- opnfv = forms.BooleanField(disabled=True, required=False)
- installer = forms.ModelChoiceField(queryset=Installer.objects.all(), disabled=True, required=False)
- scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), disabled=True, required=False)
+ def __init__(self, *args, **kwargs):
+ imageQS = kwargs.pop("imageQS")
+ super(HostSoftwareDefinitionForm, self).__init__(*args, **kwargs)
+ self.fields['image'] = forms.ModelChoiceField(queryset=imageQS)
class WorkflowSelectionForm(forms.Form):
@@ -461,9 +412,9 @@ class SnapshotHostSelectForm(forms.Form):
host = forms.CharField()
-class SnapshotMetaForm(forms.Form):
+class BasicMetaForm(forms.Form):
name = forms.CharField()
- description = forms.CharField()
+ description = forms.CharField(widget=forms.Textarea)
class ConfirmationForm(forms.Form):
@@ -475,3 +426,23 @@ class ConfirmationForm(forms.Form):
(False, "Cancel")
)
)
+
+
+class OPNFVSelectionForm(forms.Form):
+ installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=True)
+ scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=True)
+
+
+class OPNFVNetworkRoleForm(forms.Form):
+ role = forms.CharField(max_length=200, disabled=True, required=False)
+
+ def __init__(self, *args, config_bundle, **kwargs):
+ super(OPNFVNetworkRoleForm, self).__init__(*args, **kwargs)
+ self.fields['network'] = forms.ModelChoiceField(
+ queryset=config_bundle.bundle.networks.all()
+ )
+
+
+class OPNFVHostRoleForm(forms.Form):
+ host_name = forms.CharField(max_length=200, disabled=True, required=False)
+ role = forms.ModelChoiceField(queryset=OPNFVRole.objects.all().order_by("name").distinct("name"))
diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py
index 966582c..6c6bd9a 100644
--- a/dashboard/src/workflow/models.py
+++ b/dashboard/src/workflow/models.py
@@ -10,6 +10,8 @@
from django.shortcuts import render
from django.contrib import messages
+from django.http import HttpResponse
+from django.utils import timezone
import yaml
import requests
@@ -17,8 +19,9 @@ import requests
from workflow.forms import ConfirmationForm
from api.models import JobFactory
from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
-from resource_inventory.models import Image, GenericInterface
+from resource_inventory.models import Image, GenericInterface, OPNFVConfig, HostOPNFVConfig, NetworkRole
from resource_inventory.resource_manager import ResourceManager
+from resource_inventory.pdf_templater import PDFTemplater
from notifier.manager import NotificationHandler
from booking.models import Booking
@@ -26,13 +29,11 @@ from booking.models import Booking
class BookingAuthManager():
LFN_PROJECTS = ["opnfv"] # TODO
- def parse_url(self, info_url):
- """
- will return the PTL in the INFO file on success, or None
- """
+ def parse_github_url(self, url):
+ project_leads = []
try:
- parts = info_url.split("/")
- if parts[0].find("http") > -1: # the url include http(s)://
+ parts = url.split("/")
+ if "http" in parts[0]: # the url include http(s)://
parts = parts[2:]
if parts[-1] != "INFO.yaml":
return None
@@ -47,13 +48,94 @@ class BookingAuthManager():
info_file = requests.get(url, timeout=15).text
info_parsed = yaml.load(info_file)
ptl = info_parsed.get('project_lead')
- if not ptl:
+ if ptl:
+ project_leads.append(ptl)
+ sub_ptl = info_parsed.get("subproject_lead")
+ if sub_ptl:
+ project_leads.append(sub_ptl)
+
+ except Exception:
+ pass
+
+ return project_leads
+
+ def parse_gerrit_url(self, url):
+ project_leads = []
+ try:
+ halfs = url.split("?")
+ parts = halfs[0].split("/")
+ args = halfs[1].split(";")
+ if "http" in parts[0]: # the url include http(s)://
+ parts = parts[2:]
+ if "f=INFO.yaml" not in args:
+ return None
+ if "gerrit.opnfv.org" not in parts[0]:
return None
- return ptl
+ try:
+ i = args.index("a=blob")
+ args[i] = "a=blob_plain"
+ except ValueError:
+ pass
+ # recreate url
+ halfs[1] = ";".join(args)
+ halfs[0] = "/".join(parts)
+ # now to download and parse file
+ url = "https://" + "?".join(halfs)
+ info_file = requests.get(url, timeout=15).text
+ info_parsed = yaml.load(info_file)
+ ptl = info_parsed.get('project_lead')
+ if ptl:
+ project_leads.append(ptl)
+ sub_ptl = info_parsed.get("subproject_lead")
+ if sub_ptl:
+ project_leads.append(sub_ptl)
except Exception:
return None
+ return project_leads
+
+ def parse_opnfv_git_url(self, url):
+ project_leads = []
+ try:
+ parts = url.split("/")
+ if "http" in parts[0]: # the url include http(s)://
+ parts = parts[2:]
+ if "INFO.yaml" not in parts[-1]:
+ return None
+ if "git.opnfv.org" not in parts[0]:
+ return None
+ if parts[-2] == "tree":
+ parts[-2] = "plain"
+ # now to download and parse file
+ url = "https://" + "/".join(parts)
+ info_file = requests.get(url, timeout=15).text
+ info_parsed = yaml.load(info_file)
+ ptl = info_parsed.get('project_lead')
+ if ptl:
+ project_leads.append(ptl)
+ sub_ptl = info_parsed.get("subproject_lead")
+ if sub_ptl:
+ project_leads.append(sub_ptl)
+
+ except Exception:
+ return None
+
+ return project_leads
+
+ def parse_url(self, info_url):
+ """
+ will return the PTL in the INFO file on success, or None
+ """
+ if "github" in info_url:
+ return self.parse_github_url(info_url)
+
+ if "gerrit.opnfv.org" in info_url:
+ return self.parse_gerrit_url(info_url)
+
+ if "git.opnfv.org" in info_url:
+ return self.parse_opnfv_git_url(info_url)
+
def booking_allowed(self, booking, repo):
"""
This is the method that will have to change whenever the booking policy changes in the Infra
@@ -61,23 +143,67 @@ class BookingAuthManager():
currently checks if the booking uses multiple servers. if it does, then the owner must be a PTL,
which is checked using the provided info file
"""
- if len(booking.resource.template.getHosts()) < 2:
- return True # if they only have one server, we dont care
if booking.owner.userprofile.booking_privledge:
return True # admin override for this user
+ if Booking.objects.filter(owner=booking.owner, end__gt=timezone.now()).count() >= 3:
+ return False
+ if len(booking.resource.template.getHosts()) < 2:
+ return True # if they only have one server, we dont care
if repo.BOOKING_INFO_FILE not in repo.el:
return False # INFO file not provided
- ptl_info = self.parse_url(repo.BOOKING_INFO_FILE)
- return ptl_info and ptl_info == booking.owner.userprofile.email_addr
+ ptl_info = self.parse_url(repo.el.get(repo.BOOKING_INFO_FILE))
+ for ptl in ptl_info:
+ if ptl['email'] == booking.owner.userprofile.email_addr:
+ return True
+ return False
-class WorkflowStep(object):
+class WorkflowStepStatus(object):
+ UNTOUCHED = 0
+ INVALID = 100
+ VALID = 200
+
+class WorkflowStep(object):
template = 'bad_request.html'
title = "Generic Step"
description = "You were led here by mistake"
short_title = "error"
metastep = None
+ # phasing out metastep:
+
+ valid = WorkflowStepStatus.UNTOUCHED
+ message = ""
+
+ enabled = True
+
+ def cleanup(self):
+ raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete implemented cleanup() method")
+
+ def enable(self):
+ if not self.enabled:
+ self.enabled = True
+
+ def disable(self):
+ if self.enabled:
+ self.cleanup()
+ self.enabled = False
+
+ def set_invalid(self, message, code=WorkflowStepStatus.INVALID):
+ self.valid = code
+ self.message = message
+
+ def set_valid(self, message, code=WorkflowStepStatus.VALID):
+ self.valid = code
+ self.message = message
+
+ def to_json(self):
+ return {
+ 'title': self.short_title,
+ 'enabled': self.enabled,
+ 'valid': self.valid,
+ 'message': self.message,
+ }
def __init__(self, id, repo=None):
self.repo = repo
@@ -114,29 +240,73 @@ 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"
description = "Does this all look right?"
- def get_vlan_warning(self):
- grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
- if not grb:
- return 0
- vlan_manager = grb.lab.vlan_manager
- if vlan_manager is None:
- return 0
- hosts = grb.getHosts()
- for host in hosts:
- for interface in host.generic_interfaces.all():
- for vlan in interface.vlans.all():
- if vlan.public:
- if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
- return 1
- else:
- if not vlan_manager.is_available(vlan.vlan_id):
- return 1 # There is a problem with these vlans
- return 0
+ short_title = "confirm"
def get_context(self):
context = super(Confirmation_Step, self).get_context()
@@ -145,7 +315,6 @@ class Confirmation_Step(WorkflowStep):
self.repo_get(self.repo.CONFIRMATION),
default_flow_style=False
).strip()
- context['vlan_warning'] = self.get_vlan_warning()
return context
@@ -164,9 +333,10 @@ class Confirmation_Step(WorkflowStep):
errors = self.flush_to_db()
if errors:
messages.add_message(request, messages.ERROR, "ERROR OCCURRED: " + errors)
- return render(request, self.template, context)
- messages.add_message(request, messages.SUCCESS, "Confirmed")
- return render(request, self.template, context)
+ else:
+ messages.add_message(request, messages.SUCCESS, "Confirmed")
+
+ return HttpResponse('')
elif data == "False":
context["bypassed"] = "true"
messages.add_message(request, messages.SUCCESS, "Canceled")
@@ -175,39 +345,8 @@ class Confirmation_Step(WorkflowStep):
pass
else:
- if "vlan_input" in request.POST:
- if request.POST.get("vlan_input") == "True":
- self.translate_vlans()
- return self.render(request)
pass
- def translate_vlans(self):
- grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB, False)
- if not grb:
- return 0
- vlan_manager = grb.lab.vlan_manager
- if vlan_manager is None:
- return 0
- hosts = grb.getHosts()
- for host in hosts:
- for interface in host.generic_interfaces.all():
- for vlan in interface.vlans.all():
- if not vlan.public:
- if not vlan_manager.is_available(vlan.vlan_id):
- vlan.vlan_id = vlan_manager.get_vlan()
- vlan.save()
- else:
- if not vlan_manager.public_vlan_is_available(vlan.vlan_id):
- pub_vlan = vlan_manager.get_public_vlan()
- vlan.vlan_id = pub_vlan.vlan
- vlan.save()
-
-
-class Workflow():
-
- steps = []
- active_index = 0
-
class Repository():
@@ -216,6 +355,8 @@ class Repository():
RESOURCE_SELECT = "resource_select"
CONFIRMATION = "confirmation"
SELECTED_GRESOURCE_BUNDLE = "selected generic bundle pk"
+ SELECTED_CONFIG_BUNDLE = "selected config bundle pk"
+ SELECTED_OPNFV_CONFIG = "selected opnfv deployment config"
GRESOURCE_BUNDLE_MODELS = "generic_resource_bundle_models"
GRESOURCE_BUNDLE_INFO = "generic_resource_bundle_info"
BOOKING = "booking"
@@ -223,11 +364,11 @@ class Repository():
GRB_LAST_HOSTLIST = "grb_network_previous_hostlist"
BOOKING_FORMS = "booking_forms"
SWCONF_HOSTS = "swconf_hosts"
- SWCONF_SELECTED_GRB = "swconf_selected_grb_pk"
- BOOKING_SELECTED_GRB = "booking_selected_grb_pk"
BOOKING_MODELS = "booking models"
CONFIG_MODELS = "configuration bundle models"
+ OPNFV_MODELS = "opnfv configuration models"
SESSION_USER = "session owner user account"
+ SESSION_MANAGER = "session manager for current session"
VALIDATED_MODEL_GRB = "valid grb config model instance in db"
VALIDATED_MODEL_CONFIG = "valid config model instance in db"
VALIDATED_MODEL_BOOKING = "valid booking model instance in db"
@@ -238,7 +379,24 @@ class Repository():
SNAPSHOT_DESC = "description of the snapshot"
BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
+ # migratory elements of segmented workflow
+ # each of these is the end result of a different workflow.
+ HAS_RESULT = "whether or not workflow has a result"
+ RESULT_KEY = "key for target index that result will be put into in parent"
+ RESULT = "result object from workflow"
+
+ def get_child_defaults(self):
+ return_tuples = []
+ for key in [self.SELECTED_GRESOURCE_BUNDLE, self.SESSION_USER]:
+ return_tuples.append((key, self.el.get(key)))
+ return return_tuples
+
+ def set_defaults(self, defaults):
+ for key, value in defaults:
+ self.el[key] = value
+
def get(self, key, default, id):
+
self.add_get_history(key, id)
return self.el.get(key, default)
@@ -263,16 +421,33 @@ 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()
if errors:
return errors
+ else:
+ self.el[self.HAS_RESULT] = True
+ self.el[self.RESULT_KEY] = self.SELECTED_GRESOURCE_BUNDLE
+ return
if self.CONFIG_MODELS in self.el:
errors = self.make_software_config_bundle()
if errors:
return errors
+ else:
+ self.el[self.HAS_RESULT] = True
+ self.el[self.RESULT_KEY] = self.SELECTED_CONFIG_BUNDLE
+ return
+
+ if self.OPNFV_MODELS in self.el:
+ errors = self.make_opnfv_config()
+ if errors:
+ return errors
+ else:
+ self.el[self.HAS_RESULT] = True
+ self.el[self.RESULT_KEY] = self.SELECTED_OPNFV_CONFIG
if self.BOOKING_MODELS in self.el:
errors = self.make_booking()
@@ -290,6 +465,8 @@ class Repository():
if not booking_id:
return "SNAP, No booking ID provided"
booking = Booking.objects.get(pk=booking_id)
+ if booking.start > timezone.now() or booking.end < timezone.now():
+ return "Booking is not active"
name = self.el.get(self.SNAPSHOT_NAME)
if not name:
return "SNAP, no name provided"
@@ -305,6 +482,16 @@ class Repository():
image.owner = owner
image.host_type = host.profile
image.save()
+ try:
+ current_image = host.config.image
+ image.os = current_image.os
+ image.save()
+ except Exception:
+ pass
+ JobFactory.makeSnapshotTask(image, booking, host)
+
+ self.el[self.RESULT] = image
+ self.el[self.HAS_RESULT] = True
def make_generic_resource_bundle(self):
owner = self.el[self.SESSION_USER]
@@ -334,45 +521,52 @@ class Repository():
except Exception as e:
return "GRB, saving hosts generated exception: " + str(e) + " CODE:0x0005"
+ if 'networks' in models:
+ for net in models['networks'].values():
+ net.bundle = bundle
+ net.save()
+
if 'interfaces' in models:
for interface_set in models['interfaces'].values():
for interface in interface_set:
try:
interface.host = interface.host
interface.save()
- except Exception as e:
+ except Exception:
return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
else:
return "GRB, no interface set provided. CODE:0x001a"
- if 'vlans' in models:
- for resource_name, mapping in models['vlans'].items():
- for profile_name, vlan_set in mapping.items():
+ if 'connections' in models:
+ for resource_name, mapping in models['connections'].items():
+ for profile_name, connection_set in mapping.items():
interface = GenericInterface.objects.get(
profile__name=profile_name,
host__resource__name=resource_name,
host__resource__bundle=models['bundle']
)
- for vlan in vlan_set:
+ for connection in connection_set:
try:
- vlan.save()
- interface.vlans.add(vlan)
+ connection.network = connection.network
+ connection.save()
+ interface.connections.add(connection)
except Exception as e:
- return "GRB, saving vlan " + str(vlan) + " failed. Exception: " + str(e) + ". CODE:0x0017"
+ return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017"
else:
return "GRB, no vlan set provided. CODE:0x0018"
else:
return "GRB no models given. CODE:0x0001"
- self.el[self.VALIDATED_MODEL_GRB] = bundle
+ self.el[self.RESULT] = bundle
+ self.el[self.HAS_RESULT] = True
return False
def make_software_config_bundle(self):
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:
@@ -403,26 +597,30 @@ class Repository():
else:
pass
- self.el[self.VALIDATED_MODEL_CONFIG] = bundle
+ self.el[self.RESULT] = bundle
return False
def make_booking(self):
models = self.el[self.BOOKING_MODELS]
owner = self.el[self.SESSION_USER]
- if self.BOOKING_SELECTED_GRB in self.el:
- selected_grb = self.el[self.BOOKING_SELECTED_GRB]
- else:
- return "BOOK, no selected resource. CODE:0x000e"
-
- if not self.reserve_vlans(selected_grb):
- return "BOOK, vlans not available"
-
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 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"
if not booking.end:
@@ -443,7 +641,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)
@@ -459,6 +656,12 @@ class Repository():
booking.collaborators.add(collaborator)
try:
+ booking.pdf = PDFTemplater.makePDF(booking)
+ booking.save()
+ except Exception as e:
+ return "BOOK, failed to create Pod Desriptor File: " + str(e)
+
+ try:
JobFactory.makeCompleteJob(booking)
except Exception as e:
return "BOOK, serializing for api generated exception: " + str(e) + " CODE:0xFFFF"
@@ -468,32 +671,62 @@ class Repository():
except Exception as e:
return "BOOK, saving booking generated exception: " + str(e) + " CODE:0x0016"
- def reserve_vlans(self, grb):
- """
- True is success
- """
- vlans = []
- public_vlan = None
- vlan_manager = grb.lab.vlan_manager
- if vlan_manager is None:
- return True
- for host in grb.getHosts():
- for interface in host.generic_interfaces.all():
- for vlan in interface.vlans.all():
- if vlan.public:
- public_vlan = vlan
- else:
- vlans.append(vlan.vlan_id)
-
- try:
- vlan_manager.reserve_vlans(vlans)
- vlan_manager.reserve_public_vlan(public_vlan.vlan_id)
- return True
- except Exception:
- return False
+ self.el[self.RESULT] = booking
+ self.el[self.HAS_RESULT] = True
+
+ def make_opnfv_config(self):
+ opnfv_models = self.el[self.OPNFV_MODELS]
+ config_bundle = self.el[self.SELECTED_CONFIG_BUNDLE]
+ if not config_bundle:
+ return "No Configuration bundle selected"
+ info = opnfv_models.get("meta", {})
+ name = info.get("name", False)
+ desc = info.get("description", False)
+ if not (name and desc):
+ return "No name or description given"
+ installer = opnfv_models['installer_chosen']
+ if not installer:
+ return "No OPNFV Installer chosen"
+ scenario = opnfv_models['scenario_chosen']
+ if not scenario:
+ return "No OPNFV Scenario chosen"
+
+ opnfv_config = OPNFVConfig.objects.create(
+ bundle=config_bundle,
+ name=name,
+ description=desc,
+ installer=installer,
+ scenario=scenario
+ )
+
+ network_roles = opnfv_models['network_roles']
+ for net_role in network_roles:
+ opnfv_config.networks.add(
+ NetworkRole.objects.create(
+ name=net_role['role'],
+ network=net_role['network']
+ )
+ )
+
+ host_roles = opnfv_models['host_roles']
+ for host_role in host_roles:
+ config = config_bundle.hostConfigurations.get(
+ host__resource__name=host_role['host_name']
+ )
+ HostOPNFVConfig.objects.create(
+ role=host_role['role'],
+ host_config=config,
+ opnfv_config=opnfv_config
+ )
+
+ self.el[self.RESULT] = opnfv_config
+ self.el[self.HAS_RESULT] = True
def __init__(self):
self.el = {}
self.el[self.CONFIRMATION] = {}
+ self.el["active_step"] = 0
+ self.el[self.HAS_RESULT] = False
+ self.el[self.RESULT] = None
self.get_history = {}
self.put_history = {}
diff --git a/dashboard/src/workflow/opnfv_workflow.py b/dashboard/src/workflow/opnfv_workflow.py
new file mode 100644
index 0000000..7d499ec
--- /dev/null
+++ b/dashboard/src/workflow/opnfv_workflow.py
@@ -0,0 +1,299 @@
+##############################################################################
+# Copyright (c) 2018 Parker Berberian, Sawyer Bergeron, and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+
+from django.forms import formset_factory
+
+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(AbstractSelectOrCreate):
+ title = "Select Software Configuration"
+ description = "Choose the software bundle you wish to use as a base for your OPNFV configuration"
+ short_title = "software config"
+ form = SWConfigSelectorForm
+
+ def __init__(self, *args, **kwargs):
+ super().__init__(*args, **kwargs)
+ self.select_repo_key = self.repo.SELECTED_CONFIG_BUNDLE
+
+ def get_form_queryset(self):
+ user = self.repo_get(self.repo.SESSION_USER)
+ 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):
+ template = 'config_bundle/steps/pick_installer.html'
+ title = 'Pick OPNFV Installer'
+ description = 'Choose which OPNFV installer to use'
+ short_title = "opnfv installer"
+ modified_key = "installer_step"
+
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ installer = models.get("installer_chosen")
+ scenario = models.get("scenario_chosen")
+ if not (installer and scenario):
+ return
+ confirm['installer'] = installer.name
+ confirm['scenario'] = scenario.name
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
+ def get_context(self):
+ context = super(Pick_Installer, self).get_context()
+
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ initial = {
+ "installer": models.get("installer_chosen"),
+ "scenario": models.get("scenario_chosen")
+ }
+
+ context["form"] = OPNFVSelectionForm(initial=initial)
+ return context
+
+ def post_render(self, request):
+ form = OPNFVSelectionForm(request.POST)
+ if form.is_valid():
+ installer = form.cleaned_data['installer']
+ scenario = form.cleaned_data['scenario']
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ models['installer_chosen'] = installer
+ models['scenario_chosen'] = scenario
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+ self.update_confirmation()
+ self.set_valid("Step Completed")
+ else:
+ self.set_invalid("Please select an Installer and Scenario")
+
+ return self.render(request)
+
+
+class Assign_Network_Roles(WorkflowStep):
+ template = 'config_bundle/steps/assign_network_roles.html'
+ title = 'Pick Network Roles'
+ description = 'Choose what role each network should get'
+ short_title = "network roles"
+ modified_key = "net_roles_step"
+
+ """
+ to do initial filling, repo should have a "network_roles" array with the following structure for each element:
+ {
+ "role": <NetworkRole object ref>,
+ "network": <Network object ref>
+ }
+ """
+ def create_netformset(self, roles, config_bundle, data=None):
+ roles_initial = []
+ set_roles = self.repo_get(self.repo.OPNFV_MODELS, {}).get("network_roles")
+ if set_roles:
+ roles_initial = set_roles
+ else:
+ for role in OPNFV_SETTINGS.NETWORK_ROLES:
+ roles_initial.append({"role": role})
+
+ Formset = formset_factory(OPNFVNetworkRoleForm, extra=0)
+ kwargs = {
+ "initial": roles_initial,
+ "form_kwargs": {"config_bundle": config_bundle}
+ }
+ formset = None
+ if data:
+ formset = Formset(data, **kwargs)
+ else:
+ formset = Formset(**kwargs)
+ return formset
+
+ def get_context(self):
+ context = super(Assign_Network_Roles, self).get_context()
+ config_bundle = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
+ if config_bundle is None:
+ context["unavailable"] = True
+ return context
+
+ roles = OPNFV_SETTINGS.NETWORK_ROLES
+ formset = self.create_netformset(roles, config_bundle)
+ context['formset'] = formset
+
+ return context
+
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ roles = models.get("network_roles")
+ if not roles:
+ return
+ confirm['network roles'] = {}
+ for role in roles:
+ confirm['network roles'][role['role']] = role['network'].name
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
+ def post_render(self, request):
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ 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():
+ results = []
+ for form in net_role_formset:
+ results.append({
+ "role": form.cleaned_data['role'],
+ "network": form.cleaned_data['network']
+ })
+ models['network_roles'] = results
+ self.set_valid("Completed")
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+ self.update_confirmation()
+ else:
+ self.set_invalid("Please complete all fields")
+ return self.render(request)
+
+
+class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in sw workflow, merge the two?
+ template = 'config_bundle/steps/assign_host_roles.html'
+ title = 'Pick Host Roles'
+ description = "Choose the role each machine will have in your OPNFV pod"
+ short_title = "host roles"
+ modified_key = "host_roles_step"
+
+ def create_host_role_formset(self, hostlist=[], data=None):
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ host_roles = models.get("host_roles", [])
+ if not host_roles:
+ for host in hostlist:
+ initial = {"host_name": host.resource.name}
+ host_roles.append(initial)
+ models['host_roles'] = host_roles
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+
+ HostFormset = formset_factory(OPNFVHostRoleForm, extra=0)
+
+ kwargs = {"initial": host_roles}
+ formset = None
+ if data:
+ formset = HostFormset(data, **kwargs)
+ else:
+ formset = HostFormset(**kwargs)
+
+ return formset
+
+ def get_context(self):
+ context = super(Assign_Host_Roles, self).get_context()
+ config = self.repo_get(self.repo.SELECTED_CONFIG_BUNDLE)
+ if config is None:
+ context['error'] = "Please select a Configuration on the first step"
+
+ formset = self.create_host_role_formset(hostlist=config.bundle.getHosts())
+ context['formset'] = formset
+
+ return context
+
+ def get_host_role_mapping(self, host_roles, hostname):
+ for obj in host_roles:
+ if hostname == obj['host_name']:
+ return obj
+ return None
+
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ roles = models.get("host_roles")
+ if not roles:
+ return
+ confirm['host roles'] = {}
+ for role in roles:
+ confirm['host roles'][role['host_name']] = role['role'].name
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
+ def post_render(self, request):
+ formset = self.create_host_role_formset(data=request.POST)
+
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ host_roles = models.get("host_roles", [])
+
+ has_jumphost = False
+ if formset.is_valid():
+ for form in formset:
+ hostname = form.cleaned_data['host_name']
+ role = form.cleaned_data['role']
+ mapping = self.get_host_role_mapping(host_roles, hostname)
+ mapping['role'] = role
+ if "jumphost" in role.name.lower():
+ has_jumphost = True
+
+ models['host_roles'] = host_roles
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+ self.update_confirmation()
+
+ if not has_jumphost:
+ self.set_invalid('Must have at least one "Jumphost" per POD')
+ else:
+ self.set_valid("Completed")
+ else:
+ self.set_invalid("Please complete all fields")
+
+ return self.render(request)
+
+
+class MetaInfo(WorkflowStep):
+ template = 'config_bundle/steps/config_software.html'
+ title = "Other Info"
+ description = "Give your software config a name, description, and other stuff"
+ short_title = "config info"
+
+ def get_context(self):
+ context = super(MetaInfo, self).get_context()
+
+ initial = self.repo_get(self.repo.OPNFV_MODELS, {}).get("meta", {})
+ context["form"] = BasicMetaForm(initial=initial)
+ return context
+
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ meta = models.get("meta")
+ if not meta:
+ return
+ confirm['name'] = meta['name']
+ confirm['description'] = meta['description']
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
+ def post_render(self, request):
+ models = self.repo_get(self.repo.OPNFV_MODELS, {})
+ info = models.get("meta", {})
+
+ form = BasicMetaForm(request.POST)
+ if form.is_valid():
+ info['name'] = form.cleaned_data['name']
+ info['description'] = form.cleaned_data['description']
+ models['meta'] = info
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+ self.update_confirmation()
+ self.set_valid("Complete")
+ else:
+ self.set_invalid("Please correct the errors shown below")
+
+ self.repo_put(self.repo.OPNFV_MODELS, models)
+ return self.render(request)
diff --git a/dashboard/src/workflow/resource_bundle_workflow.py b/dashboard/src/workflow/resource_bundle_workflow.py
index 712c92b..06737d2 100644
--- a/dashboard/src/workflow/resource_bundle_workflow.py
+++ b/dashboard/src/workflow/resource_bundle_workflow.py
@@ -10,6 +10,7 @@
from django.shortcuts import render
from django.forms import formset_factory
+from django.conf import settings
import json
import re
@@ -25,11 +26,12 @@ from workflow.forms import (
)
from resource_inventory.models import (
GenericResourceBundle,
- Vlan,
GenericInterface,
GenericHost,
GenericResource,
- HostProfile
+ HostProfile,
+ Network,
+ NetworkConnection
)
from dashboard.exceptions import (
InvalidVlanConfigurationException,
@@ -50,65 +52,47 @@ class Define_Hardware(WorkflowStep):
description = "Choose the type and amount of machines you want"
short_title = "hosts"
+ def __init__(self, *args, **kwargs):
+ self.form = None
+ super().__init__(*args, **kwargs)
+
def get_context(self):
context = super(Define_Hardware, self).get_context()
- selection_data = {"hosts": {}, "labs": {}}
- models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- hosts = models.get("hosts", [])
- for host in hosts:
- profile_id = "host_" + str(host.profile.id)
- if profile_id not in selection_data['hosts']:
- selection_data['hosts'][profile_id] = []
- selection_data['hosts'][profile_id].append({"host_name": host.resource.name, "class": profile_id})
-
- if models.get("bundle", GenericResourceBundle()).lab:
- selection_data['labs'] = {"lab_" + str(models.get("bundle").lab.lab_user.id): "true"}
-
- form = HardwareDefinitionForm(
- selection_data=selection_data
- )
- context['form'] = form
+ context['form'] = self.form or HardwareDefinitionForm()
return context
- def render(self, request):
- self.context = self.get_context()
- return render(request, self.template, self.context)
-
def update_models(self, data):
- data = json.loads(data['filter_field'])
+ data = data['filter_field']
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
models['hosts'] = [] # This will always clear existing data when this step changes
models['interfaces'] = {}
if "bundle" not in models:
models['bundle'] = GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER))
- host_data = data['hosts']
+ host_data = data['host']
names = {}
- for host_dict in host_data:
- id = host_dict['class']
- # bit of formatting
- id = int(id.split("_")[-1])
+ for host_profile_dict in host_data.values():
+ id = host_profile_dict['id']
profile = HostProfile.objects.get(id=id)
# instantiate genericHost and store in repo
- name = host_dict['host_name']
- if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
- raise InvalidHostnameException("Hostname must comply to RFC 952 and all extensions to it until this point")
- if name in names:
- raise NonUniqueHostnameException("All hosts must have unique names")
- names[name] = True
- genericResource = GenericResource(bundle=models['bundle'], name=name)
- genericHost = GenericHost(profile=profile, resource=genericResource)
- models['hosts'].append(genericHost)
- for interface_profile in profile.interfaceprofile.all():
- genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
- if genericHost.resource.name not in models['interfaces']:
- models['interfaces'][genericHost.resource.name] = []
- models['interfaces'][genericHost.resource.name].append(genericInterface)
+ for name in host_profile_dict['values'].values():
+ if not re.match(r"(?=^.{1,253}$)(^([A-Za-z0-9-_]{1,62}\.)*[A-Za-z0-9-_]{1,63})", name):
+ raise InvalidHostnameException("Invalid hostname: '" + name + "'")
+ if name in names:
+ raise NonUniqueHostnameException("All hosts must have unique names")
+ names[name] = True
+ genericResource = GenericResource(bundle=models['bundle'], name=name)
+ genericHost = GenericHost(profile=profile, resource=genericResource)
+ models['hosts'].append(genericHost)
+ for interface_profile in profile.interfaceprofile.all():
+ genericInterface = GenericInterface(profile=interface_profile, host=genericHost)
+ if genericHost.resource.name not in models['interfaces']:
+ models['interfaces'][genericHost.resource.name] = []
+ models['interfaces'][genericHost.resource.name].append(genericInterface)
# add selected lab to models
- for lab_dict in data['labs']:
- if list(lab_dict.values())[0]: # True for lab the user selected
- lab_user_id = int(list(lab_dict.keys())[0].split("_")[-1])
- models['bundle'].lab = Lab.objects.get(lab_user__id=lab_user_id)
+ for lab_dict in data['lab'].values():
+ if lab_dict['selected']:
+ models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
break # if somehow we get two 'true' labs, we only use one
# return to repo
@@ -133,12 +117,11 @@ class Define_Hardware(WorkflowStep):
if self.form.is_valid():
self.update_models(self.form.cleaned_data)
self.update_confirmation()
- self.metastep.set_valid("Step Completed")
+ self.set_valid("Step Completed")
else:
- self.metastep.set_invalid("Please complete the fields highlighted in red to continue")
- pass
+ self.set_invalid("Please complete the fields highlighted in red to continue")
except Exception as e:
- self.metastep.set_invalid(str(e))
+ self.set_invalid(str(e))
self.context = self.get_context()
return render(request, self.template, self.context)
@@ -168,53 +151,55 @@ class Define_Nets(WorkflowStep):
except Exception:
return None
+ def make_mx_host_dict(self, generic_host):
+ host = {
+ 'id': generic_host.resource.name,
+ 'interfaces': [],
+ 'value': {
+ "name": generic_host.resource.name,
+ "description": generic_host.profile.description
+ }
+ }
+ for iface in generic_host.profile.interfaceprofile.all():
+ host['interfaces'].append({
+ "name": iface.name,
+ "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
+ })
+ return host
+
def get_context(self):
- # TODO: render *primarily* on hosts in repo models
context = super(Define_Nets, self).get_context()
- context['form'] = NetworkDefinitionForm()
+ context.update({
+ 'form': NetworkDefinitionForm(),
+ 'debug': settings.DEBUG,
+ 'hosts': [],
+ 'added_hosts': [],
+ 'removed_hosts': []
+ })
+ vlans = self.get_vlans()
+ if vlans:
+ context['vlans'] = vlans
try:
- context['hosts'] = []
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- vlans = self.get_vlans()
- if vlans:
- context['vlans'] = vlans
hosts = models.get("hosts", [])
- hostlist = self.repo_get(self.repo.GRB_LAST_HOSTLIST, None)
- added_list = []
- added_dict = {}
- context['added_hosts'] = []
- if hostlist is not None:
- new_hostlist = []
- for host in models['hosts']:
- intcount = host.profile.interfaceprofile.count()
- new_hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
- context['removed_hosts'] = list(set(hostlist) - set(new_hostlist))
- added_list = list(set(new_hostlist) - set(hostlist))
- for hoststr in added_list:
- key = hoststr.split("*")[0]
- added_dict[key] = hoststr
+ # calculate if the selected hosts have changed
+ added_hosts = set()
+ host_set = set(self.repo_get(self.repo.GRB_LAST_HOSTLIST, []))
+ if len(host_set):
+ new_host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
+ context['removed_hosts'] = [h.split("*")[0] for h in (host_set - new_host_set)]
+ added_hosts.update([h.split("*")[0] for h in (new_host_set - host_set)])
+
+ # add all host info to context
for generic_host in hosts:
- host_profile = generic_host.profile
- host = {}
- host['id'] = generic_host.resource.name
- host['interfaces'] = []
- for iface in host_profile.interfaceprofile.all():
- host['interfaces'].append(
- {
- "name": iface.name,
- "description": "speed: " + str(iface.speed) + "M\ntype: " + iface.nic_type
- }
- )
- host['value'] = {"name": generic_host.resource.name}
- host['value']['description'] = generic_host.profile.description
- context['hosts'].append(json.dumps(host))
- if host['id'] in added_dict:
- context['added_hosts'].append(json.dumps(host))
+ host = self.make_mx_host_dict(generic_host)
+ host_serialized = json.dumps(host)
+ context['hosts'].append(host_serialized)
+ if host['id'] in added_hosts:
+ context['added_hosts'].append(host_serialized)
bundle = models.get("bundle", False)
- if bundle and bundle.xml:
- context['xml'] = bundle.xml
- else:
- context['xml'] = False
+ if bundle:
+ context['xml'] = bundle.xml or False
except Exception:
pass
@@ -224,27 +209,24 @@ class Define_Nets(WorkflowStep):
def post_render(self, request):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
if 'hosts' in models:
- hostlist = []
- for host in models['hosts']:
- intcount = host.profile.interfaceprofile.count()
- hostlist.append(str(host.resource.name) + "*" + str(host.profile) + "&" + str(intcount))
- self.repo_put(self.repo.GRB_LAST_HOSTLIST, hostlist)
+ host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
+ self.repo_put(self.repo.GRB_LAST_HOSTLIST, host_set)
try:
xmlData = request.POST.get("xml")
self.updateModels(xmlData)
# update model with xml
- self.metastep.set_valid("Networks applied successfully")
+ self.set_valid("Networks applied successfully")
except ResourceAvailabilityException:
- self.metastep.set_invalid("Public network not availble")
- except Exception:
- self.metastep.set_invalid("An error occurred when applying networks")
+ self.set_invalid("Public network not availble")
+ except Exception as e:
+ self.set_invalid("An error occurred when applying networks: " + str(e))
return self.render(request)
def updateModels(self, xmlData):
models = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {})
- models["vlans"] = {}
- given_hosts, interfaces = self.parseXml(xmlData)
- vlan_manager = models['bundle'].lab.vlan_manager
+ models["connections"] = {}
+ models['networks'] = {}
+ given_hosts, interfaces, networks = self.parseXml(xmlData)
existing_host_list = models.get("hosts", [])
existing_hosts = {} # maps id to host
for host in existing_host_list:
@@ -252,104 +234,133 @@ class Define_Nets(WorkflowStep):
bundle = models.get("bundle", GenericResourceBundle(owner=self.repo_get(self.repo.SESSION_USER)))
+ for net_id, net in networks.items():
+ network = Network()
+ network.name = net['name']
+ network.bundle = bundle
+ network.is_public = net['public']
+ models['networks'][net_id] = network
+
for hostid, given_host in given_hosts.items():
existing_host = existing_hosts[hostid[5:]]
for ifaceId in given_host['interfaces']:
iface = interfaces[ifaceId]
- if existing_host.resource.name not in models['vlans']:
- models['vlans'][existing_host.resource.name] = {}
- models['vlans'][existing_host.resource.name][iface['profile_name']] = []
- for network in iface['networks']:
- vlan_id = network['network']['vlan']
- is_public = network['network']['public']
- if is_public:
- public_net = vlan_manager.get_public_vlan()
- if public_net is None:
- raise ResourceAvailabilityException("No public networks available")
- vlan_id = vlan_manager.get_public_vlan().vlan
- vlan = Vlan(vlan_id=vlan_id, tagged=network['tagged'], public=is_public)
- models['vlans'][existing_host.resource.name][iface['profile_name']].append(vlan)
+ if existing_host.resource.name not in models['connections']:
+ models['connections'][existing_host.resource.name] = {}
+ models['connections'][existing_host.resource.name][iface['profile_name']] = []
+ for connection in iface['connections']:
+ network_id = connection['network']
+ net = models['networks'][network_id]
+ connection = NetworkConnection(vlan_is_tagged=connection['tagged'], network=net)
+ models['connections'][existing_host.resource.name][iface['profile_name']].append(connection)
bundle.xml = xmlData
self.repo_put(self.repo.GRESOURCE_BUNDLE_MODELS, models)
- # serialize and deserialize xml from mxGraph
- def parseXml(self, xmlString):
- parent_nets = {} # map network ports to networks
- networks = {} # maps net id to network object
- hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
- interfaces = {} # maps id -> interface
+ def decomposeXml(self, xmlString):
+ """
+ This function takes in an xml doc from our front end
+ and returns dictionaries that map cellIds to the xml
+ nodes themselves. There is no unpacking of the
+ xml objects, just grouping and organizing
+ """
+
+ connections = {}
+ networks = {}
+ hosts = {}
+ interfaces = {}
+ network_ports = {}
+
xmlDom = minidom.parseString(xmlString)
root = xmlDom.documentElement.firstChild
- netids = {}
- untagged_ints = {}
for cell in root.childNodes:
cellId = cell.getAttribute('id')
+ group = cellId.split("_")[0]
+ parentGroup = cell.getAttribute("parent").split("_")[0]
+ # place cell into correct group
if cell.getAttribute("edge"):
- # cell is a network connection
- escaped_json_str = cell.getAttribute("value")
- json_str = escaped_json_str.replace('&quot;', '"')
- attributes = json.loads(json_str)
- tagged = attributes['tagged']
- interface = None
- network = None
- src = cell.getAttribute("source")
- tgt = cell.getAttribute("target")
- if src in parent_nets:
- # src is a network port
- network = networks[parent_nets[src]]
- if tgt in untagged_ints and not tagged:
- raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
- interface = interfaces[tgt]
- untagged_ints[tgt] = True
- else:
- network = networks[parent_nets[tgt]]
- if src in untagged_ints and not tagged:
- raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
- interface = interfaces[src]
- untagged_ints[src] = True
- interface['networks'].append({"network": network, "tagged": tagged})
-
- elif "network" in cellId: # cell is a network
- escaped_json_str = cell.getAttribute("value")
- json_str = escaped_json_str.replace('&quot;', '"')
- net_info = json.loads(json_str)
- nid = net_info['vlan_id']
- public = net_info['public']
- try:
- int_netid = int(nid)
- assert public or int_netid > 1, "Net id is 1 or lower"
- assert int_netid < 4095, "Net id is 4095 or greater"
- except Exception:
- raise InvalidVlanConfigurationException("VLAN ID is not an integer more than 1 and less than 4095")
- if nid in netids:
- raise NetworkExistsException("Non unique network id found")
- else:
- pass
- network = {"name": net_info['name'], "vlan": net_info['vlan_id'], "public": public}
- netids[net_info['vlan_id']] = True
- networks[cellId] = network
-
- elif "host" in cellId: # cell is a host/machine
- # TODO gather host info
- cell_json_str = cell.getAttribute("value")
- cell_json = json.loads(cell_json_str)
- host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
- hosts[cellId] = host
-
- elif cell.hasAttribute("parent"):
- parentId = cell.getAttribute('parent')
- if "network" in parentId:
- parent_nets[cellId] = parentId
- elif "host" in parentId:
- # TODO gather iface info
- cell_json_str = cell.getAttribute("value")
- cell_json = json.loads(cell_json_str)
- iface = {"name": cellId, "networks": [], "profile_name": cell_json['name']}
- hosts[parentId]['interfaces'].append(cellId)
- interfaces[cellId] = iface
- return hosts, interfaces
+ connections[cellId] = cell
+
+ elif "network" in group:
+ networks[cellId] = cell
+
+ elif "host" in group:
+ hosts[cellId] = cell
+
+ elif "host" in parentGroup:
+ interfaces[cellId] = cell
+
+ # make network ports also map to thier network
+ elif "network" in parentGroup:
+ network_ports[cellId] = cell.getAttribute("parent") # maps port ID to net ID
+
+ return connections, networks, hosts, interfaces, network_ports
+
+ # serialize and deserialize xml from mxGraph
+ def parseXml(self, xmlString):
+ networks = {} # maps net name to network object
+ hosts = {} # cotains id -> hosts, each containing interfaces, referencing networks
+ interfaces = {} # maps id -> interface
+ untagged_ifaces = set() # used to check vlan config
+ network_names = set() # used to check network names
+ xml_connections, xml_nets, xml_hosts, xml_ifaces, xml_ports = self.decomposeXml(xmlString)
+
+ # parse Hosts
+ for cellId, cell in xml_hosts.items():
+ cell_json_str = cell.getAttribute("value")
+ cell_json = json.loads(cell_json_str)
+ host = {"interfaces": [], "name": cellId, "profile_name": cell_json['name']}
+ hosts[cellId] = host
+
+ # parse networks
+ for cellId, cell in xml_nets.items():
+ escaped_json_str = cell.getAttribute("value")
+ json_str = escaped_json_str.replace('&quot;', '"')
+ net_info = json.loads(json_str)
+ net_name = net_info['name']
+ public = net_info['public']
+ if net_name in network_names:
+ raise NetworkExistsException("Non unique network name found")
+ network = {"name": net_name, "public": public, "id": cellId}
+ networks[cellId] = network
+ network_names.add(net_name)
+
+ # parse interfaces
+ for cellId, cell in xml_ifaces.items():
+ parentId = cell.getAttribute('parent')
+ cell_json_str = cell.getAttribute("value")
+ cell_json = json.loads(cell_json_str)
+ iface = {"name": cellId, "connections": [], "profile_name": cell_json['name']}
+ hosts[parentId]['interfaces'].append(cellId)
+ interfaces[cellId] = iface
+
+ # parse connections
+ for cellId, cell in xml_connections.items():
+ escaped_json_str = cell.getAttribute("value")
+ json_str = escaped_json_str.replace('&quot;', '"')
+ attributes = json.loads(json_str)
+ tagged = attributes['tagged']
+ interface = None
+ network = None
+ src = cell.getAttribute("source")
+ tgt = cell.getAttribute("target")
+ if src in interfaces:
+ interface = interfaces[src]
+ network = networks[xml_ports[tgt]]
+ else:
+ interface = interfaces[tgt]
+ network = networks[xml_ports[src]]
+
+ if not tagged:
+ if interface['name'] in untagged_ifaces:
+ raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
+ untagged_ifaces.add(interface['name'])
+
+ # add connection to interface
+ interface['connections'].append({"tagged": tagged, "network": network['id']})
+
+ return hosts, interfaces, networks
class Resource_Meta_Info(WorkflowStep):
@@ -390,10 +401,10 @@ class Resource_Meta_Info(WorkflowStep):
tmp = tmp[:60] + "..."
confirm_info["description"] = tmp
self.repo_put(self.repo.CONFIRMATION, confirm)
- self.metastep.set_valid("Step Completed")
+ self.set_valid("Step Completed")
else:
- self.metastep.set_invalid("Please correct the fields highlighted in red to continue")
+ self.set_invalid("Please correct the fields highlighted in red to continue")
pass
return self.render(request)
diff --git a/dashboard/src/workflow/snapshot_workflow.py b/dashboard/src/workflow/snapshot_workflow.py
index 4ddc397..5414784 100644
--- a/dashboard/src/workflow/snapshot_workflow.py
+++ b/dashboard/src/workflow/snapshot_workflow.py
@@ -8,13 +8,13 @@
##############################################################################
-import datetime
+from django.utils import timezone
import json
from booking.models import Booking
from resource_inventory.models import Host, Image
from workflow.models import WorkflowStep
-from workflow.forms import SnapshotMetaForm, SnapshotHostSelectForm
+from workflow.forms import BasicMetaForm, SnapshotHostSelectForm
class Select_Host_Step(WorkflowStep):
@@ -27,7 +27,7 @@ class Select_Host_Step(WorkflowStep):
context = super(Select_Host_Step, self).get_context()
context['form'] = SnapshotHostSelectForm()
booking_hosts = {}
- now = datetime.datetime.now()
+ now = timezone.now()
user = self.repo_get(self.repo.SESSION_USER)
bookings = Booking.objects.filter(start__lt=now, end__gt=now, owner=user)
for booking in bookings:
@@ -52,11 +52,11 @@ class Select_Host_Step(WorkflowStep):
def post_render(self, request):
host_data = request.POST.get("host")
if not host_data:
- self.metastep.set_invalid("Please select a host")
+ self.set_invalid("Please select a host")
return self.render(request)
host = json.loads(host_data)
if 'name' not in host or 'booking' not in host:
- self.metastep.set_invalid("Invalid host selected")
+ self.set_invalid("Invalid host selected")
return self.render(request)
name = host['name']
booking_id = host['booking']
@@ -75,7 +75,7 @@ class Select_Host_Step(WorkflowStep):
snap_confirm['host'] = name
confirm['snapshot'] = snap_confirm
self.repo_put(self.repo.CONFIRMATION, confirm)
- self.metastep.set_valid("Success")
+ self.set_valid("Success")
return self.render(request)
@@ -87,11 +87,18 @@ class Image_Meta_Step(WorkflowStep):
def get_context(self):
context = super(Image_Meta_Step, self).get_context()
- context['form'] = SnapshotMetaForm()
+ name = self.repo_get(self.repo.SNAPSHOT_NAME, False)
+ desc = self.repo_get(self.repo.SNAPSHOT_DESC, False)
+ form = None
+ if name and desc:
+ form = BasicMetaForm(initial={"name": name, "description": desc})
+ else:
+ form = BasicMetaForm()
+ context['form'] = form
return context
def post_render(self, request):
- form = SnapshotMetaForm(request.POST)
+ form = BasicMetaForm(request.POST)
if form.is_valid():
name = form.cleaned_data['name']
self.repo_put(self.repo.SNAPSHOT_NAME, name)
@@ -105,8 +112,8 @@ class Image_Meta_Step(WorkflowStep):
confirm['snapshot'] = snap_confirm
self.repo_put(self.repo.CONFIRMATION, confirm)
- self.metastep.set_valid("Success")
+ self.set_valid("Success")
else:
- self.metastep.set_invalid("Please Fill out the Form")
+ self.set_invalid("Please Fill out the Form")
return self.render(request)
diff --git a/dashboard/src/workflow/sw_bundle_workflow.py b/dashboard/src/workflow/sw_bundle_workflow.py
index 26ade22..0c558fc 100644
--- a/dashboard/src/workflow/sw_bundle_workflow.py
+++ b/dashboard/src/workflow/sw_bundle_workflow.py
@@ -11,33 +11,13 @@
from django.forms import formset_factory
from workflow.models import WorkflowStep
-from workflow.forms import SoftwareConfigurationForm, HostSoftwareDefinitionForm
-from workflow.booking_workflow import Resource_Select
-from resource_inventory.models import Image, GenericHost, ConfigBundle, HostConfiguration, Installer, OPNFVConfig
+from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
+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.SWCONF_SELECTED_GRB
- self.confirm_key = "configuration"
-
- def get_default_entry(self):
- booking_grb = self.repo_get(self.repo.BOOKING_SELECTED_GRB)
- if booking_grb:
- return booking_grb
- created_grb = self.repo_get(self.repo.GRESOURCE_BUNDLE_MODELS, {}).get("bundle", None)
- return created_grb
-
- 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):
@@ -46,52 +26,61 @@ class Define_Software(WorkflowStep):
description = "Choose the opnfv and image of your machines"
short_title = "host config"
- def create_hostformset(self, hostlist):
+ def build_filter_data(self, hosts_data):
+ """
+ returns a 2D array of images to exclude
+ based on the ordering of the passed
+ hosts_data
+ """
+ filter_data = []
+ user = self.repo_get(self.repo.SESSION_USER)
+ lab = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE).lab
+ for i, host_data in enumerate(hosts_data):
+ host = GenericHost.objects.get(pk=host_data['host_id'])
+ wrong_owner = Image.objects.exclude(owner=user).exclude(public=True)
+ wrong_host = Image.objects.exclude(host_type=host.profile)
+ wrong_lab = Image.objects.exclude(from_lab=lab)
+ excluded_images = wrong_owner | wrong_host | wrong_lab
+ filter_data.append([])
+ for image in excluded_images:
+ filter_data[i].append(image.pk)
+ return filter_data
+
+ def create_hostformset(self, hostlist, data=None):
hosts_initial = []
host_configs = self.repo_get(self.repo.CONFIG_MODELS, {}).get("host_configs", False)
if host_configs:
for config in host_configs:
- host_initial = {'host_id': config.host.id, 'host_name': config.host.resource.name}
- host_initial['role'] = config.opnfvRole
- host_initial['image'] = config.image
- hosts_initial.append(host_initial)
-
+ hosts_initial.append({
+ 'host_id': config.host.id,
+ 'host_name': config.host.resource.name,
+ 'headnode': config.is_head_node,
+ 'image': config.image
+ })
else:
for host in hostlist:
- host_initial = {'host_id': host.id, 'host_name': host.resource.name}
-
- hosts_initial.append(host_initial)
+ hosts_initial.append({
+ 'host_id': host.id,
+ 'host_name': host.resource.name
+ })
HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
- host_formset = HostFormset(initial=hosts_initial)
+ filter_data = self.build_filter_data(hosts_initial)
- filter_data = {}
- user = self.repo_get(self.repo.SESSION_USER)
- i = 0
- for host_data in hosts_initial:
- host_profile = None
- try:
- host = GenericHost.objects.get(pk=host_data['host_id'])
- host_profile = host.profile
- except Exception:
- for host in hostlist:
- if host.resource.name == host_data['host_name']:
- host_profile = host.profile
- break
- excluded_images = Image.objects.exclude(owner=user).exclude(public=True)
- excluded_images = excluded_images | Image.objects.exclude(host_type=host.profile)
- lab = self.repo_get(self.repo.SWCONF_SELECTED_GRB).lab
- excluded_images = excluded_images | Image.objects.exclude(from_lab=lab)
- filter_data["id_form-" + str(i) + "-image"] = []
- for image in excluded_images:
- filter_data["id_form-" + str(i) + "-image"].append(image.name)
- i += 1
+ class SpecialHostFormset(HostFormset):
+ def get_form_kwargs(self, index):
+ kwargs = super(SpecialHostFormset, self).get_form_kwargs(index)
+ if index is not None:
+ kwargs['imageQS'] = Image.objects.exclude(pk__in=filter_data[index])
+ return kwargs
- return host_formset, filter_data
+ if data:
+ return SpecialHostFormset(data, initial=hosts_initial)
+ return SpecialHostFormset(initial=hosts_initial)
def get_host_list(self, grb=None):
if grb is None:
- grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False)
+ grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
if not grb:
return []
if grb.id:
@@ -102,16 +91,16 @@ class Define_Software(WorkflowStep):
def get_context(self):
context = super(Define_Software, self).get_context()
- grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB, False)
+ grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE, False)
if grb:
context["grb"] = grb
- formset, filter_data = self.create_hostformset(self.get_host_list(grb))
+ formset = self.create_hostformset(self.get_host_list(grb))
context["formset"] = formset
- context["filter_data"] = filter_data
+ context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1)
else:
context["error"] = "Please select a resource first"
- self.metastep.set_invalid("Step requires information that is not yet provided by previous step")
+ self.set_invalid("Step requires information that is not yet provided by previous step")
return context
@@ -122,58 +111,51 @@ class Define_Software(WorkflowStep):
confirm = self.repo_get(self.repo.CONFIRMATION, {})
- HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
- formset = HostFormset(request.POST)
hosts = self.get_host_list()
+ models['headnode_index'] = request.POST.get("headnode", 1)
+ formset = self.create_hostformset(hosts, data=request.POST)
+ has_headnode = False
if formset.is_valid():
models['host_configs'] = []
- i = 0
confirm_hosts = []
- for form in formset:
+ for i, form in enumerate(formset):
host = hosts[i]
- i += 1
image = form.cleaned_data['image']
- # checks image compatability
- grb = self.repo_get(self.repo.SWCONF_SELECTED_GRB)
- lab = None
- if grb:
- lab = grb.lab
- try:
- owner = self.repo_get(self.repo.SESSION_USER)
- q = Image.objects.filter(owner=owner) | Image.objects.filter(public=True)
- q.filter(host_type=host.profile)
- q.filter(from_lab=lab)
- q.get(id=image.id) # will throw exception if image is not in q
- except:
- self.metastep.set_invalid("Image " + image.name + " is not compatible with host " + host.resource.name)
- role = form.cleaned_data['role']
+ headnode = form.cleaned_data['headnode']
+ if headnode:
+ has_headnode = True
bundle = models['bundle']
hostConfig = HostConfiguration(
host=host,
image=image,
bundle=bundle,
- opnfvRole=role
+ is_head_node=headnode
)
models['host_configs'].append(hostConfig)
- confirm_host = {"name": host.resource.name, "image": image.name, "role": role.name}
- confirm_hosts.append(confirm_host)
+ confirm_hosts.append({
+ "name": host.resource.name,
+ "image": image.name,
+ "headnode": headnode
+ })
+
+ if not has_headnode:
+ self.set_invalid('Must have one "Headnode" per POD')
+ return self.render(request)
self.repo_put(self.repo.CONFIG_MODELS, models)
if "configuration" not in confirm:
confirm['configuration'] = {}
confirm['configuration']['hosts'] = confirm_hosts
self.repo_put(self.repo.CONFIRMATION, confirm)
- self.metastep.set_valid("Completed")
+ self.set_valid("Completed")
else:
- self.metastep.set_invalid("Please complete all fields")
+ self.set_invalid("Please complete all fields")
return self.render(request)
class Config_Software(WorkflowStep):
template = 'config_bundle/steps/config_software.html'
- form = SoftwareConfigurationForm
- context = {'workspace_form': form}
title = "Other Info"
description = "Give your software config a name, description, and other stuff"
short_title = "config info"
@@ -187,58 +169,30 @@ class Config_Software(WorkflowStep):
if bundle:
initial['name'] = bundle.name
initial['description'] = bundle.description
- opnfv = models.get("opnfv", False)
- if opnfv:
- initial['installer'] = opnfv.installer
- initial['scenario'] = opnfv.scenario
- else:
- initial['opnfv'] = False
- supported = {}
- for installer in Installer.objects.all():
- supported[str(installer)] = []
- for scenario in installer.sup_scenarios.all():
- supported[str(installer)].append(str(scenario))
-
- context["form"] = SoftwareConfigurationForm(initial=initial)
- context['supported'] = supported
-
+ context["form"] = BasicMetaForm(initial=initial)
return context
def post_render(self, request):
- try:
- models = self.repo_get(self.repo.CONFIG_MODELS, {})
- if "bundle" not in models:
- models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
+ models = self.repo_get(self.repo.CONFIG_MODELS, {})
+ if "bundle" not in models:
+ models['bundle'] = ConfigBundle(owner=self.repo_get(self.repo.SESSION_USER))
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "configuration" not in confirm:
- confirm['configuration'] = {}
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ if "configuration" not in confirm:
+ confirm['configuration'] = {}
- form = self.form(request.POST)
- if form.is_valid():
- models['bundle'].name = form.cleaned_data['name']
- models['bundle'].description = form.cleaned_data['description']
- if form.cleaned_data['opnfv']:
- installer = form.cleaned_data['installer']
- scenario = form.cleaned_data['scenario']
- opnfv = OPNFVConfig(
- bundle=models['bundle'],
- installer=installer,
- scenario=scenario
- )
- models['opnfv'] = opnfv
- confirm['configuration']['installer'] = form.cleaned_data['installer'].name
- confirm['configuration']['scenario'] = form.cleaned_data['scenario'].name
-
- confirm['configuration']['name'] = form.cleaned_data['name']
- confirm['configuration']['description'] = form.cleaned_data['description']
- self.metastep.set_valid("Complete")
- else:
- self.metastep.set_invalid("Please correct the errors shown below")
+ form = BasicMetaForm(request.POST)
+ if form.is_valid():
+ models['bundle'].name = form.cleaned_data['name']
+ models['bundle'].description = form.cleaned_data['description']
- self.repo_put(self.repo.CONFIG_MODELS, models)
- self.repo_put(self.repo.CONFIRMATION, confirm)
+ confirm['configuration']['name'] = form.cleaned_data['name']
+ confirm['configuration']['description'] = form.cleaned_data['description']
+ self.set_valid("Complete")
+ else:
+ self.set_invalid("Please correct the errors shown below")
+
+ self.repo_put(self.repo.CONFIG_MODELS, models)
+ self.repo_put(self.repo.CONFIRMATION, confirm)
- except Exception:
- pass
return self.render(request)
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/views.py b/dashboard/src/workflow/views.py
index e5ef5c6..7ed9031 100644
--- a/dashboard/src/workflow/views.py
+++ b/dashboard/src/workflow/views.py
@@ -8,12 +8,14 @@
##############################################################################
-from django.http import HttpResponse, HttpResponseGone
+from django.http import HttpResponseGone, JsonResponse
from django.shortcuts import render
+from django.urls import reverse
import uuid
from workflow.workflow_manager import ManagerTracker, SessionManager
+from booking.models import Booking
import logging
logger = logging.getLogger(__name__)
@@ -29,12 +31,33 @@ def attempt_auth(request):
return None
+def get_redirect_response(result):
+ if not result:
+ return {}
+
+ # need to get type of result, and switch on the type
+ # since has_result, result must be populated with a valid object
+ if isinstance(result, Booking):
+ return {
+ 'redir_url': reverse('booking:booking_detail', kwargs={'booking_id': result.id})
+ }
+ else:
+ return {}
+
+
def delete_session(request):
- try:
+ manager = attempt_auth(request)
+
+ if not manager:
+ return HttpResponseGone("No session found that relates to current request")
+
+ not_last_workflow, result = manager.pop_workflow()
+
+ if not_last_workflow: # this was not the last workflow, so don't redirect away
+ return JsonResponse({})
+ else:
del ManagerTracker.managers[request.session['manager_session']]
- return HttpResponse('')
- except Exception:
- return None
+ return JsonResponse(get_redirect_response(result))
def step_view(request):
@@ -43,7 +66,12 @@ def step_view(request):
# no manager found, redirect to "lost" page
return no_workflow(request)
if request.GET.get('step') is not None:
- manager.goto(int(request.GET.get('step')))
+ if request.GET.get('step') == 'next':
+ manager.go_next()
+ elif request.GET.get('step') == 'prev':
+ manager.go_prev()
+ else:
+ raise Exception("requested action for new step had malformed contents: " + request.GET.get('step'))
return manager.render(request)
@@ -70,7 +98,8 @@ def manager_view(request):
logger.debug("edit found")
manager.add_workflow(workflow_type=request.POST.get('edit'), edit_object=int(request.POST.get('edit_id')))
elif request.POST.get('cancel') is not None:
- del ManagerTracker.managers[request.session['manager_session']]
+ if not manager.pop_workflow():
+ del ManagerTracker.managers[request.session['manager_session']]
return manager.status(request)
diff --git a/dashboard/src/workflow/workflow_factory.py b/dashboard/src/workflow/workflow_factory.py
index 9a42d86..03c8126 100644
--- a/dashboard/src/workflow/workflow_factory.py
+++ b/dashboard/src/workflow/workflow_factory.py
@@ -8,10 +8,12 @@
##############################################################################
-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
+from workflow.opnfv_workflow import Pick_Installer, Assign_Network_Roles, Assign_Host_Roles, OPNFV_Resource_Select, MetaInfo
+from workflow.models import Confirmation_Step
import uuid
@@ -19,38 +21,6 @@ import logging
logger = logging.getLogger(__name__)
-class BookingMetaWorkflow(object):
- workflow_type = 0
- color = "#0099ff"
- is_child = False
-
-
-class ResourceMetaWorkflow(object):
- workflow_type = 1
- color = "#ff6600"
-
-
-class ConfigMetaWorkflow(object):
- workflow_type = 2
- color = "#00ffcc"
-
-
-class MetaRelation(object):
- def __init__(self, *args, **kwargs):
- self.color = "#cccccc"
- self.parent = 0
- self.children = []
- self.depth = -1
-
- def to_json(self):
- return {
- 'color': self.color,
- 'parent': self.parent,
- 'children': self.children,
- 'depth': self.depth,
- }
-
-
class MetaStep(object):
UNTOUCHED = 0
@@ -69,6 +39,7 @@ class MetaStep(object):
self.short_title = "error"
self.skip_step = 0
self.valid = 0
+ self.hidden = False
self.message = ""
self.id = uuid.uuid4()
@@ -93,11 +64,19 @@ class MetaStep(object):
return self.id.int != other.id.int
+class Workflow(object):
+ def __init__(self, steps, repository):
+ self.repository = repository
+ self.steps = steps
+ self.active_index = 0
+
+
class WorkflowFactory():
booking_steps = [
Booking_Resource_Select,
SWConfig_Select,
- Booking_Meta
+ Booking_Meta,
+ OPNFV_Select,
]
resource_steps = [
@@ -114,7 +93,15 @@ class WorkflowFactory():
snapshot_steps = [
Select_Host_Step,
- Image_Meta_Step
+ Image_Meta_Step,
+ ]
+
+ opnfv_steps = [
+ OPNFV_Resource_Select,
+ Pick_Installer,
+ Assign_Network_Roles,
+ Assign_Host_Roles,
+ MetaInfo
]
def conjure(self, workflow_type=None, repo=None):
@@ -123,14 +110,19 @@ class WorkflowFactory():
self.resource_steps,
self.config_steps,
self.snapshot_steps,
+ self.opnfv_steps,
]
steps = self.make_steps(workflow_types[workflow_type], repository=repo)
- meta_steps = self.metaize(steps=steps, wf_type=workflow_type)
- return steps, meta_steps
+ return steps
+
+ def create_workflow(self, workflow_type=None, repo=None):
+ steps = self.conjure(workflow_type, repo)
+ c_step = self.make_step(Confirmation_Step, repo)
+ steps.append(c_step)
+ return Workflow(steps, repo)
def make_steps(self, step_types, repository):
- repository.el['steps'] += len(step_types)
steps = []
for step_type in step_types:
steps.append(self.make_step(step_type, repository))
@@ -140,13 +132,3 @@ class WorkflowFactory():
def make_step(self, step_type, repository):
iden = step_type.description + step_type.title + step_type.template
return step_type(iden, repository)
-
- def metaize(self, steps, wf_type):
- meta_dict = []
- for step in steps:
- meta_step = MetaStep()
- meta_step.short_title = step.short_title
- meta_dict.append(meta_step)
- step.metastep = meta_step
-
- return meta_dict
diff --git a/dashboard/src/workflow/workflow_manager.py b/dashboard/src/workflow/workflow_manager.py
index 95fefbf..80b8a67 100644
--- a/dashboard/src/workflow/workflow_manager.py
+++ b/dashboard/src/workflow/workflow_manager.py
@@ -10,11 +10,9 @@
from django.http import JsonResponse
-import random
-
from booking.models import Booking
-from workflow.workflow_factory import WorkflowFactory, MetaStep, MetaRelation
-from workflow.models import Repository, Confirmation_Step
+from workflow.workflow_factory import WorkflowFactory
+from workflow.models import Repository
from resource_inventory.models import (
GenericResourceBundle,
ConfigBundle,
@@ -27,86 +25,64 @@ logger = logging.getLogger(__name__)
class SessionManager():
+ def active_workflow(self):
+ return self.workflows[-1]
def __init__(self, request=None):
- self.repository = Repository()
- self.repository.el[self.repository.SESSION_USER] = request.user
- self.repository.el['active_step'] = 0
- self.steps = []
+ self.workflows = []
+
+ self.owner = request.user
+
self.factory = WorkflowFactory()
- c_step = WorkflowFactory().make_step(Confirmation_Step, self.repository)
- self.steps.append(c_step)
- metaconfirm = MetaStep()
- metaconfirm.index = 0
- metaconfirm.short_title = "confirm"
- self.repository.el['steps'] = 1
- self.metaworkflow = None
- self.metaworkflows = []
- self.metarelations = []
- self.relationreverselookup = {}
- self.initialized = False
- self.active_index = 0
- self.step_meta = [metaconfirm]
- self.relation_depth = 0
+
+ def set_step_statuses(self, superclass_type, desired_enabled=True):
+ workflow = self.active_workflow()
+ steps = workflow.steps
+ for step in steps:
+ if isinstance(step, superclass_type):
+ if desired_enabled:
+ step.enable()
+ else:
+ step.disable()
def add_workflow(self, workflow_type=None, target_id=None, **kwargs):
if target_id is not None:
self.prefill_repo(target_id, workflow_type)
- factory_steps, meta_info = self.factory.conjure(workflow_type=workflow_type, repo=self.repository)
- offset = len(meta_info)
- for relation in self.metarelations:
- if relation.depth > self.relation_depth:
- self.relation_depth = relation.depth
- if relation.parent >= self.repository.el['active_step']:
- relation.parent += offset
- for i in range(0, len(relation.children)):
- if relation.children[i] >= self.repository.el['active_step']:
- relation.children[i] += offset
- self.step_meta[self.active_index:self.active_index] = meta_info
- self.steps[self.active_index:self.active_index] = factory_steps
-
- if self.initialized:
- relation = MetaRelation()
- relation.parent = self.repository.el['active_step'] + offset
- relation.depth = self.relationreverselookup[self.step_meta[relation.parent]].depth + 1
- if relation.depth > self.relation_depth:
- self.relation_depth = relation.depth
- for i in range(self.repository.el['active_step'], offset + self.repository.el['active_step']):
- relation.children.append(i)
- self.relationreverselookup[self.step_meta[i]] = relation
- relation.color = "#%06x" % random.randint(0, 0xFFFFFF)
- self.metarelations.append(relation)
- else:
- relation = MetaRelation()
- relation.depth = 0
- relation.parent = 500000000000
- for i in range(0, len(self.step_meta)):
- relation.children.append(i)
- self.relationreverselookup[self.step_meta[i]] = relation
- self.metarelations.append(relation)
- self.initialized = True
+
+ repo = Repository()
+ if(len(self.workflows) >= 1):
+ defaults = self.workflows[-1].repository.get_child_defaults()
+ repo.set_defaults(defaults)
+ repo.el[repo.HAS_RESULT] = False
+ repo.el[repo.SESSION_USER] = self.owner
+ repo.el[repo.SESSION_MANAGER] = self
+ self.workflows.append(
+ self.factory.create_workflow(
+ workflow_type=workflow_type,
+ repo=repo
+ )
+ )
+
+ def pop_workflow(self):
+ multiple_wfs = len(self.workflows) > 1
+ if multiple_wfs:
+ if self.workflows[-1].repository.el[Repository.RESULT]: # move result
+ key = self.workflows[-1].repository.el[Repository.RESULT_KEY]
+ result = self.workflows[-1].repository.el[Repository.RESULT]
+ self.workflows[-2].repository.el[key] = result
+ self.workflows.pop()
+ current_repo = self.workflows[-1].repository
+ return (multiple_wfs, current_repo.el[current_repo.RESULT])
def status(self, request):
try:
- steps = []
- for step in self.step_meta:
- steps.append(step.to_json())
- parents = {}
- children = {}
+ meta_json = []
+ for step in self.active_workflow().steps:
+ meta_json.append(step.to_json())
responsejson = {}
- responsejson["steps"] = steps
- responsejson["active"] = self.repository.el['active_step']
- responsejson["relations"] = []
- i = 0
- for relation in self.metarelations:
- responsejson["relations"].append(relation.to_json())
- children[relation.parent] = i
- for child in relation.children:
- parents[child] = i
- i += 1
- responsejson['max_depth'] = self.relation_depth
- responsejson['parents'] = parents
- responsejson['children'] = children
+ responsejson["steps"] = meta_json
+ responsejson["active"] = self.active_workflow().repository.el['active_step']
+ responsejson["workflow_count"] = len(self.workflows)
return JsonResponse(responsejson, safe=False)
except Exception:
pass
@@ -115,16 +91,35 @@ class SessionManager():
# filter out when a step needs to handle post/form data
# if 'workflow' in post data, this post request was meant for me, not step
if request.method == 'POST' and request.POST.get('workflow', None) is None:
- return self.steps[self.active_index].post_render(request)
- return self.steps[self.active_index].render(request)
+ return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
+ return self.active_workflow().steps[self.active_workflow().active_index].render(request)
def post_render(self, request):
- return self.steps[self.active_index].post_render(request)
-
- def goto(self, num, **kwargs):
- self.repository.el['active_step'] = int(num)
- self.active_index = int(num)
- # TODO: change to include some checking
+ return self.active_workflow().steps[self.active_workflow().active_index].post_render(request)
+
+ def get_active_step(self):
+ return self.active_workflow().steps[self.active_workflow().active_index]
+
+ def go_next(self, **kwargs):
+ # need to verify current step is valid to allow this
+ if self.get_active_step().valid < 200:
+ return
+ next_step = self.active_workflow().active_index + 1
+ if next_step >= len(self.active_workflow().steps):
+ raise Exception("Out of bounds request for step")
+ while not self.active_workflow().steps[next_step].enabled:
+ next_step += 1
+ self.active_workflow().repository.el['active_step'] = next_step
+ self.active_workflow().active_index = next_step
+
+ def go_prev(self, **kwargs):
+ prev_step = self.active_workflow().active_index - 1
+ if prev_step < 0:
+ raise Exception("Out of bounds request for step")
+ while not self.active_workflow().steps[prev_step].enabled:
+ prev_step -= 1
+ self.active_workflow().repository.el['active_step'] = prev_step
+ self.active_workflow().active_index = prev_step
def prefill_repo(self, target_id, workflow_type):
self.repository.el[self.repository.EDIT] = True
@@ -142,29 +137,28 @@ class SessionManager():
def prefill_booking(self, booking):
models = self.make_booking_models(booking)
confirmation = self.make_booking_confirm(booking)
- self.repository.el[self.repository.BOOKING_MODELS] = models
- self.repository.el[self.repository.CONFIRMATION] = confirmation
- self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template)
- self.repository.el[self.repository.BOOKING_SELECTED_GRB] = self.make_grb_models(booking.resource.template)['bundle']
- self.repository.el[self.repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)
+ self.active_workflow().repository.el[self.active_workflow().repository.BOOKING_MODELS] = models
+ self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirmation
+ self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = self.make_grb_models(booking.resource.template)
+ self.active_workflow().repository.el[self.active_workflow().repository.SELECTED_GRESOURCE_BUNDLE] = self.make_grb_models(booking.resource.template)['bundle']
+ self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = self.make_config_models(booking.config_bundle)
def prefill_resource(self, resource):
models = self.make_grb_models(resource)
confirm = self.make_grb_confirm(resource)
- self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = models
- self.repository.el[self.repository.CONFIRMATION] = confirm
+ self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = models
+ self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
def prefill_config(self, config):
models = self.make_config_models(config)
confirm = self.make_config_confirm(config)
- self.repository.el[self.repository.CONFIG_MODELS] = models
- self.repository.el[self.repository.CONFIRMATION] = confirm
+ self.active_workflow().repository.el[self.active_workflow().repository.CONFIG_MODELS] = models
+ self.active_workflow().repository.el[self.active_workflow().repository.CONFIRMATION] = confirm
grb_models = self.make_grb_models(config.bundle)
- self.repository.el[self.repository.GRESOURCE_BUNDLE_MODELS] = grb_models
- self.repository.el[self.repository.SWCONF_SELECTED_GRB] = config.bundle
+ self.active_workflow().repository.el[self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS] = grb_models
def make_grb_models(self, resource):
- models = self.repository.el.get(self.repository.GRESOURCE_BUNDLE_MODELS, {})
+ models = self.active_workflow().repository.el.get(self.active_workflow().repository.GRESOURCE_BUNDLE_MODELS, {})
models['hosts'] = []
models['bundle'] = resource
models['interfaces'] = {}
@@ -181,7 +175,7 @@ class SessionManager():
return models
def make_grb_confirm(self, resource):
- confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+ confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
confirm['resource'] = {}
confirm['resource']['hosts'] = []
confirm['resource']['lab'] = resource.lab.lab_user.username
@@ -190,7 +184,7 @@ class SessionManager():
return confirm
def make_config_models(self, config):
- models = self.repository.el.get(self.repository.CONFIG_MODELS, {})
+ models = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIG_MODELS, {})
models['bundle'] = config
models['host_configs'] = []
for host_conf in HostConfiguration.objects.filter(bundle=config):
@@ -199,7 +193,7 @@ class SessionManager():
return models
def make_config_confirm(self, config):
- confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+ confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
confirm['configuration'] = {}
confirm['configuration']['hosts'] = []
confirm['configuration']['name'] = config.name
@@ -213,7 +207,7 @@ class SessionManager():
return confirm
def make_booking_models(self, booking):
- models = self.repository.el.get(self.repository.BOOKING_MODELS, {})
+ models = self.active_workflow().repository.el.get(self.active_workflow().repository.BOOKING_MODELS, {})
models['booking'] = booking
models['collaborators'] = []
for user in booking.collaborators.all():
@@ -221,7 +215,7 @@ class SessionManager():
return models
def make_booking_confirm(self, booking):
- confirm = self.repository.el.get(self.repository.CONFIRMATION, {})
+ confirm = self.active_workflow().repository.el.get(self.active_workflow().repository.CONFIRMATION, {})
confirm['booking'] = {}
confirm['booking']['length'] = (booking.end - booking.start).days
confirm['booking']['project'] = booking.project