diff --git a/ajax/templates/ajax/openstackDeploymentStatus.html b/ajax/templates/ajax/openstackDeploymentStatus.html index 7c7ca87..312462c 100644 --- a/ajax/templates/ajax/openstackDeploymentStatus.html +++ b/ajax/templates/ajax/openstackDeploymentStatus.html @@ -5,6 +5,11 @@ {% else %} is_deployed = true; {% endif %} + + function showSnapSetEditor() { + manuallyTogglePanel('createSnapDet', ''); + } + @@ -12,7 +17,7 @@ {% if stack == None %} - @@ -32,7 +37,7 @@ - @@ -40,7 +45,7 @@ - @@ -104,5 +109,24 @@ onclick="javascript: refreshDeploymentStatus('{{ topology_id }}');"/> + + + + + + + + + {% endif %}
+ Not yet deployed to OpenStack!
Status + {{ stack.stack_status }}
Status Detail + {{ stack.stack_status_reason }}
+ HEAT Snapshots +
+ + Create Snapshots + +   + + List Snapshots + +
diff --git a/ajax/templates/ajax/scriptOutput.html b/ajax/templates/ajax/scriptOutput.html index 3d456ea..60f9c3d 100644 --- a/ajax/templates/ajax/scriptOutput.html +++ b/ajax/templates/ajax/scriptOutput.html @@ -16,7 +16,7 @@ - Manage Scripts + Manage Scripts diff --git a/ajax/templates/ajax/scripts.html b/ajax/templates/ajax/scripts.html index 804de90..388fa50 100644 --- a/ajax/templates/ajax/scripts.html +++ b/ajax/templates/ajax/scripts.html @@ -31,7 +31,7 @@ {% endfor %} - Manage Scripts + Manage Scripts diff --git a/ajax/templates/ajax/snapshot_list.html b/ajax/templates/ajax/snapshot_list.html new file mode 100644 index 0000000..f25ca61 --- /dev/null +++ b/ajax/templates/ajax/snapshot_list.html @@ -0,0 +1,61 @@ +{% extends "base.html" %} +{% block title %}Wistar - Lab Rat - Snapshot List{% endblock %} +{% load staticfiles %} +{% block content %} +
+ +

Stack Snapshot List

