display assigned ip's and interfaces on dashboard
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Thu, 4 May 2017 14:51:34 +0000 (16:51 +0200)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Thu, 4 May 2017 14:51:34 +0000 (16:51 +0200)
src/emuvim/api/rest/compute.py
src/emuvim/dashboard/css/main.css
src/emuvim/dashboard/js/main.js
src/emuvim/dashboard/js/main_upb.js [new file with mode: 0755]
src/emuvim/dcemulator/net.py
src/emuvim/dcemulator/node.py

index 8d46aa2..07d059f 100755 (executable)
@@ -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
index 22cff1b..a518f28 100755 (executable)
@@ -45,7 +45,7 @@ body {
 }
 
 .interface_name {
-   width: 100px;
+   width: 150px;
 }
 
 .interface_ip {
index 116bf25..1fcdf4c 100755 (executable)
@@ -66,7 +66,7 @@ function update_table_container(data)
     // clear table
     $("#table_container").empty();
     // header
-    $("#table_container").append('<tr class="tbl-head"><td>Datacenter</td><td>Container</td><td>Image</td><td>docker0</td><td>Networking [datacenter port | interface | ip]</td></tr>');
+    $("#table_container").append('<tr class="tbl-head"><td>Datacenter</td><td>Container</td><td>Image</td><td>docker0</td><td>Networking   [datacenter port | interface | ip]</td></tr>');
     // 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 (executable)
index 0000000..0fb707d
--- /dev/null
@@ -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('<tr class="tbl-head"><td>Label</td><td>Int. Name</td><td>Switch</td><td>Num. Containers</td><td>VNFs</td></tr>');
+    // fill table
+    $.each(data, function(i, item) {
+        var row_str = "";
+        row_str += '<tr class="tbl-row clickable_row" id="datacenter_row_' + i +'">';
+        row_str += '<td>' + item.label + '1</td>';
+        row_str += '<td>' + item.internalname + '</td>';
+        row_str += '<td>' + item.switch + '</td>';
+        row_str += '<td><span class="badge">' + item.n_running_containers + '</span></td>';
+        //row_str += '<td><span class="badge">' + Object.keys(item.metadata).length + '</span></td>';
+        row_str += '<td>' + item.vnf_list + '</span></td>';
+        row_str += '<tr>';
+       $("#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('<tr class="tbl-head"><td>Datacenter</td><td>Container</td><td>Image</td><td>docker0</td><td>Status</td></tr>');
+    // fill table
+    $.each(data, function(i, item) {
+        var row_str = "";
+        row_str += '<tr class="tbl-row clickable_row" id="container_row_' + i +'">';
+        row_str += '<td>' + item[1].datacenter + '</td>';
+        row_str += '<td>' + item[0] + '</td>';
+        row_str += '<td>' + item[1].image + '</td>';
+        row_str += '<td><code>' + item[1].docker_network + '<code></td>';
+        if(item[1].state.Status == "running")
+            row_str += '<td><span class="label label-success">running</span></td>';
+        else
+            row_str += '<td><span class="label label-danger">stopped</span></td>';
+        row_str += '<tr>';
+       $("#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();  
+        }
+    });
+
+});
index faae0e3..8973bb9 100755 (executable)
@@ -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:
index c594f8f..18febb4 100755 (executable)
@@ -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):