From: peusterm Date: Thu, 2 Feb 2017 16:49:22 +0000 (+0100) Subject: Merge branch 'master' into master X-Git-Tag: v3.1~45^2~12^2 X-Git-Url: https://osm.etsi.org/gitweb/?a=commitdiff_plain;h=8f2063d50f6d8520ca5f960c65e5eed2c5cd7bee;hp=8eac3cc3f7f58f7490ab6714217d985b8992af70;p=osm%2Fvim-emu.git Merge branch 'master' into master --- diff --git a/dashboard/README.md b/dashboard/README.md new file mode 100644 index 0000000..db15c9d --- /dev/null +++ b/dashboard/README.md @@ -0,0 +1,4 @@ +# son-emu Dashboard + +A simple web-based dashboard that polls the REST API and displays running services etc. It does not do much more than son-cli but it looks nicer and improves the visualization of the emulator state for live demos. + diff --git a/dashboard/css/main.css b/dashboard/css/main.css new file mode 100644 index 0000000..7b10dbc --- /dev/null +++ b/dashboard/css/main.css @@ -0,0 +1,34 @@ +body { + margin: 16px; + height: 100%; +} + +#logo { + height: 90px; + text-align: right; +} + +#content { +} + + +.tbl-head { + font-weight: bold; + border-bottom: 1px solid #2c3e50; + background-color: #c9ced3; +} + +.tbl-head > td { + padding-top: 4px; + padding-bottom: 4px; +} + +.tbl-row > td { + padding-top: 4px; + padding-bottom: 4px; + +} + +.spacer { + height: 20px; +} diff --git a/dashboard/img/SONATA_new.png b/dashboard/img/SONATA_new.png new file mode 100644 index 0000000..8fd99f4 Binary files /dev/null and b/dashboard/img/SONATA_new.png differ diff --git a/dashboard/index.html b/dashboard/index.html new file mode 100644 index 0000000..3c31bec --- /dev/null +++ b/dashboard/index.html @@ -0,0 +1,97 @@ + + + + + + + + MeDICINE Dashboard + + + + + + + + + + + + + + + + + + + + + +
+
+

MeDICINE Dashboard

+ +
+ + +
+
+ + +
+ +
Emulated Datacenters  0Lateness: -
+ + +
+
+ +
 
