Merge branch 'master' into master
authorpeusterm <manuel.peuster@uni-paderborn.de>
Thu, 2 Feb 2017 16:49:22 +0000 (17:49 +0100)
committerpeusterm <manuel.peuster@uni-paderborn.de>
Thu, 2 Feb 2017 16:49:22 +0000 (17:49 +0100)
1  2 
src/emuvim/api/rest/compute.py
src/emuvim/api/rest/monitor.py
src/emuvim/api/rest/network.py
src/emuvim/dcemulator/net.py

@@@ -32,6 -32,8 +32,8 @@@ import jso
  
  logging.basicConfig(level=logging.INFO)
  
+ CORS_HEADER = {'Access-Control-Allow-Origin': '*'}
  dcs = {}
  
  
@@@ -48,13 -50,8 +50,13 @@@ class Compute(Resource)
      """
      global dcs
  
 -    def put(self, dc_label, compute_name):
 +    def put(self, dc_label, compute_name, resource=None, value=None):
 +        # check if resource update
 +        if resource and value:
 +           c = self._update_resource(dc_label, compute_name, resource, value)
 +           return c.getStatus(), 200
  
 +        # deploy new container
          # check if json data is a dict
          data = request.json
          if data is None:
              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
 +        d = dcs.get(dc_label).net.getNodeByName(compute_name)
 +        if resource == 'cpu':
 +            cpu_period = int(dcs.get(dc_label).net.cpu_period)
 +            cpu_quota = int(cpu_period * float(value))
 +            #put default values back
 +            if float(value) <= 0:
 +                cpu_period = 100000
 +                cpu_quota = -1
 +            d.updateCpuLimit(cpu_period=cpu_period, cpu_quota=cpu_quota)
 +        return d
 +
 +
      def get(self, dc_label, compute_name):
  
          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 -123,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):
      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):
      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
@@@ -39,6 -39,8 +39,8 @@@ import jso
  
  logging.basicConfig(level=logging.INFO)
  
+ CORS_HEADER = {'Access-Control-Allow-Origin': '*'}
  net = None
  
  
@@@ -53,31 -55,25 +55,31 @@@ class MonitorInterfaceAction(Resource)
      """
      global net
  
 -    def put(self, vnf_name, vnf_interface=None, metric='tx_packets'):
 +    def put(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=None):
          logging.debug("REST CALL: start monitor VNF interface")
          try:
 -            c = net.monitor_agent.setup_metric(vnf_name, vnf_interface, metric)
 +            if cookie:
 +                c = net.monitor_agent.setup_flow(vnf_name, vnf_interface, metric, cookie)
 +            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'):
 +    def delete(self, vnf_name, vnf_interface=None, metric='tx_packets', cookie=None):
          logging.debug("REST CALL: stop monitor VNF interface")
          try:
 -            c = net.monitor_agent.stop_metric(vnf_name, vnf_interface, metric)
 +            if cookie:
 +                c = net.monitor_agent.stop_flow(vnf_name, vnf_interface, metric, cookie)
 +            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):
          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):
 +    """
 +    Add or remove flow monitoring on chains between VNFs.
 +    These chain links are implemented as flow entries in the networks' SDN switches.
 +    The monitoring is an extra flow entry on top of the existing chain, with a specific match. (preserving the chaining)
 +    The counters of this new monitoring flow are exported
 +    :param vnf_src_name: VNF name of the source of the link
 +    :param vnf_dst_name: VNF name of the destination of the link
 +    :param vnf_src_interface: VNF interface name of the source of the link
 +    :param vnf_dst_interface: VNF interface name of the destination of the link
 +    :param weight: weight of the link (can be useful for routing calculations)
 +    :param match: OpenFlow match format of the flow entry
 +    :param bidirectional: boolean value if the link needs to be implemented from src to dst and back
 +    :param cookie: cookie value, identifier of the flow entry to be installed.
 +    :param priority: integer indicating the priority of the flow entry
 +    :param skip_vlan_tag: boolean to indicate whether a new vlan tag should be created for this chain
 +    :param monitor: boolean to indicate whether a new vlan tag should be created for this chain
 +    :param monitor_placement: 'tx'=place the monitoring flowrule at the beginning of the chain, 'rx'=place at the end of the chain
 +    :param metric: tx_packet_rate, tx_byte_rate, rx_packet_rate, rx_byte_rate
 +    :return: message string indicating if the chain action is succesful or not
 +    """
 +
 +    # the global net is set from the topology file, and connected via connectDCNetwork function in rest_api_endpoint.py
 +    global net
 +
 +    def put(self, vnf_src_name, vnf_dst_name):
 +        logging.debug("REST CALL: monitor link flow add")
 +
 +        try:
 +            command = 'add-flow'
 +            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")
 +
 +        try:
 +            command = 'del-flows'
 +            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...
 +        # no check if vnfs are really connected to this datacenter...
 +        try:
 +            # check if json data is a dict
 +            data = request.json
 +            if data is None:
 +                data = {}
 +            elif type(data) is not dict:
 +                data = json.loads(request.json)
 +
 +            vnf_src_interface = data.get("vnf_src_interface")
 +            vnf_dst_interface = data.get("vnf_dst_interface")
 +            weight = data.get("weight")
 +            match = data.get("match")
 +            bidirectional = data.get("bidirectional")
 +            cookie = data.get("cookie")
 +            priority = data.get("priority")
 +            skip_vlan_tag = data.get("skip_vlan_tag")
 +            monitor = data.get("monitor")
 +            monitor_placement = data.get("monitor_placement")
 +
 +            #first install monitor flow
 +            c1 = net.setChain(
 +                vnf_src_name, vnf_dst_name,
 +                vnf_src_interface=vnf_src_interface,
 +                vnf_dst_interface=vnf_dst_interface,
 +                cmd=command,
 +                weight=weight,
 +                match=match,
 +                bidirectional=bidirectional,
 +                cookie=cookie,
 +                priority=priority,
 +                skip_vlan_tag=skip_vlan_tag,
 +                monitor=monitor,
 +                monitor_placement=monitor_placement)
 +
 +            #then export monitor flow
 +            metric = data.get("metric")
 +            if 'rx' in monitor_placement:
 +                vnf_name = vnf_dst_name
 +                vnf_interface = vnf_dst_interface
 +            elif 'tx' in monitor_placement:
 +                vnf_name = vnf_src_name
 +                vnf_interface = vnf_src_interface
 +
 +            c2 = 'command unknown'
 +            if command == 'add-flow':
 +                c2 = net.monitor_agent.setup_flow(vnf_name, vnf_interface, metric, cookie)
 +            elif command == 'del-flows':
 +                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
 +
 +class MonitorSkewAction(Resource):
 +    """
 +    Monitor the counters of a VNF interface
 +    :param vnf_name: name of the VNF to be monitored
 +    :param resource: the resource to be monitored (cpu, mem, ...)
 +    :return: message string indicating if the monitor action is succesful or not
 +    """
 +    global net
 +
 +    def put(self, vnf_name, resource_name='cpu'):
 +        logging.debug("REST CALL: start monitor skewness")
 +        try:
 +            # configure skewmon
 +            c = net.monitor_agent.update_skewmon(vnf_name, resource_name, action='start')
 +
 +            # return monitor message response
 +            return  str(c), 200
 +        except Exception as ex:
 +            logging.exception("API error.")
 +            return ex.message, 500
 +
 +    def delete(self, vnf_name, resource_name='cpu'):
 +        logging.debug("REST CALL: stop monitor skewness")
 +        try:
 +            # configure skewmon
 +            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
