from workflow.forms import (
MultipleSelectFilterField,
- MultipleSelectFilterWidget,
- FormUtils)
+ MultipleSelectFilterWidget)
from account.models import UserProfile
from resource_inventory.models import Image, Installer, Scenario
from workflow.forms import SearchableSelectMultipleField
from django.urls import reverse
from resource_inventory.models import ResourceBundle, ResourceProfile, Image, ResourceQuery
-from resource_inventory.resource_manager import ResourceManager
-from account.models import Lab, Downtime
+from account.models import Downtime
from booking.models import Booking
from booking.stats import StatisticsManager
from booking.forms import HostReImageForm
-from booking.forms import FormUtils
+from workflow.forms import FormUtils
from api.models import JobFactory
from workflow.views import login
from booking.forms import QuickBookingForm
from booking.quick_deployer import create_from_form, drop_filter
-from workflow.forms import (MultipleSelectFilterField,
- MultipleSelectFilterWidget)
+
def quick_create_clear_fields(request):
request.session['quick_create_forminfo'] = None
# TODO: template might not be a good name because this is a collection of lots of configured resources
id = models.AutoField(primary_key=True)
- name = models.CharField(max_length=300, unique=True)
+ name = models.CharField(max_length=300)
xml = models.TextField()
owner = models.ForeignKey(User, null=True, on_delete=models.SET_NULL)
lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL, related_name="resourcetemplates")
image = models.ForeignKey("Image", on_delete=models.PROTECT)
template = models.ForeignKey(ResourceTemplate, related_name="resourceConfigurations", null=True, on_delete=models.CASCADE)
is_head_node = models.BooleanField(default=False)
- # name = models.CharField(max_length=300)
+ name = models.CharField(max_length=3000, default="<Hostname>")
def __str__(self):
return "config with " + str(self.template) + " and image " + str(self.image)
connections = models.ManyToManyField(NetworkConnection)
def __str__(self):
- return "type " + str(self.profile.name) + " on host " + str(self.profile.host.name)
+ return "type " + str(self.profile) + " on host " + str(self.resource_config)
"""
from django.template.loader import render_to_string
import booking
-from resource_inventory.models import Server, InterfaceProfile
+from resource_inventory.models import Server
class PDFTemplater:
this.dropdown_count++;
const label = document.createElement("H5")
label.appendChild(document.createTextNode(node['name']))
- label.classList.add("p-1", "m-1");
+ label.classList.add("p-1", "m-1", "flex-grow-1");
div.appendChild(label);
- let input = this.make_input(div, node, prepopulate);
- input.classList.add("flex-grow-1", "p-1", "m-1");
- div.appendChild(input);
let remove_btn = this.make_remove_button(div, node);
remove_btn.classList.add("p-1", "m-1");
div.appendChild(remove_btn);
const node = this.filter_items[node_id]
const parent = div.parentNode;
div.parentNode.removeChild(div);
- delete this.result[node.class][node.id]['values'][div.id];
+ this.result[node.class][node.id]['count']--;
//checks if we have removed last item in class
- if(jQuery.isEmptyObject(this.result[node.class][node.id]['values'])){
+ if(this.result[node.class][node.id]['count'] == 0){
delete this.result[node.class][node.id];
this.clear(node);
}
updateObjectResult(node, childKey, childValue){
if(!this.result[node.class][node.id])
- this.result[node.class][node.id] = {selected: true, id: node.model_id, values: {}}
+ this.result[node.class][node.id] = {selected: true, id: node.model_id, count: 0}
- this.result[node.class][node.id]['values'][childKey] = childValue;
+ this.result[node.class][node.id]['count']++;
}
finish(){
}
class NetworkStep {
- constructor(debug, xml, hosts, added_hosts, removed_host_ids, graphContainer, overviewContainer, toolbarContainer){
- if(!this.check_support())
+ // expects:
+ //
+ // debug: bool
+ // resources: {
+ // id: {
+ // id: int,
+ // value: {
+ // description: string,
+ // },
+ // interfaces: [
+ // id: int,
+ // name: str,
+ // description: str,
+ // connections: [
+ // {
+ // network: int, [networks.id]
+ // tagged: bool
+ // }
+ // ],
+ // ],
+ // }
+ // }
+ // networks: {
+ // id: {
+ // id: int,
+ // name: str,
+ // public: bool,
+ // }
+ // }
+ //
+ constructor(debug, resources, networks, graphContainer, overviewContainer, toolbarContainer){
+ if(!this.check_support()) {
+ console.log("Aborting, browser is not supported");
return;
+ }
this.currentWindow = null;
this.netCount = 0;
this.editor = new mxEditor();
this.graph = this.editor.graph;
+ window.global_graph = this.graph;
+ window.network_rr_index = 5;
+
this.editor.setGraphContainer(graphContainer);
this.doGlobalConfig();
- this.prefill(xml, hosts, added_hosts, removed_host_ids);
+
+ let mx_networks = {}
+
+ for(const network_id in networks) {
+ let network = networks[network_id];
+
+ mx_networks[network_id] = this.populateNetwork(network);
+ }
+
+ this.prefillHosts(resources, mx_networks);
+
this.addToolbarButton(this.editor, toolbarContainer, 'zoomIn', '', "/static/img/mxgraph/zoom_in.png", true);
this.addToolbarButton(this.editor, toolbarContainer, 'zoomOut', '', "/static/img/mxgraph/zoom_out.png", true);
this.graph.addListener(mxEvent.CELL_CONNECTED, function(sender, event) {this.cellConnectionHandler(sender, event)}.bind(this));
//hooks up double click functionality
this.graph.dblClick = function(evt, cell) {this.doubleClickHandler(evt, cell);}.bind(this);
-
- if(!this.has_public_net){
- this.addPublicNetwork();
- }
}
check_support(){
return true;
}
- prefill(xml, hosts, added_hosts, removed_host_ids){
- //populate existing data
- if(xml){
- this.restoreFromXml(xml, this.editor);
- } else if(hosts){
- for(const host of hosts)
- this.makeHost(host);
- }
+ /**
+ * Expects
+ * mx_interface: mxCell for the interface itself
+ * network: mxCell for the outer network
+ * tagged: bool
+ */
+ connectNetwork(mx_interface, network, tagged) {
+ var cell = new mxCell(
+ "connection from " + network + " to " + mx_interface,
+ new mxGeometry(0, 0, 50, 50));
+ cell.edge = true;
+ cell.geometry.relative = true;
+ cell.setValue(JSON.stringify({tagged: tagged}));
+
+ let terminal = this.getClosestNetworkCell(mx_interface.geometry.y, network);
+ let edge = this.graph.addEdge(cell, null, mx_interface, terminal);
+ this.colorEdge(edge, terminal, true);
+ this.graph.refresh(edge);
+ }
- //apply any changes
- if(added_hosts){
- for(const host of added_hosts)
- this.makeHost(host);
- this.updateHosts([]); //TODO: why?
+ /**
+ * Expects:
+ *
+ * to: desired y axis position of the matching cell
+ * within: graph cell for a full network, with all child cells
+ *
+ * Returns:
+ * an mx cell, the one vertically closest to the desired value
+ *
+ * Side effect:
+ * modifies the <rr_index> on the <within> parameter
+ */
+ getClosestNetworkCell(to, within) {
+ if(window.network_rr_index === undefined) {
+ window.network_rr_index = 5;
+ }
+
+ let child_keys = within.children.keys();
+ let children = Array.from(within.children);
+ let index = (window.network_rr_index++) % children.length;
+
+ let child = within.children[child_keys[index]];
+
+ return children[index];
+ }
+
+ /** Expects
+ *
+ * hosts: {
+ * id: {
+ * id: int,
+ * value: {
+ * description: string,
+ * },
+ * interfaces: [
+ * id: int,
+ * name: str,
+ * description: str,
+ * connections: [
+ * {
+ * network: int, [networks.id]
+ * tagged: bool
+ * }
+ * ],
+ * ],
+ * }
+ * }
+ *
+ * network_mappings: {
+ * <django network id>: <mxnetwork id>
+ * }
+ *
+ * draws given hosts into the mxgraph
+ */
+ prefillHosts(hosts, network_mappings){
+ for(const host_id in hosts) {
+ this.makeHost(hosts[host_id], network_mappings);
}
- this.updateHosts(removed_host_ids);
}
cellConnectionHandler(sender, event){
color = kvp[1];
}
}
+
edge.setStyle('strokeColor=' + color);
+ } else {
+ console.log("Failed to color " + edge + ", " + terminal + ", " + source);
}
}
return true;
}
}
+
return false;
};
return ret_val;
}
+ // expects:
+ //
+ // {
+ // id: int,
+ // name: str,
+ // public: bool,
+ // }
+ //
+ // returns:
+ // mxgraph id of network
+ populateNetwork(network) {
+ let mxNet = this.makeMxNetwork(network.name, network.public);
+ this.makeSidebarNetwork(network.name, mxNet.color, mxNet.element_id);
+
+ if( network.public ) {
+ this.has_public_net = true;
+ }
+
+ return mxNet.element_id;
+ }
+
addPublicNetwork() {
const net = this.makeMxNetwork("public", true);
this.makeSidebarNetwork("public", net['color'], net['element_id']);
document.getElementById("network_list").appendChild(newNet);
}
- makeHost(hostInfo) {
+ /**
+ * Expects format:
+ * {
+ * 'id': int,
+ * 'value': {
+ * 'description': string,
+ * },
+ * 'interfaces': [
+ * {
+ * id: int,
+ * name: str,
+ * description: str,
+ * connections: [
+ * {
+ * network: int, <django network id>,
+ * tagged: bool
+ * }
+ * ]
+ * }
+ * ]
+ * }
+ *
+ * network_mappings: {
+ * <django network id>: <mxnetwork id>
+ * }
+ */
+ makeHost(hostInfo, network_mappings) {
const value = JSON.stringify(hostInfo['value']);
const interfaces = hostInfo['interfaces'];
const width = 100;
false
);
port.getGeometry().offset = new mxPoint(-4*interfaces[i].name.length -2,0);
+ const iface = interfaces[i];
+ for( const connection of iface.connections ) {
+ const network = this
+ .graph
+ .getModel()
+ .getCell(network_mappings[connection.network]);
+
+ this.connectNetwork(port, network, connection.tagged);
+ }
this.graph.refresh(port);
}
this.graph.refresh(host);
Design a Pod
</a>
<a href="#" onclick="create_workflow(2)" class="list-group-item list-group-item-action list-group-item-secondary">
- Configure a Pod
- </a>
- <a href="#" onclick="create_workflow(3)" class="list-group-item list-group-item-action list-group-item-secondary">
Create a Snapshot
</a>
- <a href="#" onclick="create_workflow(4)" class="list-group-item list-group-item-action list-group-item-secondary">
+ <a href="#" onclick="create_workflow(3)" class="list-group-item list-group-item-action list-group-item-secondary">
Configure OPNFV
</a>
</div>
debug = true;
{% endif %}
- let xml = '';
- {% if xml %}
- xml = '{{xml|safe}}';
- {% endif %}
-
- let hosts = [];
- {% for host in hosts %}
- hosts.push({{host|safe}});
- {% endfor %}
-
- let added_hosts = [];
- {% for host in added_hosts %}
- added_hosts.push({{host|safe}});
- {% endfor %}
+ const False = false;
+ const True = true;
- let removed_host_ids = {{removed_hosts|safe}};
+ let resources = {{resources|safe}};
+ let networks = {{networks|safe}};
network_step = new NetworkStep(
debug,
- xml,
- hosts,
- added_hosts,
- removed_host_ids,
+ resources,
+ networks,
document.getElementById('graphContainer'),
document.getElementById('outlineContainer'),
document.getElementById('toolbarContainer'),
labs[lab_node['id']] = lab_node
for template in ResourceManager.getInstance().getAvailableResourceTemplates(lab, user):
-
resource_node = {
'form': {"name": "host_name", "type": "text", "placeholder": "hostname"},
'id': "resource_" + str(template.id),
class HardwareDefinitionForm(forms.Form):
- def __init__(self, *args, **kwargs):
+ def __init__(self, user, *args, **kwargs):
super(HardwareDefinitionForm, self).__init__(*args, **kwargs)
- attrs = FormUtils.getLabData(multiple_hosts=True)
+ attrs = FormUtils.getLabData(multiple_hosts=True, user=user)
self.fields['filter_field'] = MultipleSelectFilterField(
widget=MultipleSelectFilterWidget(**attrs)
)
class HostSoftwareDefinitionForm(forms.Form):
- host_name = forms.CharField(max_length=200, disabled=True, required=False)
+ host_name = forms.CharField(max_length=200, disabled=False, required=True)
headnode = forms.BooleanField(required=False, widget=forms.HiddenInput)
def __init__(self, *args, **kwargs):
from workflow.forms import ConfirmationForm
from api.models import JobFactory
from dashboard.exceptions import ResourceAvailabilityException, ModelValidationException
-from resource_inventory.models import Image, InterfaceConfiguration, OPNFVConfig, ResourceOPNFVConfig, NetworkRole
+from resource_inventory.models import Image, OPNFVConfig, ResourceOPNFVConfig, NetworkRole
from resource_inventory.resource_manager import ResourceManager
from resource_inventory.pdf_templater import PDFTemplater
from notifier.manager import NotificationHandler
self.set_valid("Confirmed")
elif data == "False":
+ self.repo.cancel()
self.set_valid("Canceled")
else:
self.set_invalid("Bad Form Contents")
SNAPSHOT_DESC = "description of the snapshot"
BOOKING_INFO_FILE = "the INFO.yaml file for this user's booking"
+ # new keys for migration to using ResourceTemplates:
+ RESOURCE_TEMPLATE_MODELS = "current working model of resource template"
+
# 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"
else:
history[key].append(id)
+ def cancel(self):
+ if self.RESOURCE_TEMPLATE_MODELS in self.el:
+ models = self.el[self.RESOURCE_TEMPLATE_MODELS]
+ if models['template'].temporary:
+ models['template'].delete()
+ # deleting current template should cascade delete all
+ # necessary related models
+
def make_models(self):
if self.SNAPSHOT_MODELS in self.el:
errors = self.make_snapshot()
owner = self.el[self.SESSION_USER]
if self.RESOURCE_TEMPLATE_MODELS in self.el:
models = self.el[self.RESOURCE_TEMPLATE_MODELS]
- if 'hosts' in models:
- hosts = models['hosts']
- else:
- return "GRB has no hosts. CODE:0x0002"
- if 'bundle' in models:
- bundle = models['bundle']
- else:
- return "GRB, no bundle in models. CODE:0x0003"
-
- try:
- bundle.owner = owner
- bundle.save()
- except Exception as e:
- return "GRB, saving bundle generated exception: " + str(e) + " CODE:0x0004"
- try:
- for host in hosts:
- genericresource = host.resource
- genericresource.bundle = bundle
- genericresource.save()
- host.resource = genericresource
- host.save()
- 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:
- return "GRB, saving interface " + str(interface) + " failed. CODE:0x0019"
- else:
- return "GRB, no interface set provided. CODE:0x001a"
-
- if 'connections' in models:
- for resource_name, mapping in models['connections'].items():
- for profile_name, connection_set in mapping.items():
- interface = InterfaceConfiguration.objects.get(
- profile__name=profile_name,
- host__resource__name=resource_name,
- host__resource__bundle=models['bundle']
- )
- for connection in connection_set:
- try:
- connection.network = connection.network
- connection.save()
- interface.connections.add(connection)
- except Exception as e:
- return "GRB, saving vlan " + str(connection) + " failed. Exception: " + str(e) + ". CODE:0x0017"
- else:
- return "GRB, no vlan set provided. CODE:0x0018"
+ models['template'].owner = owner
+ models['template'].temporary = False
+ models['template'].save()
+ self.el[self.RESULT] = models['template']
+ self.el[self.HAS_RESULT] = True
+ return False
else:
return "GRB no models given. CODE:0x0001"
- 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:
from django.conf import settings
+from django.forms import formset_factory
+
+from typing import List
import json
-import re
from xml.dom import minidom
+import traceback
from workflow.models import WorkflowStep
from account.models import Lab
HardwareDefinitionForm,
NetworkDefinitionForm,
ResourceMetaForm,
+ HostSoftwareDefinitionForm,
)
from resource_inventory.models import (
- ResourceProfile,
ResourceTemplate,
ResourceConfiguration,
InterfaceConfiguration,
Network,
- NetworkConnection
+ NetworkConnection,
+ Image,
)
from dashboard.exceptions import (
InvalidVlanConfigurationException,
NetworkExistsException,
- InvalidHostnameException,
- NonUniqueHostnameException,
ResourceAvailabilityException
)
def get_context(self):
context = super(Define_Hardware, self).get_context()
- context['form'] = self.form or HardwareDefinitionForm()
+ user = self.repo_get(self.repo.SESSION_USER)
+ context['form'] = self.form or HardwareDefinitionForm(user)
return context
def update_models(self, data):
data = data['filter_field']
models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- models['hosts'] = [] # This will always clear existing data when this step changes
+ models['resources'] = [] # This will always clear existing data when this step changes
+ models['connections'] = []
models['interfaces'] = {}
- if "bundle" not in models:
- models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
- host_data = data['host']
- names = {}
- for host_profile_dict in host_data.values():
- id = host_profile_dict['id']
- profile = ResourceProfile.objects.get(id=id)
+ if "template" not in models:
+ template = ResourceTemplate.objects.create(temporary=True)
+ models['template'] = template
+
+ resource_data = data['resource']
+
+ new_template = models['template']
+
+ public_network = Network.objects.create(name="public", bundle=new_template, is_public=True)
+
+ all_networks = {public_network.id: public_network}
+
+ for resource_template_dict in resource_data.values():
+ id = resource_template_dict['id']
+ old_template = ResourceTemplate.objects.get(id=id)
+
# instantiate genericHost and store in repo
- 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
- resourceConfig = ResourceConfiguration(profile=profile, template=models['bundle'])
- models['hosts'].append(resourceConfig)
- for interface_profile in profile.interfaceprofile.all():
- genericInterface = InterfaceConfiguration(profile=interface_profile, resource_config=resourceConfig)
- if resourceConfig.name not in models['interfaces']:
- models['interfaces'][resourceConfig.name] = []
- models['interfaces'][resourceConfig.name].append(genericInterface)
+ for _ in range(0, resource_template_dict['count']):
+ resource_configs = old_template.resourceConfigurations.all()
+ for config in resource_configs:
+ # need to save now for connections to refer to it later
+ new_config = ResourceConfiguration.objects.create(
+ profile=config.profile,
+ image=config.image,
+ name=config.name,
+ template=new_template)
+
+ for interface_config in config.interface_configs.all():
+ new_interface_config = InterfaceConfiguration.objects.create(
+ profile=interface_config.profile,
+ resource_config=new_config)
+
+ for connection in interface_config.connections.all():
+ network = None
+ if connection.network.is_public:
+ network = public_network
+ else:
+ # check if network is known
+ if connection.network.id not in all_networks:
+ # create matching one
+ new_network = Network(
+ name=connection.network.name + "_" + str(new_config.id),
+ bundle=new_template,
+ is_public=False)
+ new_network.save()
+
+ all_networks[connection.network.id] = new_network
+
+ network = all_networks[connection.network.id]
+
+ new_connection = NetworkConnection(
+ network=network,
+ vlan_is_tagged=connection.vlan_is_tagged)
+
+ new_interface_config.save() # can't do later because M2M on next line
+ new_connection.save()
+
+ new_interface_config.connections.add(new_connection)
+
+ unique_resource_ref = new_config.name + "_" + str(new_config.id)
+ if unique_resource_ref not in models['interfaces']:
+ models['interfaces'][unique_resource_ref] = []
+ models['interfaces'][unique_resource_ref].append(interface_config)
+
+ models['resources'].append(new_config)
+
+ models['networks'] = all_networks
# add selected lab to models
for lab_dict in data['lab'].values():
if lab_dict['selected']:
- models['bundle'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
+ models['template'].lab = Lab.objects.get(lab_user__id=lab_dict['id'])
+ models['template'].save()
break # if somehow we get two 'true' labs, we only use one
# return to repo
def update_confirmation(self):
confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "resource" not in confirm:
- confirm['resource'] = {}
- confirm['resource']['hosts'] = []
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {"hosts": []})
- for host in models['hosts']:
- host_dict = {"name": host.resource.name, "profile": host.profile.name}
- confirm['resource']['hosts'].append(host_dict)
- if "lab" in models:
- confirm['resource']['lab'] = models['lab'].lab_user.username
+ if "template" not in confirm:
+ confirm['template'] = {}
+ confirm['template']['resources'] = []
+ models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
+ if 'template' in models:
+ for resource in models['template'].getConfigs():
+ host_dict = {"name": resource.name, "profile": resource.profile.name}
+ confirm['template']['resources'].append(host_dict)
+ if "template" in models:
+ confirm['template']['lab'] = models['template'].lab.lab_user.username
self.repo_put(self.repo.CONFIRMATION, confirm)
def post(self, post_data, user):
try:
- self.form = HardwareDefinitionForm(post_data)
+ user = self.repo_get(self.repo.SESSION_USER)
+ self.form = HardwareDefinitionForm(user, post_data)
if self.form.is_valid():
self.update_models(self.form.cleaned_data)
self.update_confirmation()
else:
self.set_invalid("Please complete the fields highlighted in red to continue")
except Exception as e:
+ print("Caught exception: " + str(e))
+ traceback.print_exc()
self.set_invalid(str(e))
+class Define_Software(WorkflowStep):
+ template = 'config_bundle/steps/define_software.html'
+ title = "Pick Software"
+ description = "Choose the opnfv and image of your machines"
+ short_title = "host config"
+
+ def build_filter_data(self, hosts_data):
+ """
+ Build list of Images to filter out.
+
+ 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.RESOURCE_TEMPLATE_MODELS)['template'].lab
+ for i, host_data in enumerate(hosts_data):
+ host = ResourceConfiguration.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 = []
+ configs = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("resources")
+ if configs:
+ for config in configs:
+ hosts_initial.append({
+ 'host_id': config.id,
+ 'host_name': config.name,
+ 'headnode': config.is_head_node,
+ 'image': config.image
+ })
+ else:
+ for host in hostlist:
+ hosts_initial.append({
+ 'host_id': host.id,
+ 'host_name': host.name
+ })
+
+ HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
+ filter_data = self.build_filter_data(hosts_initial)
+
+ 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
+
+ if data:
+ return SpecialHostFormset(data, initial=hosts_initial)
+ return SpecialHostFormset(initial=hosts_initial)
+
+ def get_host_list(self, grb=None):
+ return self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS).get("resources")
+
+ def get_context(self):
+ context = super(Define_Software, self).get_context()
+
+ context["formset"] = self.create_hostformset(self.get_host_list())
+
+ return context
+
+ def post(self, post_data, user):
+ hosts = self.get_host_list()
+
+ # TODO: fix headnode in form, currently doesn't return a selected one
+ # models['headnode_index'] = post_data.get("headnode", 1)
+ formset = self.create_hostformset(hosts, data=post_data)
+ has_headnode = False
+ if formset.is_valid():
+ for i, form in enumerate(formset):
+ host = hosts[i]
+ image = form.cleaned_data['image']
+ hostname = form.cleaned_data['host_name']
+ headnode = form.cleaned_data['headnode']
+ if headnode:
+ has_headnode = True
+ host.is_head_node = headnode
+ host.name = hostname
+ host.image = image
+ host.save()
+
+ self.set_valid("Completed")
+ else:
+ self.set_invalid("Please complete all fields")
+
+
class Define_Nets(WorkflowStep):
template = 'resource/steps/pod_definition.html'
title = "Define Networks"
except Exception:
return None
+ def make_mx_network_dict(self, network):
+ return {
+ 'id': network.id,
+ 'name': network.name,
+ 'public': network.is_public
+ }
+
+ def make_mx_resource_dict(self, resource_config):
+ resource_dict = {
+ 'id': resource_config.id,
+ 'interfaces': [],
+ 'value': {
+ 'name': resource_config.name,
+ 'id': resource_config.id,
+ 'description': resource_config.profile.description
+ }
+ }
+
+ for interface_config in resource_config.interface_configs.all():
+ connections = []
+ for connection in interface_config.connections.all():
+ connections.append({'tagged': connection.vlan_is_tagged, 'network': connection.network.id})
+
+ interface_dict = {
+ "id": interface_config.id,
+ "name": interface_config.profile.name,
+ "description": "speed: " + str(interface_config.profile.speed) + "M\ntype: " + interface_config.profile.nic_type,
+ "connections": connections
+ }
+
+ resource_dict['interfaces'].append(interface_dict)
+
+ return resource_dict
+
def make_mx_host_dict(self, generic_host):
host = {
'id': generic_host.profile.name,
})
return host
+ # first step guards this one, so can't get here without at least empty
+ # models being populated by step one
def get_context(self):
context = super(Define_Nets, self).get_context()
context.update({
'form': NetworkDefinitionForm(),
'debug': settings.DEBUG,
+ 'resources': {},
+ 'networks': {},
+ 'vlans': [],
+ # remove others
'hosts': [],
'added_hosts': [],
'removed_hosts': []
})
- vlans = self.get_vlans()
- if vlans:
- context['vlans'] = vlans
- try:
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- hosts = models.get("hosts", [])
- # calculate if the selected hosts have changed
- added_hosts = set()
- host_set = set(self.repo_get(self.repo.RCONFIG_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 = 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:
- context['xml'] = bundle.xml or False
- except Exception:
- pass
+ models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS) # infallible, guarded by prior step
+ for resource in models['resources']:
+ d = self.make_mx_resource_dict(resource)
+ context['resources'][d['id']] = d
+
+ for network in models['networks'].values():
+ d = self.make_mx_network_dict(network)
+ context['networks'][d['id']] = d
return context
def post(self, post_data, user):
- models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- if 'hosts' in models:
- host_set = set([h.resource.name + "*" + h.profile.name for h in models['hosts']])
- self.repo_put(self.repo.RCONFIG_LAST_HOSTLIST, host_set)
try:
xmlData = post_data.get("xml")
self.updateModels(xmlData)
except ResourceAvailabilityException:
self.set_invalid("Public network not availble")
except Exception as e:
+ traceback.print_exc()
self.set_invalid("An error occurred when applying networks: " + str(e))
+ def resetNetworks(self, networks: List[Network]): # potentially just pass template here?
+ for network in networks:
+ network.delete()
+
def updateModels(self, xmlData):
models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
- 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:
- existing_hosts[host.resource.name] = host
+ given_hosts = None
+ interfaces = None
+ networks = None
+ try:
+ given_hosts, interfaces, networks = self.parseXml(xmlData)
+ except Exception as e:
+ print("tried to parse Xml, got exception instead:")
+ print(e)
+
+ existing_rconfig_list = models.get("resources", [])
+ existing_rconfigs = {} # maps id to host
+ for rconfig in existing_rconfig_list:
+ existing_rconfigs["host_" + str(rconfig.id)] = rconfig
- bundle = models.get("bundle", ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER)))
+ bundle = models.get("template") # hard fail if not in repo
+
+ self.resetNetworks(models['networks'].values())
+ models['networks'] = {}
for net_id, net in networks.items():
- network = Network()
- network.name = net['name']
- network.bundle = bundle
- network.is_public = net['public']
+ network = Network.objects.create(
+ name=net['name'],
+ bundle=bundle,
+ is_public=net['public'])
+
models['networks'][net_id] = network
+ network.save()
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['connections']:
- models['connections'][existing_host.resource.name] = {}
- models['connections'][existing_host.resource.name][iface['profile_name']] = []
+
+ iface_config = InterfaceConfiguration.objects.get(id=iface['config_id'])
+ if iface_config.resource_config.template.id != bundle.id:
+ raise ValidationError("User does not own the template they are editing")
+
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
+ connection.save()
+ iface_config.connections.add(connection)
+ iface_config.save()
self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
def decomposeXml(self, xmlString):
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']}
+ host = {"interfaces": [], "name": cellId, "hostname": cell_json['name']}
hosts[cellId] = host
# parse networks
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']}
+ iface = {"graph_id": cellId, "connections": [], "config_id": cell_json['id'], "profile_name": cell_json['name']}
hosts[parentId]['interfaces'].append(cellId)
interfaces[cellId] = iface
network = networks[xml_ports[src]]
if not tagged:
- if interface['name'] in untagged_ifaces:
+ if interface['config_id'] in untagged_ifaces:
raise InvalidVlanConfigurationException("More than one untagged vlan on an interface")
- untagged_ifaces.add(interface['name'])
+ untagged_ifaces.add(interface['config_id'])
# add connection to interface
interface['connections'].append({"tagged": tagged, "network": network['id']})
description = "Please fill out the rest of the information about your resource"
short_title = "pod info"
+ def update_confirmation(self):
+ confirm = self.repo_get(self.repo.CONFIRMATION, {})
+ if "template" not in confirm:
+ confirm['template'] = {}
+ models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
+ if "template" in models:
+ confirm['template']['description'] = models['template'].description
+ confirm['template']['name'] = models['template'].name
+ self.repo_put(self.repo.CONFIRMATION, confirm)
+
def get_context(self):
context = super(Resource_Meta_Info, self).get_context()
name = ""
desc = ""
- bundle = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("bundle", False)
- if bundle and bundle.name:
+ models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, None)
+ bundle = models['template']
+ if bundle:
name = bundle.name
desc = bundle.description
context['form'] = ResourceMetaForm(initial={"bundle_name": name, "bundle_description": desc})
models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {})
name = form.cleaned_data['bundle_name']
desc = form.cleaned_data['bundle_description']
- bundle = models.get("bundle", ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER)))
+ bundle = models['template'] # infallible
bundle.name = name
bundle.description = desc
- models['bundle'] = bundle
+ bundle.save()
self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models)
confirm = self.repo_get(self.repo.CONFIRMATION)
if "resource" not in confirm:
+++ /dev/null
-##############################################################################
-# Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, Sean Smith, and others.
-#
-# All rights reserved. This program and the accompanying materials
-# are made available under the terms of the Apache License, Version 2.0
-# which accompanies this distribution, and is available at
-# http://www.apache.org/licenses/LICENSE-2.0
-##############################################################################
-
-
-from django.forms import formset_factory
-
-from workflow.models import WorkflowStep
-from workflow.forms import BasicMetaForm, HostSoftwareDefinitionForm
-from workflow.booking_workflow import Abstract_Resource_Select
-from resource_inventory.models import Image, ResourceConfiguration, ResourceTemplate
-
-
-class SWConf_Resource_Select(Abstract_Resource_Select):
- workflow_type = "configuration"
-
-
-class Define_Software(WorkflowStep):
- template = 'config_bundle/steps/define_software.html'
- title = "Pick Software"
- description = "Choose the opnfv and image of your machines"
- short_title = "host config"
-
- def build_filter_data(self, hosts_data):
- """
- Build list of Images to filter out.
-
- 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_RESOURCE_TEMPLATE).lab
- for i, host_data in enumerate(hosts_data):
- host = ResourceConfiguration.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:
- hosts_initial.append({
- 'host_id': config.id,
- 'host_name': config.profile.name,
- 'headnode': config.is_head_node,
- 'image': config.image
- })
- else:
- for host in hostlist:
- hosts_initial.append({
- 'host_id': host.id,
- 'host_name': host.profile.name
- })
-
- HostFormset = formset_factory(HostSoftwareDefinitionForm, extra=0)
- filter_data = self.build_filter_data(hosts_initial)
-
- 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
-
- 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.SELECTED_RESOURCE_TEMPLATE, False)
- if not grb:
- return []
- if grb.id:
- return ResourceConfiguration.objects.filter(template=grb)
- generic_hosts = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("hosts", [])
- return generic_hosts
-
- def get_context(self):
- context = super(Define_Software, self).get_context()
-
- grb = self.repo_get(self.repo.SELECTED_RESOURCE_TEMPLATE, False)
-
- if grb:
- context["grb"] = grb
- formset = self.create_hostformset(self.get_host_list(grb))
- context["formset"] = formset
- context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1)
- else:
- context["error"] = "Please select a resource first"
- self.set_invalid("Step requires information that is not yet provided by previous step")
-
- return context
-
- def post(self, post_data, user):
- models = self.repo_get(self.repo.CONFIG_MODELS, {})
- if "bundle" not in models:
- models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
-
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
-
- hosts = self.get_host_list()
- models['headnode_index'] = post_data.get("headnode", 1)
- formset = self.create_hostformset(hosts, data=post_data)
- has_headnode = False
- if formset.is_valid():
- models['host_configs'] = []
- confirm_hosts = []
- for i, form in enumerate(formset):
- host = hosts[i]
- if host.is_head_node:
- has_headnode = True
- models['host_configs'].append(host)
- confirm_hosts.append({
- "name": host.profile.name,
- "image": host.image.name,
- "headnode": host.is_head_node
- })
-
- if not has_headnode:
- self.set_invalid('Must have one "Headnode" per POD')
- return
-
- 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.set_valid("Completed")
- else:
- self.set_invalid("Please complete all fields")
-
-
-class Config_Software(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(Config_Software, self).get_context()
-
- initial = {}
- models = self.repo_get(self.repo.CONFIG_MODELS, {})
- bundle = models.get("bundle", False)
- if bundle:
- initial['name'] = bundle.name
- initial['description'] = bundle.description
- context["form"] = BasicMetaForm(initial=initial)
- return context
-
- def post(self, post_data, user):
- models = self.repo_get(self.repo.CONFIG_MODELS, {})
- if "bundle" not in models:
- models['bundle'] = ResourceTemplate(owner=self.repo_get(self.repo.SESSION_USER))
-
- confirm = self.repo_get(self.repo.CONFIRMATION, {})
- if "configuration" not in confirm:
- confirm['configuration'] = {}
-
- form = BasicMetaForm(post_data)
- if form.is_valid():
- models['bundle'].name = form.cleaned_data['name']
- models['bundle'].description = form.cleaned_data['description']
-
- 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)
if not manager:
return no_workflow(request)
- has_more_workflows, result = manager.pop_workflow()
+ has_more_workflows, result = manager.pop_workflow(discard=True)
if not has_more_workflows: # this was the last workflow, so delete the reference to it in the tracker
del ManagerTracker.managers[request.session['manager_session']]
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.resource_bundle_workflow import Define_Hardware, Define_Nets, Resource_Meta_Info, Define_Software
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
resource_steps = [
Define_Hardware,
+ Define_Software,
Define_Nets,
Resource_Meta_Info,
]
- config_steps = [
- SWConf_Resource_Select,
- Define_Software,
- Config_Software,
- ]
-
- # resource_steps = [
- # Define_Hardware,
- # Define_Nets,
- # Define_Software,
- # Software,
- # ]
-
snapshot_steps = [
Select_Host_Step,
Image_Meta_Step,
workflow_types = [
self.booking_steps,
self.resource_steps,
- self.config_steps,
self.snapshot_steps,
self.opnfv_steps,
]
return reverse('booking:booking_detail', kwargs={'booking_id': self.result.id})
return "/"
- def pop_workflow(self):
+ def pop_workflow(self, discard=False):
multiple_wfs = len(self.workflows) > 1
if multiple_wfs:
if self.workflows[-1].repository.el[Repository.RESULT]: # move result
else:
current_repo = prev_workflow.repository
self.result = current_repo.el[current_repo.RESULT]
+ if discard:
+ current_repo.cancel()
return multiple_wfs, self.result
def status(self, request):