merge network/monitoring cli commands
authorstevenvanrossem <steven.vanrossem@intec.ugent.be>
Tue, 16 Feb 2016 13:52:39 +0000 (14:52 +0100)
committerstevenvanrossem <steven.vanrossem@intec.ugent.be>
Tue, 16 Feb 2016 13:52:39 +0000 (14:52 +0100)
emuvim/api/zerorpcapi_DCNetwork.py [new file with mode: 0644]
emuvim/cli/monitor.py [new file with mode: 0644]
emuvim/cli/network.py
emuvim/cli/son-emu-cli
emuvim/dcemulator/monitoring.py [new file with mode: 0644]
emuvim/dcemulator/net.py
start_dcnetwork [new file with mode: 0644]
start_example_chain [new file with mode: 0644]

diff --git a/emuvim/api/zerorpcapi_DCNetwork.py b/emuvim/api/zerorpcapi_DCNetwork.py
new file mode 100644 (file)
index 0000000..7402f3d
--- /dev/null
@@ -0,0 +1,112 @@
+"""\r
+Distributed Cloud Emulator (dcemulator)\r
+(c) 2015 by Manuel Peuster <manuel.peuster@upb.de>\r
+"""\r
+\r
+import logging\r
+import threading\r
+import zerorpc\r
+import site\r
+from subprocess import Popen\r
+\r
+logging.basicConfig(level=logging.INFO)\r
+\r
+\r
+class ZeroRpcApiEndpointDCNetwork(object):\r
+    """\r
+    Simple API endpoint that offers a zerorpc-based\r
+    interface. This interface will be used by the\r
+    default command line client.\r
+    It can be used as a reference to implement\r
+    REST interfaces providing the same semantics,\r
+    like e.g. OpenStack compute API.\r
+    """\r
+\r
+    def __init__(self, listenip, port, DCNetwork=None):\r
+        if DCNetwork :\r
+            self.connectDCNetwork(DCNetwork)\r
+        self.ip = listenip\r
+        self.port = port\r
+        logging.debug("Created monitoring API endpoint %s(%s:%d)" % (\r
+            self.__class__.__name__, self.ip, self.port))\r
+\r
+        # start Ryu controller with rest-API\r
+        python_install_path = site.getsitepackages()[0]\r
+        ryu_path = python_install_path + '/ryu/app/ofctl_rest.py'\r
+        ryu_cmd =  'ryu-manager'\r
+        self.ryu_process = Popen([ryu_cmd,ryu_path])\r
+\r
+\r
+    def connectDCNetwork(self, net):\r
+        self.net = net\r
+        logging.info("Connected DCNetwork(%s) to API endpoint %s(%s:%d)" % (\r
+            net.name, self.__class__.__name__, self.ip, self.port))\r
+\r
+    def start(self):\r
+        thread = threading.Thread(target=self._api_server_thread, args=())\r
+        thread.daemon = True\r
+        thread.start()\r
+        logging.debug("Started API endpoint %s(%s:%d)" % (\r
+            self.__class__.__name__, self.ip, self.port))\r
+\r
+    def _api_server_thread(self):\r
+        s = zerorpc.Server(DCNetworkApi(self.net))\r
+        s.bind("tcp://%s:%d" % (self.ip, self.port))\r
+        s.run()\r
+\r
+    def stop(self):\r
+        # stop ryu controller\r
+        logging.info("Stop the monitoring API endpoint")\r
+        self.ryu_process.terminate()\r
+        #self.ryu_process.kill()\r
+        return\r
+\r
+\r
+class DCNetworkApi(object):\r
+    """\r
+        Just pass through the corresponding request to the\r
+        selected data center. Do not implement provisioning\r
+        logic here because will will have multiple API\r
+        endpoint implementations at the end.\r
+    """\r
+\r
+    def __init__(self, net):\r
+        self.net = net\r
+\r
+    def network_action_start(self, vnf_src_name, vnf_dst_name):\r
+        # call DCNetwork method, not really datacenter specific API for now...\r
+        # provided dc name needs to be part of API endpoint\r
+        # no check if vnfs are really connected to this datacenter...\r
+        logging.debug("RPC CALL: network chain start")\r
+        try:\r
+            c = self.net.setChain(\r
+                vnf_src_name, vnf_dst_name)\r
+            return str(c)\r
+        except Exception as ex:\r
+            logging.exception("RPC error.")\r
+            return ex.message\r
+\r
+    def network_action_stop(self, vnf_src_name, vnf_dst_name):\r
+        # call DCNetwork method, not really datacenter specific API for now...\r
+        # provided dc name needs to be part of API endpoint\r
+        # no check if vnfs are really connected to this datacenter...\r
+        logging.debug("RPC CALL: network chain stop")\r
+        try:\r
+            c = self.net.setChain(\r
+                vnf_src_name, vnf_dst_name, cmd='del-flows')\r
+            return c\r
+        except Exception as ex:\r
+            logging.exception("RPC error.")\r
+            return ex.message\r
+\r
+    # get egress(default) or ingress rate of a vnf\r
+    def monitor_get_rate(self, vnf_name, direction):\r
+        logging.debug("RPC CALL: get rate")\r
+        try:\r
+            c = self.net.monitor_agent.get_rate(vnf_name, direction)\r
+            return c\r
+        except Exception as ex:\r
+            logging.exception("RPC error.")\r
+            return ex.message\r
+\r
+\r
diff --git a/emuvim/cli/monitor.py b/emuvim/cli/monitor.py
new file mode 100644 (file)
index 0000000..6885a3c
--- /dev/null
@@ -0,0 +1,53 @@
+"""\r
+son-emu network CLI\r
+(c) 2016 by Manuel Peuster <manuel.peuster@upb.de>\r
+"""\r
+\r
+import argparse\r
+import pprint\r
+from tabulate import tabulate\r
+import zerorpc\r
+\r
+\r
+pp = pprint.PrettyPrinter(indent=4)\r
+\r
+class ZeroRpcClient(object):\r
+\r
+    def __init__(self):\r
+        self.c = zerorpc.Client()\r
+        # TODO connect to DCNetwork API\r
+        #self.c.connect("tcp://127.0.0.1:4242")  # TODO hard coded for now. we'll change this later\r
+        self.c.connect("tcp://127.0.0.1:5151")\r
+        self.cmds = {}\r
+\r
+    def execute_command(self, args):\r
+        if getattr(self, args["command"]) is not None:\r
+            # call the local method with the same name as the command arg\r
+            getattr(self, args["command"])(args)\r
+        else:\r
+            print "Command not implemented."\r
+\r
+    def get_rate(self, args):\r
+        r = self.c.monitor_get_rate(\r
+            args.get("vnf_name"),\r
+            args.get("direction"))\r
+        pp.pprint(r)\r
+\r
+\r
+parser = argparse.ArgumentParser(description='son-emu network')\r
+parser.add_argument(\r
+    "command",\r
+    help="Action to be executed: get_rate")\r
+parser.add_argument(\r
+    "--vnf_name", "-vnf", dest="vnf_name",\r
+    help="vnf name to be monitored")\r
+parser.add_argument(\r
+    "--direction", "-d", dest="direction",\r
+    help="in (ingress rate) or out (egress rate)")\r
+\r
+def main(argv):\r
+    print "This is the son-emu monitor CLI."\r
+    print "Arguments: %s" % str(argv)\r
+    args = vars(parser.parse_args(argv))\r
+    c = ZeroRpcClient()\r
+    c.execute_command(args)\r
index 080b0ac..fd7851a 100755 (executable)
@@ -1,9 +1,64 @@
-"""
-son-emu network CLI
-(c) 2016 by Manuel Peuster <manuel.peuster@upb.de>
-"""
-
-
-def main(argv):
-    print "This is the son-emu network CLI."
-    print "Arguments: %s" % str(argv)
+"""\r
+son-emu network CLI\r
+(c) 2016 by Manuel Peuster <manuel.peuster@upb.de>\r
+"""\r
+\r
+import argparse\r
+import pprint\r
+from tabulate import tabulate\r
+import zerorpc\r
+\r
+\r
+pp = pprint.PrettyPrinter(indent=4)\r
+\r
+class ZeroRpcClient(object):\r
+\r
+    def __init__(self):\r
+        self.c = zerorpc.Client()\r
+        # TODO connect to DCNetwork API\r
+        #self.c.connect("tcp://127.0.0.1:4242")  # TODO hard coded for now. we'll change this later\r
+        self.c.connect("tcp://127.0.0.1:5151")\r
+        self.cmds = {}\r
+\r
+    def execute_command(self, args):\r
+        if getattr(self, args["command"]) is not None:\r
+            # call the local method with the same name as the command arg\r
+            getattr(self, args["command"])(args)\r
+        else:\r
+            print "Command not implemented."\r
+\r
+    def add(self, args):\r
+        r = self.c.network_action_start(\r
+            #args.get("datacenter"),\r
+            args.get("source"),\r
+            args.get("destination"))\r
+        pp.pprint(r)\r
+\r
+    def remove(self, args):\r
+        r = self.c.network_action_stop(\r
+            #args.get("datacenter"),\r
+            args.get("source"),\r
+            args.get("destination"))\r
+        pp.pprint(r)\r
+\r
+\r
+parser = argparse.ArgumentParser(description='son-emu network')\r
+parser.add_argument(\r
+    "command",\r
+    help="Action to be executed: add|remove")\r
+parser.add_argument(\r
+    "--datacenter", "-d", dest="datacenter",\r
+    help="Data center to in which the network action should be initiated")\r
+parser.add_argument(\r
+    "--source", "-src", dest="source",\r
+    help="vnf name of the source of the chain")\r
+parser.add_argument(\r
+    "--destination", "-dst", dest="destination",\r
+    help="vnf name of the destination of the chain")\r
+\r
+def main(argv):\r
+    print "This is the son-emu network CLI."\r
+    print "Arguments: %s" % str(argv)\r
+    args = vars(parser.parse_args(argv))\r
+    c = ZeroRpcClient()\r
+    c.execute_command(args)\r
index ba7a292..2a1e598 100755 (executable)
@@ -16,7 +16,7 @@ import sys
 import compute
 import network
 import datacenter
 import compute
 import network
 import datacenter
