From 24a23b5498713b16d956a9baf7aa112b0707f935 Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Fri, 18 Feb 2011 18:01:17 +0100 Subject: [PATCH] NOT WORKING COMMIT! started adding netns testbed instance support --- examples/execution1.py | 34 +++ src/nepi/core/attributes.py | 2 + src/nepi/core/description.py | 1 + src/nepi/core/testbed.py | 29 +- src/nepi/testbeds/netns/__init__.py | 72 +---- src/nepi/testbeds/netns/description.py | 72 +++++ src/nepi/testbeds/netns/testbed.py | 358 +++++++++++++++++++++++++ src/nepi/util/graphical_info.py | 14 + src/nepi/util/parser/_xml.py | 22 +- 9 files changed, 516 insertions(+), 88 deletions(-) create mode 100644 examples/execution1.py create mode 100644 src/nepi/testbeds/netns/description.py create mode 100644 src/nepi/testbeds/netns/testbed.py create mode 100644 src/nepi/util/graphical_info.py diff --git a/examples/execution1.py b/examples/execution1.py new file mode 100644 index 00000000..53f475a1 --- /dev/null +++ b/examples/execution1.py @@ -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() + diff --git a/src/nepi/core/attributes.py b/src/nepi/core/attributes.py index d4622300..2018b46e 100644 --- a/src/nepi/core/attributes.py +++ b/src/nepi/core/attributes.py @@ -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): diff --git a/src/nepi/core/description.py b/src/nepi/core/description.py index e0e51107..75c3f41b 100644 --- a/src/nepi/core/description.py +++ b/src/nepi/core/description.py @@ -526,3 +526,4 @@ class ExperimentDescription(object): def destroy(self): for testbed_description in self.testbed_descriptions: testbed_description.destroy() + diff --git a/src/nepi/core/testbed.py b/src/nepi/core/testbed.py index aaf7434e..1537cdaf 100644 --- a/src/nepi/core/testbed.py +++ b/src/nepi/core/testbed.py @@ -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): diff --git a/src/nepi/testbeds/netns/__init__.py b/src/nepi/testbeds/netns/__init__.py index 7ae173cf..de0eb933 100644 --- a/src/nepi/testbeds/netns/__init__.py +++ b/src/nepi/testbeds/netns/__init__.py @@ -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 index 00000000..25179d47 --- /dev/null +++ b/src/nepi/testbeds/netns/description.py @@ -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 index 00000000..4f0d66f5 --- /dev/null +++ b/src/nepi/testbeds/netns/testbed.py @@ -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 index 00000000..12a40bd8 --- /dev/null +++ b/src/nepi/util/graphical_info.py @@ -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 + diff --git a/src/nepi/util/parser/_xml.py b/src/nepi/util/parser/_xml.py index 000260b5..925f392a 100644 --- a/src/nepi/util/parser/_xml.py +++ b/src/nepi/util/parser/_xml.py @@ -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: -- 2.43.0