Adding dummy ns3 fd-net-device test
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Wed, 26 Nov 2014 22:49:38 +0000 (23:49 +0100)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Wed, 26 Nov 2014 22:49:38 +0000 (23:49 +0100)
src/nepi/resources/ns3/classes/lr_wpan_net_device.py [new file with mode: 0644]
src/nepi/resources/ns3/ns3fdnetdevice.py [new file with mode: 0644]
src/nepi/resources/ns3/ns3pipechanel.py [new file with mode: 0644]
src/nepi/resources/ns3/ns3wrapper.py
test/resources/linux/ns3/ns3fdnetdevice.py [new file with mode: 0755]

diff --git a/src/nepi/resources/ns3/classes/lr_wpan_net_device.py b/src/nepi/resources/ns3/classes/lr_wpan_net_device.py
new file mode 100644 (file)
index 0000000..0a74fff
--- /dev/null
@@ -0,0 +1,50 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2014 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.trace import Trace, TraceAttr
+from nepi.execution.resource import ResourceManager, clsinit_copy, \
+        ResourceState, reschedule_delay
+from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice 
+
+@clsinit_copy
+class NS3LrWpanNetDevice(NS3BaseNetDevice):
+    _rtype = "ns3::LrWpanNetDevice"
+
+    @classmethod
+    def _register_attributes(cls):
+        
+        attr_useacks = Attribute("UseAcks",
+            "Request acknowledgments for data frames.",
+            type = Types.Bool,
+            default = "True",  
+            allowed = None,
+            range = None,    
+            flags = Flags.Reserved | Flags.Construct)
+
+        cls._register_attribute(attr_useacks)
+
+
+
+    @classmethod
+    def _register_traces(cls):
+        pass
+
+    def __init__(self, ec, guid):
+        super(NS3LrWpanNetDevice, self).__init__(ec, guid)
+        self._home = "ns3-lr-wpan-net-device-%s" % self.guid
diff --git a/src/nepi/resources/ns3/ns3fdnetdevice.py b/src/nepi/resources/ns3/ns3fdnetdevice.py
new file mode 100644 (file)
index 0000000..3e42d23
--- /dev/null
@@ -0,0 +1,42 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2014 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.resource import clsinit_copy
+from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice
+
+@clsinit_copy
+class NS3BaseFdNetDevice(NS3BaseNetDevice):
+    _rtype = "abstract::ns3::FdNetDevice"
+
+    @property
+    def _rms_to_wait(self):
+        rms = set([self.node, self.node.ipv4])
+        return rms
+
+    def _configure_mac_address(self):
+        # The wifimac is the one responsible for
+        # configuring the MAC address
+        pass
+
+    def _connect_object(self):
+        node = self.node
+        if node and node.uuid not in self.connected:
+            self.simulation.invoke(node.uuid, "AddDevice", self.uuid)
+            self._connected.add(node.uuid)
+        
diff --git a/src/nepi/resources/ns3/ns3pipechanel.py b/src/nepi/resources/ns3/ns3pipechanel.py
new file mode 100644 (file)
index 0000000..c74b24d
--- /dev/null
@@ -0,0 +1,89 @@
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2014 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.attribute import Attribute, Flags, Types
+from nepi.execution.resource import clsinit_copy
+from nepi.resources.ns3.ns3base import NS3Base
+
+import socket
+
+@clsinit_copy
+class NS3BasePipeChannel(NS3Base):
+    """ Interconnects two FdNetDevices with a PIPE
+    """
+    _rtype = "ns3::PipeChannel"
+
+    def __init__(self, ec, guid):
+        super(NS3BasePipeChannel, self).__init__(ec, guid)
+        self._devices = None
+
+    @property
+    def devices(self):
+        if not self._devices:
+            from nepi.resources.ns3.ns3fdnetdevice import NS3BaseFdNetDevice
+            devices = self.get_connected(NS3BaseFdNetDevice.get_rtype())
+            if not devices or len(devices) != 2: 
+                msg = "PipeChannel must be connected to exactly to two FdNetDevices"
+                self.error(msg)
+                raise RuntimeError, msg
+
+            self._devices = devices
+        
+        return self._devices
+
+    @property
+    def node(self):
+        return self.devices[0].node
+
+    @property
+    def _rms_to_wait(self):
+        rms = set(self.devices)
+        return rms
+
+    def _instantiate_object(self):
+        """ The pipe channel does not really exists as an ns-3 object.
+        Do nothing, just validate that the simulator is in real time
+        mode, otherwise it is not going to work.
+        """
+        mode = self.simulator.get("simulatorImplementationType")
+        if mode != "ns3::RealtimeSimulatorImpl":
+            msg = "The simulation must run in real time!!"
+                self.error(msg)
+                raise RuntimeError, msg
+
+    def _connect_object(self):
+        dev1 = self.devices[0]
+        dev2 = self.devices[1]
+
+        if dev1.uuid not in self.connected and dev2.uuid not in self.connected:
+            (s0, s1) = socket.socketpair(socket.AF_UNIX, socket.SOCK_STREAM, 0)
+            self._send_fd(dev1, s0)
+            self._send_fd(dev2, s1)
+
+    def _send_fd(self, dev, fd):
+        import passfd
+        
+        address = self.simulation.invoke(dev.uuid, "recvFD")
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        sock.connect(address)
+        passfd.sendfd(sock, fd, '0')
+
+        self._connected.add(dev.uuid)
+        dev._connected.add(self.uuid)
+
index ebd0d67..eadd71f 100644 (file)
@@ -262,51 +262,60 @@ class NS3Wrapper(object):
 
         if operation == "isRunning":
             result = self.is_running