-
+import monitor
 
 def main():
     if len(sys.argv) < 2:
 
 def main():
     if len(sys.argv) < 2:
@@ -28,6 +28,8 @@ def main():
         network.main(sys.argv[2:])
     elif sys.argv[1] == "datacenter":
         datacenter.main(sys.argv[2:])
         network.main(sys.argv[2:])
     elif sys.argv[1] == "datacenter":
         datacenter.main(sys.argv[2:])
+       elif sys.argv[1] == "monitor":
+        monitor.main(sys.argv[2:])
 
 if __name__ == '__main__':
     main()
 
 if __name__ == '__main__':
     main()
diff --git a/emuvim/dcemulator/monitoring.py b/emuvim/dcemulator/monitoring.py
new file mode 100644 (file)
index 0000000..094c09b
--- /dev/null
@@ -0,0 +1,62 @@
+__author__ = 'Administrator'\r
+\r
+import urllib2\r
+import logging\r
+from mininet.node import  OVSSwitch\r
+import ast\r
+logging.basicConfig(level=logging.INFO)\r
+\r
+"""\r
+class to read openflow stats from the Ryu controller of the DCNEtwork\r
+"""\r
+\r
+class DCNetworkMonitor():\r
+    def __init__(self, net):\r
+        self.net = net\r
+        # link to REST_API\r
+        self.ip = '0.0.0.0'\r
+        self.port = '8080'\r
+        self.REST_api = 'http://{0}:{1}'.format(self.ip,self.port)\r
+\r
+\r
+    def get_rate(self, vnf_name, direction='tx'):\r
+        try:\r
+            vnf_switch = self.net.DCNetwork_graph.neighbors(str(vnf_name))\r
+\r
+            if len(vnf_switch) > 1:\r
+                logging.info("vnf: {0} has multiple ports".format(vnf_name))\r
+                return\r
+            elif len(vnf_switch) == 0:\r
+                logging.info("vnf: {0} is not connected".format(vnf_name))\r
+                return\r
+            else:\r
+                vnf_switch = vnf_switch[0]\r
+            next_node = self.net.getNodeByName(vnf_switch)\r
+\r
+            if not isinstance( next_node, OVSSwitch ):\r
+                logging.info("vnf: {0} is not connected to switch".format(vnf_name))\r
+                return\r
+\r
+            mon_port = self.net.DCNetwork_graph[vnf_name][vnf_switch]['dst_port']\r
+            switch_dpid = x = int(str(next_node.dpid),16)\r
+\r
+            ret = self.REST_cmd('stats/port', switch_dpid)\r
+            port_stat_dict = ast.literal_eval(ret)\r
+            for port_stat in port_stat_dict[str(switch_dpid)]:\r
+                if port_stat['port_no'] == mon_port:\r
+                    return port_stat\r
+                    break\r
+\r
+            return ret\r
+\r
+        except Exception as ex:\r
+            logging.exception("get_txrate error.")\r
+            return ex.message\r
+\r
+\r
+\r
+    def REST_cmd(self, prefix, dpid):\r
+        url = self.REST_api + '/' + str(prefix) + '/' + str(dpid)\r
+        req = urllib2.Request(url)\r
+        ret = urllib2.urlopen(req).read()\r
+        return ret
\ No newline at end of file
index 7b238b8..8379cd3 100755 (executable)
@@ -9,6 +9,8 @@ from mininet.node import Controller, OVSKernelSwitch, Switch, Docker, Host
 from mininet.cli import CLI
 from mininet.log import setLogLevel, info
 from mininet.link import TCLink, Link
 from mininet.cli import CLI
 from mininet.log import setLogLevel, info
 from mininet.link import TCLink, Link
+import networkx as nx
+from monitoring import DCNetworkMonitor
 
 from node import Datacenter, EmulatorCompute
 
 
 from node import Datacenter, EmulatorCompute
 
@@ -26,8 +28,15 @@ class DCNetwork(Dockernet):
         # create a Mininet/Dockernet network
         # call original Docker.__init__ and setup default controller
         Dockernet.__init__(
         # create a Mininet/Dockernet network
         # call original Docker.__init__ and setup default controller
         Dockernet.__init__(
-            self, controller=Controller, switch=OVSKernelSwitch, **kwargs)
-        self.addController('c0')
+            self, controller=RemoteController, switch=OVSKernelSwitch, **kwargs)
+        #self.addController('c0')
+
+        # graph of the complete DC network
+        self.DCNetwork_graph=nx.DiGraph()
+
+        # monitoring agent
+        self.monitor_agent = DCNetworkMonitor(self)
+
 
     def addDatacenter(self, label, metadata={}):
         """
 
     def addDatacenter(self, label, metadata={}):
         """
@@ -74,13 +83,37 @@ class DCNetwork(Dockernet):
             if not "ip" in params["params2"]:
                 params["params2"]["ip"] = self.getNextIp()
 
             if not "ip" in params["params2"]:
                 params["params2"]["ip"] = self.getNextIp()
 
