From 17b6e881a3813e6f1f99100ed384f974eaff36a3 Mon Sep 17 00:00:00 2001 From: stevenvanrossem Date: Thu, 4 May 2017 16:51:34 +0200 Subject: [PATCH] display assigned ip's and interfaces on dashboard --- src/emuvim/api/rest/compute.py | 14 ++- src/emuvim/dashboard/css/main.css | 2 +- src/emuvim/dashboard/js/main.js | 2 +- src/emuvim/dashboard/js/main_upb.js | 187 ++++++++++++++++++++++++++++ src/emuvim/dcemulator/net.py | 12 +- src/emuvim/dcemulator/node.py | 93 +++++++++++--- 6 files changed, 285 insertions(+), 25 deletions(-) create mode 100755 src/emuvim/dashboard/js/main_upb.js diff --git a/src/emuvim/api/rest/compute.py b/src/emuvim/api/rest/compute.py index 8d46aa2..07d059f 100755 --- a/src/emuvim/api/rest/compute.py +++ b/src/emuvim/api/rest/compute.py @@ -123,13 +123,21 @@ class ComputeList(Resource): if dc_label is None or dc_label == 'None': # return list with all compute nodes in all DCs all_containers = [] + all_extSAPs = [] for dc in dcs.itervalues(): all_containers += dc.listCompute() - return [(c.name, c.getStatus()) for c in all_containers], 200, CORS_HEADER + all_extSAPs += dc.listExtSAPs() + + extSAP_list = [(sap.name, sap.getStatus()) for sap in all_extSAPs] + container_list = [(c.name, c.getStatus()) for c in all_containers] + total_list = container_list + extSAP_list + return total_list, 200, CORS_HEADER else: # return list of compute nodes for specified DC - return [(c.name, c.getStatus()) - for c in dcs.get(dc_label).listCompute()], 200, CORS_HEADER + container_list = [(c.name, c.getStatus()) for c in dcs.get(dc_label).listCompute()] + extSAP_list = [(sap.name, sap.getStatus()) for sap in dcs.get(dc_label).listExtSAPs()] + total_list = container_list + extSAP_list + return total_list, 200, CORS_HEADER except Exception as ex: logging.exception("API error.") return ex.message, 500, CORS_HEADER diff --git a/src/emuvim/dashboard/css/main.css b/src/emuvim/dashboard/css/main.css index 22cff1b..a518f28 100755 --- a/src/emuvim/dashboard/css/main.css +++ b/src/emuvim/dashboard/css/main.css @@ -45,7 +45,7 @@ body { } .interface_name { - width: 100px; + width: 150px; } .interface_ip { diff --git a/src/emuvim/dashboard/js/main.js b/src/emuvim/dashboard/js/main.js index 116bf25..1fcdf4c 100755 --- a/src/emuvim/dashboard/js/main.js +++ b/src/emuvim/dashboard/js/main.js @@ -66,7 +66,7 @@ function update_table_container(data) // clear table $("#table_container").empty(); // header - $("#table_container").append('DatacenterContainerImagedocker0Networking [datacenter port | interface | ip]'); + $("#table_container").append('DatacenterContainerImagedocker0Networking [datacenter port | interface | ip]'); // fill table $.each(data, function(i, item) { var row_str = ""; diff --git a/src/emuvim/dashboard/js/main_upb.js b/src/emuvim/dashboard/js/main_upb.js new file mode 100755 index 0000000..0fb707d --- /dev/null +++ b/src/emuvim/dashboard/js/main_upb.js @@ -0,0 +1,187 @@ +var API_HOST = "http://127.0.0.1:5001"; +var ERROR_ALERT = false; +var TIMESTAMP = 0; +var CONNECTED = false; +var LATENESS_UPDATE_INTERVAL = 50; +var DATA_UPDATE_INTERVAL = 1000 * 10; +var LAST_UPDATE_TIMESTAMP_CONTAINER = 0; +var LAST_UPDATE_TIMESTAMP_DATACENTER = 0; + + +function update_lateness_loop() { + lateness_datacenter= (Date.now() - LAST_UPDATE_TIMESTAMP_DATACENTER) / 1000; + $("#lbl_lateness_datacenter").text("Lateness: " + Number(lateness_datacenter).toPrecision(3) + "s"); + lateness_container= (Date.now() - LAST_UPDATE_TIMESTAMP_CONTAINER) / 1000; + $("#lbl_lateness_container").text("Lateness: " + Number(lateness_container).toPrecision(3) + "s"); + // loop while connected + if(CONNECTED) + setTimeout(update_lateness_loop, LATENESS_UPDATE_INTERVAL) +} + + +function errorAjaxConnection() +{ + // only do once + if(!ERROR_ALERT) + { + ERROR_ALERT = true; + // show message + alert("ERROR!\nAPI request failed.\n\n Please check the backend connection.", function() { + // callback + ERROR_ALERT = false; + }); + } +} + + +function update_table_datacenter(data) +{ + console.debug(data) + // clear table + $("#table_datacenter").empty(); + // header + $("#table_datacenter").append('LabelInt. NameSwitchNum. ContainersVNFs'); + // fill table + $.each(data, function(i, item) { + var row_str = ""; + row_str += ''; + row_str += '' + item.label + '1'; + row_str += '' + item.internalname + ''; + row_str += '' + item.switch + ''; + row_str += '' + item.n_running_containers + ''; + //row_str += '' + Object.keys(item.metadata).length + ''; + row_str += '' + item.vnf_list + ''; + row_str += ''; + $("#table_datacenter").append(row_str); + }); + $("#lbl_datacenter_count").text(data.length); + // update lateness counter + LAST_UPDATE_TIMESTAMP_DATACENTER = Date.now(); +} + + +function update_table_container(data) +{ + console.debug(data) + // clear table + $("#table_container").empty(); + // header + $("#table_container").append('DatacenterContainerImagedocker0Status'); + // fill table + $.each(data, function(i, item) { + var row_str = ""; + row_str += ''; + row_str += '' + item[1].datacenter + ''; + row_str += '' + item[0] + ''; + row_str += '' + item[1].image + ''; + row_str += '' + item[1].docker_network + ''; + if(item[1].state.Status == "running") + row_str += 'running'; + else + row_str += 'stopped'; + row_str += ''; + $("#table_container").append(row_str); + }); + $("#lbl_container_count").text(data.length); + // update lateness counter + LAST_UPDATE_TIMESTAMP_CONTAINER = Date.now(); +} + + +function fetch_datacenter() +{ + // do HTTP request and trigger gui update on success + var request_url = API_HOST + "/restapi/datacenter"; + console.debug("fetching from: " + request_url); + $.getJSON(request_url, update_table_datacenter); +} + + +function fetch_container() +{ + // do HTTP request and trigger gui update on success + var request_url = API_HOST + "/restapi/compute"; + console.debug("fetching from: " + request_url); + $.getJSON(request_url, update_table_container); +} + + +function fetch_d3graph() +{ + // do HTTP request and trigger gui update on success + var request_url = API_HOST + "/restapi/network/d3jsgraph"; + console.debug("fetching from: " + request_url); + //$.getJSON(request_url, update_graph); +} + +function fetch_loop() +{ + // only fetch if we are connected + if(!CONNECTED) + return; + + // download data + fetch_datacenter(); + fetch_container(); + + // loop while connected + if(CONNECTED) + setTimeout(fetch_loop, DATA_UPDATE_INTERVAL); +} + + +function connect() +{ + console.info("connect()"); + // get host address + API_HOST = "http://" + $("#text_api_host").val(); + console.debug("API address: " + API_HOST); + // reset data + LAST_UPDATE_TIMESTAMP_DATACENTER = Date.now(); + LAST_UPDATE_TIMESTAMP_CONTAINER = Date.now(); + CONNECTED = true; + // restart lateness counter + update_lateness_loop(); + // restart data fetch loop + fetch_loop(); + // gui updates + $("#btn_disconnect").removeClass("disabled"); + $("#btn_connect").addClass("disabled"); +} + +function disconnect() +{ + console.info("disconnect()"); + CONNECTED = false; + // gui updates + $("#btn_connect").removeClass("disabled"); + $("#btn_disconnect").addClass("disabled"); +} + + +$(document).ready(function(){ + console.info("document ready"); + // setup global connection error handling + /* + $.ajaxSetup({ + "error": errorAjaxConnection + }); + + // add listeners + $("#btn_connect").click(connect); + $("#btn_disconnect").click(disconnect); + */ + setTimeout(fetch_datacenter, 1000);//fetch_datacenter(); + setTimeout(fetch_container, 2000);//fetch_container(); + + + // additional refresh on window focus + $(window).focus(function () { + if(CONNECTED) + { + fetch_datacenter(); + fetch_container(); + } + }); + +}); diff --git a/src/emuvim/dcemulator/net.py b/src/emuvim/dcemulator/net.py index faae0e3..8973bb9 100755 --- a/src/emuvim/dcemulator/net.py +++ b/src/emuvim/dcemulator/net.py @@ -41,7 +41,7 @@ from mininet.link import TCLink from mininet.clean import cleanup import networkx as nx from emuvim.dcemulator.monitoring import DCNetworkMonitor -from emuvim.dcemulator.node import Datacenter, EmulatorCompute +from emuvim.dcemulator.node import Datacenter, EmulatorCompute, EmulatorExtSAP from emuvim.dcemulator.resourcemodel import ResourceModelRegistrar LOG = logging.getLogger("dcemulator.net") @@ -257,7 +257,6 @@ class DCNetwork(Containernet): # make sure that 'type' is set params['type'] = params.get('type','sap_ext') self.DCNetwork_graph.add_node(sap_name, type=params['type']) - LOG.info('add ext sap: {0}'.format(sap_name)) return Containernet.addExtSAP(self, sap_name, sap_ip, **params) def removeExtSAP(self, sap_name, **params): @@ -921,7 +920,14 @@ class DCNetwork(Containernet): dict.update({match[0]:m2}) return dict - def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface): + def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface=None): + + if vnf_src_interface is None: + # take first interface by default + connected_sw = self.DCNetwork_graph.neighbors(vnf_src_name)[0] + link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw] + vnf_src_interface = link_dict[0]['src_port_id'] + for connected_sw in self.DCNetwork_graph.neighbors(vnf_src_name): link_dict = self.DCNetwork_graph[vnf_src_name][connected_sw] for link in link_dict: diff --git a/src/emuvim/dcemulator/node.py b/src/emuvim/dcemulator/node.py index c594f8f..18febb4 100755 --- a/src/emuvim/dcemulator/node.py +++ b/src/emuvim/dcemulator/node.py @@ -67,7 +67,7 @@ class EmulatorCompute(Docker): vnf_interface = str(i) dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface) # format list of tuples (name, Ip, MAC, isUp, status, dc_portname) - intf_dict = {'intf_name': str(i), 'ip': i.IP(), 'netmask': i.prefixLen, 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name} + intf_dict = {'intf_name': str(i), 'ip': "{0}/{1}".format(i.IP(), i.prefixLen), 'netmask': i.prefixLen, 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name} networkStatusList.append(intf_dict) return networkStatusList @@ -98,6 +98,62 @@ class EmulatorCompute(Docker): return status +class EmulatorExtSAP(object): + """ + Emulator specific class that defines an external service access point (SAP) for the service. + Inherits from Containernet's OVSBridge class. + Represents a single OVS switch connected to a (logical) + data center. + We can add emulator specific helper functions to it. + """ + + def __init__(self, sap_name, sap_net, datacenter, **kwargs): + + self.datacenter = datacenter # pointer to current DC + self.net = self.datacenter.net + self.name = sap_name + + LOG.debug("Starting ext SAP instance %r in data center %r" % (sap_name, str(self.datacenter))) + + # create SAP as separate OVS switch with an assigned ip address + self.ip = str(sap_net[1]) + '/' + str(sap_net.prefixlen) + self.subnet = sap_net + # allow connection to the external internet through the host + params = dict(NAT=True) + self.switch = self.net.addExtSAP(sap_name, self.ip, dpid=hex(self._get_next_extSAP_dpid())[2:], **params) + self.switch.start() + + def _get_next_extSAP_dpid(self): + global EXTSAPDPID_BASE + EXTSAPDPID_BASE += 1 + return EXTSAPDPID_BASE + + def getNetworkStatus(self): + """ + Helper method to receive information about the virtual networks + this compute instance is connected to. + """ + # get all links and find dc switch interface + networkStatusList = [] + for i in self.switch.intfList(): + vnf_name = self.name + vnf_interface = str(i) + if vnf_interface == 'lo': + continue + dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface) + # format list of tuples (name, Ip, MAC, isUp, status, dc_portname) + intf_dict = {'intf_name': str(i), 'ip': self.ip, 'netmask': i.prefixLen, 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name} + networkStatusList.append(intf_dict) + + return networkStatusList + + def getStatus(self): + return { + "name": self.switch.name, + "datacenter": self.datacenter.name, + "network": self.getNetworkStatus() + } + class Datacenter(object): """ Represents a logical data center to which compute resources @@ -124,6 +180,8 @@ class Datacenter(object): self.switch = None # keep track of running containers self.containers = {} + # keep track of attached external access points + self.extSAPs = {} # pointer to assigned resource model self._resource_model = None @@ -135,11 +193,6 @@ class Datacenter(object): DCDPID_BASE += 1 return DCDPID_BASE - def _get_next_extSAP_dpid(self): - global EXTSAPDPID_BASE - EXTSAPDPID_BASE += 1 - return EXTSAPDPID_BASE - def create(self): """ Each data center is represented by a single switch to which @@ -255,21 +308,18 @@ class Datacenter(object): return True def attachExternalSAP(self, sap_name, sap_net, **params): - # create SAP as separate OVS switch with an assigned ip address - sap_ip = str(sap_net[1]) + '/' + str(sap_net.prefixlen) - # allow connection to the external internet through the host - params = dict(NAT=True) - sap_switch = self.net.addExtSAP(sap_name, sap_ip, dpid=hex(self._get_next_extSAP_dpid())[2:], **params) - sap_switch.start() - + extSAP = EmulatorExtSAP(sap_name, sap_net, self, **params) # link SAP to the DC switch - self.net.addLink(sap_switch, self.switch, cls=Link) + self.net.addLink(extSAP.switch, self.switch, cls=Link) + self.extSAPs[sap_name] = extSAP def removeExternalSAP(self, sap_name): - sap_switch = self.net.getNodeByName(sap_name) - # link SAP to the DC switch + sap_switch = self.extSAPs[sap_name].switch + #sap_switch = self.net.getNodeByName(sap_name) + # remove link of SAP to the DC switch self.net.removeLink(link=None, node1=sap_switch, node2=self.switch) self.net.removeExtSAP(sap_name) + del self.extSAPs[sap_name] def listCompute(self): """ @@ -278,18 +328,27 @@ class Datacenter(object): """ return list(self.containers.itervalues()) + def listExtSAPs(self): + """ + Return a list of all external SAPs assigned to this + data center. + """ + return list(self.extSAPs.itervalues()) + def getStatus(self): """ Return a dict with status information about this DC. """ container_list = [name for name in self.containers] + ext_saplist = [sap_name for sap_name in self.extSAPs] return { "label": self.label, "internalname": self.name, "switch": self.switch.name, "n_running_containers": len(self.containers), "metadata": self.metadata, - "vnf_list" : container_list + "vnf_list" : container_list, + "ext SAP list" : ext_saplist } def assignResourceModel(self, rm): -- 2.25.1