Adding data progressing functions for CCN
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Sun, 10 Aug 2014 20:08:14 +0000 (22:08 +0200)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Sun, 10 Aug 2014 20:08:14 +0000 (22:08 +0200)
12 files changed:
examples/ccn_flooding/planetlab.py
src/nepi/data/processing/ccn/parser.py
src/nepi/data/processing/ping/parser.py
src/nepi/execution/ec.py
src/nepi/execution/resource.py
src/nepi/execution/runner.py
src/nepi/resources/all/collector.py
src/nepi/resources/linux/application.py
src/nepi/resources/linux/ccn/fibentry.py
src/nepi/util/netgraph.py
src/nepi/util/parsers/xml_parser.py
src/nepi/util/serializer.py

index 5834a85..766e96b 100644 (file)
@@ -26,6 +26,7 @@ from nepi.execution.runner import ExperimentRunner
 from nepi.util.netgraph import NetGraph, TopologyType
 import nepi.data.processing.ccn.parser as ccn_parser
 
+import networkx
 import socket
 import os
 
@@ -152,8 +153,6 @@ def avg_interests(ec, run):
     ## Process logs
     logs_dir = ec.run_dir
 
-    print ec.netgraph
-
     (graph,
         content_names,
         interest_expiry_count,
@@ -161,7 +160,7 @@ def avg_interests(ec, run):
         interest_count,
         content_count) = ccn_parser.process_content_history_logs(
                 logs_dir, 
-                ec.netgraph)
+                ec.netgraph.topology)
 
     shortest_path = networkx.shortest_path(graph, 
             source = ec.netgraph.sources()[0], 
@@ -170,11 +169,15 @@ def avg_interests(ec, run):
     ### Compute metric: Avg number of Interests seen per content name
     ###                 normalized by the number of nodes in the shortest path
     content_name_count = len(content_names.values())
-    nodes_in_shortest_path = len(content_names.values())
-    metric = interest_count / float(content_name_count) / float(nodes_in_shortest_path)
+    nodes_in_shortest_path = len(shortest_path) - 1
+    metric = interest_count / (float(content_name_count) * float(nodes_in_shortest_path))
 
     # TODO: DUMP RESULTS TO FILE
     # TODO: DUMP GRAPH DELAYS!
+    f = open("/tmp/metric", "w+")
+    f.write("%.2f\n" % metric)
+    f.close()
+    print " METRIC", metric
 
     return metric
 
index 1a65830..e3c1007 100644 (file)
@@ -135,12 +135,12 @@ def parse_file(filename):
 
         # If no external IP address was identified for this face
         # asume it is a local face
-        hostip = "localhost"
+        peer = "localhost"
 
         if face_id in faces:
-            hostip, port = faces[face_id]
+            peer, port = faces[face_id]
 
-        data.append((content_name, timestamp, message_type, hostip, face_id, 
+        data.append((content_name, timestamp, message_type, peer, face_id, 
             size, nonce, line))
 
     f.close()
@@ -162,18 +162,12 @@ def load_content_history(fname):
     return content_history
 
 def annotate_cn_node(graph, nid, ips2nid, data, content_history):
-    for (content_name, timestamp, message_type, hostip, face_id, 
+    for (content_name, timestamp, message_type, peer, face_id, 
             size, nonce, line) in data:
 
-        # Ignore local messages for the time being. 
-        # They could later be used to calculate the processing times
-        # of messages.
-        if peer == "localhost":
-            return
-
         # Ignore control messages for the time being
         if is_control(content_name):
-            return
+            continue
 
         if message_type == "interest_from" and \
                 peer == "localhost":
@@ -182,6 +176,12 @@ def annotate_cn_node(graph, nid, ips2nid, data, content_history):
                 peer == "localhost":
             graph.node[nid]["ccn_producer"] = True
 
+        # Ignore local messages for the time being. 
+        # They could later be used to calculate the processing times
+        # of messages.
+        if peer == "localhost":
+            continue
+
         # remove digest
         if message_type in ["content_from", "content_to"]:
             content_name = "/".join(content_name.split("/")[:-1])
@@ -190,7 +190,7 @@ def annotate_cn_node(graph, nid, ips2nid, data, content_history):
             content_history[content_name] = list()
       
         peernid = ips2nid[peer]
-        add_edge(graph, nid, peernid)
+        graph.add_edge(nid, peernid)
 
         content_history[content_name].append((timestamp, message_type, nid, 
             peernid, nonce, size, line))
@@ -202,12 +202,12 @@ def annotate_cn_graph(logs_dir, graph, parse_ping_logs = False):
     # Make a copy of the graph to ensure integrity
     graph = graph.copy()
 
-    ips2nids = dict()
+    ips2nid = dict()
 
     for nid in graph.nodes():
         ips = graph.node[nid]["ips"]
         for ip in ips:
-            ips2nids[ip] = nid
+            ips2nid[ip] = nid
 
     # Now walk through the ccnd logs...
     for dirpath, dnames, fnames in os.walk(logs_dir):
@@ -224,7 +224,7 @@ def annotate_cn_graph(logs_dir, graph, parse_ping_logs = False):
             if fname.endswith(".log"):
                 filename = os.path.join(dirpath, fname)
                 data = parse_file(filename)
-                annotate_cn_node(graph, nid, ips2nids, data, content_history)
+                annotate_cn_node(graph, nid, ips2nid, data, content_history)
 
         # Avoid storing everything in memory, instead dump to a file
         # and reference the file
@@ -358,7 +358,8 @@ def process_content_history(graph):
         content_names[content_name]["rtt"] = rtt
         content_names[content_name]["lapse"] = (interest_timestamp, content_timestamp)
 
-    return (content_names,
+    return (graph,
+        content_names,
         interest_expiry_count,
         interest_dupnonce_count,
         interest_count,
@@ -377,8 +378,8 @@ def process_content_history_logs(logs_dir, graph):
         print "Skipping: Error parsing ccnd logs", logs_dir
         raise
 
-    source = consumers(graph)[0]
-    target = producers(graph)[0]
+    source = ccn_consumers(graph)[0]
+    target = ccn_producers(graph)[0]
 
     # Process the data from the ccnd logs, but do not re compute
     # the link delay. 
index 3396207..0248c8c 100644 (file)
@@ -27,7 +27,9 @@
 # This library contains functions to parse log files generated using ping. 
 #
 
+import collections
 import re
+import os
 
 # RE to match line starting "traceroute to"
 _rre = re.compile("\d+ bytes from ((?P<hostname>[^\s]+) )?\(?(?P<ip>[^\s]+)\)??: icmp_.eq=\d+ ttl=\d+ time=(?P<time>[^\s]+) ms")
@@ -64,7 +66,7 @@ def parse_file(filename):
 
     return data
 
-def annotate_cn_node(graph, nid1, ips2nids, data):
+def annotate_cn_node(graph, nid1, ips2nid, data):
     for (target_ip, target_hostname, time) in data:
         nid2 = ips2nid[target_ip]
 
@@ -80,12 +82,12 @@ def annotate_cn_graph(logs_dir, graph):
     ping.
 
     """
-    ips2nids = dict()
+    ips2nid = dict()
 
     for nid in graph.nodes():
         ips = graph.node[nid]["ips"]
         for ip in ips:
-            ips2nids[ip] = nid
+            ips2nid[ip] = nid
 
     # Walk through the ping logs...
     for dirpath, dnames, fnames in os.walk(logs_dir):
@@ -100,7 +102,7 @@ def annotate_cn_graph(logs_dir, graph):
             if fname.endswith(".ping"):
                 filename = os.path.join(dirpath, fname)
                 data = parse_file(filename)
-                annotate_cn_node(graph, nid, ips2nids, data)
+                annotate_cn_node(graph, nid, ips2nid, data)
 
     # Take as weight the most frequent value
     for nid1, nid2 in graph.edges():
index 5664d2f..2e573dd 100644 (file)
@@ -204,7 +204,7 @@ class ExperimentController(object):
 
         # Local path where to store experiment related files (results, etc)
         if not local_dir:
-            local_dir = tempfile.mkdtemp()
+            local_dir = tempfile.gettempdir() # /tmp
 
         self._local_dir = local_dir
         self._exp_dir = os.path.join(local_dir, self.exp_id)
@@ -244,7 +244,7 @@ class ExperimentController(object):
 
         # Automatically construct experiment description 
         self._netgraph = None
-        if add_node_callback and add_edge_callback:
+        if add_node_callback or add_edge_callback or kwargs.get("topology"):
             self._build_from_netgraph(add_node_callback, add_edge_callback, 
                     **kwargs)
 
@@ -474,8 +474,16 @@ class ExperimentController(object):
         return sec
 
     def save(self, dirpath = None, format = SFormats.XML):
+        if dirpath == None:
+            dirpath = self.run_dir
+
+        try:
+            os.makedirs(dirpath)
+        except OSError:
+            pass
+
         serializer = ECSerializer()
-        path = serializer.save(self, dirpath  = None, format = format)
+        path = serializer.save(self, dirpath, format = format)
         return path
 
     def get_task(self, tid):
@@ -1038,7 +1046,7 @@ class ExperimentController(object):
         self.wait_released(guids)
 
         if self.persist:
-            self.save(dirpath = self.run_dir)
+            self.save()
 
         for guid in guids:
             if self.get(guid, "hardRelease"):
@@ -1229,12 +1237,13 @@ class ExperimentController(object):
         """
         self._netgraph = NetGraph(**kwargs)
 
-        ### Add resources to the EC
-        for nid in self.netgraph.graph.nodes():
-            add_node_callback(self, nid)
-
-        #### Add connections between resources
-        for nid1, nid2 in self.netgraph.graph.edges():
-            add_edge_callback(self, nid1, nid2)
+        if add_node_callback:
+            ### Add resources to the EC
+            for nid in self.netgraph.nodes():
+                add_node_callback(self, nid)
 
+        if add_edge_callback:
+            #### Add connections between resources
+            for nid1, nid2 in self.netgraph.edges():
+                add_edge_callback(self, nid1, nid2)
 
index f4b19f5..0f75e9a 100644 (file)
@@ -566,7 +566,7 @@ class ResourceManager(Logger):
 
                 import traceback
                 err = traceback.format_exc()
-                msg = " %s guid %d ----- FAILED TO RELEASED ----- \n %s " % (
+                msg = " %s guid %d ----- FAILED TO RELEASE ----- \n %s " % (
                         self._rtype, self.guid, err)
                 logger = Logger(self._rtype)
                 logger.debug(msg)
index 52b4bde..3a09ff4 100644 (file)
@@ -22,7 +22,6 @@ from nepi.execution.ec import ExperimentController, ECState
 import math
 import numpy
 import os
-import tempfile
 import time
 
 class ExperimentRunner(object):
@@ -92,21 +91,19 @@ class ExperimentRunner(object):
         # Force persistence of experiment controller
         ec._persist = True
 
-        dirpath = tempfile.mkdtemp()
-        filepath = ec.save(dirpath)
+        filepath = ec.save(dirpath = ec.exp_dir)
 
         samples = []
         run = 0
-        while True: 
+        stop = False
+
+        while not stop: 
             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:
@@ -114,7 +111,11 @@ class ExperimentRunner(object):
 
                     if run >= min_runs and evaluate_convergence_callback:
                         if evaluate_convergence_callback(ec, run, samples):
-                            break
+                            stop = True
+
+            if run >= min_runs and max_runs > -1 and run >= max_runs :
+                stop = True
+
             del ec
 
         return run
index 792e25f..6742fc8 100644 (file)
@@ -113,10 +113,11 @@ class Collector(ResourceManager):
 
         rms = self.get_connected()
         for rm in rms:
-            result = self.ec.trace(rm.guid, trace_name)
             fpath = os.path.join(self.store_path, "%d.%s" % (rm.guid, 
-                rename))
+                 rename))
+
             try:
+                result = self.ec.trace(rm.guid, trace_name)
                 f = open(fpath, "w")
                 f.write(result)
                 f.close()
index d095a25..ca9772d 100644 (file)
@@ -648,7 +648,7 @@ class LinuxApplication(ResourceManager):
                     if (proc and proc.poll()) or err:
                         msg = " Failed to STOP command '%s' " % self.get("command")
                         self.error(msg, out, err)
-        
+            
             super(LinuxApplication, self).do_stop()
 
     def do_release(self):
index 700ab10..4b7e9c4 100644 (file)
@@ -126,12 +126,12 @@ class LinuxFIBEntry(LinuxApplication):
         if name == "ping":
             if not self.ping:
                 return None
-            return self.ec.trace(self.ping, "stdout", attr, block, offset)
+            return self.ec.trace(self.ping.guid, "stdout", attr, block, offset)
 
         if name == "traceroute":
             if not self.traceroute:
                 return None
-            return self.ec.trace(self.traceroute, "stdout", attr, block, offset)
+            return self.ec.trace(self.traceroute.guid, "stdout", attr, block, offset)
 
         return super(LinuxFIBEntry, self).trace(name, attr, block, offset)
     
@@ -204,6 +204,7 @@ class LinuxFIBEntry(LinuxApplication):
             self.ec.set(traceroute, "target", self.get("host"))
             self.ec.set(traceroute, "earlyStart", True)
             self.ec.register_connection(traceroute, self.node.guid)
+            self.ec.register_connection(traceroute, self.guid)
             # schedule mtr deploy
             self.ec.deploy(guids=[traceroute], group = self.deployment_group)
 
index d939ece..07da169 100644 (file)
@@ -43,8 +43,8 @@ class NetGraph(object):
         """ A graph can be generated using a specified pattern 
         (LADDER, MESH, TREE, etc), or provided as an argument.
 
-            :param graph: Undirected graph to use as internal representation 
-            :type graph: networkx.Graph
+            :param topology: Undirected graph to use as internal representation 
+            :type topology: networkx.Graph
 
             :param topo_type: One of TopologyType.{LINEAR,LADDER,MESH,TREE,STAR}
             used to automatically generate the topology graph. 
@@ -78,25 +78,25 @@ class NetGraph(object):
                 edge (hyperedge) can not be modeled for the moment).
 
         """
-        self._graph = kwargs.get("graph") 
-        self._topo_type = TopologyType.ADHOC
+        self._topology = kwargs.get("topology")
+        self._topo_type = kwargs.get("topo_type", TopologyType.ADHOC)
 
-        if not self._graph and kwargs.get("topo_type") and \
-                kwargs.get("node_count"):
-            topo_type = kwargs["topo_type"]
-            node_count = kwargs["node_count"]
-            branches = kwargs.get("branches")
+        if not self.topology:
+            if kwargs.get("node_count"):
+                node_count = kwargs["node_count"]
+                branches = kwargs.get("branches")
 
-            self._topo_type = topo_type
-            self._graph = self.generate_graph(topo_type, node_count, 
-                    branches = branches)
+                self._topology = self.generate_topology(self.topo_type, 
+                        node_count, branches = branches)
+            else:
+                self._topology = networkx.Graph()
 
         if kwargs.get("assign_ips"):
             network = kwargs.get("network", "10.0.0.0")
             prefix = kwargs.get("prefix", 8)
             version = kwargs.get("version", 4)
 
-            self.assign_p2p_ips(self, network = network, prefix = prefix, 
+            self.assign_p2p_ips(network = network, prefix = prefix, 
                     version = version)
 
         if kwargs.get("assign_st"):
@@ -104,8 +104,8 @@ class NetGraph(object):
             self.select_random_leaf_source()
 
     @property
-    def graph(self):
-        return self._graph
+    def topology(self):
+        return self._topology
 
     @property
     def topo_type(self):
@@ -113,17 +113,15 @@ class NetGraph(object):
 
     @property
     def order(self):
-        return self.graph.order()
+        return self.topology.order()
 
-    @property
     def nodes(self):
-        return self.graph.nodes()
+        return self.topology.nodes()
 
-    @property
     def edges(self):
-        return self.graph.edges()
+        return self.topology.edges()
 
-    def generate_graph(self, topo_type, node_count, branches = None):
+    def generate_topology(self, topo_type, node_count, branches = None):
         if topo_type == TopologyType.LADDER:
             total_nodes = node_count/2
             graph = networkx.ladder_graph(total_nodes)
@@ -164,8 +162,8 @@ class NetGraph(object):
     def add_node(self, nid):
         nid = str(nid)
 
-        if nid not in self.graph:
-            self.graph.add_node(nid)
+        if nid not in self.topology: 
+            self.topology.add_node(nid)
 
     def add_edge(self, nid1, nid2):
         nid1 = str(nid1)
@@ -174,37 +172,61 @@ class NetGraph(object):
         self.add_node(nid1)
         self.add_node( nid2)
 
-        if nid1 not in self.graph[nid2]:
-            self.graph.add_edge(nid2, nid1)
-
-            # The weight of the edge is the delay of the link
-            self.graph.edge[nid1][nid2]["weight"] = None
-            # confidence interval of the mean RTT
-            self.graph.edge[nid1][nid2]["weight_ci"] = None
+        if nid1 not in self.topology[nid2]:
+            self.topology.add_edge(nid2, nid1)
 
     def annotate_node_ip(self, nid, ip):
-        if "ips" not in self.graph.node[nid]:
-            self.graph.node[nid]["ips"] = list()
-
-        self.graph.node[nid]["ips"].append(ip)
-    
+        if "ips" not in self.topology.node[nid]:
+            self.topology.node[nid]["ips"] = list()
+
+        self.topology.node[nid]["ips"].append(ip)
+    def node_ip_annotations(self, nid):
+        return self.topology.node[nid].get("ips", [])
+   
     def annotate_node(self, nid, name, value):
-        self.graph.node[nid][name] = value
+        if not isinstance(value, str) and not isinstance(value, int) and \
+                not isinstance(value, float) and not isinstance(value, bool):
+            raise RuntimeError, "Non-serializable annotation"
+
+        self.topology.node[nid][name] = value
     
     def node_annotation(self, nid, name):
-        return self.graph.node[nid].get(name)
+        return self.topology.node[nid].get(name)
+
+    def node_annotations(self, nid):
+        return self.topology.node[nid].keys()
     
     def del_node_annotation(self, nid, name):
-        del self.graph.node[nid][name]
+        del self.topology.node[nid][name]
 
     def annotate_edge(self, nid1, nid2, name, value):
-        self.graph.edge[nid1][nid2][name] = value
-    
+        if not isinstance(value, str) and not isinstance(value, int) and \
+                not isinstance(value, float) and not isinstance(value, bool):
+            raise RuntimeError, "Non-serializable annotation"
+
+        self.topology.edge[nid1][nid2][name] = value
+   
+    def annotate_edge_net(self, nid1, nid2, ip1, ip2, mask, network, 
+            prefixlen):
+        self.topology.edge[nid1][nid2]["net"] = dict()
+        self.topology.edge[nid1][nid2]["net"][nid1] = ip1
+        self.topology.edge[nid1][nid2]["net"][nid2] = ip2
+        self.topology.edge[nid1][nid2]["net"]["mask"] = mask
+        self.topology.edge[nid1][nid2]["net"]["network"] = network
+        self.topology.edge[nid1][nid2]["net"]["prefix"] = prefixlen
+
+    def edge_net_annotation(self, nid1, nid2):
+        return self.topology.edge[nid1][nid2].get("net", dict())
     def edge_annotation(self, nid1, nid2, name):
-        return self.graph.edge[nid1][nid2].get(name)
+        return self.topoplogy.edge[nid1][nid2].get(name)
+    def edge_annotations(self, nid1, nid2):
+        return self.topology.edge[nid1][nid2].keys()
     
     def del_edge_annotation(self, nid1, nid2, name):
-        del self.graph.edge[nid1][nid2][name]
+        del self.topology.edge[nid1][nid2][name]
 
     def assign_p2p_ips(self, network = "10.0.0.0", prefix = 8, version = 4):
         """ Assign IP addresses to each end of each edge of the network graph,
@@ -221,7 +243,7 @@ class NetGraph(object):
             :type version: int
 
         """
-        if len(networkx.connected_components(self.graph)) > 1:
+        if networkx.number_connected_components(self.topology) > 1:
             raise RuntimeError("Disconnected graph!!")
 
         # Assign IP addresses to host
@@ -236,13 +258,13 @@ class NetGraph(object):
             raise RuntimeError, "Invalid IP version %d" % version
         
         ## Clear all previusly assigned IPs
-        for nid in self.graph.node():
-            self.graph.node[nid]["ips"] = list()
+        for nid in self.topology.nodes():
+            self.topology.node[nid]["ips"] = list()
 
         ## Generate and assign new IPs
         sub_itr = net.iter_subnets(new_prefix = new_prefix)
         
-        for nid1, nid2 in self.graph.edges():
+        for nid1, nid2 in self.topology.edges():
             #### Compute subnets for each link
             
             # get a subnet of base_add with prefix /30
@@ -258,36 +280,38 @@ class NetGraph(object):
 
             ip1 = addr1.exploded
             ip2 = addr2.exploded
-            self.graph.edge[nid1][nid2]["net"] = dict()
-            self.graph.edge[nid1][nid2]["net"][nid1] = ip1
-            self.graph.edge[nid1][nid2]["net"][nid2] = ip2
-            self.graph.edge[nid1][nid2]["net"]["mask"] = mask
-            self.graph.edge[nid1][nid2]["net"]["network"] = mask
-            self.graph.edge[nid1][nid2]["net"]["prefix"] = prefixlen
+            self.annotate_edge_net(nid1, nid2, ip1, ip2, mask, network, 
+                    prefixlen)
 
             self.annotate_node_ip(nid1, ip1)
             self.annotate_node_ip(nid2, ip2)
 
     def get_p2p_info(self, nid1, nid2):
-        net = self.graph.edge[nid1][nid2]["net"]
+        net = self.topology.edge[nid1][nid2]["net"]
         return ( net[nid1], net[nid2], net["mask"], net["network"], 
                 net["prefixlen"] )
 
     def set_source(self, nid):
-        self.graph.node[nid]["source"] = True
+        self.topology.node[nid]["source"] = True
+
+    def is_source(self, nid):
+        return self.topology.node[nid].get("source")
 
     def set_target(self, nid):
-        self.graph.node[nid]["target"] = True
+        self.topology.node[nid]["target"] = True
+
+    def is_target(self, nid):
+        return self.topology.node[nid].get("target")
 
     def targets(self):
         """ Returns the nodes that are targets """
-        return [nid for nid in self.graph.nodes() \
-                if self.graph.node[nid].get("target")]
+        return [nid for nid in self.topology.nodes() \
+                if self.topology.node[nid].get("target")]
 
     def sources(self):
         """ Returns the nodes that are sources """
-        return [nid for nid in self.graph.nodes() \
-                if self.graph.node[nid].get("source")]
+        return [nid for nid in self.topology.nodes() \
+                if self.topology.node[nid].get("source")]
 
     def select_target_zero(self):
         """ Marks the node 0 as target
@@ -307,9 +331,9 @@ class NetGraph(object):
             source = leaves.pop(random.randint(0, len(leaves) - 1))
         else:
             # options must not be already sources or targets
-            options = [ k for k,v in self.graph.degree().iteritems() \
-                    if v == 1 and not self.graph.node[k].get("source") \
-                        and not self.graph.node[k].get("target")]
+            options = [ k for k,v in self.topology.degree().iteritems() \
+                    if v == 1 and not self.topology.node[k].get("source") \
+                        and not self.topology.node[k].get("target")]
 
             source = options.pop(random.randint(0, len(options) - 1))
         
index 077a12d..af57cf1 100644 (file)
@@ -17,6 +17,7 @@
 #
 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
 
+from nepi.util.netgraph import NetGraph, TopologyType 
 from nepi.util.timefuncs import stformat, tsformat
 
 from xml.dom import minidom
@@ -92,18 +93,84 @@ class ECXMLParser(object):
         ecnode.setAttribute("run_id", xmlencode(ec.run_id))
         ecnode.setAttribute("nthreads", xmlencode(ec.nthreads))
         ecnode.setAttribute("local_dir", xmlencode(ec.local_dir))
-        ecnode.setAttribute("exp_dir", xmlencode(ec.exp_dir))
-
         doc.appendChild(ecnode)
 
+        if ec.netgraph != None:
+            self._netgraph_to_xml(doc, ecnode, ec)
+
+        rmsnode = doc.createElement("rms")
+        ecnode.appendChild(rmsnode)
+
         for guid, rm in ec._resources.iteritems():
-            self._rm_to_xml(doc, ecnode, ec, guid, rm)
+            self._rm_to_xml(doc, rmsnode, ec, guid, rm)
 
         return doc
     
     def _netgraph_to_xml(self, doc, ecnode, ec):
-
-    def _rm_to_xml(self, doc, ecnode, ec, guid, rm):
+        ngnode = doc.createElement("topology")
+        ngnode.setAttribute("topo-type", xmlencode(ec.netgraph.topo_type))
+        ecnode.appendChild(ngnode)
+        
+        self. _netgraph_nodes_to_xml(doc, ngnode, ec)
+        self. _netgraph_edges_to_xml(doc, ngnode, ec)
+        
+    def _netgraph_nodes_to_xml(self, doc, ngnode, ec):
+        ngnsnode = doc.createElement("nodes")
+        ngnode.appendChild(ngnsnode)
+
+        for nid in ec.netgraph.nodes():
+            ngnnode = doc.createElement("node")
+            ngnnode.setAttribute("nid", xmlencode(nid))
+            ngnsnode.appendChild(ngnnode)
+
+            # Mark ources and targets
+            if ec.netgraph.is_source(nid):
+                ngnnode.setAttribute("source", xmlencode(True))
+
+            if ec.netgraph.is_target(nid):
+                ngnnode.setAttribute("target", xmlencode(True))
+
+            # Node annotations
+            annosnode = doc.createElement("node-annotations")
+            add_annotations = False
+            for name in ec.netgraph.node_annotations(nid):
+                add_annotations = True
+                value = ec.netgraph.node_annotation(nid, name)
+                annonode = doc.createElement("node-annotation")
+                annonode.setAttribute("name", xmlencode(name))
+                annonode.setAttribute("value", xmlencode(value))
+                annonode.setAttribute("type", from_type(value))
+                annosnode.appendChild(annonode)
+
+            if add_annotations:
+                ngnnode.appendChild(annosnode)
+
+    def _netgraph_edges_to_xml(self, doc, ngnode, ec):
+        ngesnode = doc.createElement("edges")
+        ngnode.appendChild(ngesnode)
+
+        for nid1, nid2 in ec.netgraph.edges():
+            ngenode = doc.createElement("edge")
+            ngenode.setAttribute("nid1", xmlencode(nid1))
+            ngenode.setAttribute("nid2", xmlencode(nid2))
+            ngesnode.appendChild(ngenode)
+
+            # Edge annotations
+            annosnode = doc.createElement("edge-annotations")
+            add_annotations = False
+            for name in ec.netgraph.edge_annotations(nid1, nid2):
+                add_annotations = True
+                value = ec.netgraph.edge_annotation(nid1, nid2, name)
+                annonode = doc.createElement("edge-annotation")
+                annonode.setAttribute("name", xmlencode(name))
+                annonode.setAttribute("value", xmlencode(value))
+                annonode.setAttribute("type", from_type(value))
+                annosnode.appendChild(annonode)
+
+            if add_annotations:
+                ngenode.appendChild(annosnode)
+
+    def _rm_to_xml(self, doc, rmsnode, ec, guid, rm):
         rmnode = doc.createElement("rm")
         rmnode.setAttribute("guid", xmlencode(guid))
         rmnode.setAttribute("rtype", xmlencode(rm._rtype))
@@ -122,7 +189,7 @@ class ECXMLParser(object):
             rmnode.setAttribute("release_time", xmlencode(rm._release_time))
         if rm._failed_time:
             rmnode.setAttribute("failed_time", xmlencode(rm._failed_time))
-        ecnode.appendChild(rmnode)
+        rmsnode.appendChild(rmnode)
 
         anode = doc.createElement("attributes")
         attributes = False
@@ -194,17 +261,27 @@ class ECXMLParser(object):
                 exp_id = xmldecode(ecnode.getAttribute("exp_id"))
                 run_id = xmldecode(ecnode.getAttribute("run_id"))
                 local_dir = xmldecode(ecnode.getAttribute("local_dir"))
+
+                # Configure number of preocessing threads
                 nthreads = xmldecode(ecnode.getAttribute("nthreads"))
-            
                 os.environ["NEPI_NTHREADS"] = nthreads
-                ec = ExperimentController(exp_id = exp_id, local_dir = local_dir)
+
+                # Deserialize netgraph
+                netgraph = self._netgraph_from_xml(doc, ecnode)
+                topo_type = netgraph.topo_type if netgraph else None
+
+                # Instantiate EC
+                ec = ExperimentController(exp_id = exp_id, local_dir = local_dir, 
+                        topology = netgraph.topology, topo_type = topo_type)
 
                 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)
+                rmsnode_list = ecnode.getElementsByTagName("rms")
+                if rmsnode_list:
+                    rmnode_list = rmsnode_list[0].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)
@@ -213,6 +290,70 @@ class ECXMLParser(object):
 
         return ec
 
+    def _netgraph_from_xml(self, doc, ecnode):
+        netgraph = None
+
+        topology = ecnode.getElementsByTagName("topology")
+        if topology:
+            topology = topology[0]
+            topo_type = xmldecode(topology.getAttribute("topo-type"))
+
+            netgraph = NetGraph(topo_type = topo_type)
+
+            ngnsnode_list = topology.getElementsByTagName("nodes")
+            if ngnsnode_list:
+                ngnsnode = ngnsnode_list[0].getElementsByTagName("node") 
+                for ngnnode in ngnsnode:
+                    nid = xmldecode(ngnnode.getAttribute("nid"))
+                    netgraph.add_node(nid)
+
+                    if ngnnode.hasAttribute("source"):
+                        netgraph.set_source(nid)
+                    if ngnnode.hasAttribute("target"):
+                        netgraph.set_target(nid)
+
+                    annosnode_list = ngnnode.getElementsByTagName("node-annotations")
+                    
+                    if annosnode_list:
+                        annosnode = annosnode_list[0].getElementsByTagName("node-annotation") 
+                        for annonode in annosnode:
+                            name = xmldecode(annonode.getAttribute("name"))
+
+                            if name == "ips":
+                                ips = xmldecode(annonode.getAttribute("value"), eval) # list
+                                for ip in ips:
+                                    netgraph.annotate_node_ip(nid, ip)
+                            else:
+                                value = xmldecode(annonode.getAttribute("value"))
+                                tipe = xmldecode(annonode.getAttribute("type"))
+                                value = to_type(tipe, value)
+                                netgraph.annotate_node(nid, name, value)
+
+            ngesnode_list = topology.getElementsByTagName("edges") 
+            if ngesnode_list:
+                ngesnode = ngesnode_list[0].getElementsByTagName("edge") 
+                for ngenode in ngesnode:
+                    nid1 = xmldecode(ngenode.getAttribute("nid1"))
+                    nid2 = xmldecode(ngenode.getAttribute("nid2"))
+                    netgraph.add_edge(nid1, nid2)
+
+                    annosnode_list = ngenode.getElementsByTagName("edge-annotations")
+                    if annosnode_list:
+                        annosnode = annosnode_list[0].getElementsByTagName("edge-annotation") 
+                        for annonode in annosnode:
+                            name = xmldecode(annonode.getAttribute("name"))
+
+                            if name == "net":
+                                net = xmldecode(annonode.getAttribute("value"), eval) # dict
+                                netgraph.annotate_edge_net(net[nid1], net[nid2], net["ip1"],
+                                        net["ip2"], net["mask"], net["network"], net["prefix"])
+                            else:
+                                value = xmldecode(annonode.getAttribute("value"))
+                                tipe = xmldecode(annonode.getAttribute("type"))
+                                value = to_type(tipe, value)
+                                netgraph.annotate_edge(nid1, nid2, name, value)
+        return netgraph
+
     def _rm_from_xml(self, doc, rmnode, ec, connections):
         start_time = None
         stop_time = None
@@ -292,7 +433,7 @@ class ECXMLParser(object):
             ccnnode_list = cnnode_list[0].getElementsByTagName("condition") 
             for ccnnode in ccnnode_list:
                 action = xmldecode(ccnnode.getAttribute("action"), int)
-                group = xmldecode(ccnnode.getAttribute("group"), eval)
+                group = xmldecode(ccnnode.getAttribute("group"), eval) # list
                 state = xmldecode(ccnnode.getAttribute("state"), int)
                 time = xmldecode(ccnnode.getAttribute("time"))
                 time = to_type('STRING', time)
index aaf1aff..7347c5b 100644 (file)
@@ -46,11 +46,7 @@ class ECSerializer(object):
 
         return sec
 
-    def save(self, ec, dirpath = None, format = SFormats.XML):
-        if not dirpath:
-            import tempfile
-            dirpath = tempfile.mkdtemp()
+    def save(self, ec, dirpath, format = SFormats.XML):
         date = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
         filename = "%s_%s" % (ec.exp_id, date)