+ +
+ +
Running Containers  0Lateness: -
+ + +
+
+
+ + + + + + + diff --git a/dashboard/js/main.js b/dashboard/js/main.js new file mode 100644 index 0000000..71741f2 --- /dev/null +++ b/dashboard/js/main.js @@ -0,0 +1,173 @@ +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. ContainersMetadata Items'); + // 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 += ''; + $("#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_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); + + // additional refresh on window focus + $(window).focus(function () { + if(CONNECTED) + { + fetch_datacenter(); + fetch_container(); + } + }); + +}); diff --git a/dashboard/son-emu-dashboard-screenshot.png b/dashboard/son-emu-dashboard-screenshot.png new file mode 100644 index 0000000..8274984 Binary files /dev/null and b/dashboard/son-emu-dashboard-screenshot.png differ diff --git a/src/emuvim/api/rest/compute.py b/src/emuvim/api/rest/compute.py index f0c8954..dc2b611 100755 --- a/src/emuvim/api/rest/compute.py +++ b/src/emuvim/api/rest/compute.py @@ -32,6 +32,8 @@ import json logging.basicConfig(level=logging.INFO) +CORS_HEADER = {'Access-Control-Allow-Origin': '*'} + dcs = {} @@ -72,10 +74,10 @@ class Compute(Resource): c = dcs.get(dc_label).startCompute( compute_name, image=image, command=command, network=nw_list) # return docker inspect dict - return c.getStatus(), 200 + return c.getStatus(), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def _update_resource(self, dc_label, compute_name, resource, value): #check if container exists @@ -96,18 +98,18 @@ class Compute(Resource): logging.debug("API CALL: compute status") try: - return dcs.get(dc_label).containers.get(compute_name).getStatus(), 200 + return dcs.get(dc_label).containers.get(compute_name).getStatus(), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def delete(self, dc_label, compute_name): logging.debug("API CALL: compute stop") try: - return dcs.get(dc_label).stopCompute(compute_name), 200 + return dcs.get(dc_label).stopCompute(compute_name), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def _parse_network(self, network_str): ''' @@ -140,14 +142,14 @@ class ComputeList(Resource): all_containers = [] for dc in dcs.itervalues(): all_containers += dc.listCompute() - return [(c.name, c.getStatus()) for c in all_containers], 200 + return [(c.name, c.getStatus()) for c in all_containers], 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 + for c in dcs.get(dc_label).listCompute()], 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER class DatacenterList(Resource): @@ -156,10 +158,10 @@ class DatacenterList(Resource): def get(self): logging.debug("API CALL: datacenter list") try: - return [d.getStatus() for d in dcs.itervalues()], 200 + return [d.getStatus() for d in dcs.itervalues()], 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER class DatacenterStatus(Resource): @@ -168,7 +170,7 @@ class DatacenterStatus(Resource): def get(self, dc_label): logging.debug("API CALL: datacenter status") try: - return dcs.get(dc_label).getStatus(), 200 + return dcs.get(dc_label).getStatus(), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER diff --git a/src/emuvim/api/rest/monitor.py b/src/emuvim/api/rest/monitor.py index c1c7831..6f99bdf 100755 --- a/src/emuvim/api/rest/monitor.py +++ b/src/emuvim/api/rest/monitor.py @@ -39,6 +39,8 @@ import json logging.basicConfig(level=logging.INFO) +CORS_HEADER = {'Access-Control-Allow-Origin': '*'} + net = None @@ -61,10 +63,10 @@ class MonitorInterfaceAction(Resource): else: c = net.monitor_agent.setup_metric(vnf_name, vnf_interface, metric) # return monitor message response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def delete(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=None): logging.debug("REST CALL: stop monitor VNF interface") @@ -74,10 +76,10 @@ class MonitorInterfaceAction(Resource): else: c = net.monitor_agent.stop_metric(vnf_name, vnf_interface, metric) # return monitor message response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER class MonitorFlowAction(Resource): @@ -96,20 +98,20 @@ class MonitorFlowAction(Resource): try: c = net.monitor_agent.setup_flow(vnf_name, vnf_interface, metric, cookie) # return monitor message response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def delete(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=0): logging.debug("REST CALL: stop monitor VNF interface") try: c = net.monitor_agent.stop_flow(vnf_name, vnf_interface, metric, cookie) # return monitor message response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER class MonitorLinkAction(Resource): """ @@ -144,7 +146,7 @@ class MonitorLinkAction(Resource): return self._MonitorLinkAction(vnf_src_name, vnf_dst_name, command=command) except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def delete(self, vnf_src_name, vnf_dst_name): logging.debug("REST CALL: monitor link flow remove") @@ -154,7 +156,7 @@ class MonitorLinkAction(Resource): return self._MonitorLinkAction(vnf_src_name, vnf_dst_name, command=command) except Exception as ex: logging.exception("API error.") - return ex.message, 500 + return ex.message, 500, CORS_HEADER def _MonitorLinkAction(self, vnf_src_name, vnf_dst_name, command=None): # call DCNetwork method, not really datacenter specific API for now... @@ -209,7 +211,7 @@ class MonitorLinkAction(Resource): c2 = net.monitor_agent.stop_flow(vnf_name, vnf_interface, metric, cookie) # return setChain response - return (str(c1) + " " + str(c2)), 200 + return (str(c1) + " " + str(c2)), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") return ex.message, 500 @@ -242,7 +244,8 @@ class MonitorSkewAction(Resource): c = net.monitor_agent.update_skewmon(vnf_name, resource_name, action='stop') # return monitor message response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 \ No newline at end of file + return ex.message, 500, CORS_HEADER + diff --git a/src/emuvim/api/rest/network.py b/src/emuvim/api/rest/network.py index 88ea470..c4ab23f 100755 --- a/src/emuvim/api/rest/network.py +++ b/src/emuvim/api/rest/network.py @@ -38,6 +38,7 @@ from flask import request import json logging.basicConfig(level=logging.INFO) +CORS_HEADER = {'Access-Control-Allow-Origin': '*'} # the global net is set from the topology file, and connected via connectDCNetwork function in rest_api_endpoint.py net = None @@ -109,7 +110,7 @@ class NetworkAction(Resource): monitor=monitor, monitor_placement=monitor_placement) # return setChain response - return str(c), 200 + return str(c), 200, CORS_HEADER except Exception as ex: logging.exception("API error.") - return ex.message, 500 \ No newline at end of file + return ex.message, 500, CORS_HEADER diff --git a/src/emuvim/dcemulator/net.py b/src/emuvim/dcemulator/net.py index b0838e0..1a8d938 100755 --- a/src/emuvim/dcemulator/net.py +++ b/src/emuvim/dcemulator/net.py @@ -202,7 +202,7 @@ class DCNetwork(Containernet): edge_attributes = [p for p in params if p in weight_metrics] for attr in edge_attributes: # if delay: strip ms (need number as weight in graph) - match = re.search('([0-9]*\.?[0-9]+)', params[attr]) + match = re.search('([0-9]*\.?[0-9]+)', str(params[attr])) if match: attr_number = match.group(1) else: