NOT WORKING COMMIT! started adding netns testbed instance support
authorAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 18 Feb 2011 17:01:17 +0000 (18:01 +0100)
committerAlina Quereilhac <alina.quereilhac@inria.fr>
Fri, 18 Feb 2011 17:01:17 +0000 (18:01 +0100)
examples/execution1.py [new file with mode: 0644]
src/nepi/core/attributes.py
src/nepi/core/description.py
src/nepi/core/testbed.py
src/nepi/testbeds/netns/__init__.py
src/nepi/testbeds/netns/description.py [new file with mode: 0644]
src/nepi/testbeds/netns/testbed.py [new file with mode: 0644]
src/nepi/util/graphical_info.py [new file with mode: 0644]
src/nepi/util/parser/_xml.py

diff --git a/examples/execution1.py b/examples/execution1.py
new file mode 100644 (file)
index 0000000..53f475a
--- /dev/null
@@ -0,0 +1,34 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from nepi.core.decription import AF_INET
+from nepi.testbeds import netns
+
+instance = netns.TestbedInstance(None)
+
+instance.create(2, "Node", [])
+instance.create(3, "Node", [])
+instance.create(4, "NodeInterface", [])
+instance.create_set(4, "up", True)
+instance.connect(2, "devs", 4, "node")
+instance.add_adddress(4, AF_INET, "10.0.0.1", None, None)
+instance.create(5, "NodeInterface", [])
+instance.create_set(5, "up", True)
+instance.connect(3, "devs", 5, "node")
+instance.add_adddress(5, AF_INET, "10.0.0.2", None, None)
+instance.create(6, "Switch", [])
+instance.create_set(6, "up", True)
+instance.connect(4, "switch", 6, "devs")
+instance.connect(5, "switch", 6, "devs")
+instance.create(7, "Application", [])
+instance.create_set(7, "command", "ping -qc10 10.0.0.2")
+instance.connect(7, "node", 2, "apps")
+
+instance.do_create()
+instance.do_connect()
+instance.do_configure()
+instance.start()
+import time
+time.sleep(5)
+instance.stop()
+
index d462230..2018b46 100644 (file)
@@ -32,6 +32,8 @@ class AttributesMap(object):
         return self._attributes[name].type
 
     def get_attribute_range(self, name):
+        if not self._attributes[name].range:
+            return (None, None)
         return self._attributes[name].range
 
     def get_attribute_allowed(self, name):
index e0e5110..75c3f41 100644 (file)
@@ -526,3 +526,4 @@ class ExperimentDescription(object):
     def destroy(self):
         for testbed_description in self.testbed_descriptions:
             testbed_description.destroy()
+
index aaf7434..1537cda 100644 (file)
@@ -9,35 +9,39 @@ class TestbedInstance(object):
     def __init__(self, configuration):
         pass
 
-    def create(self, guid, factory_id, parameters):
+    def create(self, guid, factory_id):
+        """Instructs creation of element """
+        raise NotImplementedError
+
+    def create_set(self, guid, name, value):
+        """Instructs setting an attribute on an element"""
         raise NotImplementedError
 
     def do_create(self):
+        """After do_create all instructed elements are created and 
+        attributes setted"""
         raise NotImplementedError
 
-    def connect(self, object1_guid, object2_guid, connect_code): 
+    def connect(self, guid1, connector_type_name1, guid2, 
+            connector_type_name2): 
         raise NotImplementedError
 
     def do_connect(self):
         raise NotImplementedError
 
-    def enable_trace(self, guid, trace_id):
+    def add_trace(self, guid, trace_id):
         raise NotImplementedError
 
-    def add_adddress(self, guid):
-        #TODO
+    def add_adddress(self, guid, family, address, netprefix, broadcast): 
         raise NotImplementedError
 
-    def add_route(self, guid):
-        #TODO
+    def add_route(self, guid, family, destination, netprefix, nexthop, 
+            interface):
         raise NotImplementedError
 
     def do_configure(self):
         raise NotImplementedError
 
-    def cross_connect(self, guid, connect_code, paremeters):
-        raise NotImplementedError
-
     def do_cross_connect(self):
         raise NotImplementedError
 