+ + + + + + + + + {% for snapshot in snapshot_list %} + + + + + + + {% endfor %} +
NameSnapshot IdStack NameOptions
+ {{snapshot.name }} + + {{ snapshot.id }} + + {{ snapshot.stack_name }} + + + +
+ +
+{% endblock %} diff --git a/ajax/urls.py b/ajax/urls.py index 59520b4..89eebff 100644 --- a/ajax/urls.py +++ b/ajax/urls.py @@ -43,7 +43,15 @@ url(r'^deployTopology/$', views.deploy_topology, name='deployTopology'), url(r'^redeployTopology/$', views.redeploy_topology, name='redeployTopology'), url(r'^deployStack/(?P[^/]+)$', views.deploy_stack, name='deployStack'), + url(r'^updateStack/$', views.update_stack, name='updateStack'), url(r'^deleteStack/(?P[^/]+)$', views.delete_stack, name='deleteStack'), + + url(r'^listSnapshot/(?P[^/]+)$', views.list_snapshot, name='listSnapshot'), + url(r'^deleteSnapshot/(?P[^/]+)/(?P[^/]+)/$', views.delete_snapshot, + name='deleteSnapshot'), + url(r'^rollbackSnapshot/(?P[^/]+)/(?P[^/]+)/$', views.rollback_snapshot, + name='rollbackSnapshot'), + url(r'^startTopology/$', views.start_topology, name='startTopology'), url(r'^pauseTopology/$', views.pause_topology, name='pauseTopology'), url(r'^manageDomain/$', views.manage_domain, name='manageDomain'), diff --git a/ajax/views.py b/ajax/views.py index c3c09fe..cdebcd6 100644 --- a/ajax/views.py +++ b/ajax/views.py @@ -874,6 +874,10 @@ def redeploy_topology(request): except ObjectDoesNotExist: return render(request, 'ajax/ajaxError.html', {'error': "Topology doesn't exist"}) + if configuration.deployment_backend == "openstack": + logger.info('Redirecting to update stack') + return update_stack(request) + try: domains = libvirtUtils.get_domains_for_topology(topology_id) config = wistarUtils.load_config_from_topology_json(topo.json, topology_id) @@ -1183,7 +1187,6 @@ def get_topology_config(request): This is useful to get a list of all objects on the topolgy, filter for objects of a specific type, and verify their boot up state. i.e. to run a command against all Junos devices for example - """ if 'topologyId' not in request.POST: return render(request, 'ajax/ajaxError.html', {'error': "No Topology Id in request"}) @@ -1208,7 +1211,6 @@ def get_topology_config(request): def execute_linux_automation(request): """ execute cli command on all linux instances in topology - """ if 'topologyId' not in request.POST: return render(request, 'ajax/ajaxError.html', {'error': "No Topology Id in request"}) @@ -1253,7 +1255,6 @@ def execute_linux_automation(request): def execute_junos_automation(request): """ execute cli command on all junos instances in topology - """ if 'topologyId' not in request.POST: return render(request, 'ajax/ajaxError.html', {'error': "No Topology Id in request"}) @@ -1486,3 +1487,164 @@ def delete_stack(request, topology_id): logger.debug(openstackUtils.delete_stack(stack_name)) return HttpResponseRedirect('/topologies/' + topology_id + '/') + + +def update_stack(request): + """ + :param request: Django request + :param topology_id: id of the topology to export + :return: renders the updated heat template + """ + required_fields = set(['topologyId']) + if not required_fields.issubset(request.POST): + return render(request, 'ajax/ajaxError.html', {'error': "Invalid Parameters in POST"}) + + topology_id = request.POST['topologyId'] + + logger.debug("-----Inside update stack-----") + try: + topology = Topology.objects.get(pk=topology_id) + except ObjectDoesNotExist: + return render(request, 'error.html', {'error': "Topology not found!"}) + try: + # let's parse the json and convert to simple lists and dicts + logger.debug("loading config") + config = wistarUtils.load_config_from_topology_json(topology.json, topology_id) + logger.debug("Config is loaded") + + # get the tenant_id of the desired project + tenant_id = openstackUtils.get_project_id(configuration.openstack_project) + logger.debug("using tenant_id of: %s" % tenant_id) + if tenant_id is None: + raise Exception("No project found for %s" % configuration.openstack_project) + + # FIXME - verify all images are in glance before jumping off here! + stack_name = topology.name.replace(' ', '_') + + port_list = openstackUtils.get_stack_ports(stack_name, tenant_id) + print(port_list) + heat_template = wistarUtils.get_heat_json_from_topology_config_for_update(config, port_list) + logger.debug("heat template created---test1") + logger.debug(heat_template) + + logger.debug(openstackUtils.update_stack_template(stack_name, heat_template)) + return refresh_deployment_status(request) + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) + + +def list_snapshot(request, topology_id): + """ + :param request: Django request + :param topology_id: id of the topology to export + :return: creates a snapshot of the heat template + """ + try: + logger.debug("Inside create Snapshot----------") + tenant_id = openstackUtils.get_project_id(configuration.openstack_project) + logger.debug("using tenant_id of: %s" % tenant_id) + if tenant_id is None: + raise Exception("No project found for %s" % configuration.openstack_project) + + logger.debug("Topology id -------------------: %s" % topology_id) + + try: + topology = Topology.objects.get(pk=topology_id) + except ObjectDoesNotExist: + logger.error('topology id %s was not found!' % topology_id) + return render(request, 'error.html', {'error': "Topology not found!"}) + + # FIXME - verify all images are in glance before jumping off here! + stack_name = topology.name.replace(' ', '_') + snapshot_list = list() + logger.debug("-------------------stack_name--------------------: %s" % stack_name) + if openstackUtils.connect_to_openstack(): + snapshot_list = openstackUtils.get_snapshot_list(tenant_id, stack_name, topology_id) + + context = {'snapshot_list': snapshot_list} + logger.debug("Before rendering-----------") + return render(request, 'ajax/snapshot_list.html', context) + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) + + +def rollback_snapshot(request, snapshot_id, topology_id): + """ + :param request: Django request + :param topology_id: id of the topology to export + :return: creates a snapshot of the heat template + """ + + try: + logger.debug("Inside rollback Snapshot----------") + tenant_id = openstackUtils.get_project_id(configuration.openstack_project) + logger.debug("using tenant_id of: %s" % tenant_id) + if tenant_id is None: + raise Exception("No project found for %s" % configuration.openstack_project) + + logger.debug("Topology id -------------------: %s" % topology_id) + + try: + topology = Topology.objects.get(pk=topology_id) + except ObjectDoesNotExist: + logger.error('topology id %s was not found!' % topology_id) + return render(request, 'error.html', {'error': "Topology not found!"}) + + # FIXME - verify all images are in glance before jumping off here! + stack_name = topology.name.replace(' ', '_') + logger.debug("Stack name: %s" % stack_name) + logger.debug("Snapshot id: %s" % snapshot_id) + + if openstackUtils.connect_to_openstack(): + logger.debug(openstackUtils.rollback_snapshot(tenant_id, stack_name, snapshot_id)) + + return HttpResponseRedirect('/topologies/' + topology_id + '/') + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) + + +def delete_snapshot(request, snapshot_id, topology_id): + """ + :param request: Django request + :param topology_id: id of the topology to export + :return: creates a snapshot of the heat template + """ + + try: + logger.debug("Inside rollback Snapshot----------") + tenant_id = openstackUtils.get_project_id(configuration.openstack_project) + logger.debug("using tenant_id of: %s" % tenant_id) + if tenant_id is None: + raise Exception("No project found for %s" % configuration.openstack_project) + + logger.debug("Topology id -------------------: %s" % topology_id) + + try: + topology = Topology.objects.get(pk=topology_id) + except ObjectDoesNotExist: + logger.error('topology id %s was not found!' % topology_id) + return render(request, 'error.html', {'error': "Topology not found!"}) + + # FIXME - verify all images are in glance before jumping off here! + stack_name = topology.name.replace(' ', '_') + logger.debug("Stack name: %s" % stack_name) + logger.debug("Snapshot id: %s" % snapshot_id) + + if openstackUtils.connect_to_openstack(): + logger.debug(openstackUtils.delete_snapshot(tenant_id, stack_name, snapshot_id)) + + return HttpResponseRedirect('/topologies/' + topology_id + '/') + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) diff --git a/common/lib/openstackUtils.py b/common/lib/openstackUtils.py index fc48a29..422dfd1 100644 --- a/common/lib/openstackUtils.py +++ b/common/lib/openstackUtils.py @@ -863,3 +863,205 @@ def do_delete(url, data=""): logger.error("Could not perform DELETE to url: %s" % url) logger.error("error was %s" % str(e)) return None + + + + +def create_stack_snapshot(stack_name, tenant_id, snapshot_name): + """ + Creates a snapshot of the Stack via a HEAT REST call + :param stack_name: name of the stack to create + :param tenant_id: tenant id of the openstack project + :param snapshot_name: name of the snapshot + :return: JSON response from HEAT-API or None on failure + """ + logger.debug("In create stack snapshot--------------") + stack_details = get_stack_details(stack_name) + if stack_details is None: + return None + else: + stack_id = str(stack_details["id"]) + + #stack_id = get_stack_details(stack_name) + create_snapshot_url = create_heat_url("/" + str(tenant_id) + "/stacks/%s/%s/snapshots" % (stack_name, stack_id)) + data = """{ + "name": "%s" + }""" % snapshot_name + logger.debug("Data before posting-----------") + logger.debug(data) + + return do_post(create_snapshot_url, data) + + + +def get_snapshot_list(tenant_id, stack_name, topology_id): + + print("In snapshot list") + + stack_details = get_stack_details(stack_name) + if stack_details is None: + return None + else: + stack_id = str(stack_details["id"]) + snapshot_list_url = create_heat_url('/%s/stacks/%s/%s/snapshots' % (tenant_id, stack_name, stack_id)) + stack_snapshot_list = do_get(snapshot_list_url) + l1 = json.loads(stack_snapshot_list) + #l2 = l1['snapshots'] + snap_list = list() + + for snap in l1["snapshots"]: + if snap["status"] == "COMPLETE": + snap_detail = get_snap_detail(snap, stack_name, topology_id) + snap_list.append(snap_detail) + logger.debug(snap_list) + return snap_list + +def get_snap_detail(snap, stack_name, topology_id): + + logger.debug("Getting snapshot details-------------") + logger.debug(snap) + snap_list = dict() + snap_list["name"] = snap["name"] + snap_list["id"] = snap["id"] + snap_list["stack_name"] = stack_name + snap_list["topology_id"] = str(topology_id) + + return snap_list + +def get_server_details(search_string): + server_details_url = create_nova_url('/servers?name=%s' % search_string) + server_details_json = do_get(server_details_url) + server_details_dict = json.loads(server_details_json) + + if server_details_dict is None: + return None + else: + server_details_list = list() + server_details_list = server_details_dict['servers'] + for det in server_details_list: + server_details = det['id'] + logger.debug(server_details) + return server_details + + + + +def delete_snapshot(tenant_id, stack_name, snapshot_id): + """ + Deletes a stack from OpenStack + :param stack_name: name of the stack to be deleted + :return: JSON response fro HEAT API + """ + logger.debug("--- delete_stack_snapshot ---") + + stack_details = get_stack_details(stack_name) + if stack_details is None: + return None + else: + stack_id = stack_details["id"] + url = create_heat_url("/%s/stacks/%s/%s/snapshots/%s" % (tenant_id, stack_name, stack_id, snapshot_id)) + return do_delete(url) + + + +def rollback_snapshot(tenant_id, stack_name, snapshot_id): + """ + Deletes a stack from OpenStack + :param stack_name: name of the stack to be deleted + :return: JSON response fro HEAT API + """ + logger.debug("--- delete_stack_snapshot ---") + data = "" + stack_details = get_stack_details(stack_name) + if stack_details is None: + return None + else: + stack_id = stack_details["id"] + url = create_heat_url("/%s/stacks/%s/%s/snapshots/%s/restore" % (tenant_id, stack_name, stack_id, snapshot_id)) + return do_post(url, data) + + + +def rebuild_instance_openstack(server_id, image_id): + logger.debug("-------Rebuild server openstack") + url = create_nova_url("/servers/%s/action" % server_id) + data = '''{ + "rebuild" : { + "imageRef" : "%s" + } + }''' % str(image_id) + + return do_post(url, data) + + + + +def update_stack_template(stack_name, template_string): + + """ + Creates a Stack via a HEAT template + :param stack_name: name of the stack to create + :param template_string: HEAT template to be used + :return: JSON response from HEAT-API or None on failure + """ + logger.debug("--- update_stack ---") + stack_details = get_stack_details(stack_name) + if stack_details is None: + return None + else: + stack_id = stack_details["id"] + + url = create_heat_url("/" + str(_tenant_id) + "/stacks/%s/%s" % (stack_name, stack_id)) + logger.debug("URL to update stack") + logger.debug(url) + data = '''{ + "disable_rollback": true, + "parameters": {}, + "template": %s + }''' % (template_string) + logger.debug("updating CREATING stack with data:") + logger.debug(data) + try: + request = urllib2.Request(url) + request.add_header("Content-Type", "application/json") + request.add_header("charset", "UTF-8") + request.add_header("X-Auth-Token", _auth_token) + request.get_method = lambda: 'PATCH' + + if data == "": + result = urllib2.urlopen(request) + else: + result = urllib2.urlopen(request, data) + + return result.read() + except URLError as e: + logger.error("Could not perform PUT to url: %s" % url) + logger.error("error was %s" % str(e)) + return None + #return do_post(url, data) + + +def get_stack_ports(stack_name, tenant_id): + stack_details = get_stack_details(stack_name) + + if stack_details is None: + return None + else: + stack_id = str(stack_details["id"]) + + try: + get_port_url = create_heat_url( '/%s/stacks/%s/%s/resources?type=OS::Neutron::Port' % (tenant_id, stack_name, stack_id)) + resources = do_get(get_port_url) + resource_dict = json.loads(resources) + resource_list = resource_dict['resources'] + print(resource_list) + port_list = list() + for port in resource_list: + port_list.append(port['resource_name']) + print(port_list) + return port_list + + except URLError as e: + logger.error("Could not perform PUT to url: %s" % url) + logger.error("error was %s" % str(e)) + return None diff --git a/common/lib/wistarUtils.py b/common/lib/wistarUtils.py index cdaf800..2bd0a04 100644 --- a/common/lib/wistarUtils.py +++ b/common/lib/wistarUtils.py @@ -304,6 +304,7 @@ def get_heat_json_from_topology_config(config, project_name='admin'): # disable port security on all other ports (in case this isn't set globally) p['port_security_enabled'] = False + p["name"] = device["name"] + "_port" + str(index) pr["properties"] = p template["resources"][device["name"] + "_port" + str(index)] = pr index += 1 @@ -311,6 +312,249 @@ def get_heat_json_from_topology_config(config, project_name='admin'): return json.dumps(template) + +def get_heat_json_from_topology_config_for_update(config, port_list): + """ + Generates heat template from the topology configuration object + use load_config_from_topology_json to get the configuration from the Topology + :param config: configuration dict from load_config_from_topology_json + :return: json encoded heat template as String + """ + + template = dict() + template["heat_template_version"] = "2013-05-23" + template["resources"] = dict() + + for network in config["networks"]: + nr = dict() + nr["type"] = "OS::Neutron::Net" + + nrp = dict() + nrp["shared"] = False + nrp["name"] = network["name"] + nrp["admin_state_up"] = True + + nr["properties"] = nrp + + nrs = dict() + nrs["type"] = "OS::Neutron::Subnet" + # + p = dict() + p["cidr"] = "1.1.1.0/24" + p["enable_dhcp"] = False + p["gateway_ip"] = "" + p["name"] = network["name"] + "_subnet" + if network["name"] == "virbr0": + p["network_id"] = configuration.openstack_mgmt_network + elif network["name"] == configuration.openstack_external_network: + p["network_id"] = configuration.openstack_external_network + else: + p["network_id"] = {"get_resource": network["name"]} + + nrs["properties"] = p + + template["resources"][network["name"]] = nr + template["resources"][network["name"] + "_subnet"] = nrs + + # cache the image_details here to avoid multiple REST calls for details about an image type + # as many topologies have lots of the same types of images around + image_details_dict = dict() + + for device in config["devices"]: + + if device["imageId"] in image_details_dict: + image_details = image_details_dict[device["imageId"]] + else: + image_details = imageUtils.get_image_detail(device["imageId"]) + image_details_dict[device["imageId"]] = image_details + + image_name = image_details["name"] + + image_disk_size = 20 + + # set the size in GB, rounding up to the nearest int + if 'size' in image_details: + current_size = int(image_details['size']) + image_disk_size = int(math.ceil(current_size / 100000000)) + + # if the flavor asks for a minimum disk size, let's see if it's larger that what we have + if "min_disk" in image_details and image_details['min_disk'] > image_disk_size: + image_disk_size = image_details["min_disk"] + + # if the user has specified a desired disk size, grab it here so we get the correct flavor + if type(image_disk_size) is int and device["resizeImage"] > image_disk_size: + image_disk_size = device["resizeImage"] + + # determine openstack flavor here + device_ram = int(device["ram"]) + device_cpu = int(device["cpu"]) + + flavor_detail = openstackUtils.get_minimum_flavor_for_specs(configuration.openstack_project, + device_cpu, + device_ram, + image_disk_size + ) + + flavor = flavor_detail["name"] + + dr = dict() + dr["type"] = "OS::Nova::Server" + dr["properties"] = dict() + dr["properties"]["flavor"] = flavor + dr["properties"]["networks"] = [] + index = 0 + for p in device["interfaces"]: + port = dict() + port["port"] = dict() + + if index == 0: + port["port"]["get_resource"] = device["name"] + "_port" + str(index) + else: + if device["name"] + "_port" + str(index) in port_list: + port["port"]["get_resource"] = device["name"] + "_port" + str(index) + "_nora" + else: + port["port"]["get_resource"] = device["name"] + "_port" + str(index) + index += 1 + dr["properties"]["networks"].append(port) + + dr["properties"]["image"] = image_name + dr["properties"]["name"] = device["name"] + + if device["configDriveSupport"]: + dr["properties"]["config_drive"] = True + dr["properties"]["user_data_format"] = "RAW" + metadata = dict() + metadata["hostname"] = device["name"] + metadata["console"] = "vidconsole" + dr["properties"]["metadata"] = metadata + + # let's check all the configDriveParams and look for a junos config + # FIXME - this may need tweaked if we need to include config drive cloud-init support for other platforms + # right now we just need to ignore /boot/loader.conf + for cfp in device["configDriveParams"]: + + if "destination" in cfp and cfp["destination"] == "/boot/loader.conf": + logger.debug("Creating loader.conf config-drive entry") + template_name = cfp["template"] + loader_string = osUtils.compile_config_drive_params_template(template_name, + device["name"], + device["label"], + device["password"], + device["ip"], + device["managementInterface"]) + + logger.debug('----------') + logger.debug(loader_string) + logger.debug('----------') + for l in loader_string.split('\n'): + if '=' in l: + left, right = l.split('=') + if left not in metadata and left != '': + metadata[left] = right.replace('"', '') + + if "destination" in cfp and cfp["destination"] == "/juniper.conf": + logger.debug("Creating juniper.conf config-drive entry") + template_name = cfp["template"] + personality_string = osUtils.compile_config_drive_params_template(template_name, + device["name"], + device["label"], + device["password"], + device["ip"], + device["managementInterface"]) + + dr["properties"]["personality"] = dict() + dr["properties"]["personality"] = {"/config/juniper.conf": personality_string} + else: + logger.debug('No juniper.conf found here ') + + if device['cloudInitSupport']: + logger.debug('creating cloud-init script') + dr["properties"]["config_drive"] = True + dr["properties"]["user_data_format"] = "RAW" + metadata = dict() + metadata["hostname"] = device["name"] + dr["properties"]["metadata"] = metadata + # grab the prefix len from the management subnet which is in the form 192.168.122.0/24 + if '/' in configuration.management_subnet: + management_prefix_len = configuration.management_subnet.split('/')[1] + else: + management_prefix_len = '24' + + management_ip = device['ip'] + '/' + management_prefix_len + + device_config = osUtils.get_cloud_init_config(device['name'], + device['label'], + management_ip, + device['managementInterface'], + device['password']) + + script_string = "" + if "configScriptId" in device and device["configScriptId"] != 0: + logger.debug("Passing script data!") + try: + script = Script.objects.get(pk=int(device["configScriptId"])) + script_string = script.script + device_config["script_param"] = device.get("configScriptParam", '') + logger.debug(script_string) + except ObjectDoesNotExist: + logger.info('config script was specified but was not found!') + + user_data_string = osUtils.render_cloud_init_user_data(device_config, script_string) + dr["properties"]["user_data"] = user_data_string + + template["resources"][device["name"]] = dr + + for device in config["devices"]: + index = 0 + for port in device["interfaces"]: + pr = dict() + pr["type"] = "OS::Neutron::Port" + p = dict() + + if port["bridge"] == "virbr0": + p["network_id"] = configuration.openstack_mgmt_network + + # specify our desired IP address on the management interface + p['fixed_ips'] = list() + fip = dict() + fip['ip_address'] = device['ip'] + p['fixed_ips'].append(fip) + + elif port["bridge"] == configuration.openstack_external_network: + p["network_id"] = configuration.openstack_external_network + else: + p["network_id"] = {"get_resource": port["bridge"]} + # disable port security on all other ports (in case this isn't set globally) + p['port_security_enabled'] = False + + if index == 0: + p["name"] = device["name"] + "_port" + str(index) + else: + if device["name"] + "_port" + str(index) in port_list: + p["name"] = device["name"] + "_port" + str(index) + "_nora" + else: + p["name"] = device["name"] + "_port" + str(index) + + pr["properties"] = p + if index == 0: + template["resources"][device["name"] + "_port" + str(index)] = pr + else: + if device["name"] + "_port" + str(index) in port_list: + template["resources"][device["name"] + "_port" + str(index) + "_nora"] = pr + else: + template["resources"][device["name"] + "_port" + str(index)] = pr + index += 1 + + return json.dumps(template) + + + + + + + + + def _get_management_macs_for_topology(topology_id): """ returns a list of all macs used for management interfaces for a topology diff --git a/topologies/templates/topologies/edit.html b/topologies/templates/topologies/edit.html index e7d823e..f4aecaa 100644 --- a/topologies/templates/topologies/edit.html +++ b/topologies/templates/topologies/edit.html @@ -1217,6 +1217,60 @@ manuallyTogglePanel('configSetCreateRow', 'none'); } +function submitSnapForm() { + var r = confirm("Create Snapshot of the template?"); + + if (r == true) { + + jQuery('#createSnapForm').submit(); + } + } + + +function rebuild_instance() { + + var doc = jQuery(document.documentElement); + doc.css('cursor', 'progress'); + + if ('getName' in s) { + instance_name = s.getName(); + + } else { + console.log('Could not rebuild instance'); + alert('Could not launch rebuild instance'); + doc.css('cursor', ''); + } + var url = '/topologies/rebuildInstance/'; + var params = { + 'instance_name' : instance_name, + 'topology_id' : '{{topo_id}}' + }; + var post = jQuery.post(url, params, function(response) { + var content = jQuery(response); + jQuery('#overlayPanel').empty().append(content); + showOverlay('#overlayPanel'); + }); + post.fail(function() { + alert('Could not perform request!'); + }); + post.always(function() { + doc.css('cursor', ''); + }); + } + + + + + + function rebuild_server_submit() { + var r = confirm("Rebuild the Instance?"); + + if (r == true) { + + jQuery('#rebuildServerForm').submit(); + } + } + function showTopoEditor() { manuallyTogglePanel('topoEditorForm', ''); manuallyTogglePanel('topoInfo', 'none'); @@ -2063,7 +2117,7 @@ - @@ -2071,20 +2125,17 @@ -
- - -
+ Instance Tools
- -
-
+   -
-
-
- +   + +   + {% if global_config.deployment_backend == "openstack" and is_deployed == true %} + + {% endif %}
@@ -2155,6 +2206,11 @@ + {% if global_config.deployment_backend == "openstack" and is_deployed == true %} + + + + {% endif %} @@ -2528,6 +2584,46 @@
+ +
+
+ + + + + + + + + + + + + + + + +
+ +
+
+ + + + + {% if topo_id != None %}
diff --git a/topologies/templates/topologies/overlay/rebuild_instance.html b/topologies/templates/topologies/overlay/rebuild_instance.html new file mode 100644 index 0000000..d31ce7d --- /dev/null +++ b/topologies/templates/topologies/overlay/rebuild_instance.html @@ -0,0 +1,62 @@ + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ X +
+

Rebuild Instance

+
+ Instance Name + + {{instance_name}} +
+ Base Image + + +
+ + + + + +   + +
+ diff --git a/topologies/urls.py b/topologies/urls.py index ce47536..ac816ef 100644 --- a/topologies/urls.py +++ b/topologies/urls.py @@ -31,6 +31,9 @@ url(r'^error/$', views.error, name='error'), url(r'^clone/(?P\d+)/$', views.clone, name='clone'), url(r'^createConfigSet/$', views.create_config_set, name='createConfigSet'), + url(r'^createSnapshot/$', views.create_snapshot_topo, name='createSnapshot'), + url(r'^rebuildInstance/$', views.rebuild_instance, name='rebuildInstance'), + url(r'^rebuildServer/$', views.rebuild_server, name='rebuildServer'), url(r'^delete/(?P\d+)/$', views.delete, name='delete'), url(r'^(?P\d+)/$', views.detail, name='detail'), url(r'^launch/(?P\d+)$', views.launch, name='launch'), diff --git a/topologies/views.py b/topologies/views.py index e6c1f85..500d340 100644 --- a/topologies/views.py +++ b/topologies/views.py @@ -502,3 +502,119 @@ def add_instance_form(request): 'dhcp_reservations': dhcp_reservations, } return render(request, 'topologies/overlay/add_instance.html', context) + + +def rebuild_instance(request): + logger.info('---------rebuild instance--------') + required_fields = set(['instance_name']) + if not required_fields.issubset(request.POST): + return render(request, 'ajax/overlayError.html', {'error': "Invalid Parameters in POST"}) + + instance_name = request.POST['instance_name'] + topo_id = request.POST['topology_id'] + try: + image_list_linux = list() + image_list = Image.objects.all().order_by('name') + for i in image_list: + if not (i.type.startswith('junos')): + image_list_linux.append(i) + + vm_types = configuration.vm_image_types + vm_types_string = json.dumps(vm_types) + logger.debug(vm_types_string) + + image_list_json = serializers.serialize('json', Image.objects.all(), fields=('name', 'type')) + logger.debug(image_list_json) + + search_string = 't%s_%s' % (topo_id, instance_name) + logger.debug(search_string) + if openstackUtils.connect_to_openstack(): + server_id = openstackUtils.get_server_details(search_string) + context = {'image_list': image_list_linux, + 'instance_name': instance_name, + 'vm_types': vm_types_string, + 'image_list_json': image_list_json, + 'server_id': server_id, + 'topo_id': topo_id + } + return render(request, 'topologies/overlay/rebuild_instance.html', context) + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) + + +def rebuild_server(request): + try: + logger.debug("Inside the rebuild method") + required_fields = set(['topoIconImageSelect', 'topo_id', 'server_id']) + if not required_fields.issubset(request.POST): + return render(request, 'ajax/overlayError.html', {'error': "Invalid Parameters in POST"}) + topology_id = request.POST["topo_id"] + server_id = request.POST["server_id"] + image_string = request.POST["topoIconImageSelect"].split(":")[2] + if openstackUtils.connect_to_openstack(): + image_id = openstackUtils.get_image_id_for_name(image_string) + + logger.debug("Parameters %s %s %s" % (server_id, topology_id, image_id)) + + if openstackUtils.connect_to_openstack(): + res = openstackUtils.rebuild_instance_openstack(server_id, image_id) + + logger.debug("----------------Response----------------") + + if res is None: + return render(request, 'error.html', {'error': "Not able to rebuild the server"}) + else: + return HttpResponseRedirect('/topologies/' + topology_id + '/') + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) + + +def create_snapshot_topo(request): + """ + :param request: Django request + :param topology_id: id of the topology to export + :param snap_name: id of the topology to export + :return: creates a snapshot of the heat template + """ + try: + logger.debug("Inside create Snapshot----------") + tenant_id = openstackUtils.get_project_id(configuration.openstack_project) + logger.debug("using tenant_id of: %s" % tenant_id) + if tenant_id is None: + raise Exception("No project found for %s" % configuration.openstack_project) + logger.debug(request.POST) + required_fields = set(['snap_name', 'snapshot_topo_id']) + if not required_fields.issubset(request.POST): + return render(request, 'ajax/ajaxError.html', {'error': "Invalid Parameters in POST"}) + + topology_id = request.POST["snapshot_topo_id"] + snap_name = request.POST["snap_name"] + logger.debug("using tenant_id of: %s" % tenant_id) + logger.debug("Topology id -------------------: %s" % topology_id) + logger.debug("Snap name -------------------: %s" % snap_name) + + try: + topology = Topology.objects.get(pk=topology_id) + except ObjectDoesNotExist: + logger.error('topology id %s was not found!' % topology_id) + return render(request, 'error.html', {'error': "Topology not found!"}) + + # FIXME - verify all images are in glance before jumping off here! + stack_name = topology.name.replace(' ', '_') + + logger.debug("-------------------stack_name--------------------: %s" % stack_name) + if openstackUtils.connect_to_openstack(): + logger.debug(openstackUtils.create_stack_snapshot(stack_name, tenant_id, snap_name)) + + return HttpResponseRedirect('/topologies/' + topology_id + '/') + + except Exception as e: + logger.debug("Caught Exception in deploy") + logger.debug(str(e)) + return render(request, 'error.html', {'error': str(e)}) +