-        return Dockernet.addLink(self, node1, node2, **params)  # TODO we need TCLinks with user defined performance here
+        link = Dockernet.addLink(self, node1, node2, **params)  # TODO we need TCLinks with user defined performance here
+
+        # add edge and assigned port number to graph in both directions between node1 and node2
+        self.DCNetwork_graph.add_edge(node1.name, node2.name, \
+                                      {'src_port': node1.ports[link.intf1], 'dst_port': node2.ports[link.intf2]})
+        self.DCNetwork_graph.add_edge(node2.name, node1.name, \
+                                       {'src_port': node2.ports[link.intf2], 'dst_port': node1.ports[link.intf1]})
+
+        return link
 
     def addDocker( self, label, **params ):
         """
         Wrapper for addDocker method to use custom container class.
         """
 
     def addDocker( self, label, **params ):
         """
         Wrapper for addDocker method to use custom container class.
         """
-        return Dockernet.addDocker(self, label, cls=EmulatorCompute, **params)
+        self.DCNetwork_graph.add_node(name)
+        return Dockernet.addDocker(self, name, cls=EmulatorCompute, **params)
+
+    def removeDocker( self, name, **params ):
+        """
+        Wrapper for removeDocker method to update graph.
+        """
+        self.DCNetwork_graph.remove_node(name)
+        return Dockernet.removeDocker(self, name, **params)
+
+    def addSwitch( self, name, add_to_graph=True, **params ):
+        """
+        Wrapper for addSwitch method to store switch also in graph.
+        """
+        if add_to_graph:
+            self.DCNetwork_graph.add_node(name)
+        return Dockernet.addSwitch(self, name, protocols='OpenFlow10,OpenFlow12,OpenFlow13', **params)
 
     def getAllContainers(self):
         """
 
     def getAllContainers(self):
         """