@@ -50,13 +54,16 @@ class TestbedInstance(object):
     def start(self, time):
         raise NotImplementedError
 
+    def action(self, time, guid, action):
+        raise NotImplementedError
+
     def stop(self, time):
         raise NotImplementedError
 
     def status(self, guid):
         raise NotImplementedError
 
-    def get_trace(self, time, guid, trace_id):
+    def trace(self, guid, trace_id):
         raise NotImplementedError
 
     def shutdown(self):
index 7ae173c..de0eb93 100644 (file)
@@ -1,73 +1,5 @@
 #!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
-from nepi.core import description 
-import sys
-
-TESTBED_ID = "netns"
-
-def add_connector_types(factory, connector_types_metadata):
-    for (connector_type_id, help, name, max, min, 
-            allowed_connector_type_ids) in connector_types_metadata:
-        factory.add_connector_type(connector_type_id, help, name, max,
-            min, allowed_connector_type_ids)
-
-def add_traces(factory, traces_metadata):
-    for (name, help) in traces_metadata: 
-            factory.add_trace(name, help)
-
-def add_attributes(factory, attributes_metadata):
-    for (name, help, type, value, range, allowed, readonly, 
-            validation_function) in attributes_metadata:
-        factory.add_attribute(name, help, type, value, range, allowed,
-            readonly, validation_function)
-
-def add_box_attributes(factory, box_attributes_metadata):
-    for (name, help, type, value, range, allowed, readonly, 
-            validation_function) in box_attributes_metadata:
-            factory.add_box_attribute(name, help, type, value, range,
-                    allowed, readonly, validation_function)
-           
-def create_factory_from_metadata(factory_id, info):
-        help = info["help"]
-        category = info["category"]
-        display_name = info["display_name"]
-        factory_type = info["factory_type"] if "factory_type" in info else None
-        if factory_type == "addressable":
-            family = info["family"]
-            max_addresses = info["max_addresses"]
-            factory = description.AddressableBoxFactory(factory_id, 
-                    display_name, family, max_addresses, help, category)
-        elif factory_type == "routing":
-            factory = description.RoutingTableBoxFactory(factory_id, 
-                    display_name, help, category)
-        else:
-            factory = description.BoxFactory(factory_id, display_name, help, category)
-        if "connector_types" in info:
-            add_connector_types(factory, info["connector_types"])
-        if "traces" in info:
-            add_traces(factory, info["traces"])
-        if "attributes" in info:
-            add_attributes(factory, info["attributes"])
-        if "box_attributes" in info:
-            add_box_attributes(factory, info["box_attributes"])
-        return factory
-
-def create_factories(version):
-    factories = list()
-    mod_name = "%s.metadata_v%s" % (__name__, version)
-    if not mod_name in sys.modules:
-        __import__(mod_name)
-    metadata = sys.modules[mod_name].get_metadata()
-    for factory_id, info in metadata.iteritems():
-        factory = create_factory_from_metadata(factory_id, info)
-        factories.append(factory)
-    return factories
-
-class TestbedFactoriesProvider(description.TestbedFactoriesProvider):
-    def __init__(self, testbed_version):
-        super(TestbedFactoriesProvider, self).__init__(TESTBED_ID, 
-                testbed_version)
-        for factory in create_factories(testbed_version):
-            self.add_factory(factory)
-
+from description import TestbedFactoriesProvider
+from testbed import TestbedInstance
diff --git a/src/nepi/testbeds/netns/description.py b/src/nepi/testbeds/netns/description.py
new file mode 100644 (file)
index 0000000..25179d4
--- /dev/null
@@ -0,0 +1,72 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from nepi.core import description 
+import sys
+
+TESTBED_ID = "netns"
+
+def add_connector_types(factory, connector_types_metadata):
+    for (connector_type_id, help, name, max, min, 
+            allowed_connector_type_ids) in connector_types_metadata:
+        factory.add_connector_type(connector_type_id, help, name, max,
+            min, allowed_connector_type_ids)
+
+def add_traces(factory, traces_metadata):
+    for (name, help) in traces_metadata: 
+            factory.add_trace(name, help)
+
+def add_attributes(factory, attributes_metadata):
+    for (name, help, type, value, range, allowed, readonly, 
+            validation_function) in attributes_metadata:
+        factory.add_attribute(name, help, type, value, range, allowed,
+            readonly, validation_function)
+
+def add_box_attributes(factory, box_attributes_metadata):
+    for (name, help, type, value, range, allowed, readonly, 
+            validation_function) in box_attributes_metadata:
+            factory.add_box_attribute(name, help, type, value, range,
+                    allowed, readonly, validation_function)
+           
+def create_factory_from_metadata(factory_id, info):
+        help = info["help"]
+        category = info["category"]
+        display_name = info["display_name"]
+        factory_type = info["factory_type"] if "factory_type" in info else None
+        if factory_type == "addressable":
+            family = info["family"]
+            max_addresses = info["max_addresses"]
+            factory = description.AddressableBoxFactory(factory_id, 
+                    display_name, family, max_addresses, help, category)
+        elif factory_type == "routing":
+            factory = description.RoutingTableBoxFactory(factory_id, 
+                    display_name, help, category)
+        else:
+            factory = description.BoxFactory(factory_id, display_name, help, category)
+        if "connector_types" in info:
+            add_connector_types(factory, info["connector_types"])
+        if "traces" in info:
+            add_traces(factory, info["traces"])
+        if "attributes" in info:
+            add_attributes(factory, info["attributes"])
+        if "box_attributes" in info:
+            add_box_attributes(factory, info["box_attributes"])
+        return factory
+
+def create_factories(version):
+    factories = list()
+    mod_name = "nepi.testbeds.netns.metadata_v%s" % (version)
+    if not mod_name in sys.modules:
+        __import__(mod_name)
+    metadata = sys.modules[mod_name].get_metadata()
+    for factory_id, info in metadata.iteritems():
+        factory = create_factory_from_metadata(factory_id, info)
+        factories.append(factory)
+    return factories
+
+class TestbedFactoriesProvider(description.TestbedFactoriesProvider):
+    def __init__(self, testbed_version):
+        super(TestbedFactoriesProvider, self).__init__(TESTBED_ID, 
+                testbed_version)
+        for factory in create_factories(testbed_version):
+            self.add_factory(factory)
diff --git a/src/nepi/testbeds/netns/testbed.py b/src/nepi/testbeds/netns/testbed.py
new file mode 100644 (file)
index 0000000..4f0d66f
--- /dev/null
@@ -0,0 +1,358 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+from nepi.core import testbed
+
+CREATE = 0
+SET = 1
+CONNECT = 2
+ADD_TRACE = 3
+ADD_ADDRESS = 4
+ADD_ROUTE = 5
+
+class TestbedConfiguration(testbed.TestbedConfiguration):
+    pass
+
+class TestbedInstance(testbed.TestbedInstance):
+    def __init__(self, configuration):
+        self._netns = self._load_netns_module(configuration)
+        self._elements = dict()
+        self._configuration = configuration
+        self._instructions = dict({
+            CREATE: dict(),
+            SET: dict(),
+            CONNECT: dict(),
+            ADD_TRACE: dict(),
+            ADD_ADDRESS: dict(),
+            ADD_ROUTE: dict()
+        })
+        self._factories = dict({
+            "Node": list(),
+            "P2PInterface": list(),
+            "TapNodeInterface": list(),
+            "NodeInterface": list(),
+            "Switch": list(),
+            "Application": list()
+        })
+        self._connections = list()
+
+    def create(self, guid, factory_id):
+        if guid in self._instructions[CREATE]:
+            # XXX: Validation
+            raise RuntimeError("cannot add two elements with the same guid")
+        if factory_id not in self._factories:
+            # XXX: Validation
+            raise RuntimeError("%s is not an allowed factory_id" % factory_id)
+        self._instructions[CREATE][guid] = factory_id
+        self._factories[factory_id].append(guid)
+
+    def create_set(self, guid, name, value):
+        if guid not in self._instructions[SET]:
+            self._instructions[SET][guid] = dict()
+        self._instructions[SET][guid][name] = value
+       
+    def connect(self, guid1, connector_type_name1, guid2, 
+            connector_type_name2):
+        if not guid1 in self._instructions[CONNECT]:
+            self._instructions[CONNECT] = dict()
+        if not connector_type_name1 in self._instructions[CONNECT][guid1]:
+             self._instructions[CONNECT][guid1][connector_type_name1] = dict()
+        self._instructions[CONNECT][guid1][connector_type_name1][guid2] = \
+                connector_type_name2
+        self._connections.append((guid1, connector_type_name1, guid2, 
+            connector_type_name2))
+
+    def add_trace(self, guid, trace_id):
+        if not guid in self._instructions[ADD_TRACE]:
+            self._instructions[ADD_TRACE][guid] = list()
+        self._instructions[ADD_TRACE][guid].append(trace_id)
+
+    def add_adddress(self, guid, family, address, netprefix, broadcast):
+        if not guid in self._instructions[ADD_ADDRESS]:
+            self._instructions[ADD_ADDRESS][guid] = list()
+        self._instructions[ADD_ADDRESS][guid].append((guid, family, address, 
+                netprefix, broadcast))
+
+    def add_route(self, guid, family, destination, netprefix, nexthop, 
+            interface):
+        if not guid in self._instructions[ADD_ROUTE]:
+            self._instructions[ADD_ROUTE][guid] = list()
+        self._instructions[ADD_ROUTE][guid].append((family, destination, 
+                netprefix, nexthop, interface)) 
+
+    def do_create(self):
+        # nodes need to be created first
+        factories_order = ["Node", "P2PInterface", "TapNodeInterface", 
+                "NodeInterface", "Switch", "Application"]
+        for guid in self._factories[factory_id] \
+                for factory_id in factories_order:
+            self._create_element(guid, factory_id)
+        # free self._factories as it is not going to be used further
+        # TODO: Check if this methods frees everithing... 
+        # maybe there are still some references!
+        self._factories = None
+
+    def do_connect(self):
+        cross_connections = list()
+        for (guid1, connector_type_name1, guid2, connector_type_name2) \
+                in self._connections:
+            if guid1 not in self._elements or guid2 not in self._elements:
+                # at least one of the elements does not belong to this
+                # TestbedIntsance and so it needs to be treated as a cross 
+                # testbed connection
+                cross_connections.append(guid1, connector_type_name1, guid2,
+                        connector_type_name2)
+            else:
+                # TODO: do Whatever is needed to connect
+                pass
+        self._connections = cross_connections
+
+    def do_configure(self):
+        raise NotImplementedError
+        #self._object.add_route(
+        #            prefix = destination, 
+        #            prefix_len = netprefix, 
+        #            nexthop = nexthop)
+
+    def do_cross_connect(self):
+        for (guid1, connector_type_name1, guid2, connector_type_name2) \
+                in self._connections:
+            # TODO: do Whatever is needed to connect
+            pass
+        # free connections list as is not going to be used further
+        self._connections = None
+
+    def set(self, time, guid, name, value):
+        raise NotImplementedError
+
+    def get(self, time, guid, name):
+        raise NotImplementedError
+
+    def start(self, time):
+        raise NotImplementedError
+
+    def action(self, time, guid, action):
+        raise NotImplementedError
+
+    def stop(self, time):
+        raise NotImplementedError
+
+    def status(self, guid):
+        raise NotImplementedError
+
+    def trace(self, guid, trace_id):
+        raise NotImplementedError
+
+    def shutdown(self):
+        raise NotImplementedError
+
+    def _netns_module(self, configuration):
+        # TODO: Do something with the configuration!!!
+        import sys
+        __import__("netns")
+        return sys.modules["netns"]
+
+    def _create_element(self, guid, factory_id):
+        paremeters = dict()
+        if guid in self._instructions[SET]:
+            parameters = self._instructions[SET][guid] 
+        if guid not in self._elements.keys():
+            if factory_id == "Node":
+                self._create_node_element(guid, parameters)
+            elif factory_id == "P2PInterface":
+                self._create_p2piface_element(guid, parameters)
+        self._set_attributes(guid, parameters)
+
+    def _create_node_element(self, guid, parameters):
+        forward_X11 = False
+        if "forward_X11" in paremeters:
+            forward_X11 = parameters["forward_X11"]
+            del parameters["forward_X11"]
+        element = self._netns.Node(forward_X11 = forward_X11)
+        self._elements[guid] = element
+
+    def _create_p2piface_element(self, guid, parameters):
+                # search in the connections the node asociated with this
+                # P2PInterface and the other P2PInterface to which it is 
+                # connected
+                connection = 
+                node1 = 
+                node2 = 
+                element1, element2 = self._netns.P2PInterface.create_pair(
+                        node1, node2)
+                self._elements[guid] = element1
+                self._elements[guid2] = element2
+
+    def _set_attributes(self, guid, paramenters):
+        for name, value in parameters:
+            # TODO: Validate attribute does not exist!!!
+            setattr(element, name, value)
+
+def connect_switch(switch_connector, interface_connector):
+    switch = switch_connector.container_element
+    interface = interface_connector.container_element
+    if switch.is_installed() and interface.is_installed():
+        switch._object.connect(interface._object)
+        return True
+    return False
+   
+#XXX: This connection function cannot be use to transfer a file descriptor
+# to a remote tap device
+def connect_fd_local(tap_connector, fdnd_connector):
+    tap = tap_connector.container_element
+    fdnd = fdnd_connector.container_element
+    if fdnd.is_installed() and tap.is_installed():
+        socket = tap.server.modules.socket
+        passfd = tap.server.modules.passfd
+        fd = tap.file_descriptor
+        address = fdnd.socket_address
+        sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
+        sock.connect(address)
+        passfd.sendfd(sock, fd, '0')
+        # TODO: after succesful transfer, the tap device should close the fd
+        return True
+    return False
+
+connections = [
+    dict({
+        'from' : [ "netns", "Node", "devs" ],
+        'to' :   [ "netns", "P2PInterface", "node" ],
+        'code' : do_nothing,
+        'can_cross' : False
+    }),
+    dict({
+        'from' : [ "netns", "Node", "devs" ],
+        'to' :   [ "netns", "TapNodeInterface", "node" ],
+        'code' : do_nothing,
+        'can_cross' : False
+    }),
+    dict({
+        'from' : [ "netns", "Node", "devs" ],
+        'to' :   [ "netns", "NodeInterface", "node" ],
+        'code' : do_nothing,
+        'can_cross' : False
+    }),
+    dict({
+        'from' : [ "netns", "P2PInterface", "p2p" ],
+        'to' :   [ "netns", "P2PInterface", "p2p" ],
+        'code' : do_nothing,
+        'can_cross' : False
+    }),
+    dict({
+        'from' : [ "netns", "TapNodeInterface", "fd" ],
+        'to' :   [ "ns3", "ns3::FileDescriptorNetDevice", "fd" ],
+        'code' : connect_fd_local,
+        'can_cross' : True
+    }),
+     dict({
+        'from' : [ "netns", "Switch", "devs" ],
+        'to' :   [ "netns", "NodeInterface", "switch" ],
+        'code' : connect_switch,
+        'can_cross' : False
+    }),
+    dict({
+        'from' : [ "netns", "Node", "apps" ],
+        'to' :   [ "netns", "Application", "node" ],
+        'code' : do_nothing,
+        'can_cross' : False
+    })
+]
+
+class TapNodeInterface(object):
+    def __init__(self):
+        # GET THE NODE!
+        self._object = node._object.add_tap()
+
+class NodeInterface(object):
+    def __init__(self):
+        # GET NODE
+        self._object = n.add_if()
+
+class Switch(Topo_ChannelMixin, NetnsElement):
+    def __init__(self, factory, server, container = None):
+        self._object = self.server.modules.netns.Switch()
+
+class NetnsApplication(NetnsElement):
+    def __init__(self, factory, server, container = None):
+        super(NetnsApplication, self).__init__(factory, server, container)
+        # attributes
+        self.add_string_attribute(name = "command",
+            help = "Command name")
+        self.add_string_attribute(name = "user",
+            help = "system user")
+        self.add_string_attribute(name = "stdin",
+            help = "Standard input")
+        #traces
+        stdout = StdTrace(
+                name='StdoutTrace',
+                help='Application standard output',
+                filename='stdout.txt',
+                element=self)
+        stderr = StdTrace(
+                name='StderrTrace',
+                help='Application standard error',
+                filename='stderr.txt',
+                element=self)
+        self._add_trace(stdout)
+        self._add_trace(stderr)
+    def start(self):
+        user = self.get_attribute("user").value
+
+        stdin = stdout = stderr = None
+        if self.get_attribute('stdin').value:
+            filename = self.get_attribute('stdin')
+            stdin = self.server.modules.__builtin__.open(filename, 'rb')
+
+        trace = self.get_trace("StdoutTrace")
+        if trace.is_enabled:
+            filename = trace.real_filepath
+            stdout = self.server.modules.__builtin__.open(filename, 'wb')
+
+        trace = self.get_trace("StderrTrace")
+        if trace.is_enabled:
+            filename = trace.real_filepath
+            stderr = self.server.modules.__builtin__.open(filename, 'wb')
+
+        cnctr = self.connector("node")
+        node = iter(cnctr.connections).next().get_other(cnctr)\
+            .container_element
+        force_install(node)    
+        n = node._object
+        command = self.get_attribute('command').value
+        targets = re.findall(r"%target:(.*?)%", command)
+        for target in targets:
+            try:
+                (family, address, port) = resolve_netref(target, AF_INET, 
+                    self.server.experiment )
+                command = command.replace("%%target:%s%%" % target, address.address)
+            except:
+                continue
+
+        self._object  = n.Popen(command, shell = True,  stdout = stdout, stdin = stdin,
+            stderr = stderr, user = user)
+
+    def is_completed(self):
+        if self._object is not None:
+            returncode = self._object.poll()
+            if returncode is not None:
+                return True
+        return False
+
+class StdTrace(Trace):
+    def install(self):
+        pass
+
+    @property
+    def real_filepath(self):
+        filename = self.get_attribute("Filename").value
+        return self.element.server.get_trace_filepath(filename)
+
+    def read_trace(self):
+        filename = self.get_attribute("Filename").value
+        return self.element.server.read_trace(filename)
+
+def force_install(object):
+    if not object.is_installed():
+        object.install()
+
diff --git a/src/nepi/util/graphical_info.py b/src/nepi/util/graphical_info.py
new file mode 100644 (file)
index 0000000..12a40bd
--- /dev/null
@@ -0,0 +1,14 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+# vim:ts=4:sw=4:et:ai:sts=4
+
+class GraphicalInfo(object):
+    """ This class allows to describe the position and dimensions of a 
+    2D object in a GUI canvas"""
+    def __init__(self, label):
+        self.x = 0.0
+        self.y = 0.0
+        self.width = 0.0
+        self.height = 0.0
+        self.label = label
+
index 000260b..925f392 100644 (file)
@@ -22,6 +22,7 @@ class XmlExperimentParser(ExperimentParser):
                 self.box_data_to_xml(doc, elements_tags, guid, data)
         doc.appendChild(exp_tag)
         xml = doc.toprettyxml(indent="    ", encoding="UTF-8")
+        print xml
         return xml
 
     def testbed_data_to_xml(self, doc, parent_tag, guid, data):
@@ -64,35 +65,37 @@ class XmlExperimentParser(ExperimentParser):
 
     def factory_attributes_data_to_xml(self, doc, parent_tag, guid, data):
         factory_attributes_tag = doc.createElement("factory_attributes")
-        parent_tag.appendChild(factory_attributes_tag)
         for (name, value) in data.get_factory_attribute_data(guid):
             factory_attribute_tag = doc.createElement("factory_attribute") 
             factory_attributes_tag.appendChild(factory_attribute_tag)
             factory_attribute_tag.setAttribute("name", name)
             factory_attribute_tag.setAttribute("value", str(value))
             factory_attribute_tag.setAttribute("type", self.type_to_standard(value))
+        if factory_attributes_tag.hasChildNodes():
+            parent_tag.appendChild(factory_attributes_tag)
 
     def attributes_data_to_xml(self, doc, parent_tag, guid, data):
         attributes_tag = doc.createElement("attributes") 
-        parent_tag.appendChild(attributes_tag)
         for name, value in data.get_attribute_data(guid):
             attribute_tag = doc.createElement("attribute") 
             attributes_tag.appendChild(attribute_tag)
             attribute_tag.setAttribute("name", name)
             attribute_tag.setAttribute("value", str(value))
             attribute_tag.setAttribute("type", self.type_to_standard(value))
+        if attributes_tag.hasChildNodes():
+            parent_tag.appendChild(attributes_tag)
 
     def traces_data_to_xml(self, doc, parent_tag, guid, data):
         traces_tag = doc.createElement("traces") 
-        parent_tag.appendChild(traces_tag)
         for name in data.get_trace_data(guid):
             trace_tag = doc.createElement("trace") 
             traces_tag.appendChild(trace_tag)
             trace_tag.setAttribute("name", name)
+        if traces_tag.hasChildNodes():
+            parent_tag.appendChild(traces_tag)
 
     def addresses_data_to_xml(self, doc, parent_tag, guid, data):
         addresses_tag = doc.createElement("addresses") 
-        parent_tag.appendChild(addresses_tag)
         for (autoconfigure, address, family, netprefix, broadcast) \
                 in data.get_address_data(guid):
             address_tag = doc.createElement("address") 
@@ -107,10 +110,11 @@ class XmlExperimentParser(ExperimentParser):
                 address_tag.setAttribute("NetPrefix", str(netprefix))
             if broadcast:
                 address_tag.setAttribute("Broadcast", str(broadcast))
+        if addresses_tag.hasChildNodes():
+            parent_tag.appendChild(addresses_tag)
 
     def routes_data_to_xml(self, doc, parent_tag, guid, data):
         routes_tag = doc.createElement("routes") 
