diff options
Diffstat (limited to 'src/workflow/resource_bundle_workflow.py')
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 614 |
1 files changed, 0 insertions, 614 deletions
diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py deleted file mode 100644 index 4e288b5..0000000 --- a/src/workflow/resource_bundle_workflow.py +++ /dev/null @@ -1,614 +0,0 @@ -############################################################################## -# 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.conf import settings -from django.forms import formset_factory -from django.core.exceptions import ValidationError - -from typing import List - -import re -import json -from xml.dom import minidom -import traceback - -from workflow.models import WorkflowStep -from account.models import Lab -from workflow.forms import ( - HardwareDefinitionForm, - NetworkDefinitionForm, - ResourceMetaForm, - HostSoftwareDefinitionForm, -) -from resource_inventory.models import ( - ResourceTemplate, - ResourceConfiguration, - InterfaceConfiguration, - Network, - NetworkConnection, - Image, -) -from dashboard.exceptions import ( - InvalidVlanConfigurationException, - NetworkExistsException, - ResourceAvailabilityException -) - -import logging -logger = logging.getLogger(__name__) - - -class Define_Hardware(WorkflowStep): - - template = 'resource/steps/define_hardware.html' - title = "Define Hardware" - 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() - 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['resources'] = [] # This will always clear existing data when this step changes - models['connections'] = [] - models['interfaces'] = {} - 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 _ 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['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 - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - - def update_confirmation(self): - confirm = self.repo_get(self.repo.CONFIRMATION, {}) - 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: - 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() - self.set_valid("Step Completed") - 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.form = None - self.set_invalid("Please select a lab.") - - -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(architecture=host.profile.architecture) - 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 i in range(len(configs)): - default_name = 'laas-node' - if i > 0: - default_name = default_name + "-" + str(i + 1) - hosts_initial.append({ - 'host_id': configs[i].id, - 'host_name': default_name, - 'headnode': False, - 'image': configs[i].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() - 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 - # RFC921: They must start with a letter, end with a letter or digit and have only letters or digits or hyphen as interior characters - if bool(re.match("^[A-Za-z0-9-]*$", hostname)) is False: - self.set_invalid("Device names must only contain alphanumeric characters and dashes.") - return - if not hostname[0].isalpha() or not hostname[-1].isalnum(): - self.set_invalid("Device names must start with a letter and end with a letter or digit.") - return - for j in range(i): - if j != i and hostname == hosts[j].name: - self.set_invalid("Devices must have unique names. Please try again.") - return - host.save() - - if not has_headnode and len(hosts) > 0: - self.set_invalid("No headnode. Please set a headnode.") - return - - 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" - description = "Use the tool below to draw the network topology of your POD" - short_title = "networking" - form = NetworkDefinitionForm - - def get_vlans(self): - vlans = self.repo_get(self.repo.VLANS) - if vlans: - return vlans - # try to grab some vlans from lab - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - if "bundle" not in models: - return None - lab = models['bundle'].lab - if lab is None or lab.vlan_manager is None: - return None - try: - vlans = lab.vlan_manager.get_vlans(count=lab.vlan_manager.block_size) - self.repo_put(self.repo.VLANS, vlans) - return vlans - 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, - 'interfaces': [], - 'value': { - "name": generic_host.profile.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 - - # 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': [] - }) - - 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): - try: - xmlData = post_data.get("xml") - self.updateModels(xmlData) - # update model with xml - self.set_valid("Networks applied successfully") - 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, {}) - 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("template") # hard fail if not in repo - - self.resetNetworks(models['networks'].values()) - models['networks'] = {} - - for net_id, net in networks.items(): - 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(): - for ifaceId in given_host['interfaces']: - iface = interfaces[ifaceId] - - 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) - connection.save() - iface_config.connections.add(connection) - iface_config.save() - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - - def decomposeXml(self, xmlString): - """ - Translate XML into useable data. - - 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 - 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"): - 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, "hostname": 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('"', '"') - 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 = {"graph_id": cellId, "connections": [], "config_id": cell_json['id'], "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('"', '"') - 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['config_id'] in untagged_ifaces: - raise InvalidVlanConfigurationException("More than one untagged vlan on an interface") - untagged_ifaces.add(interface['config_id']) - - # add connection to interface - interface['connections'].append({"tagged": tagged, "network": network['id']}) - - return hosts, interfaces, networks - - -class Resource_Meta_Info(WorkflowStep): - template = 'resource/steps/meta_info.html' - title = "Extra Info" - 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 = "" - 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}) - return context - - def post(self, post_data, user): - form = ResourceMetaForm(post_data) - if form.is_valid(): - models = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}) - name = form.cleaned_data['bundle_name'] - desc = form.cleaned_data['bundle_description'] - bundle = models['template'] # infallible - bundle.name = name - bundle.description = desc - bundle.save() - self.repo_put(self.repo.RESOURCE_TEMPLATE_MODELS, models) - confirm = self.repo_get(self.repo.CONFIRMATION) - if "resource" not in confirm: - confirm['resource'] = {} - confirm_info = confirm['resource'] - confirm_info["name"] = name - tmp = desc - if len(tmp) > 60: - tmp = tmp[:60] + "..." - confirm_info["description"] = tmp - self.repo_put(self.repo.CONFIRMATION, confirm) - self.set_valid("Step Completed") - else: - self.set_invalid("Please complete all fields.") |