Fix: Improved performance of REST getStatus endpoint.
[osm/vim-emu.git] / src / emuvim / dcemulator / node.py
index 8612096..87085d8 100755 (executable)
@@ -1,31 +1,29 @@
-"""
-Copyright (c) 2015 SONATA-NFV and Paderborn University
-ALL RIGHTS RESERVED.
-
-Licensed under the Apache License, Version 2.0 (the "License");
-you may not use this file except in compliance with the License.
-You may obtain a copy of the License at
-
-    http://www.apache.org/licenses/LICENSE-2.0
-
-Unless required by applicable law or agreed to in writing, software
-distributed under the License is distributed on an "AS IS" BASIS,
-WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-See the License for the specific language governing permissions and
-limitations under the License.
-
-Neither the name of the SONATA-NFV [, ANY ADDITIONAL AFFILIATION]
-nor the names of its contributors may be used to endorse or promote
-products derived from this software without specific prior written
-permission.
-
-This work has been performed in the framework of the SONATA project,
-funded by the European Commission under Grant number 671517 through
-the Horizon 2020 and 5G-PPP programmes. The authors would like to
-acknowledge the contributions of their colleagues of the SONATA
-partner consortium (www.sonata-nfv.eu).
-"""
-from mininet.node import Docker, OVSBridge
+# Copyright (c) 2015 SONATA-NFV and Paderborn University
+# ALL RIGHTS RESERVED.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#    http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Neither the name of the SONATA-NFV, Paderborn University
+# nor the names of its contributors may be used to endorse or promote
+# products derived from this software without specific prior written
+# permission.
+#
+# This work has been performed in the framework of the SONATA project,
+# funded by the European Commission under Grant number 671517 through
+# the Horizon 2020 and 5G-PPP programmes. The authors would like to
+# acknowledge the contributions of their colleagues of the SONATA
+# partner consortium (www.sonata-nfv.eu).
+from mininet.node import Docker
 from mininet.link import Link
 from emuvim.dcemulator.resourcemodel import NotEnoughResourcesAvailable
 import logging
@@ -38,6 +36,7 @@ LOG.setLevel(logging.DEBUG)
 DCDPID_BASE = 1000  # start of switch dpid's used for data center switches
 EXTSAPDPID_BASE = 2000  # start of switch dpid's used for external SAP switches
 
+
 class EmulatorCompute(Docker):
     """
     Emulator specific compute node class.
@@ -51,7 +50,9 @@ class EmulatorCompute(Docker):
             self, name, dimage, **kwargs):
         self.datacenter = kwargs.get("datacenter")  # pointer to current DC
         self.flavor_name = kwargs.get("flavor_name")
-        LOG.debug("Starting compute instance %r in data center %r" % (name, str(self.datacenter)))
+        self._network_state_cache = None
+        LOG.debug("Starting compute instance %r in data center %r" %
+                  (name, str(self.datacenter)))
         # call original Docker.__init__
         Docker.__init__(self, name, dimage, **kwargs)
 
@@ -65,20 +66,27 @@ class EmulatorCompute(Docker):
         for i in self.intfList():
             vnf_name = self.name
             vnf_interface = str(i)
-            dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface)
+            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': "{0}/{1}".format(i.IP(), i.prefixLen), '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
 
     def getStatus(self):
         """
         Helper method to receive information about this compute instance.
         """
+        # inspect container
+        cinspect = self.dcli.inspect_container(self.dc)
+        # inspect networking (slow, so do only once)
+        if self._network_state_cache is None:
+            self._network_state_cache = self.getNetworkStatus()
+        # build status
         status = {}
         status["name"] = self.name
-        status["network"] = self.getNetworkStatus()
+        status["network"] = self._network_state_cache
         status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
         status["image"] = self.dimage
         status["flavor_name"] = self.flavor_name
@@ -88,10 +96,10 @@ class EmulatorCompute(Docker):
         status["cpuset"] = self.resources.get('cpuset_cpus')
         status["mem_limit"] = self.resources.get('mem_limit')
         status["memswap_limit"] = self.resources.get('memswap_limit')
-        status["state"] = self.dcli.inspect_container(self.dc)["State"]
-        status["id"] = self.dcli.inspect_container(self.dc)["Id"]
-        status["short_id"] = self.dcli.inspect_container(self.dc)["Id"][:12]
-        status["hostname"] = self.dcli.inspect_container(self.dc)["Config"]['Hostname']
+        status["state"] = cinspect["State"]
+        status["id"] = cinspect["Id"]
+        status["short_id"] = cinspect["Id"][:12]
+        status["hostname"] = cinspect["Config"]['Hostname']
         status["datacenter"] = (None if self.datacenter is None
                                 else self.datacenter.label)
 
@@ -113,14 +121,16 @@ class EmulatorExtSAP(object):
         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)))
+        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 = 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):
@@ -140,9 +150,11 @@ class EmulatorExtSAP(object):
             vnf_interface = str(i)
             if vnf_interface == 'lo':
                 continue
-            dc_port_name = self.datacenter.net.find_connected_dc_interface(vnf_name, vnf_interface)
+            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}
+            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
@@ -154,6 +166,7 @@ class EmulatorExtSAP(object):
             "network": self.getNetworkStatus()
         }
 
+
 class Datacenter(object):
     """
     Represents a logical data center to which compute resources
@@ -174,7 +187,8 @@ class Datacenter(object):
         self.label = label
         # dict to store arbitrary metadata (e.g. latitude and longitude)
         self.metadata = metadata
-        # path to which resource information should be logged (e.g. for experiments). None = no logging
+        # path to which resource information should be logged (e.g. for
+        # experiments). None = no logging
         self.resource_log_path = resource_log_path
         # first prototype assumes one "bigswitch" per DC
         self.switch = None
@@ -208,7 +222,8 @@ class Datacenter(object):
     def start(self):
         pass
 
-    def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", properties=dict(), **params):
+    def startCompute(self, name, image=None, command=None, network=None,
+                     flavor_name="tiny", properties=dict(), **params):
         """
         Create a new container as compute resource and connect it to this
         data center.
@@ -221,6 +236,7 @@ class Datacenter(object):
         :return:
         """
         assert name is not None
+        default_net = {"id": "emu0"}
         # no duplications
         if name in [c.name for c in self.net.getAllContainers()]:
             raise Exception("Container with name %s already exists." % name)
@@ -228,12 +244,17 @@ class Datacenter(object):
         if image is None:
             image = "ubuntu:trusty"
         if network is None:
-            network = {}  # {"ip": "10.0.0.254/8"}
+            network = {}
         if isinstance(network, dict):
-            network = [network]  # if we have only one network, put it in a list
+            if len(network) < 1:
+                # create at least one default interface
+                network = default_net
+            # if we have only one network, put it in a list
+            network = [network]
         if isinstance(network, list):
             if len(network) < 1:
-                network.append({})
+                # create at least one default interface
+                network.append(default_net)
 
         # apply hard-set resource limits=0
         cpu_percentage = params.get('cpu_percent')
@@ -245,24 +266,24 @@ class Datacenter(object):
         properties['VNF_NAME'] = name
         # create the container
         d = self.net.addDocker(
-            "%s" % (name),
+            str(name),
             dimage=image,
             dcmd=command,
             datacenter=self,
             flavor_name=flavor_name,
-            environment = env,
+            environment=env,
             **params
         )
 
-
-
         # apply resource limits to container if a resource model is defined
         if self._resource_model is not None:
             try:
                 self._resource_model.allocate(d)
-                self._resource_model.write_allocation_log(d, self.resource_log_path)
+                self._resource_model.write_allocation_log(
+                    d, self.resource_log_path)
             except NotEnoughResourcesAvailable as ex:
-                LOG.warning("Allocation of container %r was blocked by resource model." % name)
+                LOG.warning(
+                    "Allocation of container %r was blocked by resource model." % name)
                 LOG.info(ex.message)
                 # ensure that we remove the container
                 self.net.removeDocker(name)
@@ -272,14 +293,16 @@ class Datacenter(object):
         # if no --net option is given, network = [{}], so 1 empty dict in the list
         # this results in 1 default interface with a default ip address
         for nw in network:
-            # clean up network configuration (e.g. RTNETLINK does not allow ':' in intf names
+            # clean up network configuration (e.g. RTNETLINK does not allow ':'
+            # in intf names
             if nw.get("id") is not None:
                 nw["id"] = self._clean_ifname(nw["id"])
-            # TODO we cannot use TCLink here (see: https://github.com/mpeuster/containernet/issues/3)
-            self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
+            # TODO we cannot use TCLink here (see:
+            # https://github.com/mpeuster/containernet/issues/3)
+            self.net.addLink(d, self.switch, params1=nw,
+                             cls=Link, intfName1=nw.get('id'))
         # do bookkeeping
         self.containers[name] = d
-
         return d  # we might use UUIDs for naming later on
 
     def stopCompute(self, name):
@@ -289,7 +312,8 @@ class Datacenter(object):
         assert name is not None
         if name not in self.containers:
             raise Exception("Container with name %s not found." % name)
-        LOG.debug("Stopping compute instance %r in data center %r" % (name, str(self)))
+        LOG.debug("Stopping compute instance %r in data center %r" %
+                  (name, str(self)))
 
         #  stop the monitored metrics
         if self.net.monitor_agent is not None:
@@ -298,7 +322,8 @@ class Datacenter(object):
         # call resource model and free resources
         if self._resource_model is not None:
             self._resource_model.free(self.containers[name])
-            self._resource_model.write_free_log(self.containers[name], self.resource_log_path)
+            self._resource_model.write_free_log(
+                self.containers[name], self.resource_log_path)
 
         # remove links
         self.net.removeLink(
@@ -318,7 +343,7 @@ class Datacenter(object):
 
     def removeExternalSAP(self, sap_name):
         sap_switch = self.extSAPs[sap_name].switch
-        #sap_switch = self.net.getNodeByName(sap_name)
+        # 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)
@@ -350,8 +375,8 @@ class Datacenter(object):
             "switch": self.switch.name,
             "n_running_containers": len(self.containers),
             "metadata": self.metadata,
-            "vnf_list" : container_list,
-            "ext SAP list" : ext_saplist
+            "vnf_list": container_list,
+            "ext SAP list": ext_saplist
         }
 
     def assignResourceModel(self, rm):
@@ -361,7 +386,8 @@ class Datacenter(object):
         :return:
         """
         if self._resource_model is not None:
-            raise Exception("There is already an resource model assigned to this DC.")
+            raise Exception(
+                "There is already an resource model assigned to this DC.")
         self._resource_model = rm
         self.net.rm_registrar.register(self, rm)
         LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
@@ -381,4 +407,3 @@ class Datacenter(object):
         name = name.replace(".", "-")
         name = name.replace("_", "-")
         return name
-