++            return ex.message, 500, CORS_HEADER
++
@@@ -38,8 -38,9 +38,9 @@@ from flask import reques
  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
  
  
@@@ -55,9 -56,6 +56,9 @@@ class NetworkAction(Resource)
      :param bidirectional: boolean value if the link needs to be implemented from src to dst and back
      :param cookie: cookie value, identifier of the flow entry to be installed.
      :param priority: integer indicating the priority of the flow entry
 +    :param skip_vlan_tag: boolean to indicate whether a new vlan tag should be created for this chain
 +    :param monitor: boolean to indicate whether a new vlan tag should be created for this chain
 +    :param monitor_placement: 'tx'=place the monitoring flowrule at the beginning of the chain, 'rx'=place at the end of the chain
      :return: message string indicating if the chain action is succesful or not
      """
  
              bidirectional = data.get("bidirectional")
              cookie = data.get("cookie")
              priority = data.get("priority")
 +            skip_vlan_tag = data.get("skip_vlan_tag")
 +            monitor = data.get("monitor")
 +            monitor_placement = data.get("monitor_placement")
 +
              c = net.setChain(
                  vnf_src_name, vnf_dst_name,
                  vnf_src_interface=vnf_src_interface,
                  match=match,
                  bidirectional=bidirectional,
                  cookie=cookie,
 -                priority=priority)
 +                priority=priority,
 +                skip_vlan_tag=skip_vlan_tag,
 +                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
+             return ex.message, 500, CORS_HEADER
@@@ -32,7 -32,6 +32,7 @@@ import tim
  from subprocess import Popen
  import re
  import requests
 +import os
  
  from mininet.net import Containernet
  from mininet.node import Controller, DefaultController, OVSSwitch, OVSKernelSwitch, Docker, RemoteController
@@@ -47,9 -46,6 +47,9 @@@ from emuvim.dcemulator.resourcemodel im
  LOG = logging.getLogger("dcemulator.net")
  LOG.setLevel(logging.DEBUG)
  
 +# default CPU period used for cpu percentage-based cfs values (microseconds)
 +CPU_PERIOD = 1000000
 +
  class DCNetwork(Containernet):
      """
      Wraps the original Mininet/Containernet class and provides
