Added serialization to EC
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 1 Aug 2014 07:45:27 +0000 (09:45 +0200)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 1 Aug 2014 07:45:27 +0000 (09:45 +0200)
13 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/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/serializer.py [new file with mode: 0644]
test/design/box.py [deleted file]
test/util/parser.py [deleted file]
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..cf41983 100644 (file)
@@ -24,6 +24,7 @@ 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
 
 # TODO: use multiprocessing instead of threading
 # TODO: Allow to reconnect to a running experiment instance! (reconnect mode vs deploy mode)
@@ -151,6 +152,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 +207,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 +253,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 +380,17 @@ class ExperimentController(object):
                 guids.append(guid)
 
                 time.sleep(0.5)
-  
+
+    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 +405,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 +414,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 +1040,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..49a690e 100644 (file)
@@ -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/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..7dc00c0
--- /dev/null
@@ -0,0 +1,274 @@
+#
+#    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 xml.dom import minidom
+
+import sys
+import os
+
+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 value == None:
+        return str("None")
+
+    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:
+        if value == "None":
+            return None
+        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:
+                cnnode = 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 = ecnode.getAttribute("exp_id")
+                run_id = ecnode.getAttribute("run_id")
+                nthreads = int(ecnode.getAttribute("nthreads"))
+            
+                os.environ["NEPI_NTHREADS"] = str(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 = int(rmnode.getAttribute("guid"))
+        rtype = xmldecode(rmnode.getAttribute("rtype"))
+        state = int(rmnode.getAttribute("state"))
+
+        if rmnode.hasAttribute("start_time"):
+            start_time = xmldecode(rmnode.getAttribute("start_time"))
+        if rmnode.hasAttribute("stop_time"):
+            stop_time = xmldecode(rmnode.getAttribute("stop_time"))
+        if rmnode.hasAttribute("discover_time"):
+            dicover_time = xmldecode(rmnode.getAttribute("discover_time"))
+        if rmnode.hasAttribute("provision_time"):
+            provision_time = xmldecode(rmnode.getAttribute("provision_time"))
+        if rmnode.hasAttribute("ready_time"):
+            ready_time = xmldecode(rmnode.getAttribute("ready_time"))
+        if rmnode.hasAttribute("release_time"):
+            release_time = xmldecode(rmnode.getAttribute("release_time"))
+        if rmnode.hasAttribute("failed_time"):
+            failed_time = xmldecode(rmnode.getAttribute("failed_time"))
+
+        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"))
+                type = xmldecode(aanode.getAttribute("type"))
+                value = to_type(type, 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 = int(ccnode.getAttribute("guid"))
+                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 = int(ccnnode.getAttribute("action"))
+                group = int(ccnnode.getAttribute("group"))
+                state = int(ccnnode.getAttribute("state"))
+                time = ccnnode.getAttribute("time")
+                ec.register_condition(guid, action, group, state, time = time)
+                 
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/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/serializer.py b/test/util/serializer.py
new file mode 100755 (executable)
index 0000000..7d7cf6a
--- /dev/null
@@ -0,0 +1,143 @@
+#!/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 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 SerializeTestCase(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)
+        print filepath
+
+        ec.deploy()
+
+        # Wait until nodes and apps are deployed
+        ec.wait_finished(apps)
+
+        # Do the experiment controller shutdown
+        ec.shutdown()
+
+        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))
+
+                       
+if __name__ == '__main__':
+    unittest.main()
+