@@ -102,3 +135,46 @@ class DCNetwork(Dockernet):
 
     def CLI(self):
         CLI(self)
 
     def CLI(self):
         CLI(self)
+
+    # to remove chain do setChain( src, dst, cmd='del-flows')
+    def setChain(self, vnf_src_name, vnf_dst_name, cmd='add-flow'):
+        # get shortest path
+        path = nx.shortest_path(self.DCNetwork_graph, vnf_src_name, vnf_dst_name)
+        logging.info("Path between {0} and {1}: {2}".format(vnf_src_name, vnf_dst_name, path))
+
+        current_hop = vnf_src_name
+        for i in range(0,len(path)):
+            next_hop = path[path.index(current_hop)+1]
+            next_node = self.getNodeByName(next_hop)
+
+            if next_hop == vnf_dst_name:
+                return 0
+            elif not isinstance( next_node, OVSSwitch ):
+                logging.info("Next node: {0} is not a switch".format(next_hop1))
+                return 0
+
+
+            switch_inport = self.DCNetwork_graph[current_hop][next_hop]['dst_port']
+            next2_hop = path[path.index(current_hop)+2]
+            switch_outport = self.DCNetwork_graph[next_hop][next2_hop]['src_port']
+
+            logging.info("add flow in switch: {0} in_port: {1} out_port: {2}".format(next_node.name, switch_inport, switch_outport))
+            # set of entry via ovs-ofctl
+            # TODO use rest API of ryu to set flow entries to correct witch dpid
+            if isinstance( next_node, OVSSwitch ):
+                match = 'in_port=%s' % switch_inport
+
+                if cmd=='add-flow':
+                    action = 'action=%s' % switch_outport
+                    s = ','
+                    ofcmd = s.join([match,action])
+                elif cmd=='del-flows':
+                    ofcmd = match
+                else:
+                    ofcmd=''
+
+                next_node.dpctl(cmd, ofcmd)
+
+            current_hop = next_hop
+
+        return 1
\ No newline at end of file
diff --git a/start_dcnetwork b/start_dcnetwork
new file mode 100644 (file)
index 0000000..aca405b
--- /dev/null
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# start DC Network
+cd emuvim/
+python example_topology.py
+
+
diff --git a/start_example_chain b/start_example_chain
new file mode 100644 (file)
index 0000000..8ec8a1f
--- /dev/null
@@ -0,0 +1,16 @@
+#!/bin/bash
+
+
+# deploy VNFs
+cd emuvim/cli/
+./son-emu-cli compute start -d dc1 -n tsrc -i traffic_source -c ./start.sh
+./son-emu-cli compute start -d dc2 -n fw -i firewall -c ./start.sh
+./son-emu-cli compute start -d dc3 -n tsink -i traffic_sink -c ./start.sh
+
+# setup links in the chain
+./son-emu-cli network add -src tsrc -dst fw
+./son-emu-cli network add -src fw -dst tsink
+./son-emu-cli network add -src tsink -dst tsrc
+
+
+