@@@ -59,7 -55,7 +59,7 @@@
      """
  
      def __init__(self, controller=RemoteController, monitor=False,
 -                 enable_learning = True,   # in case of RemoteController (Ryu), learning switch behavior can be turned off/on
 +                 enable_learning=False, # learning switch behavior of the default ovs switches icw Ryu controller can be turned off/on, needed for E-LAN functionality
                   dc_emulation_max_cpu=1.0,  # fraction of overall CPU time for emulation
                   dc_emulation_max_mem=512,  # emulation max mem in MB
                   **kwargs):
          # members
          self.dcs = {}
          self.ryu_process = None
 +        #list of deployed nsds.E_Lines and E_LANs (uploaded from the dummy gatekeeper)
 +        self.deployed_nsds = []
 +        self.deployed_elines = []
 +        self.deployed_elans = []
 +        self.installed_chains = []
 +
  
          # always cleanup environment before we start the emulator
          self.killRyu()
          Containernet.__init__(
              self, switch=OVSKernelSwitch, controller=controller, **kwargs)
  
 +        # default switch configuration
 +        enable_ryu_learning = False
 +        if enable_learning :
 +            self.failMode = 'standalone'
 +            enable_ryu_learning = True
 +        else:
 +            self.failMode = 'secure'
 +
          # Ryu management
          if controller == RemoteController:
              # start Ryu controller
 -            self.startRyu(learning_switch=enable_learning)
 +            self.startRyu(learning_switch=enable_ryu_learning)
  
          # add the specified controller
          self.addController('c0', controller=controller)
          # initialize resource model registrar
          self.rm_registrar = ResourceModelRegistrar(
              dc_emulation_max_cpu, dc_emulation_max_mem)
 +        self.cpu_period = CPU_PERIOD
  
      def addDatacenter(self, label, metadata={}, resource_log_path=None):
          """
          """
          assert node1 is not None
          assert node2 is not None
 -        LOG.debug("addLink: n1=%s n2=%s" % (str(node1), str(node2)))
 +
          # ensure type of node1
          if isinstance( node1, basestring ):
              if node1 in self.dcs:
          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:
          attr_dict2.update(attr_dict)
          self.DCNetwork_graph.add_edge(node2.name, node1.name, attr_dict=attr_dict2)
  
 +        LOG.debug("addLink: n1={0} intf1={1} -- n2={2} intf2={3}".format(
 +            str(node1),node1_port_name, str(node2), node2_port_name))
 +
          return link
  
      def addDocker( self, label, **params ):
          """
          Wrapper for addSwitch method to store switch also in graph.
          """
 +
 +        # add this switch to the global topology overview
          if add_to_graph:
              self.DCNetwork_graph.add_node(name)
 -        return Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
 +
 +        # set the learning switch behavior
 +        if 'failMode' in params :
 +            failMode = params['failMode']
 +        else :
 +            failMode = self.failMode
 +
 +        s = Containernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', failMode=failMode, **params)
 +
 +        # set flow entry that enables learning switch behavior (needed to enable E-LAN functionality)
 +        #LOG.info('failmode {0}'.format(failMode))
 +        #if failMode == 'standalone' :
 +        #    LOG.info('add NORMAL')
 +        #    s.dpctl('add-flow', 'actions=NORMAL')
 +
 +        return s
  
      def getAllContainers(self):
          """
      def CLI(self):
          CLI(self)
  
 +    def setLAN(self, vnf_list):
 +        """
 +        setup an E-LAN network by assigning the same VLAN tag to each DC interface of the VNFs in the E-LAN
 +
 +        :param vnf_list: names of the VNFs in this E-LAN  [{name:,interface:},...]
 +        :return:
 +        """
 +        src_sw = None
 +        src_sw_inport_nr = 0
 +        src_sw_inport_name = None
 +
 +        # get a vlan tag for this E-LAN
 +        vlan = self.vlans.pop()
 +
 +        for vnf in vnf_list:
 +            vnf_src_name = vnf['name']
 +            vnf_src_interface = vnf['interface']
 +
 +            # check if port is specified (vnf:port)
 +            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:
 +                    if (link_dict[link]['src_port_id'] == vnf_src_interface or
 +                                link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
 +                        # found the right link and connected switch
 +                        src_sw = connected_sw
 +                        src_sw_inport_nr = link_dict[link]['dst_port_nr']
 +                        src_sw_inport_name = link_dict[link]['dst_port_name']
 +                        break
 +
 +            # set the tag on the dc switch interface
 +            LOG.debug('set E-LAN: vnf name: {0} interface: {1} tag: {2}'.format(vnf_src_name, vnf_src_interface,vlan))
 +            switch_node = self.getNodeByName(src_sw)
 +            self._set_vlan_tag(switch_node, src_sw_inport_name, vlan)
 +
 +    def _addMonitorFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None,
 +                       tag=None, **kwargs):
 +        """
 +        Add a monitoring flow entry that adds a special flowentry/counter at the begin or end of a chain.
 +        So this monitoring flowrule exists on top of a previously defined chain rule and uses the same vlan tag/routing.
 +        :param vnf_src_name:
 +        :param vnf_dst_name:
 +        :param vnf_src_interface:
 +        :param vnf_dst_interface:
 +        :param tag: vlan tag to be used for this chain (same tag as existing chain)
 +        :param monitor_placement: 'tx' or 'rx' indicating to place the extra flowentry resp. at the beginning or end of the chain
 +        :return:
 +        """
 +
 +        src_sw = None
 +        src_sw_inport_nr = 0
 +        src_sw_inport_name = None
 +        dst_sw = None
 +        dst_sw_outport_nr = 0
 +        dst_sw_outport_name = None
 +
 +        LOG.debug("call AddMonitorFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
 +                  vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
 +
 +        #check if port is specified (vnf:port)
 +        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:
 +                if (link_dict[link]['src_port_id'] == vnf_src_interface or
 +                        link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
 +                    # found the right link and connected switch
 +                    src_sw = connected_sw
 +                    src_sw_inport_nr = link_dict[link]['dst_port_nr']
 +                    src_sw_inport_name = link_dict[link]['dst_port_name']
 +                    break
 +
 +        if vnf_dst_interface is None:
 +            # take first interface by default
 +            connected_sw = self.DCNetwork_graph.neighbors(vnf_dst_name)[0]
 +            link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
 +            vnf_dst_interface = link_dict[0]['dst_port_id']
 +
 +        vnf_dst_name = vnf_dst_name.split(':')[0]
 +        for connected_sw in self.DCNetwork_graph.neighbors(vnf_dst_name):
 +            link_dict = self.DCNetwork_graph[connected_sw][vnf_dst_name]
 +            for link in link_dict:
 +                if link_dict[link]['dst_port_id'] == vnf_dst_interface or \
 +                        link_dict[link]['dst_port_name'] == vnf_dst_interface:  # Fix: we might also get interface names, e.g, from a son-emu-cli call
 +                    # found the right link and connected switch
 +                    dst_sw = connected_sw
 +                    dst_sw_outport_nr = link_dict[link]['src_port_nr']
 +                    dst_sw_outport_name = link_dict[link]['src_port_name']
 +                    break
 +
 +        if not tag >= 0:
 +            LOG.exception('tag not valid: {0}'.format(tag))
 +
 +        # get shortest path
 +        try:
 +            # returns the first found shortest path
 +            # if all shortest paths are wanted, use: all_shortest_paths
 +            path = nx.shortest_path(self.DCNetwork_graph, src_sw, dst_sw, weight=kwargs.get('weight'))
 +        except:
 +            LOG.exception("No path could be found between {0} and {1} using src_sw={2} and dst_sw={3}".format(
 +                vnf_src_name, vnf_dst_name, src_sw, dst_sw))
 +            LOG.debug("Graph nodes: %r" % self.DCNetwork_graph.nodes())
 +            LOG.debug("Graph edges: %r" % self.DCNetwork_graph.edges())
 +            for e, v in self.DCNetwork_graph.edges():
 +                LOG.debug("%r" % self.DCNetwork_graph[e][v])
 +            return "No path could be found between {0} and {1}".format(vnf_src_name, vnf_dst_name)
 +
 +        LOG.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
 +
 +        current_hop = src_sw
 +        switch_inport_nr = src_sw_inport_nr
 +
 +        cmd = kwargs.get('cmd')
 +
 +        #iterate through the path to install the flow-entries
 +        for i in range(0,len(path)):
 +            current_node = self.getNodeByName(current_hop)
 +
 +            if path.index(current_hop) < len(path)-1:
 +                next_hop = path[path.index(current_hop)+1]
 +            else:
 +                #last switch reached
 +                next_hop = vnf_dst_name
 +
 +            next_node = self.getNodeByName(next_hop)
 +
 +            if next_hop == vnf_dst_name:
 +                switch_outport_nr = dst_sw_outport_nr
 +                LOG.info("end node reached: {0}".format(vnf_dst_name))
 +            elif not isinstance( next_node, OVSSwitch ):
 +                LOG.info("Next node: {0} is not a switch".format(next_hop))
 +                return "Next node: {0} is not a switch".format(next_hop)
 +            else:
 +                # take first link between switches by default
 +                index_edge_out = 0
 +                switch_outport_nr = self.DCNetwork_graph[current_hop][next_hop][index_edge_out]['src_port_nr']
 +
 +
 +           # set of entry via ovs-ofctl
 +            if isinstance( current_node, OVSSwitch ):
 +                kwargs['vlan'] = tag
 +                kwargs['path'] = path
 +                kwargs['current_hop'] = current_hop
 +                kwargs['switch_inport_name'] = src_sw_inport_name
 +                kwargs['switch_outport_name'] = dst_sw_outport_name
 +                kwargs['skip_vlan_tag'] = True
 +
 +                monitor_placement = kwargs.get('monitor_placement').strip()
 +                # put monitor flow at the dst switch
 +                insert_flow = False
 +                if monitor_placement == 'tx' and path.index(current_hop) == 0:  # first node:
 +                    insert_flow = True
 +                # put monitoring flow at the src switch
 +                elif monitor_placement == 'rx' and path.index(current_hop) == len(path) - 1:  # last node:
 +                    insert_flow = True
 +                elif monitor_placement not in ['rx', 'tx']:
 +                    LOG.exception('invalid monitor command: {0}'.format(monitor_placement))
 +
 +
 +                if self.controller == RemoteController and insert_flow:
 +                    ## set flow entry via ryu rest api
 +                    self._set_flow_entry_ryu_rest(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
 +                    break
 +                elif insert_flow:
 +                    ## set flow entry via ovs-ofctl
 +                    self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
 +                    break
 +
 +            # take first link between switches by default
 +            if isinstance( next_node, OVSSwitch ):
 +                switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
 +                current_hop = next_hop
 +
 +        return "path {2} between {0} and {1}".format(vnf_src_name, vnf_dst_name, cmd)
 +
 +
      def setChain(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
          """
          Chain 2 vnf interfaces together by installing the flowrules in the switches along their path.
          :param cookie: cookie for the installed flowrules (can be used later as identifier for a set of installed chains)
          :param match: custom match entry to be added to the flowrules (default: only in_port and vlan tag)
          :param priority: custom flowrule priority
 +<<<<<<< HEAD
 +        :param monitor: boolean to indicate whether this chain is a monitoring chain
 +        :param tag: vlan tag to be used for this chain (pre-defined or new one if none is specified)
 +=======
          :param path: custom path between the two VNFs (list of switches)
 +>>>>>>> upstream/master
          :return: output log string
          """
 +
 +        # special procedure for monitoring flows
 +        if kwargs.get('monitor'):
 +
 +            # check if chain already exists
 +            found_chains = [chain_dict for chain_dict in self.installed_chains if
 +             (chain_dict['vnf_src_name'] == vnf_src_name and chain_dict['vnf_src_interface'] == vnf_src_interface
 +             and chain_dict['vnf_dst_name'] == vnf_dst_name and chain_dict['vnf_dst_interface'] == vnf_dst_interface)]
 +
 +            if len(found_chains) > 0:
 +                # this chain exists, so need an extra monitoring flow
 +                # assume only 1 chain per vnf/interface pair
 +                LOG.debug('*** installing monitoring chain on top of pre-defined chain from {0}:{1} -> {2}:{3}'.
 +                            format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
 +                tag = found_chains[0]['tag']
 +                ret = self._addMonitorFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface,
 +                                     tag=tag, table_id=0, **kwargs)
 +                return ret
 +            else:
 +                # no chain existing (or E-LAN) -> install normal chain
 +                LOG.warning('*** installing monitoring chain without pre-defined NSD chain from {0}:{1} -> {2}:{3}'.
 +                            format(vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface))
 +                pass
 +
 +
          cmd = kwargs.get('cmd')
 -        if cmd == 'add-flow':
 +        if cmd == 'add-flow' or cmd == 'del-flows':
              ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
              if kwargs.get('bidirectional'):
                  if kwargs.get('path') is not None:
                      kwargs['path'] = list(reversed(kwargs.get('path')))
                  ret = ret +'\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
  
 -        elif cmd == 'del-flows':
 -            ret = self._chainAddFlow(vnf_src_name, vnf_dst_name, vnf_src_interface, vnf_dst_interface, **kwargs)
 -            if kwargs.get('bidirectional'):
 -                if kwargs.get('path') is not None:
 -                    kwargs['path'] = list(reversed(kwargs.get('path')))
 -                ret = ret + '\n' + self._chainAddFlow(vnf_dst_name, vnf_src_name, vnf_dst_interface, vnf_src_interface, **kwargs)
 -
          else:
              ret = "Command unknown"
  
      def _chainAddFlow(self, vnf_src_name, vnf_dst_name, vnf_src_interface=None, vnf_dst_interface=None, **kwargs):
  
          src_sw = None
 -        dst_sw = None
          src_sw_inport_nr = 0
 +        src_sw_inport_name = None
 +        dst_sw = None
          dst_sw_outport_nr = 0
 +        dst_sw_outport_name = None
  
          LOG.debug("call chainAddFlow vnf_src_name=%r, vnf_src_interface=%r, vnf_dst_name=%r, vnf_dst_interface=%r",
                    vnf_src_name, vnf_src_interface, vnf_dst_name, vnf_dst_interface)
                      # found the right link and connected switch
                      src_sw = connected_sw
                      src_sw_inport_nr = link_dict[link]['dst_port_nr']
 +                    src_sw_inport_name = link_dict[link]['dst_port_name']
                      break
  
          if vnf_dst_interface is None:
                      # found the right link and connected switch
                      dst_sw = connected_sw
                      dst_sw_outport_nr = link_dict[link]['src_port_nr']
 +                    dst_sw_outport_name = link_dict[link]['src_port_name']
                      break
  
          path = kwargs.get('path')
          current_hop = src_sw
          switch_inport_nr = src_sw_inport_nr
  
 -        # choose free vlan if path contains more than 1 switch
 +        # choose free vlan
 +        ## if path contains more than 1 switch
          cmd = kwargs.get('cmd')
          vlan = None
          if cmd == 'add-flow':
 -            if len(path) > 1:
 +            if kwargs.get('tag'):
 +                # use pre-defined tag
 +                vlan = kwargs.get('tag')
 +            else:
                  vlan = self.vlans.pop()
  
 +        # store the used vlan tag to identify this chain
 +        if not kwargs.get('monitor'):
 +            chain_dict = {}
 +            chain_dict['vnf_src_name'] = vnf_src_name
 +            chain_dict['vnf_dst_name'] = vnf_dst_name
 +            chain_dict['vnf_src_interface'] = vnf_src_interface
 +            chain_dict['vnf_dst_interface'] = vnf_dst_interface
 +            chain_dict['tag'] = vlan
 +            self.installed_chains.append(chain_dict)
 +
 +        #iterate through the path to install the flow-entries
          for i in range(0,len(path)):
              current_node = self.getNodeByName(current_hop)
  
              if isinstance( current_node, OVSSwitch ):
                  kwargs['vlan'] = vlan
                  kwargs['path'] = path
 +                kwargs['current_hop'] = current_hop
 +                kwargs['switch_inport_name'] = src_sw_inport_name
 +                kwargs['switch_outport_name'] = dst_sw_outport_name
                  kwargs['pathindex'] = i
  
                  if self.controller == RemoteController:
                      ## set flow entry via ovs-ofctl
                      self._set_flow_entry_dpctl(current_node, switch_inport_nr, switch_outport_nr, **kwargs)
  
 -
 -
              # take first link between switches by default
              if isinstance( next_node, OVSSwitch ):
                  switch_inport_nr = self.DCNetwork_graph[current_hop][next_hop][0]['dst_port_nr']
  
          vlan = kwargs.get('vlan')
          priority = kwargs.get('priority')
 +        # flag to not set the ovs port vlan tag
 +        skip_vlan_tag = kwargs.get('skip_vlan_tag')
 +        # table id to put this flowentry
 +        table_id = kwargs.get('table_id')
 +        if not table_id:
 +            table_id = 0
  
          s = ','
          if match_input:
          if priority:
              flow['priority'] = int(priority)
  
 +        flow['table_id'] = table_id
 +
          flow['actions'] = []
  
          # possible Ryu actions, match fields:
              prefix = 'stats/flowentry/add'
              if vlan != None:
                  if index == 0:  # first node
 -                    action = {}
 -                    action['type'] = 'PUSH_VLAN'  # Push a new VLAN tag if a input frame is non-VLAN-tagged
 -                    action['ethertype'] = 33024   # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
 -                    flow['actions'].append(action)
 -                    action = {}
 -                    action['type'] = 'SET_FIELD'
 -                    action['field'] = 'vlan_vid'
 -                    # ryu expects the field to be masked
 -                    action['value'] = vlan | 0x1000
 -                    flow['actions'].append(action)
 -                elif index == len(path) -1:  # last node
 -                    match += ',dl_vlan=%s' % vlan
 -                    action = {}
 -                    action['type'] = 'POP_VLAN'
 -                    flow['actions'].append(action)
 +                    # set vlan tag in ovs instance (to isolate E-LANs)
 +                    if not skip_vlan_tag:
 +                        in_port_name = kwargs.get('switch_inport_name')
 +                        self._set_vlan_tag(node, in_port_name, vlan)
 +                    # set vlan push action if more than 1 switch in the path
 +                    if len(path) > 1:
 +                        action = {}
 +                        action['type'] = 'PUSH_VLAN'  # Push a new VLAN tag if a input frame is non-VLAN-tagged
 +                        action['ethertype'] = 33024   # Ethertype 0x8100(=33024): IEEE 802.1Q VLAN-tagged frame
 +                        flow['actions'].append(action)
 +                        action = {}
 +                        action['type'] = 'SET_FIELD'
 +                        action['field'] = 'vlan_vid'
 +                        # ryu expects the field to be masked
 +                        action['value'] = vlan | 0x1000
 +                        flow['actions'].append(action)
 +
 +                elif index == len(path) - 1:  # last node
 +                    # set vlan tag in ovs instance (to isolate E-LANs)
 +                    if not skip_vlan_tag:
 +                        out_port_name = kwargs.get('switch_outport_name')
 +                        self._set_vlan_tag(node, out_port_name, vlan)
 +                    # set vlan pop action if more than 1 switch in the path
 +                    if len(path) > 1:
 +                        match += ',dl_vlan=%s' % vlan
 +                        action = {}
 +                        action['type'] = 'POP_VLAN'
 +                        flow['actions'].append(action)
 +
                  else:  # middle nodes
                      match += ',dl_vlan=%s' % vlan
 +
              # output action must come last
              action = {}
              action['type'] = 'OUTPUT'
          flow['match'] = self._parse_match(match)
          self.ryu_REST(prefix, data=flow)
  
 +    def _set_vlan_tag(self, node, switch_port, tag):
 +        node.vsctl('set', 'port {0} tag={1}'.format(switch_port,tag))
 +        LOG.debug("set vlan in switch: {0} in_port: {1} vlan tag: {2}".format(node.name, switch_port, tag))
 +
      def _set_flow_entry_dpctl(self, node, switch_inport_nr, switch_outport_nr, **kwargs):
 +
          match = 'in_port=%s' % switch_inport_nr
  
          cookie = kwargs.get('cookie')
      def startRyu(self, learning_switch=True):
          # start Ryu controller with rest-API
          python_install_path = site.getsitepackages()[0]
 -        ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
 +        # ryu default learning switch
 +        #ryu_path = python_install_path + '/ryu/app/simple_switch_13.py'
 +        #custom learning switch that installs a default NORMAL action in the ovs switches
 +        dir_path = os.path.dirname(os.path.realpath(__file__))
 +        ryu_path = dir_path + '/son_emu_simple_switch_13.py'
          ryu_path2 = python_install_path + '/ryu/app/ofctl_rest.py'
          # change the default Openflow controller port to 6653 (official IANA-assigned port number), as used by Mininet
          # Ryu still uses 6633 as default
          FNULL = open("/tmp/ryu.log", 'w')
          if learning_switch:
              self.ryu_process = Popen([ryu_cmd, ryu_path, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
 +            LOG.debug('starting ryu-controller with {0}'.format(ryu_path))
 +            LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
          else:
              # no learning switch, but with rest api
              self.ryu_process = Popen([ryu_cmd, ryu_path2, ryu_option, ryu_of_port], stdout=FNULL, stderr=FNULL)
 +            LOG.debug('starting ryu-controller with {0}'.format(ryu_path2))
          time.sleep(1)
  
      def killRyu(self):
                  dict.update({match[0]:m2})
          return dict
  
 +    def find_connected_dc_interface(self, vnf_src_name, vnf_src_interface):
 +        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:
 +                if (link_dict[link]['src_port_id'] == vnf_src_interface or
 +                        link_dict[link]['src_port_name'] == vnf_src_interface):  # Fix: we might also get interface names, e.g, from a son-emu-cli call
 +                    # found the right link and connected switch
 +                    src_sw = connected_sw
 +                    src_sw_inport_nr = link_dict[link]['dst_port_nr']
 +                    src_sw_inport_name = link_dict[link]['dst_port_name']
 +                    return src_sw_inport_name