From: Alina Quereilhac Date: Thu, 3 Mar 2011 16:28:34 +0000 (+0100) Subject: Design + Execution first netns prototype working X-Git-Tag: nepi_v2~198 X-Git-Url: http://git.onelab.eu/?a=commitdiff_plain;h=7703d41133f44ac27eeabbffd5d415cc768b160c;p=nepi.git Design + Execution first netns prototype working --- diff --git a/examples/design1.py b/examples/design1.py index 3c6cb769..77373bdd 100644 --- a/examples/design1.py +++ b/examples/design1.py @@ -1,12 +1,12 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from nepi.core.description import ExperimentDescription -from nepi.testbeds import netns +from nepi.core.design import ExperimentDescription, FactoriesProvider exp_desc = ExperimentDescription() testbed_version = "01" -netns_provider = netns.TestbedFactoriesProvider(testbed_version) +testbed_id = "netns" +netns_provider = FactoriesProvider(testbed_id, testbed_version) netns_desc = exp_desc.add_testbed_description(netns_provider) node1 = netns_desc.create("Node") diff --git a/examples/execution1.py b/examples/execution1.py index 53f475a1..b1c1e555 100644 --- a/examples/execution1.py +++ b/examples/execution1.py @@ -1,27 +1,31 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from nepi.core.decription import AF_INET +from nepi.core.design import AF_INET from nepi.testbeds import netns -instance = netns.TestbedInstance(None) +user = "alina" +testbed_version = "01" +config = netns.TestbedConfiguration() +instance = netns.TestbedInstance(testbed_version, config) -instance.create(2, "Node", []) -instance.create(3, "Node", []) -instance.create(4, "NodeInterface", []) +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.add_adddress(4, AF_INET, "10.0.0.1", 24, 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.add_adddress(5, AF_INET, "10.0.0.2", 24, 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(7, "Application") instance.create_set(7, "command", "ping -qc10 10.0.0.2") +instance.create_set(7, "user", user) instance.connect(7, "node", 2, "apps") instance.do_create() @@ -31,4 +35,5 @@ instance.start() import time time.sleep(5) instance.stop() +instance.shutdown() diff --git a/src/nepi/__init__.py b/src/nepi/__init__.py index e69de29b..944510b6 100644 --- a/src/nepi/__init__.py +++ b/src/nepi/__init__.py @@ -0,0 +1,6 @@ +""" +NEPI (Network Experiment Programming Interface) v 1.0.0 (22 Feb 2011) +Licensed under #TODO: Check license + +Provides a uniform API to describe, deploy and control network experiments for heterogeneous experimentation platforms. +""" diff --git a/src/nepi/core/attributes.py b/src/nepi/core/attributes.py index 2018b46e..faca972c 100644 --- a/src/nepi/core/attributes.py +++ b/src/nepi/core/attributes.py @@ -19,8 +19,8 @@ class AttributesMap(object): def set_attribute_value(self, name, value): self._attributes[name].value = value - def set_attribute_readonly(self, name, value): - self._attributes[name].readonly = value + def set_attribute_readonly(self, name, readonly = True): + self._attributes[name].readonly = (readonly == True) def get_attribute_value(self, name): return self._attributes[name].value @@ -42,15 +42,19 @@ class AttributesMap(object): def get_attribute_readonly(self, name): return self._attributes[name].readonly + def get_attribute_visible(self, name): + return self._attributes[name].visible + def is_attribute_modified(self, name): return self._attributes[name].modified def add_attribute(self, name, help, type, value = None, range = None, - allowed = None, readonly = False, validation_function = None): + allowed = None, readonly = False, visible = True, + validation_function = None): if name in self._attributes: raise AttributeError("Attribute %s already exists" % name) attribute = Attribute(name, help, type, value, range, allowed, readonly, - validation_function) + visible, validation_function) self._attributes[name] = attribute def del_attribute(self, name): @@ -69,7 +73,8 @@ class Attribute(object): types = [STRING, BOOL, ENUM, DOUBLE, INTEGER] def __init__(self, name, help, type, value = None, range = None, - allowed = None, readonly = False, validation_function = None): + allowed = None, readonly = False, visible = True, + validation_function = None): if not type in Attribute.types: raise AttributeError("invalid type %s " % type) self.name = name @@ -77,7 +82,10 @@ class Attribute(object): self._help = help self._value = value self._validation_function = validation_function + # readonly attributes can be seen but not changed by users self._readonly = (readonly == True) + # invisible attributes cannot be seen or changed by users + self._visible = (visible == True) self._modified = False # range: max and min possible values self._range = range @@ -92,6 +100,10 @@ class Attribute(object): def help(self): return self._help + @property + def visible(self): + return self._visible + @property def readonly(self): return self._readonly diff --git a/src/nepi/core/description.py b/src/nepi/core/design.py similarity index 70% rename from src/nepi/core/description.py rename to src/nepi/core/design.py index 75c3f41b..52d3ddf7 100644 --- a/src/nepi/core/description.py +++ b/src/nepi/core/design.py @@ -1,46 +1,47 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +""" +Experiment design API +""" + from nepi.core.attributes import AttributesMap, Attribute +from nepi.core.metadata import Metadata from nepi.util import validation +from nepi.util.constants import AF_INET, AF_INET6 from nepi.util.guid import GuidGenerator from nepi.util.graphical_info import GraphicalInfo from nepi.util.parser._xml import XmlExperimentParser import sys -AF_INET = 0 -AF_INET6 = 1 - class ConnectorType(object): - """A ConnectorType defines a kind of connector that can be used in an Object. - """ - def __init__(self, connector_type_id, help, name, max = -1, min = 0): + def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0): super(ConnectorType, self).__init__() - """ - ConnectorType(name, help, display_name, max, min): - - connector_type_id: (unique) identifier for this type. - Typically: testbed_id + factory_id + name - - name: descriptive name for the user - - help: help text - - max: amount of connections that this type support, -1 for no limit - - min: minimum amount of connections to this type of connector - """ if max == -1: max = sys.maxint elif max <= 0: raise RuntimeError( - 'The maximum number of connections allowed need to be more than 0') + "The maximum number of connections allowed need to be more than 0") if min < 0: raise RuntimeError( - 'The minimum number of connections allowed needs to be at least 0') - self._connector_type_id = connector_type_id - self._help = help + "The minimum number of connections allowed needs to be at least 0") + # connector_type_id -- univoquely identifies a connector type + # across testbeds + self._connector_type_id = (testbed_id.lower(), factory_id.lower(), + name.lower()) + # name -- display name for the connector type self._name = name + # help -- help text + self._help = help + # max -- maximum amount of connections that this type support, + # -1 for no limit self._max = max + # min -- minimum amount of connections required by this type of connector self._min = min - # list of connector_type_ids with which this connector_type is allowed - # to connect - self._allowed_connector_type_ids = list() + # allowed_connections -- keys in the dictionary correspond to the + # connector_type_id for possible connections. The value indicates if + # the connection is allowed accros different testbed instances + self._allowed_connections = dict() @property def connector_type_id(self): @@ -62,11 +63,15 @@ class ConnectorType(object): def min(self): return self._min - def add_allowed_connector_type_id(self, connector_type_id): - self._allowed_connector_type_ids.append(connector_type_id) + def add_allowed_connection(self, testbed_id, factory_id, name, can_cross): + self._allowed_connections[(testbed_id.lower(), + factory_id.lower(), name.lower())] = can_cross - def can_connect(self, connector_type_id): - return connector_type_id in self._allowed_connector_type_ids + def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2): + if not connector_type_id in self._allowed_connections.keys(): + return False + can_cross = self._allowed_connections[connector_type_id] + return can_cross or (testbed_guid1 == testbed_guid2) class Connector(object): """A Connector sepcifies the connection points in an Object""" @@ -74,7 +79,7 @@ class Connector(object): super(Connector, self).__init__() self._box = box self._connector_type = connector_type - self._connections = dict() + self._connections = list() @property def box(self): @@ -86,58 +91,60 @@ class Connector(object): @property def connections(self): - return self._connections.values() + return self._connections def is_full(self): - """Return True if the connector has the maximum number of connections""" + """Return True if the connector has the maximum number of connections + """ return len(self.connections) == self.connector_type.max def is_complete(self): - """Return True if the connector has the minimum number of connections""" + """Return True if the connector has the minimum number of connections + """ return len(self.connections) >= self.connector_type.min def is_connected(self, connector): - return connector._key in self._connections + return connector in self._connections def connect(self, connector): - if self.is_full() or connector.is_full(): - raise RuntimeError("Connector is full") if not self.can_connect(connector) or not connector.can_connect(self): raise RuntimeError("Could not connect.") - self._connections[connector._key] = connector - connector._connections[self._key] = self + self._connections.append(connector) + connector._connections.append(self) def disconnect(self, connector): - if connector._key not in self._connections or\ - self._key not in connector._connections: + if connector not in self._connections or\ + self not in connector._connections: raise RuntimeError("Could not disconnect.") - del self._connections[connector._key] - del connector._connections[self._key] + self._connections.remove(connector) + connector._connections.remove(self) def can_connect(self, connector): + if self.is_full() or connector.is_full(): + return False + if self.is_connected(connector): + return False connector_type_id = connector.connector_type.connector_type_id - return self.connector_type.can_connect(connector_type_id) + testbed_guid1 = self.box.testbed_guid + testbed_guid2 = connector.box.testbed_guid + return self.connector_type.can_connect(connector_type_id, + testbed_guid1, testbed_guid2) def destroy(self): for connector in self.connections: self.disconnect(connector) self._box = self._connectors = None - @property - def _key(self): - return "%d_%s" % (self.box.guid, - self.connector_type.connector_type_id) - class Trace(AttributesMap): - def __init__(self, name, help, enabled = False): + def __init__(self, trace_id, help, enabled = False): super(Trace, self).__init__() - self._name = name + self._trace_id = trace_id self._help = help self.enabled = enabled @property - def name(self): - return self._name + def trace_id(self): + return self._trace_id @property def help(self): @@ -167,6 +174,7 @@ class Address(AttributesMap): help = "Network prefix for the address", type = Attribute.INTEGER, range = prefix_range, + value = 24 if family == AF_INET else 64, validation_function = validation.is_integer) if family == AF_INET: self.add_attribute(name = "Broadcast", @@ -204,32 +212,34 @@ class Route(AttributesMap): validation_function = address_validation) class Box(AttributesMap): - def __init__(self, guid, factory, container = None): + def __init__(self, guid, factory, testbed_guid, container = None): super(Box, self).__init__() - # general unique id + # guid -- global unique identifier self._guid = guid - # factory identifier or name + # factory_id -- factory identifier or name self._factory_id = factory.factory_id - # boxes can be nested inside other 'container' boxes + # testbed_guid -- parent testbed guid + self._testbed_guid = testbed_guid + # container -- boxes can be nested inside other 'container' boxes self._container = container - # traces for the box + # traces -- list of available traces for the box self._traces = dict() - # connectors for the box + # connectors -- list of available connectors for the box self._connectors = dict() - # factory attributes for box construction + # factory_attributes -- factory attributes for box construction self._factory_attributes = list() - + # graphical_info -- GUI position information self.graphical_info = GraphicalInfo(str(self._guid)) for connector_type in factory.connector_types: connector = Connector(self, connector_type) self._connectors[connector_type.name] = connector for trace in factory.traces: - tr = Trace(trace.name, trace.help, trace.enabled) - self._traces[trace.name] = tr + tr = Trace(trace.trace_id, trace.help, trace.enabled) + self._traces[trace.trace_id] = tr for attr in factory.box_attributes: self.add_attribute(attr.name, attr.help, attr.type, attr.value, - attr.range, attr.allowed, attr.readonly, + attr.range, attr.allowed, attr.readonly, attr.visible, attr.validation_function) for attr in factory.attributes: self._factory_attributes.append(attr) @@ -242,6 +252,10 @@ class Box(AttributesMap): def factory_id(self): return self._factory_id + @property + def testbed_guid(self): + return self._testbed_guid + @property def container(self): return self._container @@ -269,8 +283,8 @@ class Box(AttributesMap): def connector(self, name): return self._connectors[name] - def trace(self, name): - return self._traces[name] + def trace(self, trace_id): + return self._traces[trace_id] def destroy(self): super(Box, self).destroy() @@ -281,11 +295,12 @@ class Box(AttributesMap): self._connectors = self._traces = self._factory_attributes = None class AddressableBox(Box): - def __init__(self, guid, factory, family, max_addresses = 1, container = None): - super(AddressableBox, self).__init__(guid, factory, container) - self._family = family + def __init__(self, guid, factory, testbed_guid, container = None): + super(AddressableBox, self).__init__(guid, factory, testbed_guid, + container) + self._family = factory.get_attribute_value("Family") # maximum number of addresses this box can have - self._max_addresses = max_addresses + self._max_addresses = factory.get_attribute_value("MaxAddresses") self._addresses = list() @property @@ -337,14 +352,16 @@ class RoutingTableBox(Box): self.delete_route(route) self._route = None -class BoxFactory(AttributesMap): - def __init__(self, factory_id, display_name, help = None, category = None): - super(BoxFactory, self).__init__() +class Factory(AttributesMap): + def __init__(self, factory_id, allow_addresses = False, + allow_routes = False, Help = None, category = None): + super(Factory, self).__init__() self._factory_id = factory_id + self._allow_addresses = (allow_addresses == True) + self._allow_routes = (allow_routes == True) self._help = help self._category = category - self._display_name = display_name - self._connector_types = set() + self._connector_types = list() self._traces = list() self._box_attributes = list() @@ -352,6 +369,14 @@ class BoxFactory(AttributesMap): def factory_id(self): return self._factory_id + @property + def allow_addresses(self): + return self._allow_addresses + + @property + def allow_routes(self): + return self._allow_routes + @property def help(self): return self._help @@ -360,10 +385,6 @@ class BoxFactory(AttributesMap): def category(self): return self._category - @property - def display_name(self): - return self._display_name - @property def connector_types(self): return self._connector_types @@ -376,53 +397,43 @@ class BoxFactory(AttributesMap): def box_attributes(self): return self._box_attributes - def add_connector_type(self, connector_type_id, help, name, max = -1, - min = 0, allowed_connector_type_ids = []): - connector_type = ConnectorType(connector_type_id, help, name, max, min) - for connector_type_id in allowed_connector_type_ids: - connector_type.add_allowed_connector_type_id(connector_type_id) - self._connector_types.add(connector_type) + def add_connector_type(self, connector_type): + self._connector_types.append(connector_type) - def add_trace(self, name, help, enabled = False): - trace = Trace(name, help, enabled) + def add_trace(self, trace_id, help, enabled = False): + trace = Trace(trace_id, help, enabled) self._traces.append(trace) def add_box_attribute(self, name, help, type, value = None, range = None, - allowed = None, readonly = False, validation_function = None): + allowed = None, readonly = False, visible = True, + validation_function = None): attribute = Attribute(name, help, type, value, range, allowed, readonly, - validation_function) + visible, validation_function) self._box_attributes.append(attribute) def create(self, guid, testbed_description): - return Box(guid, self) + if self._allow_addresses: + return AddressableBox(guid, self, testbed_description.guid) + elif self._allow_routes: + return RoutingTableBox(guid, self, testbed_description.guid) + else: + return Box(guid, self, testbed_description.guid) def destroy(self): - super(BoxFactory, self).destroy() + super(Factory, self).destroy() self._connector_types = None -class AddressableBoxFactory(BoxFactory): - def __init__(self, factory_id, display_name, family, max_addresses = 1, - help = None, category = None): - super(AddressableBoxFactory, self).__init__(factory_id, - display_name, help, category) - self._family = family - self._max_addresses = 1 - - def create(self, guid, testbed_description): - return AddressableBox(guid, self, self._family, - self._max_addresses) - -class RoutingTableBoxFactory(BoxFactory): - def create(self, guid, testbed_description): - return RoutingTableBox(guid, self) - -class TestbedFactoriesProvider(object): +class FactoriesProvider(object): def __init__(self, testbed_id, testbed_version): - super(TestbedFactoriesProvider, self).__init__() + super(FactoriesProvider, self).__init__() self._testbed_id = testbed_id self._testbed_version = testbed_version self._factories = dict() + metadata = Metadata(testbed_id, testbed_version) + for factory in metadata.build_design_factories(): + self.add_factory(factory) + @property def testbed_id(self): return self._testbed_id @@ -527,3 +538,15 @@ class ExperimentDescription(object): for testbed_description in self.testbed_descriptions: testbed_description.destroy() +# TODO: When the experiment xml is passed to the controller to execute it +# NetReferences in the xml need to be solved +# +#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 + diff --git a/src/nepi/core/execute.py b/src/nepi/core/execute.py new file mode 100644 index 00000000..aa8b8af2 --- /dev/null +++ b/src/nepi/core/execute.py @@ -0,0 +1,213 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from nepi.core.attributes import AttributesMap +import sys + +class ConnectorType(object): + def __init__(self, testbed_id, factory_id, name, max = -1, min = 0): + super(ConnectorType, self).__init__() + if max == -1: + max = sys.maxint + elif max <= 0: + raise RuntimeError( + "The maximum number of connections allowed need to be more than 0") + if min < 0: + raise RuntimeError( + "The minimum number of connections allowed needs to be at least 0") + # connector_type_id -- univoquely identifies a connector type + # across testbeds + self._connector_type_id = (testbed_id.lower(), factory_id.lower(), + name.lower()) + # name -- display name for the connector type + self._name = name + # max -- maximum amount of connections that this type support, + # -1 for no limit + self._max = max + # min -- minimum amount of connections required by this type of connector + self._min = min + # from_connections -- connections where the other connector is the "From" + # to_connections -- connections where the other connector is the "To" + # keys in the dictionary correspond to the + # connector_type_id for possible connections. The value is a tuple: + # (can_cross, connect) + # can_cross: indicates if the connection is allowed accros different + # testbed instances + # code: is the connection function to be invoked when the elements + # are connected + self._from_connections = dict() + self._to_connections = dict() + + @property + def connector_type_id(self): + return self._connector_type_id + + @property + def name(self): + return self._name + + @property + def max(self): + return self._max + + @property + def min(self): + return self._min + + def add_from_connection(self, testbed_id, factory_id, name, can_cross, code): + self._from_connections[(testbed_id.lower(), factory_id.lower(), + name.lower())] = (can_cross, code) + + def add_to_connection(self, testbed_id, factory_id, name, can_cross, code): + self._to_connections[(testbed_id.lower(), factory_id.lower(), + name.lower())] = (can_cross, code) + + def can_connect(self, testbed_id, factory_id, name, count, + must_cross = False): + connector_type_id = (testbed_id.lower(), factory_id.lower(), + name.lower()) + if connector_type_id in self._from_connections: + (can_cross, code) = self._from_connections[connector_type_id] + elif connector_type_id in self._to_connections: + (can_cross, code) = self._to_connections[connector_type_id] + else: + return False + return not must_cross or can_cross + + def code_to_connect(self, testbed_id, factory_id, name): + connector_type_id = (testbed_id.lower(), factory_id.lower(), + name.lower()) + if not connector_type_id in self._to_connections.keys(): + return False + (can_cross, code) = self._to_connections[connector_type_id] + return code + +# TODO: create_function, start_function, stop_function, status_function +# need a definition! +class Factory(AttributesMap): + def __init__(self, factory_id, create_function, start_function, + stop_function, status_function, allow_addresses = False, + allow_routes = False): + super(Factory, self).__init__() + self._factory_id = factory_id + self._allow_addresses = (allow_addresses == True) + self._allow_routes = (allow_routes == True) + self._create_function = create_function + self._start_function = start_function + self._stop_function = stop_function + self._status_function = status_function + self._connector_types = dict() + self._traces = list() + + @property + def factory_id(self): + return self._factory_id + + @property + def allow_addresses(self): + return self._allow_addresses + + @property + def allow_routes(self): + return self._allow_routes + + @property + def create_function(self): + return self._create_function + + @property + def start_function(self): + return self._start_function + + @property + def stop_function(self): + return self._stop_function + + @property + def status_function(self): + return self._status_function + + @property + def traces(self): + return self._traces + + def connector_type(self, name): + return self._connector_types[name] + + def add_connector_type(self, connector_type): + self._connector_types[connector_type.name] = connector_type + + def add_trace(self, trace_id): + self._traces.append(trace_id) + +class TestbedConfiguration(AttributesMap): + pass + +class TestbedInstance(object): + def __init__(self, testbed_version, configuration): + pass + + 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, guid1, connector_type_name1, guid2, + connector_type_name2): + raise NotImplementedError + + def cross_connect(self, guid, connector_type_name, cross_guid, + cross_testbed_id, cross_factory_id, cross_connector_type_name): + raise NotImplementedError + + def do_connect(self): + raise NotImplementedError + + def add_trace(self, guid, trace_id): + raise NotImplementedError + + def add_adddress(self, guid, family, address, netprefix, broadcast): + raise NotImplementedError + + def add_route(self, guid, family, destination, netprefix, nexthop, + interface): + raise NotImplementedError + + def do_configure(self): + raise NotImplementedError + + def do_cross_connect(self): + raise NotImplementedError + + 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 + diff --git a/src/nepi/core/metadata.py b/src/nepi/core/metadata.py new file mode 100644 index 00000000..57b5dfda --- /dev/null +++ b/src/nepi/core/metadata.py @@ -0,0 +1,251 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import sys + +class VersionedMetadataInfo(object): + @property + def connections_types(self): + """ dictionary of dictionaries with allowed connection information. + connector_id: dict({ + "help": help text, + "name": connector type name, + "max": maximum number of connections allowed (-1 for no limit), + "min": minimum number of connections allowed + }), + """ + raise NotImplementedError + + @property + def connections(self): + """ array of dictionaries with allowed connection information. + dict({ + "from": (testbed_id1, factory_id1, connector_type_name1), + "to": (testbed_id2, factory_id2, connector_type_name2), + "code": connection function to invoke upon connection creation + "can_cross": whether the connection can be done across testbed + instances + }), + """ + raise NotImplementedError + + @property + def attributes(self): + """ dictionary of dictionaries of all available attributes. + "attribute_id": dict({ + "name": attribute name, + "help": help text, + "type": attribute type, + "value": default attribute value, + "range": (maximum, minimun) values else None if not defined, + "allowed": array of posible values, + "readonly": whether the attribute is read only for the user, + "visible": whether the attribute is visible for the user, + "validation_function": validation function for the attribute + }) + """ + raise NotImplementedError + + @property + def traces(self): + """ dictionary of dictionaries of all available traces. + trace_id: dict({ + "name": trace name, + "help": help text + }) + """ + raise NotImplementedError + + @property + def factories_order(self): + """ list of factory ids that indicates the order in which the elements + should be instantiated. + """ + raise NotImplementedError + + @property + def factories_info(self): + """ dictionary of dictionaries of factory specific information + factory_id: dict({ + "allow_addresses": whether the box allows adding IP addresses, + "allow_routes": wether the box allows adding routes, + "help": help text, + "category": category the element belongs to, + "create_function": function for element instantiation, + "start_function": function for element starting, + "stop_function": function for element stoping, + "status_function": function for retrieving element status, + "factory_attributes": list of references to attribute_ids, + "box_attributes": list of regerences to attribute_ids, + "traces": list of references to trace_id + "connector_types": list of references to connector_types + }) + """ + raise NotImplementedError + +class Metadata(object): + def __init__(self, testbed_id, version): + self._version = version + self._testbed_id = testbed_id + self._metadata = self._load_versioned_metadata_info() + + @property + def factories_order(self): + return self._metadata.factories_order + + def build_design_factories(self): + from nepi.core.design import Factory + factories = list() + for factory_id, info in self._metadata.factories_info.iteritems(): + help = info["help"] + category = info["category"] + allow_addresses = info["allow_addresses"] \ + if "allow_addresses" in info else False + allow_routes = info["allow_routes"] \ + if "allow_routes" in info else False + factory = Factory(factory_id, allow_addresses, allow_routes, + help, category) + self._add_attributes(factory, info, "factory_attributes") + self._add_attributes(factory, info, "box_attributes", True) + self._add_design_traces(factory, info) + self._add_design_connector_types(factory, info) + factories.append(factory) + return factories + + def build_execute_factories(self): + from nepi.core.execute import Factory + factories = list() + for factory_id, info in self._metadata.factories_info.iteritems(): + create_function = info["create_function"] + start_function = info["start_function"] + stop_function = info["stop_function"] + status_function = info["status_function"] + allow_addresses = info["allow_addresses"] \ + if "allow_addresses" in info else False + allow_routes = info["allow_routes"] \ + if "allow_routes" in info else False + factory = Factory(factory_id, create_function, start_function, + stop_function, status_function, allow_addresses, + allow_routes) + self._add_attributes(factory, info, "factory_attributes") + self._add_attributes(factory, info, "box_attributes") + self._add_execute_traces(factory, info) + self._add_execute_connector_types(factory, info) + factories.append(factory) + return factories + + def _load_versioned_metadata_info(self): + mod_name = "nepi.testbeds.%s.metadata_v%s" % (self._testbed_id.lower(), + self._version) + if not mod_name in sys.modules: + __import__(mod_name) + return sys.modules[mod_name] + + def _add_attributes(self, factory, info, attributes_key, + box_attributes = False): + if attributes_key in info: + for attribute_id in info[attributes_key]: + attribute_info = self._metadata.attributes[attribute_id] + name = attribute_info["name"] + help = attribute_info["help"] + type = attribute_info["type"] + value = attribute_info["value"] + range = attribute_info["range"] + allowed = attribute_info["allowed"] + readonly = attribute_info["readonly"] + visible = attribute_info["visible"] + validation_function = attribute_info["validation_function"] + if box_attributes: + factory.add_box_attribute(name, help, type, value, range, + allowed, readonly, visible, validation_function) + else: + factory.add_attribute(name, help, type, value, range, + allowed, readonly, visible, validation_function) + + def _add_design_traces(self, factory, info): + if "traces" in info: + for trace in info["traces"]: + trace_info = self._metadata.traces[trace] + trace_id = trace_info["name"] + help = trace_info["help"] + factory.add_trace(trace_id, help) + + def _add_execute_traces(self, factory, info): + if "traces" in info: + for trace in info["traces"]: + trace_info = self._metadata.traces[trace] + trace_id = trace_info["name"] + factory.add_trace(trace_id) + + def _add_design_connector_types(self, factory, info): + from nepi.core.design import ConnectorType + if "connector_types" in info: + connections = dict() + for connection in self._metadata.connections: + from_ = connection["from"] + to = connection["to"] + can_cross = connection["can_cross"] + if from_ not in connections: + connections[from_] = list() + if to not in connections: + connections[to] = list() + connections[from_].append((to, can_cross)) + connections[to].append((from_, can_cross)) + for connector_id in info["connector_types"]: + connector_type_info = self._metadata.connector_types[ + connector_id] + name = connector_type_info["name"] + help = connector_type_info["help"] + max = connector_type_info["max"] + min = connector_type_info["min"] + testbed_id = self._testbed_id + factory_id = factory.factory_id + connector_type = ConnectorType(testbed_id, factory_id, name, + help, max, min) + for (to, can_cross) in connections[(testbed_id, factory_id, + name)]: + (testbed_id_to, factory_id_to, name_to) = to + connector_type.add_allowed_connection(testbed_id_to, + factory_id_to, name_to, can_cross) + factory.add_connector_type(connector_type) + + def _add_execute_connector_types(self, factory, info): + from nepi.core.execute import ConnectorType + if "connector_types" in info: + from_connections = dict() + to_connections = dict() + for connection in self._metadata.connections: + from_ = connection["from"] + to = connection["to"] + can_cross = connection["can_cross"] + code = connection["code"] + if from_ not in from_connections: + from_connections[from_] = list() + if to not in to_connections: + to_connections[to] = list() + from_connections[from_].append((to, can_cross, code)) + to_connections[to].append((from_, can_cross, code)) + for connector_id in info["connector_types"]: + connector_type_info = self._metadata.connector_types[ + connector_id] + name = connector_type_info["name"] + max = connector_type_info["max"] + min = connector_type_info["min"] + testbed_id = self._testbed_id + factory_id = factory.factory_id + connector_type = ConnectorType(testbed_id, factory_id, name, + max, min) + connector_key = (testbed_id, factory_id, name) + if connector_key in to_connections: + for (from_, can_cross, code) in to_connections[connector_key]: + (testbed_id_from, factory_id_from, name_from) = from_ + connector_type.add_from_connection(testbed_id_from, + factory_id_from, name_from, can_cross, code) + if connector_key in from_connections: + for (to, can_cross, code) in from_connections[(testbed_id, + factory_id, name)]: + (testbed_id_to, factory_id_to, name_to) = to + connector_type.add_to_connection(testbed_id_to, + factory_id_to, name_to, can_cross, code) + factory.add_connector_type(connector_type) + diff --git a/src/nepi/core/protocol.py b/src/nepi/core/protocol.py deleted file mode 100644 index 51559202..00000000 --- a/src/nepi/core/protocol.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- - -# PROTOCOL MESSAGES -CREATE = 0 -CONNECT = 1 -SET = 2 -GET = 3 -START = 4 -STOP = 5 -STATUS = 6 -ENABLE_TRACE = 7 -DISABLE_TRACE = 8 -GET_TRACE = 9 -ADD_ADDRES = 10 -ADD_ROUTE = 11 -SHUTDOWN = 12 - -tesbed_messages = { - CREATE: "CREATE,%d,%d,%s,[%s]", # CREATE,time,guid,factory_id,parameters - CONNECT: "CONNECT,%d,%d,%d,%d,%s,%s", # CONNECT,time,object1_guid,object2_guid,connector1_id,connector2_id - SET: "%d,%d,%s,%s", # SET,time,guid,name,value - GET: "%d,%d,%s", # GET,time,guid,name - START: "%d,%d", # START,time,guid - STOP: "%d,%d", # STOP,time,guid - STATUS: "%d,%d", # STATUS,time,guid - ENABLE_TRACE: "%d,%d,%s", # ENABLE_TRACE,time,guid,trace_id - DISABLE_TRACE: "%d,%d,%s", # DISABLE_TRACE,time,guid,trace_id - GET_TRACE: "%d,%d,%s", # GET_TRACE,time,guid,trace_id - ADD_ADDRESSES: "%d,%d", # ADD_ADDRESSES,time,guid - ADD_ROUTE: "%d,%d", # ADD_ROUTE,time,guid - SHUTDOWN: "%d,%d" , # SHUTDOWN,time.guid - } - diff --git a/src/nepi/core/testbed.py b/src/nepi/core/testbed.py deleted file mode 100644 index 1537cdaf..00000000 --- a/src/nepi/core/testbed.py +++ /dev/null @@ -1,71 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -from nepi.core.attributes import AttributesMap - -class TestbedConfiguration(AttributesMap): - pass - -class TestbedInstance(object): - def __init__(self, configuration): - pass - - 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, guid1, connector_type_name1, guid2, - connector_type_name2): - raise NotImplementedError - - def do_connect(self): - raise NotImplementedError - - def add_trace(self, guid, trace_id): - raise NotImplementedError - - def add_adddress(self, guid, family, address, netprefix, broadcast): - raise NotImplementedError - - def add_route(self, guid, family, destination, netprefix, nexthop, - interface): - raise NotImplementedError - - def do_configure(self): - raise NotImplementedError - - def do_cross_connect(self): - raise NotImplementedError - - 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 - diff --git a/src/nepi/testbeds/netns/__init__.py b/src/nepi/testbeds/netns/__init__.py index de0eb933..b53983a4 100644 --- a/src/nepi/testbeds/netns/__init__.py +++ b/src/nepi/testbeds/netns/__init__.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from description import TestbedFactoriesProvider -from testbed import TestbedInstance +from constants import TESTBED_ID +from execute import TestbedConfiguration, TestbedInstance + diff --git a/src/nepi/testbeds/netns/constants.py b/src/nepi/testbeds/netns/constants.py new file mode 100644 index 00000000..7f44537e --- /dev/null +++ b/src/nepi/testbeds/netns/constants.py @@ -0,0 +1,5 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +TESTBED_ID = "netns" + diff --git a/src/nepi/testbeds/netns/description.py b/src/nepi/testbeds/netns/description.py deleted file mode 100644 index 25179d47..00000000 --- a/src/nepi/testbeds/netns/description.py +++ /dev/null @@ -1,72 +0,0 @@ -#!/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/execute.py b/src/nepi/testbeds/netns/execute.py new file mode 100644 index 00000000..11c622f6 --- /dev/null +++ b/src/nepi/testbeds/netns/execute.py @@ -0,0 +1,308 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from constants import TESTBED_ID +from nepi.core import execute +from nepi.core.attributes import Attribute +from nepi.core.metadata import Metadata +from nepi.util import validation +from nepi.util.constants import AF_INET, AF_INET6 + +class TestbedConfiguration(execute.TestbedConfiguration): + def __init__(self): + super(TestbedConfiguration, self).__init__() + self.add_attribute("EnableDebug", "Enable netns debug output", + Attribute.BOOL, False, None, None, False, validation.is_bool) + +class TestbedInstance(execute.TestbedInstance): + def __init__(self, testbed_version, configuration): + self._configuration = configuration + self._testbed_id = TESTBED_ID + self._testbed_version = testbed_version + self._factories = dict() + self._elements = dict() + self._create = dict() + self._set = dict() + self._connect = dict() + self._cross_connect = dict() + self._add_trace = dict() + self._add_address = dict() + self._add_route = dict() + + self._metadata = Metadata(self._testbed_id, self._testbed_version) + for factory in self._metadata.build_execute_factories(): + self._factories[factory.factory_id] = factory + + self._netns = self._load_netns_module(configuration) + + @property + def elements(self): + return self._elements + + @property + def netns(self): + return self._netns + + def create(self, guid, factory_id): + if factory_id not in self._factories: + raise RuntimeError("Invalid element type %s for Netns version %s" % + (factory_id, self._testbed_version)) + if guid in self._create: + raise RuntimeError("Cannot add elements with the same guid: %d" % + guid) + self._create[guid] = factory_id + + def create_set(self, guid, name, value): + if not guid in self._create: + raise RuntimeError("Element guid %d doesn't exist" % guid) + factory_id = self._create[guid] + factory = self._factories[factory_id] + if not factory.has_attribute(name): + raise RuntimeError("Invalid attribute %s for element type %s" % + (name, factory_id)) + factory.set_attribute_value(name, value) + if guid not in self._set: + self._set[guid] = dict() + self._set[guid][name] = value + + def connect(self, guid1, connector_type_name1, guid2, + connector_type_name2): + factory_id1 = self._create[guid1] + factory_id2 = self._create[guid2] + count = self._get_connection_count(guid1, connector_type_name1) + factory1 = self._factories[factory_id1] + connector_type = factory1.connector_type(connector_type_name1) + connector_type.can_connect(self._testbed_id, factory_id2, + connector_type_name2, count) + if not guid1 in self._connect: + self._connect[guid1] = dict() + if not connector_type_name1 in self._connect[guid1]: + self._connect[guid1][connector_type_name1] = dict() + self._connect[guid1][connector_type_name1][guid2] = \ + connector_type_name2 + if not guid2 in self._connect: + self._connect[guid2] = dict() + if not connector_type_name2 in self._connect[guid2]: + self._connect[guid2][connector_type_name2] = dict() + self._connect[guid2][connector_type_name2][guid1] = \ + connector_type_name1 + + def cross_connect(self, guid, connector_type_name, cross_guid, + cross_testbed_id, cross_factory_id, cross_connector_type_name): + factory_id = self._create[guid] + count = self._get_connection_count(guid, connector_type_name) + factory = self._factories[factory_id] + connector_type = factory.connector_type(connector_type_name) + connector_type.can_connect(cross_testbed_id, cross_factory_id, + cross_connector_type_name, count, must_cross = True) + if not guid in self._connect: + self._cross_connect[guid] = dict() + if not connector_type_name in self._cross_connect[guid]: + self._cross_connect[guid][connector_type_name] = dict() + self._cross_connect[guid][connector_type_name] = \ + (cross_guid, cross_testbed_id, cross_factory_id, + cross_connector_type_name) + + def add_trace(self, guid, trace_id): + if not guid in self._create: + raise RuntimeError("Element guid %d doesn't exist" % guid) + factory_id = self._create[guid] + factory = self._factories[factory_id] + if not trace_id in factory_traces: + raise RuntimeError("Element type %s doesn't support trace %s" % + (factory_id, trace_id)) + if not guid in self._add_trace: + self._add_trace[guid] = list() + self._add_trace[guid].append(trace_id) + + def add_adddress(self, guid, family, address, netprefix, broadcast): + if not guid in self._create: + raise RuntimeError("Element guid %d doesn't exist" % guid) + factory_id = self._create[guid] + factory = self._factories[factory_id] + if not factory.allow_addresses: + raise RuntimeError("Element type %s doesn't support addresses" % + factory_id) + max_addresses = factory.get_attribute_value("MaxAddresses") + if guid in self._add_address: + count_addresses = len(self._add_address[guid]) + if max_addresses == count_addresses: + raise RuntimeError("Element guid %d of type %s can't accept \ + more addresses" % (guid, family_id)) + else: + self._add_address[guid] = list() + self._add_address[guid].append((family, address, netprefix, broadcast)) + + def add_route(self, guid, family, destination, netprefix, nexthop, + interface): + if not guid in self._create: + raise RuntimeError("Element guid %d doesn't exist" % guid) + factory_id = self._create[guid] + factory = self._factories[factory_id] + if not factory.allow_routes: + raise RuntimeError("Element type %s doesn't support routes" % + factory_id) + if not guid in self._add_route: + self._add_route[guid] = list() + self._add_route[guid].append((family, destination, netprefix, nexthop, + interface)) + + def do_create(self): + guids = dict() + for guid, factory_id in self._create.iteritems(): + if not factory_id in guids: + guids[factory_id] = list() + guids[factory_id].append(guid) + for factory_id in self._metadata.factories_order: + if factory_id not in guids: + continue + factory = self._factories[factory_id] + for guid in guids[factory_id]: + parameters = dict() if guid not in self._set else \ + self._set[guid] + factory.create_function(self, guid, parameters) + element = self._elements[guid] + if element: + for name, value in parameters.iteritems(): + setattr(element, name, value) + + def do_connect(self): + for guid1, connections in self._connect.iteritems(): + element1 = self._elements[guid1] + factory_id1 = self._create[guid1] + factory1 = self._factories[factory_id1] + for connector_type_name1, connections2 in connections.iteritems(): + connector_type1 = factory1.connector_type(connector_type_name1) + for guid2, connector_type_name2 in connections2.iteritems(): + element2 = self._elements[guid2] + factory_id2 = self._create[guid2] + # Connections are executed in a "From -> To" direction only + # This explicitly ignores the "To -> From" (mirror) + # connections of every connection pair. + code_to_connect = connector_type1.code_to_connect( + self._testbed_id, factory_id2, + connector_type_name2) + if code_to_connect: + code_to_connect(element1, element2) + + def do_configure(self): + # TODO: add traces! + # add addressess + for guid, addresses in self._add_address.iteritems(): + element = self._elements[guid] + for address in addresses: + (family, address, netprefix, broadcast) = address + if family == AF_INET: + element.add_v4_address(address, netprefix) + # add routes + for guid, routes in self._add_route.iteritems(): + element = self._elements[guid] + for route in routes: + # TODO: family and interface not used!!!!! + (family, destination, netprefix, nexthop, interfaces) = routes + element.add_route(prefix = destination, prefix_len = netprefix, + nexthop = nexthop) + + def do_cross_connect(self): + for guid, cross_connections in self._cross_connect.iteritems(): + element = self._elements[guid] + factory_id = self._create[guid] + factory = self._factories[factory_id] + for connector_type_name, cross_connection in \ + cross_connections.iteritems(): + connector_type = factory.connector_type(connector_type_name) + (cross_testbed_id, cross_factory_id, + cross_connector_type_name) = cross_connection + code_to_connect = connector_type.code_to_connect( + cross_guid, cross_testbed_id, cross_factory_id, + cross_conector_type_name) + if code_to_connect: + code_to_connect(element, cross_guid) + + def set(self, time, guid, name, value): + # TODO: take on account schedule time for the task + element = self._elements[guid] + setattr(element, name, value) + + def get(self, time, guid, name): + # TODO: take on account schedule time for the task + element = self._elements[guid] + return getattr(element, name) + + def start(self, time = "0s"): + for guid, factory_id in self._create.iteritems(): + factory = self._factories[factory_id] + start_function = factory.start_function + if start_function: + traces = [] if guid not in self._add_trace else \ + self._add_trace[guid] + parameters = dict() if guid not in self._set else \ + self._set[guid] + start_function(self, guid, parameters, traces) + + def action(self, time, guid, action): + raise NotImplementedError + + def stop(self, time = "0s"): + for guid, factory_id in self._create.iteritems(): + factory = self._factories[factory_id] + stop_function = factory.stop_function + if stop_function: + traces = [] if guid not in self._add_trace else \ + self._add_trace[guid] + stop_function(self, guid, traces) + + def status(self, guid): + for guid, factory_id in self._create.iteritems(): + factory = self._factories[factory_id] + status_function = factory.status_function + if status_function: + result = status_function(self, guid) + + def trace(self, guid, trace_id): + raise NotImplementedError + + def shutdown(self): + for element in self._elements.values(): + element.destroy() + + def get_connected(self, guid, connector_type_name, + other_connector_type_name): + """searchs the connected elements for the specific connector_type_name + pair""" + if guid not in self._connect: + return [] + # all connections for all connectors for guid + all_connections = self._connect[guid] + if connector_type_name not in all_connections: + return [] + # all connections for the specific connector + connections = all_connections[connector_type_name] + specific_connections = [otr_guid for otr_guid, otr_connector_type_name \ + in connections.iteritems() if \ + otr_connector_type_name == other_connector_type_name] + return specific_connections + + def _load_netns_module(self, configuration): + # TODO: Do something with the configuration!!! + import sys + __import__("netns") + netns_mod = sys.modules["netns"] + # enable debug + enable_debug = configuration.get_attribute_value("EnableDebug") + if enable_debug: + netns_mod.environ.set_log_level(netns_mod.environ.LOG_DEBUG) + return netns_mod + + def _get_connection_count(self, guid, connection_type_name): + count = 0 + cross_count = 0 + if guid in self._connect and connection_type_name in \ + self._connect[guid]: + count = len(self._connect[guid][connection_type_name]) + if guid in self._cross_connect and connection_type_name in \ + self._cross_connect[guid]: + cross_count = len(self._cross_connect[guid][connection_type_name]) + return count + cross_count + + diff --git a/src/nepi/testbeds/netns/metadata_v01.py b/src/nepi/testbeds/netns/metadata_v01.py index bfa8caa0..e3712777 100644 --- a/src/nepi/testbeds/netns/metadata_v01.py +++ b/src/nepi/testbeds/netns/metadata_v01.py @@ -1,227 +1,476 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -from nepi.core.description import AF_INET +from constants import TESTBED_ID +from nepi.core import metadata from nepi.core.attributes import Attribute from nepi.util import validation +from nepi.util.constants import AF_INET + +NODE = "Node" +P2PIFACE = "P2PInterface" +TAPIFACE = "TapNodeInterface" +NODEIFACE = "NodeInterface" +SWITCH = "Switch" +APPLICATION = "Application" + +NS3_TESTBED_ID = "ns3" +FDNETDEV = "ns3::FileDescriptorNetDevice" + +### Connection functions #### + +def connect_switch(switch, interface): + switch.connect(interface) + +#XXX: This connection function cannot be use to transfer a file descriptor +# to a remote tap device +def connect_fd_local(tap, fdnd): + import passfd + import socket + 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 + +### Creation functions ### + +def create_node(testbed_instance, guid, parameters): + forward_X11 = False + if "forward_X11" in parameters: + forward_X11 = parameters["forward_X11"] + del parameters["forward_X11"] + element = testbed_instance.netns.Node(forward_X11 = forward_X11) + testbed_instance.elements[guid] = element + +def create_p2piface(testbed_instance, guid, parameters): + # search for the node asociated with the p2piface + node1_guid = testbed_instance.get_connected(guid, "node", "devs") + if len(node1_guid) == 0: + raise RuntimeError("Can't instantiate interface %d outside netns \ + node" % guid) + node1 = testbed_instance.elements[node1_guid[0]] + # search for the pair p2piface + p2p_guid = testbed_instance.get_connected(guid, "p2p","p2p") + if len(p2p_guid) == 0: + raise RuntimeError("Can't instantiate p2p interface %d. \ + Missing interface pair" % guid) + guid2 = p2p_guid[0] + node2_guid = testbed_instance.get_connected(guid2, "node", "devs") + if len(node2_guid) == 0: + raise RuntimeError("Can't instantiate interface %d outside netns \ + node" % guid2) + node2 = testbed_instance.elements[node2_guid[0]] + element1, element2 = testbed_instance.netns.P2PInterface.create_pair( + node1, node2) + testbed_instance.elements[guid] = element1 + testbed_instance.elements[guid2] = element2 + +def create_tapiface(testbed_instance, guid, parameters): + node_guid = testbed_instance.get_connected(guid, "node", "devs") + if len(node_guid) == 0: + raise RuntimeError("Can't instantiate interface %d outside netns \ + node" % guid) + node = testbed_instance.elements[node_guid[0]] + element = node.add_tap() + testbed_instance.elements[guid] = element + +def create_nodeiface(testbed_instance, guid, parameters): + node_guid = testbed_instance.get_connected(guid, "node", "devs") + if len(node_guid) == 0: + raise RuntimeError("Can't instantiate interface %d outside netns \ + node" % guid) + node = testbed_instance.elements[node_guid[0]] + element = node.add_if() + testbed_instance.elements[guid] = element + +def create_switch(testbed_instance, guid, parameters): + element = testbed_instance.netns.Switch() + testbed_instance.elements[guid] = element + +def create_application(testbed_instance, guid, parameters): + testbed_instance.elements[guid] = None # Delayed construction + +### Start/Stop functions ### + +def start_application(testbed_instance, guid, parameters, traces): + user = parameters["user"] + command = parameters["command"] + stdout = stderr = None + if "stdout" in traces: + filename = "%d_%s" % (guid, "stdout") + stdout = open(filename, "wb") + if "stderr" in traces: + filename = "%d_%s" % (guid, "stderr") + stderr = open(filename, "wb") + + node_guid = testbed_instance.get_connected(guid, "node", "apps") + if len(node_guid) == 0: + raise RuntimeError("Can't instantiate interface %d outside netns \ + node" % guid) + node = testbed_instance.elements[node_guid[0]] + element = node.Popen(command, shell = True, stdout = stdout, + stderr = stderr, user = user) + testbed_instance.elements[guid] = element + +### Status functions ### + +def status_application(testbed_instance, guid): + if guid not in testbed_instance.elements.keys(): + return None + app = testbed.elements[guid] + return app.poll() + +### Factory information ### + +connector_types = dict({ + "apps": dict({ + "help": "Connector from node to applications", + "name": "apps", + "max": -1, + "min": 0 + }), + "devs": dict({ + "help": "Connector from node to network interfaces", + "name": "devs", + "max": -1, + "min": 0 + }), + "node": dict({ + "help": "Connector to a Node", + "name": "node", + "max": 1, + "min": 1 + }), + "p2p": dict({ + "help": "Connector to a P2PInterface", + "name": "p2p", + "max": 1, + "min": 0 + }), + "fd": dict({ + "help": "Connector to a network interface that can receive a file descriptor", + "name": "fd", + "max": 1, + "min": 0 + }), + "switch": dict({ + "help": "Connector to a switch", + "name": "switch", + "max": 1, + "min": 0 + }) + }) + +connections = [ + dict({ + "from": (TESTBED_ID, NODE, "devs"), + "to": (TESTBED_ID, P2PIFACE, "node"), + "code": None, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, NODE, "devs"), + "to": (TESTBED_ID, TAPIFACE, "node"), + "code": None, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, NODE, "devs"), + "to": (TESTBED_ID, NODEIFACE, "node"), + "code": None, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, P2PIFACE, "p2p"), + "to": (TESTBED_ID, P2PIFACE, "p2p"), + "code": None, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, TAPIFACE, "fd"), + "to": (NS3_TESTBED_ID, FDNETDEV, "fd"), + "code": connect_fd_local, + "can_cross": True + }), + dict({ + "from": (TESTBED_ID, SWITCH, "devs"), + "to": (TESTBED_ID, NODEIFACE, "switch"), + "code": connect_switch, + "can_cross": False + }), + dict({ + "from": (TESTBED_ID, NODE, "apps"), + "to": (TESTBED_ID, APPLICATION, "node"), + "code": None, + "can_cross": False + }) +] + +attributes = dict({ + "forward_X11": dict({ + "name": "forward_X11", + "help": "Forward x11 from main namespace to the node", + "type": Attribute.BOOL, + "value": False, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_bool + }), + "lladdr": dict({ + "name": "lladdr", + "help": "Mac address", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_mac_address + }), + "up": dict({ + "name": "up", + "help": "Link up", + "type": Attribute.BOOL, + "value": False, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_bool + }), + "device_name": dict({ + "name": "name", + "help": "Device name", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_string + }), + "mtu": dict({ + "name": "mtu", + "help": "Maximum transmition unit for device", + "type": Attribute.INTEGER, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_integer + }), + "broadcast": dict({ + "name": "broadcast", + "help": "Broadcast address", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_string # TODO: should be is address! + }), + "multicast": dict({ + "name": "multicast", + "help": "Multicast enabled", + "type": Attribute.BOOL, + "value": False, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_bool + }), + "arp": dict({ + "name": "arp", + "help": "ARP enabled", + "type": Attribute.BOOL, + "value": False, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_bool + }), + "command": dict({ + "name": "command", + "help": "Command line string", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_string + }), + "user": dict({ + "name": "user", + "help": "System user", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_string + }), + "stdin": dict({ + "name": "stdin", + "help": "Standard input", + "type": Attribute.STRING, + "value": None, + "range": None, + "allowed": None, + "readonly": False, + "visible": True, + "validation_function": validation.is_string + }), + "max_addresses": dict({ + "name": "MaxAddresses", + "help": "Maximum number of addresses allowed by the device", + "type": Attribute.INTEGER, + "value": None, + "range": None, + "allowed": None, + "readonly": True, + "visible": False, + "validation_function": validation.is_integer + }), + "family": dict({ + "name": "Family", + "help": "IP address family", + "type": Attribute.INTEGER, + "value": AF_INET, + "range": None, + "allowed": None, + "readonly": True, + "visible": False, + "validation_function": validation.is_integer + }), + + }) + +traces = dict({ + "stdout": dict({ + "name": "StdoutTrace", + "help": "Standard output stream" + }), + "stderr": dict({ + "name": "StderrTrace", + "help": "Application standard error", + }) + }) + +factories_order = [ NODE, P2PIFACE, NODEIFACE, TAPIFACE, SWITCH, + APPLICATION ] + +factories_info = dict({ + NODE: dict({ + "allow_routes": True, + "help": "Emulated Node with virtualized network stack", + "category": "topology", + "create_function": create_node, + "start_function": None, + "stop_function": None, + "status_function": None, + "box_attributes": ["forward_X11"], + "connector_types": ["devs", "apps"] + }), + P2PIFACE: dict({ + "allow_addresses": True, + "help": "Point to point network interface", + "category": "devices", + "create_function": create_p2piface, + "start_function": None, + "stop_function": None, + "status_function": None, + "factory_attributes": ["family", "max_addresses"], + "box_attributes": ["lladdr", "up", "device_name", "mtu", + "multicast", "broadcast", "arp"], + "connector_types": ["node", "p2p"] + }), + TAPIFACE: dict({ + "allow_addresses": True, + "help": "Tap device network interface", + "category": "devices", + "create_function": create_tapiface, + "start_function": None, + "stop_function": None, + "status_function": None, + "factory_attributes": ["family", "max_addresses"], + "box_attributes": ["lladdr", "up", "device_name", "mtu", + "multicast", "broadcast", "arp"], + "connector_types": ["node", "fd"] + }), + NODEIFACE: dict({ + "allow_addresses": True, + "help": "Node network interface", + "category": "devices", + "create_function": create_nodeiface, + "start_function": None, + "stop_function": None, + "status_function": None, + "factory_attributes": ["family", "max_addresses"], + "box_attributes": ["lladdr", "up", "device_name", "mtu", + "multicast", "broadcast", "arp"], + "connector_types": ["node", "switch"] + }), + SWITCH: dict({ + "display_name": "Switch", + "help": "Switch interface", + "category": "devices", + "create_function": create_switch, + "start_function": None, + "stop_function": None, + "status_function": None, + "box_attributes": ["up", "device_name", "mtu", "multicast"], + #TODO: Add attribute ("Stp", help, type, value, range, allowed, readonly, validation_function), + #TODO: Add attribute ("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function), + #TODO: Add attribute ("HelloTime", help, type, value, range, allowed, readonly, validation_function), + #TODO: Add attribute ("AgeingTime", help, type, value, range, allowed, readonly, validation_function), + #TODO: Add attribute ("MaxAge", help, type, value, range, allowed, readonly, validation_function) + "connector_types": ["devs"] + }), + APPLICATION: dict({ + "help": "Generic executable command line application", + "category": "applications", + "create_function": create_application, + "start_function": start_application, + "stop_function": None, + "status_function": status_application, + "box_attributes": ["command", "user"], + "connector_types": ["node"], + "traces": ["stdout", "stderr"] + }), +}) + +class VersionedMetadataInfo(metadata.VersionedMetadataInfo): + @property + def connections_types(self): + return connection_types + + @property + def connections(self): + return connections + + @property + def attributes(self): + return attributes + + @property + def traces(self): + return traces + + @property + def factories_order(self): + return factories_order + + @property + def factories_info(self): + return factories_info -def get_metadata(): - return dict({ - "Node": dict({ - "factory_type": "routing", - "display_name": "Node", - "help": "Node", - "category": "topology", - "connector_types": [ - ("netns_node_apps", - "Connector from node to applications", - "apps", -1, 0, - ["netns_application_node"]), - ("netns_node_devs", - "Connector from node to network interfaces", - "devs", -1, 0, - [ - "netns_nodeiface_node", - "netns_tapiface_node", - "netns_p2piface_node" - ]) - ], - "box_attributes": [ - ("forward_X11", - "Forward x11 from main namespace to the node", - Attribute.BOOL, - False, None, None, False, - validation.is_bool) - ] - }), - "P2PInterface": dict({ - "factory_type": "addressable", - "family": AF_INET, - "max_addresses": 1, - "display_name": "P2PInterface", - "help": "Point to point network interface", - "category": "devices", - "connector_types": [ - ("netns_p2piface_node", - "Connector from P2PInterface to Node", - "node", 1, 1, - ["netns_node_devs"]), - ("netns_p2piface_p2p", - "Connector to another P2PInterface", - "p2p", 1, 0, - ["netns_p2piface_p2p"]) - ], - "box_attributes": [ - ("lladdr", "Mac address", Attribute.STRING, - None, None, None, False, - validation.is_mac_address), - ("up", "Link up", Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("name", "Device name", Attribute.STRING, - None, None, None, False, - validation.is_string), - ("mtu", "Maximmum transmition unit for device", - Attribute.INTEGER, - None, None, None, False, - validation.is_integer), - ("broadcast", "Broadcast address", - Attribute.STRING, - None, None, None, False, - validation.is_string), - ("multicast", "Is multicast enabled", - Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("arp", "Is ARP enabled", Attribute.BOOL, - True, None, None, False, - validation.is_bool), - ] - }), - "TapNodeInterface": dict({ - "factory_type": "addressable", - "family": AF_INET, - "max_addresses": 1, - "display_name": "TapNodeInterface", - "help": "Tap device network interface", - "category": "devices", - "connector_types": [ - ("netns_tapiface_node", - "Connector to a Node", - "node", 1, 1, - ["netns_node_devs"]), - ("netns_tapiface_fd", - "Connector to a network interface that can receive a file descriptor", - "fd", 1, 0, - # TODO: Doesn't exist yet! - ["ns3_fdnetdev_fd"]) - ], - "box_attributes": [ - ("lladdr", "Mac address", Attribute.STRING, - None, None, None, False, - validation.is_mac_address), - ("up", "Link up", Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("name", "Device name", Attribute.STRING, - None, None, None, False, - validation.is_string), - ("mtu", "Maximmum transmition unit for device", - Attribute.INTEGER, - None, None, None, False, - validation.is_integer), - ("broadcast", "Broadcast address", - Attribute.STRING, - None, None, None, False, - validation.is_string), - ("multicast", "Is multicast enabled", - Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("arp", "Is ARP enabled", Attribute.BOOL, - True, None, None, False, - validation.is_bool), - ] - }), - "NodeInterface": dict({ - "factory_type": "addressable", - "family": AF_INET, - "max_addresses": 1, - "display_name": "NodeInterface", - "help": "Node network interface", - "category": "devices", - "connector_types": [ - ("netns_nodeiface_node", "Connector to a node", - "node", 1, 1, - ["netns_node_devs"]), - ("netns_nodeiface_switch", "Connector to a switch", - "switch", 1, 0, - ["netns_switch_devs"]) - ], - "box_attributes": [ - ("lladdr", "Mac address", Attribute.STRING, - None, None, None, False, - validation.is_mac_address), - ("up", "Link up", Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("name", "Device name", Attribute.STRING, - None, None, None, False, - validation.is_string), - ("mtu", "Maximmum transmition unit for device", - Attribute.INTEGER, - None, None, None, False, - validation.is_integer), - ("broadcast", "Broadcast address", - Attribute.STRING, - None, None, None, False, - validation.is_string), - ("multicast", "Is multicast enabled", - Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("arp", "Is ARP enabled", Attribute.BOOL, - True, None, None, False, - validation.is_bool), - ] - }), - "Switch": dict({ - "display_name": "Switch", - "help": "Switch interface", - "category": "devices", - "connector_types": [ - ("netns_switch_devs", "Connector to network interfaces", - "devs", -1, 0, - ["netns_nodeiface_switch"]) - ], - "box_attributes": [ - ("up", "Link up", Attribute.BOOL, - False, None, None, False, - validation.is_bool), - ("name", "Device name", Attribute.STRING, - None, None, None, False, - validation.is_string), - ("mtu", "Maximmum transmition unit for device", - Attribute.INTEGER, - None, None, None, False, - validation.is_integer), - ("multicast", "Is multicast enabled", - Attribute.BOOL, - None, None, None, False, - validation.is_bool), - #TODO:("Stp", help, type, value, range, allowed, readonly, validation_function), - #TODO:("ForwarddDelay", help, type, value, range, allowed, readonly, validation_function), - #TODO:("HelloTime", help, type, value, range, allowed, readonly, validation_function), - #TODO:("AgeingTime", help, type, value, range, allowed, readonly, validation_function), - #TODO:("MaxAge", help, type, value, range, allowed, readonly, validation_function) - ] - }), - "Application": dict({ - "display_name": "Application", - "help": "Generic executable command line application", - "category": "applications", - "connector_types": [ - ("netns_application_node", "Connector to a node", - "node", 1, 1, - ["netns_node_apps"]) - ], - "traces": [ - ("StdoutTrace", "Standard output"), - ("StderrTrace", "Standard error") - ], - "box_attributes": [ - ("command", "Command line", - Attribute.STRING, - None, None, None, False, - validation.is_string), - ("user", "System user", - Attribute.STRING, - None, None, None, False, - validation.is_string), - ("stdin", "Standard input", - Attribute.STRING, - None, None, None, False, - validation.is_string) - ] - }), - }) diff --git a/src/nepi/testbeds/netns/testbed.py b/src/nepi/testbeds/netns/testbed.py deleted file mode 100644 index 4f0d66f5..00000000 --- a/src/nepi/testbeds/netns/testbed.py +++ /dev/null @@ -1,358 +0,0 @@ -#!/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/constants.py b/src/nepi/util/constants.py new file mode 100644 index 00000000..da609f24 --- /dev/null +++ b/src/nepi/util/constants.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +AF_INET = 0 +AF_INET6 = 1 + diff --git a/src/nepi/util/parser/base.py b/src/nepi/util/parser/base.py index 15c18ad1..1cb17ed1 100644 --- a/src/nepi/util/parser/base.py +++ b/src/nepi/util/parser/base.py @@ -268,12 +268,9 @@ class ExperimentParser(object): self.connections_from_data(experiment_description, box_guids, data) def testbed_from_data(self, experiment_description, guid, data): + from nepi.core.design import FactoriesProvider (testbed_id, testbed_version) = data.get_testbed_data(guid) - mod_name = 'nepi.testbeds.%s' % testbed_id - if not mod_name in sys.modules: - __import__(mod_name) - testbed_mod = sys.modules[mod_name] - provider = testbed_mod.TestbedFactoriesProvider(testbed_version) + provider = FactoriesProvider(testbed_id, testbed_version) experiment_description.add_testbed_description(provider) testbed_description = experiment_description.testbed_description(guid) self.attributes_from_data(testbed_description, data) @@ -350,4 +347,3 @@ class ExperimentParser(object): if not connector.is_connected(other_connector): connector.connect(other_connector) -