+
         elif operation == "isFinished":
             result = self.is_finished
+
         elif operation == "isAppRunning":
             result = self._is_app_running(uuid)
-        else:
 
-            if operation == "addStaticRoute":
-                result = self._add_static_route(uuid, *args)
-                
-                ### DUMP
-                self.debuger.dump_invoke(result, uuid, operation, args, kwargs)
+        elif operation == "recvFD":
+            ### passFD operation binds to a different random socket 
+            ### en every execution, so the socket name that could be
+            ### dumped to the debug script using dump_invoke is
+            ### not be valid accross debug executions.
+            result = self._recv_fd(uuid, *args, **kwargs)
 
-            elif operation == "retrieveObject":
-                result = self._retrieve_object(uuid, *args, **kwargs)
-                
-                ### DUMP
-                self.debuger.dump_invoke(result, uuid, operation, args, kwargs)
+        elif operation == "addStaticRoute":
+            result = self._add_static_route(uuid, *args)
+            
+            ### DUMP - result is static, so will be dumped as plain text
+            self.debuger.dump_invoke(result, uuid, operation, args, kwargs)
 
-            else:
-                newuuid = self.make_uuid()
+        elif operation == "retrieveObject":
+            result = self._retrieve_object(uuid, *args, **kwargs)
+       
+            ### DUMP - result is static, so will be dumped as plain text
+            self.debuger.dump_invoke(result, uuid, operation, args, kwargs)
+       
+        else:
+            newuuid = self.make_uuid()
 
-                ### DUMP
-                self.debuger.dump_invoke(newuuid, uuid, operation, args, kwargs)
+            ### DUMP - result is a uuid that encoded an dynamically generated 
+            ### object
+            self.debuger.dump_invoke(newuuid, uuid, operation, args, kwargs)
 
-                if uuid.startswith(SINGLETON):
-                    obj = self._singleton(uuid)
-                else:
-                    obj = self.get_object(uuid)
-                
-                method = getattr(obj, operation)
+            if uuid.startswith(SINGLETON):
+                obj = self._singleton(uuid)
+            else:
+                obj = self.get_object(uuid)
+            
+            method = getattr(obj, operation)
 
-                # arguments starting with 'uuid' identify ns-3 C++
-                # objects and must be replaced by the actual object
-                realargs = self.replace_args(args)
-                realkwargs = self.replace_kwargs(kwargs)
+            # arguments starting with 'uuid' identify ns-3 C++
+            # objects and must be replaced by the actual object
+            realargs = self.replace_args(args)
+            realkwargs = self.replace_kwargs(kwargs)
 
-                result = method(*realargs, **realkwargs)
+            result = method(*realargs, **realkwargs)
 
-                # If the result is an object (not a base value),
-                # then keep track of the object a return the object
-                # reference (newuuid)
-                if not (result is None or type(result) in [
-                        bool, float, long, str, int]):
-                    self._objects[newuuid] = result
-                    result = newuuid
+            # If the result is an object (not a base value),
+            # then keep track of the object a return the object
+            # reference (newuuid)
+            if not (result is None or type(result) in [
+                    bool, float, long, str, int]):
+                self._objects[newuuid] = result
+                result = newuuid
 
         ### DEBUG
         self.logger.debug("RET INVOKE %s%s = %s -> %s(%s, %s) " % (
@@ -649,5 +658,28 @@ class NS3Wrapper(object):
 
         return newuuid
 
+    def _recv_fd(self, uuid):
+        """ Waits on a local address to receive a file descriptor
+        from a local process. The file descriptor is associated
+        to a FdNetDevice to stablish communication between the
+        simulation and what ever process writes on that file descriptor
+        """
+
+        def recvfd(sock, fdnd):
+            (fd, msg) = passfd.recvfd(sock)
+            # Store a reference to the endpoint to keep the socket alive
+            fdnd.SetFileDescriptor(fd)
+        
+        import passfd
+        import socket
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        sock.bind("")
+        address = sock.getsockname()
+        
+        fdnd = self.get_object(uuid)
+        t = threading.Thread(target=recvfd, args=(sock,fdnd))
+        t.start()
+
+        return address
 
 
diff --git a/test/resources/linux/ns3/ns3fdnetdevice.py b/test/resources/linux/ns3/ns3fdnetdevice.py
new file mode 100755 (executable)
index 0000000..326ac45
--- /dev/null
@@ -0,0 +1,127 @@
+#!/usr/bin/env python
+#
+#    NEPI, a framework to manage network experiments
+#    Copyright (C) 2013 INRIA
+#
+#    This program is free software: you can redistribute it and/or modify
+#    it under the terms of the GNU General Public License as published by
+#    the Free Software Foundation, either version 3 of the License, or
+#    (at your option) any later version.
+#
+#    This program is distributed in the hope that it will be useful,
+#    but WITHOUT ANY WARRANTY; without even the implied warranty of
+#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+#    GNU General Public License for more details.
+#
+#    You should have received a copy of the GNU General Public License
+#    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+#
+# Author: Alina Quereilhac <alina.quereilhac@inria.fr>
+
+from nepi.execution.ec import ExperimentController 
+from nepi.execution.trace import TraceAttr
+
+from test_utils import skipIfNotAlive
+
+import os
+import time
+import unittest
+
+def add_ns3_node(ec, simu):
+    node = ec.register_resource("ns3::Node")
+    ec.register_connection(node, simu)
+
+    ipv4 = ec.register_resource("ns3::Ipv4L3Protocol")
+    ec.register_connection(node, ipv4)
+
+    arp = ec.register_resource("ns3::ArpL3Protocol")
+    ec.register_connection(node, arp)
+    
+    icmp = ec.register_resource("ns3::Icmpv4L4Protocol")
+    ec.register_connection(node, icmp)
+
+    udp = ec.register_resource("ns3::UdpL4Protocol")
+    ec.register_connection(node, udp)
+
+    return node
+
+def add_fd_device(ec, node, ip, prefix):
+    dev = ec.register_resource("ns3::FdNetDevice")
+    ec.set(dev, "ip", ip)
+    ec.set(dev, "prefix", prefix)
+    ec.register_connection(node, dev)
+
+    #queue = ec.register_resource("ns3::DropTailQueue")
+    #ec.register_connection(dev, queue)
+
+    return dev
+
+class LinuxNS3FdNetDeviceTest(unittest.TestCase):
+    def setUp(self):
+        self.fedora_host = "nepi2.pl.sophia.inria.fr"
+        self.fedora_user = "inria_nepi"
+        self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'])
+
+    @skipIfNotAlive
+    def t_dummy(self, host, user = None, identity = None):
+        ec = ExperimentController(exp_id = "test-ns3-fd-dummy")
+        
+        node = ec.register_resource("LinuxNode")
+        if host == "localhost":
+            ec.set(node, "hostname", "localhost")
+        else:
+            ec.set(node, "hostname", host)
+            ec.set(node, "username", user)
+            ec.set(node, "identity", identity)
+        
+        ec.set(node, "cleanProcesses", True)
+        #ec.set(node, "cleanExperiment", True)
+
+        simu = ec.register_resource("LinuxNS3Simulation")
+        ec.set(simu, "simulatorImplementationType", "ns3::RealtimeSimulatorImpl")
+        ec.set(simu, "checksumEnabled", True)
+        ec.set(simu, "verbose", True)
+        ec.register_connection(simu, node)
+
+        nsnode1 = add_ns3_node(ec, simu)
+        dev1 = add_fd_device(ec, nsnode1, "10.0.0.1", "30")
+
+        nsnode2 = add_ns3_node(ec, simu)
+        dev2 = add_fd_device(ec, nsnode2, "10.0.0.2", "30")
+        
+        channel = ec.register_resource("ns3::PipeChannel")
+        ec.register_connection(channel, dev1)
+        ec.register_connection(channel, dev2)
+
+        ### create pinger
+        ping = ec.register_resource("ns3::V4Ping")
+        ec.set (ping, "Remote", "10.0.0.2")
+        ec.set (ping, "Interval", "1s")
+        ec.set (ping, "Verbose", True)
+        ec.set (ping, "StartTime", "0s")
+        ec.set (ping, "StopTime", "20s")
+        ec.register_connection(ping, nsnode1)
+
+        ec.deploy()
+
+        ec.wait_finished([ping])
+
+        stdout = ec.trace(simu, "stdout") 
+
+        print stdout
+
+        expected = "20 packets transmitted, 20 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+
+        ec.shutdown()
+
+    def ztest_dummy_fedora(self):
+        self.t_dummy(self.fedora_host, self.fedora_user, self.fedora_identity)
+
+    def test_dummy_local(self):
+        self.t_dummy("localhost")
+
+
+if __name__ == '__main__':
+    unittest.main()
+