-        parent_tag.appendChild(routes_tag)
         for (family, destination, netprefix, nexthop, interface) \
                 in data.get_route_data(guid):
             route_tag = doc.createElement("route") 
@@ -120,10 +124,11 @@ class XmlExperimentParser(ExperimentParser):
             route_tag.setAttribute("NetPrefix", str(netprefix))
             route_tag.setAttribute("NextHop", str(nexthop))
             route_tag.setAttribute("Interface", str(interface))
+        if routes_tag.hasChildNodes():
+            parent_tag.appendChild(routes_tag)
 
     def connections_data_to_xml(self, doc, parent_tag, guid, data):
         connections_tag = doc.createElement("connections") 
-        parent_tag.appendChild(connections_tag)
         for (connector_type_name, other_guid, other_connector_type_name) \
                 in data.get_connection_data(guid):
                 connection_tag = doc.createElement("connection") 
@@ -132,6 +137,8 @@ class XmlExperimentParser(ExperimentParser):
                 connection_tag.setAttribute("other_guid", str(other_guid))
                 connection_tag.setAttribute("other_connector",
                         other_connector_type_name)
+        if connections_tag.hasChildNodes():
+            parent_tag.appendChild(connections_tag)
 
     def from_xml(self, experiment_description, xml):
         data = ExperimentData()
@@ -141,8 +148,9 @@ class XmlExperimentParser(ExperimentParser):
         for testbed_tag in testbed_tag_list:
             if testbed_tag.nodeType == doc.ELEMENT_NODE:
                 testbed_guid = int(testbed_tag.getAttribute("guid"))
-                self.testbed_data_from_xml(testbed_tag, data)
                 elements_tag = testbed_tag.getElementsByTagName("elements")[0] 
+                elements_tag = testbed_tag.removeChild(elements_tag)
+                self.testbed_data_from_xml(testbed_tag, data)
                 element_tag_list = elements_tag.getElementsByTagName("element")
                 for element_tag in element_tag_list:
                     if element_tag.nodeType == doc.ELEMENT_NODE: