Merge pull request #211 from stevenvanrossem/master
[osm/vim-emu.git] / src / emuvim / dcemulator / node.py
index 3258a9f..5cfc9ef 100755 (executable)
@@ -1,6 +1,29 @@
 """
 """
-Distributed Cloud Emulator (dcemulator)
-(c) 2015 by Manuel Peuster <manuel.peuster@upb.de>
+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
 from mininet.link import Link
 """
 from mininet.node import Docker
 from mininet.link import Link
@@ -9,17 +32,16 @@ import logging
 import time
 import json
 
 import time
 import json
 
-LOG = logging.getLogger("dcemulator")
+LOG = logging.getLogger("dcemulator.node")
 LOG.setLevel(logging.DEBUG)
 
 
 DCDPID_BASE = 1000  # start of switch dpid's used for data center switches
 
 LOG.setLevel(logging.DEBUG)
 
 
 DCDPID_BASE = 1000  # start of switch dpid's used for data center switches
 
-
 class EmulatorCompute(Docker):
     """
     Emulator specific compute node class.
 class EmulatorCompute(Docker):
     """
     Emulator specific compute node class.
-    Inherits from Dockernet's Docker host class.
+    Inherits from Containernet's Docker host class.
     Represents a single container connected to a (logical)
     data center.
     We can add emulator specific helper functions to it.
     Represents a single container connected to a (logical)
     data center.
     We can add emulator specific helper functions to it.
@@ -38,9 +60,17 @@ class EmulatorCompute(Docker):
         Helper method to receive information about the virtual networks
         this compute instance is connected to.
         """
         Helper method to receive information about the virtual networks
         this compute instance is connected to.
         """
-        # format list of tuples (name, Ip, MAC, isUp, status)
-        return [{'intf_name':str(i), 'ip':i.IP(), 'mac':i.MAC(), 'up':i.isUp(), 'status':i.status()}
-                for i in self.intfList()]
+        # get all links and find dc switch interface
+        networkStatusList = []
+        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)
+            # format list of tuples (name, Ip, MAC, isUp, status, dc_portname)
+            intf_dict = {'intf_name': str(i), 'ip': i.IP(), 'mac': i.MAC(), 'up': i.isUp(), 'status': i.status(), 'dc_portname': dc_port_name}
+            networkStatusList.append(intf_dict)
+
+        return networkStatusList
 
     def getStatus(self):
         """
 
     def getStatus(self):
         """
@@ -52,16 +82,19 @@ class EmulatorCompute(Docker):
         status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
         status["image"] = self.dimage
         status["flavor_name"] = self.flavor_name
         status["docker_network"] = self.dcinfo['NetworkSettings']['IPAddress']
         status["image"] = self.dimage
         status["flavor_name"] = self.flavor_name
-        status["cpu_quota"] = self.cpu_quota
-        status["cpu_period"] = self.cpu_period
-        status["cpu_shares"] = self.cpu_shares
-        status["cpuset"] = self.cpuset
-        status["mem_limit"] = self.mem_limit
-        status["memswap_limit"] = self.memswap_limit
+        status["cpu_quota"] = self.resources.get('cpu_quota')
+        status["cpu_period"] = self.resources.get('cpu_period')
+        status["cpu_shares"] = self.resources.get('cpu_shares')
+        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["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["datacenter"] = (None if self.datacenter is None
                                 else self.datacenter.label)
         status["datacenter"] = (None if self.datacenter is None
                                 else self.datacenter.label)
+
         return status
 
 
         return status
 
 
@@ -82,7 +115,7 @@ class Datacenter(object):
         self.name = "dc%d" % Datacenter.DC_COUNTER
         Datacenter.DC_COUNTER += 1
         # use this for user defined names that can be longer than self.name
         self.name = "dc%d" % Datacenter.DC_COUNTER
         Datacenter.DC_COUNTER += 1
         # use this for user defined names that can be longer than self.name
-        self.label = label  
+        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
         # 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
@@ -117,7 +150,7 @@ class Datacenter(object):
     def start(self):
         pass
 
     def start(self):
         pass
 
-    def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny"):
+    def startCompute(self, name, image=None, command=None, network=None, flavor_name="tiny", **params):
         """
         Create a new container as compute resource and connect it to this
         data center.
         """
         Create a new container as compute resource and connect it to this
         data center.
@@ -143,15 +176,25 @@ class Datacenter(object):
             if len(network) < 1:
                 network.append({})
 
             if len(network) < 1:
                 network.append({})
 
+        # apply hard-set resource limits=0
+        cpu_percentage = params.get('cpu_percent')
+        if cpu_percentage:
+            params['cpu_period'] = self.net.cpu_period
+            params['cpu_quota'] = self.net.cpu_period * float(cpu_percentage)
+
         # create the container
         d = self.net.addDocker(
             "%s" % (name),
             dimage=image,
             dcmd=command,
             datacenter=self,
         # create the container
         d = self.net.addDocker(
             "%s" % (name),
             dimage=image,
             dcmd=command,
             datacenter=self,
-            flavor_name=flavor_name
+            flavor_name=flavor_name,
+            environment = {'VNF_NAME':name},
+            **params
         )
 
         )
 
+
+
         # apply resource limits to container if a resource model is defined
         if self._resource_model is not None:
             try:
         # apply resource limits to container if a resource model is defined
         if self._resource_model is not None:
             try:
@@ -168,7 +211,10 @@ 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:
         # 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:
-            # TODO we cannot use TCLink here (see: https://github.com/mpeuster/dockernet/issues/3)
+            # 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'))
         # do bookkeeping
         self.containers[name] = d
             self.net.addLink(d, self.switch, params1=nw, cls=Link, intfName1=nw.get('id'))
         # do bookkeeping
         self.containers[name] = d
@@ -202,6 +248,24 @@ class Datacenter(object):
 
         return True
 
 
         return True
 
+    def attachExternalSAP(self, sap_name, sap_ip):
+        # create SAP as OVS internal interface
+        sap_intf = self.switch.attachInternalIntf(sap_name, sap_ip)
+
+        # add this as a link to the DCnetwork graph, so it is available for routing
+        attr_dict2 = {'src_port_id': sap_name, 'src_port_nr': None,
+                      'src_port_name': sap_name,
+                      'dst_port_id': self.switch.ports[sap_intf], 'dst_port_nr': self.switch.ports[sap_intf],
+                      'dst_port_name': sap_intf.name}
+        self.net.DCNetwork_graph.add_edge(sap_name, self.switch.name, attr_dict=attr_dict2)
+
+        attr_dict2 = {'dst_port_id': sap_name, 'dst_port_nr': None,
+                      'dst_port_name': sap_name,
+                      'src_port_id': self.switch.ports[sap_intf], 'src_port_nr': self.switch.ports[sap_intf],
+                      'src_port_name': sap_intf.name}
+        self.net.DCNetwork_graph.add_edge(self.switch.name, sap_name, attr_dict=attr_dict2)
+
+
     def listCompute(self):
         """
         Return a list of all running containers assigned to this
     def listCompute(self):
         """
         Return a list of all running containers assigned to this
@@ -233,3 +297,19 @@ class Datacenter(object):
         self.net.rm_registrar.register(self, rm)
         LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
 
         self.net.rm_registrar.register(self, rm)
         LOG.info("Assigned RM: %r to DC: %r" % (rm, self))
 
+    @staticmethod
+    def _clean_ifname(name):
+        """
+        Cleans up given string to be a
+        RTNETLINK compatible interface name.
+        :param name: string
+        :return: string
+        """
+        if name is None:
+            return "if0"
+        name = name.replace(":", "-")
+        name = name.replace(" ", "-")
+        name = name.replace(".", "-")
+        name = name.replace("_", "-")
+        return name
+