Merging nepi-3.1-multirun into nepi-3-dev
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Sun, 3 Aug 2014 18:13:16 +0000 (20:13 +0200)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Sun, 3 Aug 2014 18:13:16 +0000 (20:13 +0200)
39 files changed:
setup.py
src/nepi/design/__init__.py [deleted file]
src/nepi/design/box.py [deleted file]
src/nepi/execution/attribute.py
src/nepi/execution/ec.py
src/nepi/execution/resource.py
src/nepi/execution/runner.py [new file with mode: 0644]
src/nepi/resources/all/collector.py
src/nepi/resources/ns3/ns3base.py
src/nepi/resources/ns3/ns3node.py
src/nepi/util/parser.py [deleted file]
src/nepi/util/parsers/__init__.py [new file with mode: 0644]
src/nepi/util/parsers/xml_parser.py [new file with mode: 0644]
src/nepi/util/plotter.py [new file with mode: 0644]
src/nepi/util/serializer.py [new file with mode: 0644]
test/design/box.py [deleted file]
test/execution/runner.py [new file with mode: 0755]
test/resources/linux/application.py
test/resources/linux/ccn/ccncat.py [changed mode: 0644->0755]
test/resources/linux/ccn/ccnpeek.py [changed mode: 0644->0755]
test/resources/linux/ccn/ccnping.py [changed mode: 0644->0755]
test/resources/linux/ccn/fibentry.py [changed mode: 0644->0755]
test/resources/linux/multirun.py [new file with mode: 0755]
test/resources/linux/ns3/ccn/ns3dceccn.py [changed mode: 0644->0755]
test/resources/linux/ns3/ccn/ns3dceccnpeek.py [changed mode: 0644->0755]
test/resources/linux/ns3/ns3client.py [changed mode: 0644->0755]
test/resources/linux/ns3/ns3dceapplication.py [changed mode: 0644->0755]
test/resources/linux/ns3/ns3dceping.py [changed mode: 0644->0755]
test/resources/linux/ns3/ns3simulation.py [changed mode: 0644->0755]
test/resources/linux/ns3/serialization.py [new file with mode: 0755]
test/resources/linux/serialization.py [new file with mode: 0755]
test/resources/omf/omf6_vlc_wrong_critical.py [changed mode: 0644->0755]
test/resources/omf/omf6_vlc_wrong_non_critical.py [changed mode: 0644->0755]
test/resources/omf/set_hook.py [changed mode: 0644->0755]
test/util/parallel.py [changed mode: 0644->0755]
test/util/parser.py [deleted file]
test/util/plot.py [deleted file]
test/util/plotter.py [new file with mode: 0755]
test/util/serializer.py [new file with mode: 0755]

index 00001d8..c8247ec 100755 (executable)
--- a/setup.py
+++ b/setup.py
@@ -13,7 +13,6 @@ setup(
         platforms   = "Linux, OSX",
         packages    = [
             "nepi",
-            "nepi.design",
             "nepi.execution",
             "nepi.resources",
             "nepi.resources.all",
@@ -27,7 +26,8 @@ setup(
             "nepi.resources.omf",
             "nepi.resources.planetlab",
             "nepi.resources.planetlab.openvswitch",
-            "nepi.util"],
+            "nepi.util",
+            "nepi.util.parsers"],
         package_dir = {"": "src"},
         package_data = {
             "nepi.resources.planetlab" : [ "scripts/*.py" ],
diff --git a/src/nepi/design/__init__.py b/src/nepi/design/__init__.py
deleted file mode 100644 (file)
index 013e4b7..0000000
+++ /dev/null
@@ -1 +0,0 @@
-#!/usr/bin/python
diff --git a/src/nepi/design/box.py b/src/nepi/design/box.py
deleted file mode 100644 (file)
index 2da0710..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-#
-#    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.util import guid
-
-guid_gen = guid.GuidGenerator()
-
-class Attributes(object):
-    def __init__(self):
-        super(Attributes, self).__init__()
-        self._attributes = dict()
-
-    def __getattr__(self, name):
-        try:
-            return self._attributes[name]
-        except:
-            return super(Attributes, self).__getattribute__(name)
-
-    def __setattr__(self, name, value):
-        try:
-            if value == None:
-                old = self._attributes[name]
-                del self._attributes[name]
-                return old
-
-            self._attributes[name] = value
-            return value
-        except:
-            return super(Attributes, self).__setattr__(name, value)
-
-class Connections(object):
-    def __init__(self):
-        super(Connections, self).__init__()
-        self._connections = set()
-
-    def __getattr__(self, guid_or_label):
-        try:
-            for b in self._connections:
-                if guid_or_label in [b.guid, b.label]:
-                    return b
-        except:
-            return super(Connections, self).__getattribute__(guid_or_label)
-
-class Box(object):
-    def __init__(self, label = None, guid = None):
-        super(Box, self).__init__()
-        self._guid = guid_gen.next(guid)
-        self._a = Attributes()
-        self._c = Connections()
-        self._tags = set()
-        self.label = label or self._guid
-
-        # Graphical information to draw box
-        self.x = 0
-        self.y = 0
-        self.width = 4
-        self.height = 4
-
-    @property
-    def tags(self):
-        return self._tags
-
-    @property
-    def attributes(self):
-        return self._a._attributes.keys()
-
-    @property
-    def a(self):
-        return self._a
-
-    @property
-    def c(self):
-        return self._c
-
-    @property
-    def guid(self):
-        return self._guid
-
-    @property
-    def connections(self):
-        return set(self._c._connections)
-
-    def tadd(self, name):
-        self._tags.add(name)
-
-    def tdel(self, name):
-        self._tags.remove(name)
-
-    def thas(self, name):
-        return name in self._tags
-
-    def connect(self, box, cascade = True):
-        self._c._connections.add(box)
-        if cascade:
-            box.connect(self, cascade = False)
-
-    def disconnect(self, box, cascade = True):
-        self._c._connections.remove(box)
-        if cascade:
-            box.disconnect(self, cascade = False)
-
-    def is_connected(self, box):
-        return box in self.connections
-
index fa2b104..19fbfc2 100644 (file)
@@ -190,6 +190,7 @@ class Attribute(object):
         adequate validation"""
         return True
 
+    @property
     def has_changed(self):
         """ Returns true if the value has changed from the default """
         return self.value != self.default
index 257742e..31e11ef 100644 (file)
@@ -24,6 +24,8 @@ from nepi.execution.resource import ResourceFactory, ResourceAction, \
         ResourceState, ResourceState2str
 from nepi.execution.scheduler import HeapScheduler, Task, TaskStatus
 from nepi.execution.trace import TraceAttr
+from nepi.util.serializer import ECSerializer, SFormats
+from nepi.util.plotter import ECPlotter, PFormats
 
 # TODO: use multiprocessing instead of threading
 # TODO: Allow to reconnect to a running experiment instance! (reconnect mode vs deploy mode)
@@ -151,6 +153,12 @@ class ExperimentController(object):
         
     """
 
+    @classmethod
+    def load(cls, path, format = SFormats.XML):
+        serializer = ECSerializer()
+        ec = serializer.load(path)
+        return ec
+
     def __init__(self, exp_id = None): 
         super(ExperimentController, self).__init__()
 
@@ -200,8 +208,8 @@ class ExperimentController(object):
 
         # The runner is a pool of threads used to parallelize 
         # execution of tasks
-        nthreads = int(os.environ.get("NEPI_NTHREADS", "20"))
-        self._runner = ParallelRun(maxthreads = nthreads)
+        self._nthreads = 20
+        self._runner = None
 
         # Event processing thread
         self._cond = threading.Condition()
@@ -246,6 +254,14 @@ class ExperimentController(object):
         """
         return self._run_id
 
+    @property
+    def nthreads(self):
+        """ Returns the number of processing nthreads used
+
+        """
+        return self._nthreads
+
     @property
     def abort(self):
         """ Returns True if the experiment has failed and should be interrupted,
@@ -365,7 +381,23 @@ class ExperimentController(object):
                 guids.append(guid)
 
                 time.sleep(0.5)
-  
+
+    def plot(self, fpath = None, format= PFormats.FIGURE, persist = False):
+        plotter = ECPlotter()
+        fpath = plotter.plot(self, fpath = fpath, format= format, 
+                persist = persist)
+        return fpath
+
+    def serialize(self, format = SFormats.XML):
+        serializer = ECSerializer()
+        sec = serializer.load(self, format = format)
+        return sec
+
+    def save(self, path, format = SFormats.XML):
+        serializer = ECSerializer()
+        path = serializer.save(self, path, format = format)
+        return path
+
     def get_task(self, tid):
         """ Returns a task by its id
 
@@ -380,7 +412,7 @@ class ExperimentController(object):
     def get_resource(self, guid):
         """ Returns a registered ResourceManager by its guid
 
-            :param guid: Id of the task
+            :param guid: Id of the resource
             :type guid: int
             
             :rtype: ResourceManager
@@ -389,6 +421,21 @@ class ExperimentController(object):
         rm = self._resources.get(guid)
         return rm
 
+    def get_resources_by_type(self, rtype):
+        """ Returns a registered ResourceManager by its guid
+
+            :param rtype: Resource type
+            :type rtype: string
+            
+            :rtype: list of ResourceManagers
+            
+        """
+        rms = []
+        for guid, rm in self._resources.iteritems():
+            if rm.get_rtype() == type: 
+                rms.append(rm)
+        return rms
+
     def remove_resource(self, guid):
         del self._resources[guid]
 
@@ -1000,6 +1047,8 @@ class ExperimentController(object):
 
         """
 
+        self._nthreads = int(os.environ.get("NEPI_NTHREADS", str(self._nthreads)))
+        self._runner = ParallelRun(maxthreads = self.nthreads)
         self._runner.start()
 
         while not self._stop:
index 5ec0be3..c389cfe 100644 (file)
@@ -31,7 +31,7 @@ import sys
 import threading
 import weakref
 
-reschedule_delay = "1s"
+reschedule_delay = "0.5s"
 
 class ResourceAction:
     """ Action that a user can order to a Resource Manager
@@ -615,7 +615,7 @@ class ResourceManager(Logger):
         :rtype: str
         """
         attr = self._attrs[name]
-        return attr.has_changed()
+        return attr.has_changed
 
     def has_flag(self, name, flag):
         """ Returns true if the attribute has the flag 'flag'
diff --git a/src/nepi/execution/runner.py b/src/nepi/execution/runner.py
new file mode 100644 (file)
index 0000000..60e75a4
--- /dev/null
@@ -0,0 +1,161 @@
+#
+#    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
+
+import math
+import numpy
+import os
+import tempfile
+import time
+
+class ExperimentRunner(object):
+    """ The ExperimentRunner entity is reponsible of
+    re-running an experiment described by an ExperimentController 
+    multiple time.
+
+    """
+    def __init__(self):
+        super(ExperimentRunner, self).__init__()
+    
+    def run(self, ec, min_runs = 1, max_runs = -1, wait_time = 0, 
+            wait_guids = [], compute_metric_callback = None, 
+            evaluate_convergence_callback = None ):
+        """ Re-runs a same experiment multiple times
+
+        :param ec: Experiment description of experiment to run
+        :type name: ExperimentController
+        :rtype: EperimentController
+
+        :param min_runs: Minimum number of repetitions for experiment
+        :type name: int
+        :rtype: int
+
+        :param max_runs: Maximum number of repetitions for experiment
+        :type name: int
+        :rtype: int
+
+        :param wait_time: Time to wait in seconds between invoking
+            ec.deploy() and ec.release()
+        :type name: float
+        :rtype: float
+
+        :param wait_guids: List of guids to pass to ec.wait_finished
+            after invoking ec.deploy()
+        :type name: list 
+        :rtype: list of int
+
+        :param compute_metric_callback: function to invoke after each 
+            experiment run, to compute an experiment metric. 
+            It will be invoked with the ec and the run count as arguments,
+            and it must return a numeric value for the computed metric:
+
+                metric = compute_metric_callback(ec, run)
+            
+        :type name: function 
+        :rtype: function
+
+        :param evaluate_convergence_callback: function to evaluate whether the 
+            collected metric samples have converged and the experiment runner
+            can stop. It will be invoked with the ec, the run count and the
+            list of collected metric samples as argument, and it must return
+            either True or False:
+
+                stop = evaluate_convergence_callback(ec, run, metrics)
+
+            If stop is True, then the runner will exit.
+            
+        :type name: function 
+        :rtype: function
+
+        """
+
+        if (not max_runs or max_runs < 0) and not compute_metric_callback:
+            msg = "Undefined STOP condition, set stop_callback or max_runs"
+            raise RuntimeError, msg
+
+        if compute_metric_callback and not evaluate_convergence_callback:
+            evaluate_convergence_callback = self.evaluate_normal_convergence
+            ec.logger.info(" Treating data as normal to evaluate convergence. "
+                    "Experiment will stop when the standard error with 95% "
+                    "confidence interval is >= 5% of the mean of the collected samples ")
+        
+        # Set useRunId = True in Collectors to make sure results are
+        # independently stored.
+        collectors = ec.get_resources_by_type("Collector")
+        for collector in collectors:
+            collector.set("useRunId", True)
+
+        dirpath = tempfile.mkdtemp()
+        filepath = ec.save(dirpath)
+
+        samples = []
+        run = 0
+        while True: 
+            run += 1
+
+            ec = self.run_experiment(filepath, wait_time, wait_guids)
+            
+            ec.logger.info(" RUN %d \n" % run)
+
+            if run >= min_runs and max_runs > -1 and run >= max_runs :
+                break
+
+            if compute_metric_callback:
+                metric = compute_metric_callback(ec, run)
+                if metric is not None:
+                    samples.append(metric)
+
+                    if run >= min_runs and evaluate_convergence_callback:
+                        if evaluate_convergence_callback(ec, run, samples):
+                            break
+            del ec
+
+        return run
+
+    def evaluate_normal_convergence(self, ec, run, samples):
+        if len(samples) == 0:
+            msg = "0 samples collected"
+            raise RuntimeError, msg
+        
+        x = numpy.array(samples)
+        n = len(samples)
+        std = x.std()
+        se = std / math.sqrt(n)
+        m = x.mean()
+        se95 = se * 2
+        
+        ec.logger.info(" RUN %d - SAMPLES %d MEAN %.2f STD %.2f SE95%% %.2f \n" % (
+            run, n, m, std, se95 ) )
+
+        return m * 0.05 >= se95
+
+    def run_experiment(self, filepath, wait_time, wait_guids): 
+        ec = ExperimentController.load(filepath)
+
+        ec.deploy()
+
+        ec.wait_finished(wait_guids)
+        time.sleep(wait_time)
+
+        ec.release()
+
+        return ec
+
+
index bb8c1c8..af0811c 100644 (file)
@@ -28,7 +28,7 @@ import tempfile
 
 @clsinit_copy
 class Collector(ResourceManager):
-    """ The collector is reponsible of collecting traces
+    """ The collector entity is reponsible of collecting traces
     of a same type associated to RMs into a local directory.
 
     .. class:: Class Args :
index f86bea7..455e546 100644 (file)
@@ -32,6 +32,7 @@ class NS3Base(ResourceManager):
         self._uuid = None
         self._connected = set()
         self._trace_filename = dict()
+        self._node = None
 
     @property
     def connected(self):
@@ -47,10 +48,12 @@ class NS3Base(ResourceManager):
 
     @property
     def node(self):
-        from nepi.resources.ns3.ns3node import NS3BaseNode
-        nodes = self.get_connected(NS3BaseNode.get_rtype())
-        if nodes: return nodes[0]
-        return None
+        if not self._node:
+            from nepi.resources.ns3.ns3node import NS3BaseNode
+            nodes = self.get_connected(NS3BaseNode.get_rtype())
+            if nodes: self._nodes[0]
+
+        return self._node
 
     def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
         filename = self._trace_filename.get(name)
@@ -80,7 +83,7 @@ class NS3Base(ResourceManager):
 
         kwargs = dict()
         for attr in self._attrs.values():
-            if not ( attr.has_flag(Flags.Construct) and attr.has_changed() ):
+            if not ( attr.has_flag(Flags.Construct) and attr.has_changed ):
                 continue
 
             kwargs[attr.name] = attr._value
index 48a7c16..a85ba0d 100644 (file)
@@ -25,6 +25,15 @@ from nepi.resources.ns3.ns3base import NS3Base
 class NS3BaseNode(NS3Base):
     _rtype = "abstract::ns3::Node"
 
+    def __init__(self, ec, guid):
+        super(NS3BaseNode, self).__init__(ec, guid)
+        self._simulation = None
+        self._ipv4 = None
+        self._arp = None
+        self._mobility = None
+        self._devices = None
+        self._dceapplications = None
+
     @classmethod
     def _register_attributes(cls):
         enablestack = Attribute("enableStack", 
@@ -38,55 +47,72 @@ class NS3BaseNode(NS3Base):
 
     @property
     def simulation(self):
-        from nepi.resources.ns3.ns3simulation import NS3Simulation
-        for guid in self.connections:
-            rm = self.ec.get_resource(guid)
-            if isinstance(rm, NS3Simulation):
-                return rm
-
-        msg = "Node not connected to simulation"
-        self.error(msg)
-        raise RuntimeError, msg
+        if not self._simulation:
+            from nepi.resources.ns3.ns3simulation import NS3Simulation
+            for guid in self.connections:
+                rm = self.ec.get_resource(guid)
+                if isinstance(rm, NS3Simulation):
+                    self._simulation = rm
+            
+            if not self._simulation:
+                msg = "Node not connected to simulation"
+                self.error(msg)
+                raise RuntimeError, msg
+
+        return self._simulation
+         
     @property
     def ipv4(self):
-        from nepi.resources.ns3.ns3ipv4l3protocol import NS3BaseIpv4L3Protocol
-        ipv4s = self.get_connected(NS3BaseIpv4L3Protocol.get_rtype())
-        if ipv4s: return ipv4s[0]
-        return None
+        if not self._ipv4:
+            from nepi.resources.ns3.ns3ipv4l3protocol import NS3BaseIpv4L3Protocol
+            ipv4s = self.get_connected(NS3BaseIpv4L3Protocol.get_rtype())
+            if ipv4s: 
+                self._ipv4 = ipv4s[0]
+        
+        return self._ipv4
 
     @property
     def arp(self):
-        from nepi.resources.ns3.ns3arpl3protocol import NS3BaseArpL3Protocol
-        arps = self.get_connected(NS3BaseArpL3Protocol.get_rtype())
-        if arps: return arps[0]
-        return None
+        if not self._arp:
+            from nepi.resources.ns3.ns3arpl3protocol import NS3BaseArpL3Protocol
+            arps = self.get_connected(NS3BaseArpL3Protocol.get_rtype())
+            if arps: 
+                self._arp = arps[0]
+
+        return self._arp
 
     @property
     def mobility(self):
-        from nepi.resources.ns3.ns3mobilitymodel import NS3BaseMobilityModel
-        mobility = self.get_connected(NS3BaseMobilityModel.get_rtype())
-        if mobility: return mobility[0]
-        return None
+        if not self._mobility:
+            from nepi.resources.ns3.ns3mobilitymodel import NS3BaseMobilityModel
+            mobility = self.get_connected(NS3BaseMobilityModel.get_rtype())
+            if mobility: 
+                self._mobility = mobility[0]
+
+        return self._mobility
 
     @property
     def devices(self):
-        from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice
-        devices = self.get_connected(NS3BaseNetDevice.get_rtype())
+        if not self._devices:
+            from nepi.resources.ns3.ns3netdevice import NS3BaseNetDevice
+            devices = self.get_connected(NS3BaseNetDevice.get_rtype())
+
+            if not devices: 
+                msg = "Node not connected to devices"
+                self.error(msg)
+                raise RuntimeError, msg
 
-        if not devices: 
-            msg = "Node not connected to devices"
-            self.error(msg)
-            raise RuntimeError, msg
+            self._devices = devices
 
-        return devices
+        return self._devices
 
     @property
     def dceapplications(self):
-        from nepi.resources.ns3.ns3dceapplication import NS3BaseDceApplication
-        dceapplications = self.get_connected(NS3BaseDceApplication.get_rtype())
+        if not self._dceapplications:
+            from nepi.resources.ns3.ns3dceapplication import NS3BaseDceApplication
+            self._dceapplications = self.get_connected(NS3BaseDceApplication.get_rtype())
 
-        return dceapplications
+        return self._dceapplications
 
     @property
     def _rms_to_wait(self):
diff --git a/src/nepi/util/parser.py b/src/nepi/util/parser.py
deleted file mode 100644 (file)
index 58cb79b..0000000
+++ /dev/null
@@ -1,165 +0,0 @@
-#
-#    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.design.box import Box
-
-from xml.dom import minidom
-import sys
-
-STRING = "string"
-BOOL = "bool"
-INTEGER = "integer"
-DOUBLE = "float"
-
-def xmlencode(s):
-    if isinstance(s, str):
-        rv = s.decode("latin1")
-    elif not isinstance(s, unicode):
-        rv = unicode(s)
-    else:
-        rv = s
-    return rv.replace(u'\x00',u'&#0000;')
-
-def xmldecode(s):
-    return s.replace(u'&#0000',u'\x00').encode("utf8")
-
-def from_type(value):
-    if isinstance(value, str):
-        return STRING
-    if isinstance(value, bool):
-        return BOOL
-    if isinstance(value, int):
-        return INTEGER
-    if isinstance(value, float):
-        return DOUBLE
-
-def to_type(type, value):
-    if type == STRING:
-        return str(value)
-    if type == BOOL:
-        return value == "True"
-    if type == INTEGER:
-        return int(value)
-    if type == DOUBLE:
-        return float(value)
-
-class XMLParser(object):
-    def to_xml(self, box):
-        doc = minidom.Document()
-
-        root = doc.createElement("boxes")
-        doc.appendChild(root)
-
-        traversed = dict()
-        self._traverse_boxes(doc, traversed, box)
-
-        # Keep the order
-        for guid in sorted(traversed.keys()):
-            bnode = traversed[guid]
-            root.appendChild(bnode)
-       
-        try:
-            xml = doc.toprettyxml(indent="    ", encoding="UTF-8")
-        except:
-            print >>sys.stderr, "Oops: generating XML from %s" % (data,)
-            raise
-        
-        return xml
-
-    def _traverse_boxes(self, doc, traversed, box):
-        bnode = doc.createElement("box")
-        bnode.setAttribute("guid", xmlencode(box.guid))
-        bnode.setAttribute("label", xmlencode(box.label))
-        bnode.setAttribute("x", xmlencode(box.x))
-        bnode.setAttribute("y", xmlencode(box.y))
-        bnode.setAttribute("width", xmlencode(box.width))
-        bnode.setAttribute("height", xmlencode(box.height))
-
-        traversed[box.guid] = bnode
-
-        anode = doc.createElement("attributes")
-        bnode.appendChild(anode)
-        for name in sorted(box.attributes):
-            value = getattr(box.a, name)
-            aanode = doc.createElement("attribute")
-            anode.appendChild(aanode)
-            aanode.setAttribute("name", xmlencode(name))
-            aanode.setAttribute("value", xmlencode(value))
-            aanode.setAttribute("type", from_type(value))
-
-        tnode = doc.createElement("tags")
-        bnode.appendChild(tnode)
-        for tag in sorted(box.tags):
-            ttnode = doc.createElement("tag")
-            tnode.appendChild(ttnode)
-            ttnode.setAttribute("name", xmlencode(tag))
-
-        cnode = doc.createElement("connections")
-        bnode.appendChild(cnode)
-        for b in sorted(box.connections):
-            ccnode = doc.createElement("connection")
-            cnode.appendChild(ccnode)
-            ccnode.setAttribute("guid", xmlencode(b.guid))
-            if b.guid not in traversed:
-                self._traverse_boxes(doc, traversed, b)
-
-    def from_xml(self, xml):
-        doc = minidom.parseString(xml)
-        bnode_list = doc.getElementsByTagName("box")
-
-        boxes = dict()
-        connections = dict()
-
-        for bnode in bnode_list:
-            if bnode.nodeType == doc.ELEMENT_NODE:
-                guid = int(bnode.getAttribute("guid"))
-                label = xmldecode(bnode.getAttribute("label"))
-                x = float(bnode.getAttribute("x"))
-                y = float(bnode.getAttribute("y"))
-                height = float(bnode.getAttribute("height"))
-                width = float(bnode.getAttribute("width"))
-                box = Box(label=label, guid=guid)
-                boxes[guid] = box
-
-                anode_list = bnode.getElementsByTagName("attribute") 
-                for anode in anode_list:
-                    name = xmldecode(anode.getAttribute("name"))
-                    value = xmldecode(anode.getAttribute("value"))
-                    type = xmldecode(anode.getAttribute("type"))
-                    value = to_type(type, value)
-                    setattr(box.a, name, value)
-                    
-                tnode_list = bnode.getElementsByTagName("tag") 
-                for tnode in tnode_list:
-                    value = xmldecode(tnode.getAttribute("name"))
-                    box.tadd(value)
-
-                connections[box] = set()
-                cnode_list = bnode.getElementsByTagName("connection")
-                for cnode in cnode_list:
-                    guid = int(cnode.getAttribute("guid"))
-                    connections[box].add(guid)
-
-        for box, conns in connections.iteritems():
-            for guid in conns:
-                b = boxes[guid]
-                box.connect(b)
-
-        return box
-
diff --git a/src/nepi/util/parsers/__init__.py b/src/nepi/util/parsers/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/nepi/util/parsers/xml_parser.py b/src/nepi/util/parsers/xml_parser.py
new file mode 100644 (file)
index 0000000..d5bc8a3
--- /dev/null
@@ -0,0 +1,294 @@
+#
+#    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.util.timefuncs import stformat, tsformat
+
+from xml.dom import minidom
+
+import datetime
+import sys
+import os
+
+STRING = "string"
+BOOL = "bool"
+INTEGER = "integer"
+DOUBLE = "float"
+
+def xmlencode(s):
+    if isinstance(s, str):
+        rv = s.decode("latin1")
+    if isinstance(s, datetime.datetime):
+        rv = tsformat(s)
+    elif not isinstance(s, unicode):
+        rv = unicode(s)
+    else:
+        rv = s
+    return rv.replace(u'\x00',u'&#0000;')
+
+def xmldecode(s, cast = str):
+    ret = s.replace(u'&#0000',u'\x00').encode("ascii")
+    ret = cast(ret)
+    if s == "None":
+        return None
+    return ret
+
+def from_type(value):
+    if isinstance(value, bool):
+        return BOOL
+    if isinstance(value, int):
+        return INTEGER
+    if isinstance(value, float):
+        return DOUBLE
+
+    return STRING
+
+def to_type(type, value):
+    if not value:
+        return value
+
+    if type == STRING:
+        return str(value)
+    if type == BOOL:
+        return value == "True"
+    if type == INTEGER:
+        return int(value)
+    if type == DOUBLE:
+        return float(value)
+
+class ECXMLParser(object):
+    def to_xml(self, ec):
+        
+        doc = minidom.Document()
+        
+        self._ec_to_xml(doc, ec)
+       
+        try:
+            xml = doc.toprettyxml(indent="    ", encoding="UTF-8")
+        except:
+            print >>sys.stderr, "Oops: generating XML from %s" % (data,)
+            raise
+        
+        return xml
+
+    def _ec_to_xml(self, doc, ec):
+        ecnode = doc.createElement("experiment")
+        ecnode.setAttribute("exp_id", xmlencode(ec.exp_id))
+        ecnode.setAttribute("run_id", xmlencode(ec.run_id))
+        ecnode.setAttribute("nthreads", xmlencode(ec.nthreads))
+        doc.appendChild(ecnode)
+
+        for guid, rm in ec._resources.iteritems():
+            self._rm_to_xml(doc, ecnode, ec, guid, rm)
+
+        return doc
+
+    def _rm_to_xml(self, doc, ecnode, ec, guid, rm):
+        rmnode = doc.createElement("rm")
+        rmnode.setAttribute("guid", xmlencode(guid))
+        rmnode.setAttribute("rtype", xmlencode(rm._rtype))
+        rmnode.setAttribute("state", xmlencode(rm._state))
+        if rm._start_time:
+            rmnode.setAttribute("start_time", xmlencode(rm._start_time))
+        if rm._stop_time:
+            rmnode.setAttribute("stop_time", xmlencode(rm._stop_time))
+        if rm._discover_time:
+            rmnode.setAttribute("discover_time", xmlencode(rm._discover_time))
+        if rm._provision_time:    
+            rmnode.setAttribute("provision_time", xmlencode(rm._provision_time))
+        if rm._ready_time:
+            rmnode.setAttribute("ready_time", xmlencode(rm._ready_time))
+        if rm._release_time:
+            rmnode.setAttribute("release_time", xmlencode(rm._release_time))
+        if rm._failed_time:
+            rmnode.setAttribute("failed_time", xmlencode(rm._failed_time))
+        ecnode.appendChild(rmnode)
+
+        anode = doc.createElement("attributes")
+        attributes = False
+
+        for attr in rm._attrs.values():
+            if attr.has_changed:
+                attributes = True
+                aanode = doc.createElement("attribute")
+                aanode.setAttribute("name", xmlencode(attr.name))
+                aanode.setAttribute("value", xmlencode(attr.value))
+                aanode.setAttribute("type", from_type(attr.value))
+                anode.appendChild(aanode)
+    
+        if attributes: 
+            rmnode.appendChild(anode)
+
+        cnode = doc.createElement("connections")
+        connections = False
+        
+        for guid in rm._connections:
+            connections = True
+            ccnode = doc.createElement("connection")
+            ccnode.setAttribute("guid", xmlencode(guid))
+            cnode.appendChild(ccnode)
+        
+        if connections:
+           rmnode.appendChild(cnode)
+
+        cnnode = doc.createElement("conditions")
+        conditions = False
+
+        for action, conds in rm._conditions.iteritems():
+            conditions = True
+            for (group, state, time) in conds:
+                ccnnode = doc.createElement("condition")
+                ccnnode.setAttribute("action", xmlencode(action))
+                ccnnode.setAttribute("group", xmlencode(group))
+                ccnnode.setAttribute("state", xmlencode(state))
+                ccnnode.setAttribute("time", xmlencode(time))
+                cnnode.appendChild(ccnnode)
+        
+        if conditions:
+           rmnode.appendChild(cnnode)
+
+        tnode = doc.createElement("traces")
+        traces = False
+
+        for trace in rm._trcs.values():
+            if trace.enabled:
+                traces = True
+                ttnode = doc.createElement("trace")
+                ttnode.setAttribute("name", xmlencode(trace.name))
+                tnode.appendChild(ttnode)
+    
+        if traces: 
+            rmnode.appendChild(tnode)
+
+    def from_xml(self, xml):
+        doc = minidom.parseString(xml)
+        return self._ec_from_xml(doc)
+
+    def _ec_from_xml(self, doc):
+        from nepi.execution.ec import ExperimentController
+        ec = None
+        
+        ecnode_list = doc.getElementsByTagName("experiment")
+        for ecnode in ecnode_list:
+            if ecnode.nodeType == doc.ELEMENT_NODE:
+                exp_id = xmldecode(ecnode.getAttribute("exp_id"))
+                run_id = xmldecode(ecnode.getAttribute("run_id"))
+                nthreads = xmldecode(ecnode.getAttribute("nthreads"))
+            
+                os.environ["NEPI_NTHREADS"] = nthreads
+                ec = ExperimentController(exp_id = exp_id)
+
+                connections = set()
+
+                rmnode_list = ecnode.getElementsByTagName("rm")
+                for rmnode in rmnode_list:
+                    if rmnode.nodeType == doc.ELEMENT_NODE:
+                        self._rm_from_xml(doc, rmnode, ec, connections)
+
+                for (guid1, guid2) in connections:
+                    ec.register_connection(guid1, guid2)
+
+                break
+
+        return ec
+
+    def _rm_from_xml(self, doc, rmnode, ec, connections):
+        start_time = None
+        stop_time = None
+        discover_time = None
+        provision_time = None
+        ready_time = None
+        release_time = None
+        failed_time = None
+
+        guid = xmldecode(rmnode.getAttribute("guid"), int)
+        rtype = xmldecode(rmnode.getAttribute("rtype"))
+
+        # FOR NOW ONLY STATE NEW IS ALLOWED
+        state = 0
+        """
+        state = xmldecode(rmnode.getAttribute("state"), int)
+
+        if rmnode.hasAttribute("start_time"):
+            start_time = xmldecode(rmnode.getAttribute("start_time"), 
+                    datetime.datetime)
+        if rmnode.hasAttribute("stop_time"):
+            stop_time = xmldecode(rmnode.getAttribute("stop_time"), 
+                    datetime.datetime)
+        if rmnode.hasAttribute("discover_time"):
+            dicover_time = xmldecode(rmnode.getAttribute("discover_time"), 
+                    datetime.datetime)
+        if rmnode.hasAttribute("provision_time"):
+            provision_time = xmldecode(rmnode.getAttribute("provision_time"),
+                    datetime.datetime)
+        if rmnode.hasAttribute("ready_time"):
+            ready_time = xmldecode(rmnode.getAttribute("ready_time"),
+                    datetime.datetime)
+        if rmnode.hasAttribute("release_time"):
+            release_time = xmldecode(rmnode.getAttribute("release_time"),
+                    datetime.datetime)
+        if rmnode.hasAttribute("failed_time"):
+            failed_time = xmldecode(rmnode.getAttribute("failed_time"),
+                    datetime.datetime)
+        """
+
+        ec.register_resource(rtype, guid = guid)
+        rm = ec.get_resource(guid)
+        rm.set_state_time(state, "_start_time", start_time)
+        rm.set_state_time(state, "_stop_time", stop_time)
+        rm.set_state_time(state, "_discover_time", discover_time)
+        rm.set_state_time(state, "_provision_time", provision_time)
+        rm.set_state_time(state, "_ready_time", ready_time)
+        rm.set_state_time(state, "_release_time", release_time)
+        rm.set_state_time(state, "_failed_time", failed_time)
+        
+        anode_list = rmnode.getElementsByTagName("attributes")
+        if anode_list:
+            aanode_list = anode_list[0].getElementsByTagName("attribute") 
+            for aanode in aanode_list:
+                name = xmldecode(aanode.getAttribute("name"))
+                value = xmldecode(aanode.getAttribute("value"))
+                tipe = xmldecode(aanode.getAttribute("type"))
+                value = to_type(tipe, value)
+                rm.set(name, value)
+
+        cnode_list = rmnode.getElementsByTagName("connections")
+        if cnode_list:
+            ccnode_list = cnode_list[0].getElementsByTagName("connection") 
+            for ccnode in ccnode_list:
+                guid2 = xmldecode(ccnode.getAttribute("guid"), int)
+                connections.add((guid, guid2))
+
+        tnode_list = rmnode.getElementsByTagName("traces")
+        if tnode_list:
+            ttnode_list = tnode_list[0].getElementsByTagName("trace") 
+            for ttnode in ttnode_list:
+                name = xmldecode(ttnode.getAttribute("name"))
+                ec.enable_trace(guid, name)
+
+        cnnode_list = rmnode.getElementsByTagName("conditions")
+        if cnnode_list:
+            ccnnode_list = cnnode_list[0].getElementsByTagName("condition") 
+            for ccnnode in ccnnode_list:
+                action = xmldecode(ccnnode.getAttribute("action"), int)
+                group = xmldecode(ccnnode.getAttribute("group"), eval)
+                state = xmldecode(ccnnode.getAttribute("state"), int)
+                time = xmldecode(ccnnode.getAttribute("time"))
+                time = to_type('STRING', time)
+                ec.register_condition(guid, action, group, state, time = time)
+                 
diff --git a/src/nepi/util/plotter.py b/src/nepi/util/plotter.py
new file mode 100644 (file)
index 0000000..c21f598
--- /dev/null
@@ -0,0 +1,94 @@
+#
+#    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>
+
+import networkx
+import os
+
+class PFormats:
+    DOT = "dot"
+    FIGURE = "figure"
+
+class ECPlotter(object):
+    def plot(self, ec, fpath = None, format= PFormats.FIGURE, persist = False):
+        graph, labels = self._ec2graph(ec)
+
+        add_extension = False
+
+        if persist and not fpath:
+            import tempfile
+            dirpath = tempfile.mkdtemp()
+            fpath = os.path.join(dirpath, "%s_%s" % (ec.exp_id, ec.run_id)) 
+            add_extension = True
+
+        if format == PFormats.FIGURE:
+            import matplotlib.pyplot as plt
+            pos = networkx.graphviz_layout(graph, prog="neato")
+            networkx.draw(graph, pos = pos, node_color="white", 
+                    node_size = 500, with_labels=True)
+           
+            label = "\n".join(map(lambda v: "%s: %s" % (v[0], v[1]), labels.iteritems()))
+            plt.annotate(label, xy=(0.0, 0.95), xycoords='axes fraction')
+            
+            if persist:
+                if add_extension:
+                    fpath += ".png"
+
+                plt.savefig(fpath, bbox_inches="tight")
+            else:
+                plt.show()
+
+        elif format == PFormats.DOT:
+            if persist:
+                if add_extension:
+                    fpath += ".dot"
+
+                networkx.write_dot(graph, fpath)
+            else:
+                import subprocess
+                subprocess.call(["dot", "-Tps", fpath, "-o", "%s.ps" % fpath])
+                subprocess.call(["evince","%s.ps" % fpath])
+        
+        return fpath
+
+    def _ec2graph(self, ec):
+        graph = networkx.Graph(graph = dict(overlap = "false"))
+
+        labels = dict()
+        connections = set()
+
+        for guid, rm in ec._resources.iteritems():
+            label = rm.get_rtype()
+
+            graph.add_node(guid,
+                label = "%d %s" % (guid, label),
+                width = 50/72.0, # 1 inch = 72 points
+                height = 50/72.0, 
+                shape = "circle")
+
+            labels[guid] = label
+
+            for guid2 in rm.connections:
+                # Avoid adding a same connection twice
+                if (guid2, guid) not in connections:
+                    connections.add((guid, guid2))
+
+        for (guid1, guid2) in connections:
+            graph.add_edge(guid1, guid2)
+
+        return graph, labels
diff --git a/src/nepi/util/serializer.py b/src/nepi/util/serializer.py
new file mode 100644 (file)
index 0000000..9ed216f
--- /dev/null
@@ -0,0 +1,61 @@
+#
+#    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>
+
+import datetime
+import os
+
+class SFormats:
+    XML = "xml"
+    
+class ECSerializer(object):
+    def load(self, path, format = SFormats.XML):
+        if format == SFormats.XML:
+            from nepi.util.parsers.xml_parser import ECXMLParser
+            
+            parser = ECXMLParser()
+            f = open(path, "r")
+            xml = f.read()
+            f.close()
+
+            ec = parser.from_xml(xml)
+
+        return ec
+
+    def serialize(self, ec, format = SFormats.XML):
+        if format == SFormats.XML:
+            from nepi.util.parsers.xml_parser import ECXMLParser
+            
+            parser = ECXMLParser()
+            sec = parser.to_xml(ec)
+
+        return sec
+
+    def save(self, ec, path, format = SFormats.XML):
+        date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
+        filename = "%s_%s" % (ec.exp_id, date)
+
+        if format == SFormats.XML:
+            path = os.path.join(path, "%s.xml" % filename)
+            sec = self.serialize(ec, format = format)
+            f = open(path, "w")
+            f.write(sec)
+            f.close()
+
+        return path
+
diff --git a/test/design/box.py b/test/design/box.py
deleted file mode 100755 (executable)
index d1b9c03..0000000
+++ /dev/null
@@ -1,69 +0,0 @@
-#!/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.design.box import Box 
-
-import unittest
-
-class BoxDesignTestCase(unittest.TestCase):
-    def test_simple_design(self):
-        node1 = Box()
-        node2 = Box()
-
-        node1.label = "uno"
-        node2.label = "dos"
-
-        node1.tadd('nodo')
-        node2.tadd('mynodo')
-
-        self.assertEquals(node1.tags, set(['nodo']))
-        self.assertEquals(node2.tags, set(['mynodo']))
-       
-        node1.a.hola = "chau"
-        node2.a.hello = "bye"
-
-        self.assertEquals(node1.a.hola, "chau")
-        self.assertEquals(node2.a.hello, "bye")
-
-        node1.connect(node2)
-        
-        self.assertEquals(node1.connections, set([node2]))
-        self.assertEquals(node2.connections, set([node1]))
-        self.assertTrue(node1.is_connected(node2))
-        self.assertTrue(node2.is_connected(node1))
-
-        self.assertEquals(node1.c.dos.a.hello, "bye")
-        self.assertEquals(node2.c.uno.a.hola, "chau")
-       
-        node2.disconnect(node1)
-
-        self.assertEquals(node1.connections, set([]))
-        self.assertEquals(node2.connections, set([]))
-        self.assertFalse(node1.is_connected(node2))
-        self.assertFalse(node2.is_connected(node1))
-
-        self.assertRaises(AttributeError, node1.c.dos)
-        self.assertRaises(AttributeError, node2.c.uno)
-
-
-if __name__ == '__main__':
-    unittest.main()
-
diff --git a/test/execution/runner.py b/test/execution/runner.py
new file mode 100755 (executable)
index 0000000..b06d383
--- /dev/null
@@ -0,0 +1,175 @@
+#!/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.resource import ResourceManager, ResourceState, \
+        clsinit_copy, ResourceAction, ResourceFactory
+from nepi.execution.runner import ExperimentRunner
+
+import functools
+import os
+import shutil
+import tempfile
+import time
+import unittest
+
+reschedule_delay = "0.5s"
+deploy_time = 0
+run_time = 0
+
+class Link(ResourceManager):
+    _rtype = "dummy::Link"
+    def do_deploy(self):
+        time.sleep(deploy_time)
+        super(Link, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Interface(ResourceManager):
+    _rtype = "dummy::Interface"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+        link = self.get_connected(Link.get_rtype())[0]
+
+        if node.state < ResourceState.READY or \
+                link.state < ResourceState.READY:
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Interface, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Node(ResourceManager):
+    _rtype = "dummy::Node"
+
+    def do_deploy(self):
+        self.logger.debug(" -------- DO_DEPLOY ------- ")
+        time.sleep(deploy_time)
+        super(Node, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Application(ResourceManager):
+    _rtype = "dummy::Application"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+
+        if node.state < ResourceState.READY: 
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Application, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+    def do_start(self):
+        super(Application, self).do_start()
+        time.sleep(run_time)
+        self.ec.schedule("0s", self.stop)
+
+ResourceFactory.register_type(Application)
+ResourceFactory.register_type(Node)
+ResourceFactory.register_type(Interface)
+ResourceFactory.register_type(Link)
+
+class RunnerTestCase(unittest.TestCase):
+    def test_runner_max_runs(self):
+        node_count = 4
+        app_count = 2
+
+        ec = ExperimentController(exp_id = "max-runs-test")
+       
+        # Add simulated nodes and applications
+        nodes = list()
+        apps = list()
+        ifaces = list()
+
+        for i in xrange(node_count):
+            node = ec.register_resource("dummy::Node")
+            nodes.append(node)
+            
+            iface = ec.register_resource("dummy::Interface")
+            ec.register_connection(node, iface)
+            ifaces.append(iface)
+
+            for i in xrange(app_count):
+                app = ec.register_resource("dummy::Application")
+                ec.register_connection(node, app)
+                apps.append(app)
+
+        link = ec.register_resource("dummy::Link")
+
+        for iface in ifaces:
+            ec.register_connection(link, iface)
+
+        rnr = ExperimentRunner()
+        runs = rnr.run(ec, min_runs = 5, max_runs = 10, wait_guids = apps, 
+                wait_time = 0)
+
+        self.assertEquals(runs, 10)
+
+    def test_runner_convergence(self):
+        node_count = 4
+        app_count = 2
+
+        ec = ExperimentController(exp_id = "convergence-test")
+       
+        # Add simulated nodes and applications
+        nodes = list()
+        apps = list()
+        ifaces = list()
+
+        for i in xrange(node_count):
+            node = ec.register_resource("dummy::Node")
+            nodes.append(node)
+            
+            iface = ec.register_resource("dummy::Interface")
+            ec.register_connection(node, iface)
+            ifaces.append(iface)
+
+            for i in xrange(app_count):
+                app = ec.register_resource("dummy::Application")
+                ec.register_connection(node, app)
+                apps.append(app)
+
+        link = ec.register_resource("dummy::Link")
+
+        for iface in ifaces:
+            ec.register_connection(link, iface)
+
+        samples = [10, 10, 10, 10, 12, 10, 12, 10, 10, 11]
+        
+        def compute_metric_callback(samples, ec, run):
+            return samples[run-1]
+
+        metric_callback = functools.partial(compute_metric_callback, samples)
+
+        rnr = ExperimentRunner()
+        runs = rnr.run(ec, min_runs = 5, 
+                compute_metric_callback = metric_callback,
+                wait_guids = apps, 
+                wait_time = 0)
+
+        self.assertEquals(runs, 10)
+                       
+if __name__ == '__main__':
+    unittest.main()
+
index a1f1397..0a76dcc 100755 (executable)
@@ -250,6 +250,7 @@ main (void)
 
         ec.register_connection(app, node)
 
+
         ec.deploy()
 
         ec.wait_finished([app])
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/test/resources/linux/multirun.py b/test/resources/linux/multirun.py
new file mode 100755 (executable)
index 0000000..d548bd2
--- /dev/null
@@ -0,0 +1,109 @@
+#!/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.runner import ExperimentRunner 
+
+from test_utils import skipIfNotAlive, skipInteractive
+
+import functools
+import glob
+import os
+import re
+import shutil
+import time
+import tempfile
+import unittest
+
+_ping_re = re.compile("[^/]+rtt min/avg/max/mdev = (?P<min>\d\.\d+)/(?P<avg>\d\.\d+)/(?P<max>\d\.\d+)/(?P<mdev>\d\.\d+)[^/]+", re.MULTILINE)
+
+class LinuxMultiRunTestCase(unittest.TestCase):
+    def setUp(self):
+        self.fedora_host = "nepi2.pl.sophia.inria.fr"
+        self.fedora_user = "inria_nepi"
+
+        self.ubuntu_host = "roseval.pl.sophia.inria.fr"
+        self.ubuntu_user = "inria_nepi"
+        
+        self.target = "nepi5.pl.sophia.inria.fr"
+
+    @skipIfNotAlive
+    def t_simple_multirun(self, host, user, depends):
+
+        dirpath = tempfile.mkdtemp()
+
+        ec = ExperimentController(exp_id="test-condition-multirun")
+        
+        node = ec.register_resource("LinuxNode")
+        ec.set(node, "hostname", host)
+        ec.set(node, "username", user)
+        ec.set(node, "cleanHome", True)
+        ec.set(node, "cleanProcesses", True)
+
+        ping = ec.register_resource("LinuxApplication")
+        ec.set(ping, "command", "ping -c10 nepi.inria.fr")
+        ec.register_connection(ping, node)
+
+        collector = ec.register_resource("Collector")
+        ec.set(collector, "traceName", "stdout")
+        ec.set(collector, "storeDir", dirpath)
+        ec.set(collector, "useRunId", True)
+        ec.register_connection(ping, collector)
+
+        def compute_metric_callback(ping, ec, run):
+            stdout = ec.trace(ping, "stdout")
+
+            m = _ping_re.match(stdout)
+            if not m:
+                return None
+            
+            return float(m.groupdict()["min"])
+
+        metric_callback = functools.partial(compute_metric_callback, ping)
+
+        rnr = ExperimentRunner()
+        runs = rnr.run(ec, min_runs = 5, 
+                compute_metric_callback = metric_callback,
+                wait_guids = [ping],
+                wait_time = 0)
+
+        self.assertTrue(runs >= 5)
+
+        dircount = 0
+        for d in os.listdir(dirpath):
+            path = os.path.join(dirpath, d)
+            if os.path.isdir(path):
+                dircount += 1
+                logs = glob.glob(os.path.join(path, "*.stdout"))
+                self.assertEquals(len(logs), 1)
+        
+        self.assertEquals(runs, dircount)
+
+        shutil.rmtree(dirpath)
+
+    def test_simple_multirun_fedora(self):
+        self.t_simple_multirun(self.fedora_host, self.fedora_user, "nc")
+
+    def test_simple_multirun_ubuntu(self):
+        self.t_simple_multirun(self.ubuntu_host, self.ubuntu_user, "netcat")
+
+if __name__ == '__main__':
+    unittest.main()
+
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
index 8528f69..3f9443a
@@ -136,6 +136,10 @@ class LinuxNS3DceApplicationTest(unittest.TestCase):
         self.fedora_user = "inria_nepi"
         self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'])
 
+        self.ubuntu_host = "roseval.pl.sophia.inria.fr"
+        self.ubuntu_user = "inria_nepi"
+        self.ubuntu_identity = "%s/.ssh/id_rsa" % (os.environ['HOME'])
     @skipIfNotAlive
     def t_dce_ping(self, host, user = None, identity = None):
         ec = ExperimentController(exp_id = "test-dce-ping")
@@ -248,10 +252,17 @@ class LinuxNS3DceApplicationTest(unittest.TestCase):
         ### create applications
         ccnd1 = ec.register_resource("ns3::LinuxCCNDceApplication")
 
-        # NOTE THAT INSTALLATION MIGHT FAIL IF openjdk-6-jdk is not installed
-        ec.set(ccnd1, "depends", "libpcap0.8-dev openjdk-6-jdk ant1.8 autoconf "
-            "libssl-dev libexpat-dev libpcap-dev libecryptfs0 libxml2-utils auto"
-            "make gawk gcc g++ git-core pkg-config libpcre3-dev openjdk-6-jre-lib")
+        if host == self.fedora_host:
+            depends = ( " autoconf openssl-devel  expat-devel libpcap-devel "
+                " ecryptfs-utils-devel libxml2-devel automake gawk " 
+                " gcc gcc-c++ git pcre-devel make ")
+        else: # UBUNTU
+            # NOTE THAT INSTALLATION MIGHT FAIL IF openjdk-6-jdk is not installed
+            depends = ( "libpcap0.8-dev openjdk-6-jdk ant1.8 autoconf "
+                    "libssl-dev libexpat-dev libpcap-dev libecryptfs0 libxml2-utils auto"
+                    "make gawk gcc g++ git-core pkg-config libpcre3-dev openjdk-6-jre-lib")
+
+        ec.set (ccnd1, "depends", depends)
         ec.set (ccnd1, "sources", "http://www.ccnx.org/releases/ccnx-0.7.2.tar.gz")
         ec.set (ccnd1, "build", "tar zxf ${SRC}/ccnx-0.7.2.tar.gz && "
                 "cd ccnx-0.7.2 && "
@@ -341,7 +352,10 @@ class LinuxNS3DceApplicationTest(unittest.TestCase):
         ec.shutdown()
 
     def test_dce_ping_fedora(self):
-        self.t_dce_ping(self.fedora_host, self.fedora_user, self.fedora_identity)
+        self.t_dce_ping(self.fedora_host, self.fedora_user, self.fedora_identity) 
+
+    def test_dce_ping_ubuntu(self):
+        self.t_dce_ping(self.ubuntu_host, self.ubuntu_user, self.ubuntu_identity)
 
     def test_dce_ping_local(self):
         self.t_dce_ping("localhost")
@@ -349,6 +363,9 @@ class LinuxNS3DceApplicationTest(unittest.TestCase):
     def test_dce_ccn_fedora(self):
         self.t_dce_ccn(self.fedora_host, self.fedora_user, self.fedora_identity)
 
+    def test_dce_ccn_ubuntu(self):
+        self.t_dce_ccn(self.ubuntu_host, self.ubuntu_user, self.ubuntu_identity)
+
     def test_dce_ccn_local(self):
         self.t_dce_ccn("localhost")
 
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/test/resources/linux/ns3/serialization.py b/test/resources/linux/ns3/serialization.py
new file mode 100755 (executable)
index 0000000..fbf665f
--- /dev/null
@@ -0,0 +1,483 @@
+#!/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 shutil
+import time
+import tempfile
+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_point2point_device(ec, node, ip, prefix):
+    dev = ec.register_resource("ns3::PointToPointNetDevice")
+    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
+
+def add_csma_device(ec, node, ip, prefix):
+    dev = ec.register_resource("ns3::CsmaNetDevice")
+    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
+
+def add_wifi_device(ec, node, ip, prefix, 
+        access_point = False):
+    dev = ec.register_resource("ns3::WifiNetDevice")
+    ec.set(dev, "ip", ip)
+    ec.set(dev, "prefix", prefix)
+    ec.register_connection(node, dev)
+
+    phy = ec.register_resource("ns3::YansWifiPhy")
+    ec.set(phy, "Standard", "WIFI_PHY_STANDARD_80211a")
+    ec.register_connection(dev, phy)
+
+    error = ec.register_resource("ns3::NistErrorRateModel")
+    ec.register_connection(phy, error)
+
+    manager = ec.register_resource("ns3::ArfWifiManager")
+    ec.register_connection(dev, manager)
+
+    if access_point:
+        mac = ec.register_resource("ns3::ApWifiMac")
+    else:
+        mac = ec.register_resource("ns3::StaWifiMac")
+
+    ec.set(mac, "Standard", "WIFI_PHY_STANDARD_80211a")
+    ec.register_connection(dev, mac)
+
+    return dev, phy
+
+def add_random_mobility(ec, node, x, y, z, speed, bounds_width, 
+        bounds_height):
+    position = "%d:%d:%d" % (x, y, z)
+    bounds = "0|%d|0|%d" % (bounds_width, bounds_height) 
+    speed = "ns3::UniformRandomVariable[Min=%d|Max=%s]" % (speed, speed)
+    pause = "ns3::ConstantRandomVariable[Constant=1.0]"
+    
+    mobility = ec.register_resource("ns3::RandomDirection2dMobilityModel")
+    ec.set(mobility, "Position", position)
+    ec.set(mobility, "Bounds", bounds)
+    ec.set(mobility, "Speed", speed)
+    ec.set(mobility, "Pause",  pause)
+    ec.register_connection(node, mobility)
+    return mobility
+
+def add_constant_mobility(ec, node, x, y, z):
+    mobility = ec.register_resource("ns3::ConstantPositionMobilityModel") 
+    position = "%d:%d:%d" % (x, y, z)
+    ec.set(mobility, "Position", position)
+    ec.register_connection(node, mobility)
+    return mobility
+
+def add_wifi_channel(ec):
+    channel = ec.register_resource("ns3::YansWifiChannel")
+    delay = ec.register_resource("ns3::ConstantSpeedPropagationDelayModel")
+    ec.register_connection(channel, delay)
+
+    loss  = ec.register_resource("ns3::LogDistancePropagationLossModel")
+    ec.register_connection(channel, loss)
+
+    return channel
+
+class LinuxNS3SimulationSerializationTest(unittest.TestCase):
+    def setUp(self):
+        #self.fedora_host = "nepi2.pl.sophia.inria.fr"
+        self.fedora_host = "planetlab1.informatik.uni-erlangen.de"
+        self.fedora_user = "inria_nepi"
+        self.fedora_identity = "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'])
+
+    @skipIfNotAlive
+    def t_wifi_serialize(self, host, user = None, identity = None):
+        bounds_width = bounds_height = 200
+        x = y = 100
+        speed = 1
+
+        dirpath = tempfile.mkdtemp()
+        
+        ec = ExperimentController(exp_id = "test-ns3-wifi-ping")
+        
+        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, "cleanHome", True)
+
+        simu = ec.register_resource("LinuxNS3Simulation")
+        ec.set(simu, "verbose", True)
+        ec.register_connection(simu, node)
+
+        nsnode1 = add_ns3_node(ec, simu)
+        dev1, phy1 = add_wifi_device(ec, nsnode1, "10.0.0.1", "24", access_point = True)
+        mobility1 = add_constant_mobility(ec, nsnode1, x, y, 0)
+
+        nsnode2 = add_ns3_node(ec, simu)
+        dev2, phy2 = add_wifi_device(ec, nsnode2, "10.0.0.2", "24", access_point = False)
+        mobility1 = add_constant_mobility(ec, nsnode2, x, y, 0)
+        #mobility2 = add_random_mobility(ec, nsnode2, x, y, 0, speed, bounds_width, bounds_height)
+
+        # Create channel
+        chan = add_wifi_channel(ec)
+        ec.register_connection(chan, phy1)
+        ec.register_connection(chan, phy2)
+
+        ### create pinger
+        ping = ec.register_resource("ns3::V4Ping")
+        ec.set (ping, "Remote", "10.0.0.1")
+        ec.set (ping, "Interval", "1s")
+        ec.set (ping, "Verbose", True)
+        ec.set (ping, "StartTime", "1s")
+        ec.set (ping, "StopTime", "21s")
+        ec.register_connection(ping, nsnode2)
+
+        filepath = ec.save(dirpath)
+        print filepath
+        
+        ec.deploy()
+
+        ec.wait_finished([ping])
+        
+        stdout = ec.trace(simu, "stdout")
+
+        expected = "20 packets transmitted, 20 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+
+        ec.shutdown()
+
+        # Load serialized experiment
+        ec2 = ExperimentController.load(filepath)
+        
+        ec2.deploy()
+
+        ec2.wait_finished([ping])
+        
+        self.assertEquals(len(ec.resources), len(ec2.resources))
+        
+        stdout = ec2.trace(simu, "stdout")
+        expected = "20 packets transmitted, 20 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+        
+        ec2.shutdown()
+
+        shutil.rmtree(dirpath)
+
+    @skipIfNotAlive
+    def t_routing_serialize(self, host, user = None, identity = None):
+        """ 
+        network topology:
+                                n4
+                                |
+           n1 -- p2p -- n2 -- csma -- n5 -- p2p -- n6
+           |                    | 
+           ping n6              n3
+           
+
+        """
+        dirpath = tempfile.mkdtemp()
+        
+        ec = ExperimentController(exp_id = "test-ns3-routes")
+        
+        node = ec.register_resource("LinuxNode")
+        if host == "localhost":
+            ec.set(node, "hostname", host)
+        else:
+            ec.set(node, "hostname", host)
+            ec.set(node, "username", user)
+            ec.set(node, "identity", identity)
+
+        ec.set(node, "cleanProcesses", True)
+        #ec.set(node, "cleanHome", True)
+
+        simu = ec.register_resource("LinuxNS3Simulation")
+        ec.set(simu, "verbose", True)
+        ec.register_connection(simu, node)
+
+        nsnode1 = add_ns3_node(ec, simu)
+        p2p12 = add_point2point_device(ec, nsnode1, "10.0.0.1", "30")
+
+        nsnode2 = add_ns3_node(ec, simu)
+        p2p21 = add_point2point_device(ec, nsnode2, "10.0.0.2", "30")
+        csma2 = add_csma_device(ec, nsnode2, "10.0.1.1", "24")
+
+        nsnode3 = add_ns3_node(ec, simu)
+        csma3 = add_csma_device(ec, nsnode3, "10.0.1.2", "24")
+
+        nsnode4 = add_ns3_node(ec, simu)
+        csma4 = add_csma_device(ec, nsnode4, "10.0.1.3", "24")
+
+        nsnode5 = add_ns3_node(ec, simu)
+        p2p56 = add_point2point_device(ec, nsnode5, "10.0.2.1", "30")
+        csma5 = add_csma_device(ec, nsnode5, "10.0.1.4", "24")
+
+        nsnode6 = add_ns3_node(ec, simu)
+        p2p65 = add_point2point_device(ec, nsnode6, "10.0.2.2", "30")
+
+        # P2P chan1
+        p2p_chan1 = ec.register_resource("ns3::PointToPointChannel")
+        ec.set(p2p_chan1, "Delay", "0s")
+        ec.register_connection(p2p_chan1, p2p12)
+        ec.register_connection(p2p_chan1, p2p21)
+
+        # CSMA chan
+        csma_chan = ec.register_resource("ns3::CsmaChannel")
+        ec.set(csma_chan, "Delay", "0s")
+        ec.register_connection(csma_chan, csma2)
+        ec.register_connection(csma_chan, csma3)
+        ec.register_connection(csma_chan, csma4)
+        ec.register_connection(csma_chan, csma5)
+
+        # P2P chan2
+        p2p_chan2 = ec.register_resource("ns3::PointToPointChannel")
+        ec.set(p2p_chan2, "Delay", "0s")
+        ec.register_connection(p2p_chan2, p2p56)
+        ec.register_connection(p2p_chan2, p2p65)
+
+        # Add routes - n1 - n6
+        r1 = ec.register_resource("ns3::Route")
+        ec.set(r1, "network", "10.0.2.0")
+        ec.set(r1, "prefix", "30")
+        ec.set(r1, "nexthop", "10.0.0.2")
+        ec.register_connection(r1, nsnode1)
+
+        # Add routes - n2 - n6
+        r2 = ec.register_resource("ns3::Route")
+        ec.set(r2, "network", "10.0.2.0")
+        ec.set(r2, "prefix", "30")
+        ec.set(r2, "nexthop", "10.0.1.4")
+        ec.register_connection(r2, nsnode2)
+
+        # Add routes - n5 - n1
+        r5 = ec.register_resource("ns3::Route")
+        ec.set(r5, "network", "10.0.0.0")
+        ec.set(r5, "prefix", "30")
+        ec.set(r5, "nexthop", "10.0.1.1")
+        ec.register_connection(r5, nsnode5)
+
+        # Add routes - n6 - n1
+        r6 = ec.register_resource("ns3::Route")
+        ec.set(r6, "network", "10.0.0.0")
+        ec.set(r6, "prefix", "30")
+        ec.set(r6, "nexthop", "10.0.2.1")
+        ec.register_connection(r6, nsnode6)
+
+        ### create pinger
+        ping = ec.register_resource("ns3::V4Ping")
+        ec.set (ping, "Remote", "10.0.2.2")
+        ec.set (ping, "Interval", "1s")
+        ec.set (ping, "Verbose", True)
+        ec.set (ping, "StartTime", "1s")
+        ec.set (ping, "StopTime", "21s")
+        ec.register_connection(ping, nsnode1)
+
+        filepath = ec.save(dirpath)
+        print filepath
+        
+        ec.deploy()
+
+        ec.wait_finished([ping])
+        
+        stdout = ec.trace(simu, "stdout")
+
+        expected = "20 packets transmitted, 20 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+
+        ec.shutdown()
+
+        # Load serialized experiment
+        ec2 = ExperimentController.load(filepath)
+        
+        ec2.deploy()
+
+        ec2.wait_finished([ping])
+        
+        self.assertEquals(len(ec.resources), len(ec2.resources))
+        
+        stdout = ec2.trace(simu, "stdout")
+        expected = "20 packets transmitted, 20 received, 0% packet loss"
+        self.assertTrue(stdout.find(expected) > -1)
+        
+        ec2.shutdown()
+
+        shutil.rmtree(dirpath)
+
+    @skipIfNotAlive
+    def t_dce_serialize(self, host, user = None, identity = None):
+        dirpath = tempfile.mkdtemp()
+        
+        ec = ExperimentController(exp_id = "test-ns3-dce")
+        
+        node = ec.register_resource("LinuxNode")
+        if host == "localhost":
+            ec.set(node, "hostname", host)
+        else:
+            ec.set(node, "hostname", host)
+            ec.set(node, "username", user)
+            ec.set(node, "identity", identity)
+
+        ec.set(node, "cleanProcesses", True)
+        #ec.set(node, "cleanHome", True)
+
+        simu = ec.register_resource("LinuxNS3Simulation")
+        ec.set(simu, "verbose", True)
+        ec.register_connection(simu, node)
+
+        nsnode1 = add_ns3_node(ec, simu)
+        p2p1 = add_point2point_device(ec, nsnode1, "10.0.0.1", "30")
+        ec.set(p2p1, "DataRate", "5Mbps")
+
+        nsnode2 = add_ns3_node(ec, simu)
+        p2p2 = add_point2point_device(ec, nsnode2, "10.0.0.2", "30")
+        ec.set(p2p2, "DataRate", "5Mbps")
+
+        # Create channel
+        chan = ec.register_resource("ns3::PointToPointChannel")
+        ec.set(chan, "Delay", "2ms")
+
+        ec.register_connection(chan, p2p1)
+        ec.register_connection(chan, p2p2)
+
+        ### create applications
+        udp_perf = ec.register_resource("ns3::DceApplication")
+        ec.set (udp_perf, "binary", "udp-perf")
+        ec.set (udp_perf, "stackSize", 1<<20)
+        ec.set (udp_perf, "arguments", "--duration=10;--nodes=2")
+        ec.set (udp_perf, "StartTime", "1s")
+        ec.set (udp_perf, "StopTime", "20s")
+        ec.register_connection(udp_perf, nsnode1)
+
+        udp_perf_client = ec.register_resource("ns3::DceApplication")
+        ec.set (udp_perf_client, "binary", "udp-perf")
+        ec.set (udp_perf_client, "stackSize", 1<<20)
+        ec.set (udp_perf_client, "arguments", "--client;--nodes=2;--host=10.0.0.1;--duration=10")
+        ec.set (udp_perf_client, "StartTime", "2s")
+        ec.set (udp_perf_client, "StopTime", "20s")
+        ec.register_connection(udp_perf_client, nsnode2)
+
+        filepath = ec.save(dirpath)
+        
+        ec.deploy()
+
+        ec.wait_finished([udp_perf_client])
+
+        # Give time to flush the streams
+        import time
+        time.sleep(5) 
+
+        expected = "udp-perf --duration=10 --nodes=2"
+        cmdline = ec.trace(udp_perf, "cmdline")
+        self.assertTrue(cmdline.find(expected) > -1, cmdline)
+
+        expected = "Start Time: NS3 Time:          1s ("
+        status = ec.trace(udp_perf, "status")
+        self.assertTrue(status.find(expected) > -1, status)
+
+        expected = "received=1500 bytes, 1 reads (@1500 bytes) 1500"
+        stdout = ec.trace(udp_perf, "stdout")
+        self.assertTrue(stdout.find(expected) > -1, stdout)
+
+        ec.shutdown()
+
+        # Load serialized experiment
+        ec2 = ExperimentController.load(filepath)
+        
+        ec2.deploy()
+        ec2.wait_finished([udp_perf_client])
+
+        # Give time to flush the streams
+        time.sleep(5) 
+       
+        self.assertEquals(len(ec.resources), len(ec2.resources))
+        expected = "udp-perf --duration=10 --nodes=2"
+        cmdline = ec2.trace(udp_perf, "cmdline")
+        self.assertTrue(cmdline.find(expected) > -1, cmdline)
+
+        expected = "Start Time: NS3 Time:          1s ("
+        status = ec2.trace(udp_perf, "status")
+        self.assertTrue(status.find(expected) > -1, status)
+
+        expected = "received=1500 bytes, 1 reads (@1500 bytes) 1500"
+        stdout = ec2.trace(udp_perf, "stdout")
+        self.assertTrue(stdout.find(expected) > -1, stdout)
+
+        ec2.shutdown()
+
+        shutil.rmtree(dirpath)
+    
+    def test_wifi_serialize_fedora(self):
+        self.t_wifi_serialize(self.fedora_host, self.fedora_user, self.fedora_identity)
+
+    def test_wifi_serialize_local(self):
+        self.t_wifi_serialize("localhost")
+
+    def test_routing_serialize_fedora(self):
+        self.t_routing_serialize(self.fedora_host, self.fedora_user, self.fedora_identity)
+
+    def test_routing_serialize_local(self):
+        self.t_routing_serialize("localhost")
+
+    def test_dce_serialize_fedora(self):
+        self.t_dce_serialize(self.fedora_host, self.fedora_user, self.fedora_identity)
+
+    def test_dce_serialize_local(self):
+        self.t_dce_serialize("localhost")
+
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/test/resources/linux/serialization.py b/test/resources/linux/serialization.py
new file mode 100755 (executable)
index 0000000..c133ab3
--- /dev/null
@@ -0,0 +1,114 @@
+#!/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.resource import ResourceState, ResourceAction
+from nepi.execution.trace import TraceAttr
+
+from test_utils import skipIfNotAlive, skipInteractive
+
+import os
+import shutil
+import time
+import tempfile
+import unittest
+
+class LinuxSerializationTestCase(unittest.TestCase):
+    def setUp(self):
+        self.fedora_host = "nepi2.pl.sophia.inria.fr"
+        self.fedora_user = "inria_nepi"
+
+        self.ubuntu_host = "roseval.pl.sophia.inria.fr"
+        self.ubuntu_user = "inria_nepi"
+        
+        self.target = "nepi5.pl.sophia.inria.fr"
+
+    @skipIfNotAlive
+    def t_condition_serialize(self, host, user, depends):
+
+        dirpath = tempfile.mkdtemp()
+
+        ec = ExperimentController(exp_id="test-condition-serial")
+        
+        node = ec.register_resource("LinuxNode")
+        ec.set(node, "hostname", host)
+        ec.set(node, "username", user)
+        ec.set(node, "cleanHome", True)
+        ec.set(node, "cleanProcesses", True)
+
+        server = ec.register_resource("LinuxApplication")
+        cmd = "echo 'HOLA' | nc -l 3333"
+        ec.set(server, "command", cmd)
+        ec.set(server, "depends", depends)
+        ec.register_connection(server, node)
+
+        client = ec.register_resource("LinuxApplication")
+        cmd = "nc 127.0.0.1 3333"
+        ec.set(client, "command", cmd)
+        ec.register_connection(client, node)
+
+        ec.register_condition(client, ResourceAction.START, server, ResourceState.STARTED)
+
+        apps = [client, server]
+        
+        filepath = ec.save(dirpath)
+        
+        ec.deploy()
+
+        ec.wait_finished(apps)
+
+        self.assertTrue(ec.state(node) == ResourceState.STARTED)
+        self.assertTrue(ec.state(server) == ResourceState.STOPPED)
+        self.assertTrue(ec.state(client) == ResourceState.STOPPED)
+
+        stdout = ec.trace(client, "stdout")
+        self.assertTrue(stdout.strip() == "HOLA")
+
+        ec.shutdown()
+
+        # Load serialized experiment
+        ec2 = ExperimentController.load(filepath)
+        
+        ec2.deploy()
+        ec2.wait_finished(apps)
+        
+        self.assertEquals(len(ec.resources), len(ec2.resources))
+        
+        self.assertTrue(ec2.state(node) == ResourceState.STARTED)
+        self.assertTrue(ec2.state(server) == ResourceState.STOPPED)
+        self.assertTrue(ec2.state(client) == ResourceState.STOPPED)
+
+        stdout = ec2.trace(client, "stdout")
+
+        self.assertTrue(stdout.strip() == "HOLA")
+        
+        ec2.shutdown()
+
+        shutil.rmtree(dirpath)
+
+    def test_condition_serialize_fedora(self):
+        self.t_condition_serialize(self.fedora_host, self.fedora_user, "nc")
+
+    def test_condition_serialize_ubuntu(self):
+        self.t_condition_serialize(self.ubuntu_host, self.ubuntu_user, "netcat")
+
+if __name__ == '__main__':
+    unittest.main()
+
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
old mode 100644 (file)
new mode 100755 (executable)
diff --git a/test/util/parser.py b/test/util/parser.py
deleted file mode 100755 (executable)
index e9fce6b..0000000
+++ /dev/null
@@ -1,57 +0,0 @@
-#!/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.design.box import Box 
-from nepi.util.parser import XMLParser
-
-import unittest
-
-class BoxDesignTestCase(unittest.TestCase):
-    def test_to_xml(self):
-        node1 = Box()
-        node2 = Box()
-
-        node1.label = "node1"
-        node2.label = "node2"
-
-        node1.connect(node2)
-
-        node1.a.dog = "cat"
-        node1.a.one = "two"
-        node1.a.t = "q"
-
-        node1.c.node2.a.sky = "sea"
-        node2.a.bee = "honey"
-
-        node1.tadd("unooo")
-        node2.tadd("dosss")
-
-        parser = XMLParser()
-        xml = parser.to_xml(node1)
-        
-        node = parser.from_xml(xml)
-        xml2 = parser.to_xml(node)
-        
-        self.assertEquals(xml, xml2)
-
-if __name__ == '__main__':
-    unittest.main()
-
diff --git a/test/util/plot.py b/test/util/plot.py
deleted file mode 100755 (executable)
index 38001de..0000000
+++ /dev/null
@@ -1,56 +0,0 @@
-#!/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.design.box import Box 
-from nepi.util.plot import Plotter
-
-import subprocess
-import unittest
-
-class BoxPlotTestCase(unittest.TestCase):
-    def xtest_plot(self):
-        """ XXX: This test is interactive, it will open an evince instance,
-        so it should not run automatically """
-        node1 = Box(label="node1")
-        ping1 = Box(label="ping")
-        mobility1 = Box(label="mob1")
-        node2 = Box(label="node2")
-        mobility2 = Box(label="mob2")
-        iface1 = Box(label="iface1")
-        iface2 = Box(label="iface2")
-        channel = Box(label="chan")
-
-        node1.connect(ping1)
-        node1.connect(mobility1)
-        node1.connect(iface1)
-        channel.connect(iface1)
-        channel.connect(iface2)
-        node2.connect(iface2)
-        node2.connect(mobility2)
-
-        plotter = Plotter(node1)
-        fname = plotter.plot()
-        subprocess.call(["dot", "-Tps", fname, "-o", "%s.ps"%fname])
-        subprocess.call(["evince","%s.ps"%fname])
-       
-if __name__ == '__main__':
-    unittest.main()
-
diff --git a/test/util/plotter.py b/test/util/plotter.py
new file mode 100755 (executable)
index 0000000..4e1a553
--- /dev/null
@@ -0,0 +1,138 @@
+#!/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.resource import ResourceManager, ResourceState, \
+        clsinit_copy, ResourceAction, ResourceFactory
+from nepi.util.plotter import PFormats
+
+import os
+import tempfile
+import time
+import unittest
+
+reschedule_delay = "0.5s"
+deploy_time = 0
+run_time = 0
+
+class Link(ResourceManager):
+    _rtype = "dummy::Link"
+    def do_deploy(self):
+        time.sleep(deploy_time)
+        super(Link, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Interface(ResourceManager):
+    _rtype = "dummy::Interface"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+        link = self.get_connected(Link.get_rtype())[0]
+
+        if node.state < ResourceState.READY or \
+                link.state < ResourceState.READY:
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Interface, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Node(ResourceManager):
+    _rtype = "dummy::Node"
+
+    def do_deploy(self):
+        self.logger.debug(" -------- DO_DEPLOY ------- ")
+        time.sleep(deploy_time)
+        super(Node, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Application(ResourceManager):
+    _rtype = "dummy::Application"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+
+        if node.state < ResourceState.READY: 
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Application, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+    def do_start(self):
+        super(Application, self).do_start()
+        time.sleep(run_time)
+        self.ec.schedule("0s", self.stop)
+
+ResourceFactory.register_type(Application)
+ResourceFactory.register_type(Node)
+ResourceFactory.register_type(Interface)
+ResourceFactory.register_type(Link)
+
+class PlotterTestCase(unittest.TestCase):
+    def test_serialize(self):
+        node_count = 4
+        app_count = 2
+
+        ec = ExperimentController(exp_id = "plotter-test")
+       
+        # Add simulated nodes and applications
+        nodes = list()
+        apps = list()
+        ifaces = list()
+
+        for i in xrange(node_count):
+            node = ec.register_resource("dummy::Node")
+            nodes.append(node)
+            
+            iface = ec.register_resource("dummy::Interface")
+            ec.register_connection(node, iface)
+            ifaces.append(iface)
+
+            for i in xrange(app_count):
+                app = ec.register_resource("dummy::Application")
+                ec.register_connection(node, app)
+                apps.append(app)
+
+        link = ec.register_resource("dummy::Link")
+
+        for iface in ifaces:
+            ec.register_connection(link, iface)
+       
+        fpath = ec.plot(persist = True)
+        statinfo = os.stat(fpath)
+        size = statinfo.st_size
+        self.assertTrue(size > 0)
+        self.assertTrue(fpath.endswith(".png"))
+
+        fpath = ec.plot(persist = True, format = PFormats.DOT)
+        statinfo = os.stat(fpath)
+        size = statinfo.st_size
+        self.assertTrue(size > 0)
+        self.assertTrue(fpath.endswith(".dot"))
+
+        print fpath
+
+
+if __name__ == '__main__':
+    unittest.main()
+
diff --git a/test/util/serializer.py b/test/util/serializer.py
new file mode 100755 (executable)
index 0000000..3896ea2
--- /dev/null
@@ -0,0 +1,146 @@
+#!/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.resource import ResourceManager, ResourceState, \
+        clsinit_copy, ResourceAction, ResourceFactory
+
+import os
+import tempfile
+import time
+import shutil
+import unittest
+
+reschedule_delay = "0.5s"
+deploy_time = 0
+run_time = 0
+
+class Link(ResourceManager):
+    _rtype = "dummy::Link"
+    def do_deploy(self):
+        time.sleep(deploy_time)
+        super(Link, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Interface(ResourceManager):
+    _rtype = "dummy::Interface"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+        link = self.get_connected(Link.get_rtype())[0]
+
+        if node.state < ResourceState.READY or \
+                link.state < ResourceState.READY:
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Interface, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Node(ResourceManager):
+    _rtype = "dummy::Node"
+
+    def do_deploy(self):
+        self.logger.debug(" -------- DO_DEPLOY ------- ")
+        time.sleep(deploy_time)
+        super(Node, self).do_deploy()
+        self.logger.debug(" -------- DEPLOYED ------- ")
+
+class Application(ResourceManager):
+    _rtype = "dummy::Application"
+
+    def do_deploy(self):
+        node = self.get_connected(Node.get_rtype())[0]
+
+        if node.state < ResourceState.READY: 
+            self.ec.schedule(reschedule_delay, self.deploy)
+            self.logger.debug(" -------- RESCHEDULING ------- ")
+        else:
+            time.sleep(deploy_time)
+            super(Application, self).do_deploy()
+            self.logger.debug(" -------- DEPLOYED ------- ")
+
+    def do_start(self):
+        super(Application, self).do_start()
+        time.sleep(run_time)
+        self.ec.schedule("0s", self.stop)
+
+ResourceFactory.register_type(Application)
+ResourceFactory.register_type(Node)
+ResourceFactory.register_type(Interface)
+ResourceFactory.register_type(Link)
+
+class SerializerTestCase(unittest.TestCase):
+    def test_serialize(self):
+        node_count = 4
+        app_count = 2
+
+        dirpath = tempfile.mkdtemp()
+
+        ec = ExperimentController(exp_id = "serialize-test")
+       
+        # Add simulated nodes and applications
+        nodes = list()
+        apps = list()
+        ifaces = list()
+
+        for i in xrange(node_count):
+            node = ec.register_resource("dummy::Node")
+            nodes.append(node)
+            
+            iface = ec.register_resource("dummy::Interface")
+            ec.register_connection(node, iface)
+            ifaces.append(iface)
+
+            for i in xrange(app_count):
+                app = ec.register_resource("dummy::Application")
+                ec.register_connection(node, app)
+                apps.append(app)
+
+        link = ec.register_resource("dummy::Link")
+
+        for iface in ifaces:
+            ec.register_connection(link, iface)
+
+        filepath = ec.save(dirpath)
+
+        ec.deploy()
+
+        # Wait until nodes and apps are deployed
+        ec.wait_finished(apps)
+
+        # Do the experiment controller shutdown
+        ec.shutdown()
+
+        # Load serialized experiment
+        ec2 = ExperimentController.load(filepath)
+        apps = ec2.get_resources_by_type("dummy::Application")
+        ec2.deploy()
+        ec2.wait_finished(apps)
+        ec2.shutdown()
+        
+        self.assertEquals(len(ec.resources), len(ec2.resources))
+
+        shutil.rmtree(dirpath)
+                       
+if __name__ == '__main__':
+    unittest.main()
+