From 435ed8847082d65bc80d640b495a3fd9f96f1c8a Mon Sep 17 00:00:00 2001 From: Alina Quereilhac Date: Tue, 8 Feb 2011 18:28:21 +0100 Subject: [PATCH] description classes are generic classes with no testbed specific code. --- examples/design1.py | 56 +++++ src/nepi/__init__.py | 0 src/nepi/core/__init__.py | 0 src/nepi/core/attributes.py | 32 ++- src/nepi/core/description.py | 293 +++++++++++++++++++++++++ src/nepi/core/experiment.py | 49 ++++- src/nepi/core/protocol.py | 1 - src/nepi/core/testbed.py | 325 ++-------------------------- src/nepi/testbeds/__init__.py | 0 src/nepi/testbeds/netns/__init__.py | 0 src/nepi/utils/__init__.py | 0 src/nepi/utils/guid_generator.py | 11 + 12 files changed, 441 insertions(+), 326 deletions(-) create mode 100644 examples/design1.py create mode 100644 src/nepi/__init__.py create mode 100644 src/nepi/core/__init__.py create mode 100644 src/nepi/core/description.py create mode 100644 src/nepi/testbeds/__init__.py create mode 100644 src/nepi/testbeds/netns/__init__.py create mode 100644 src/nepi/utils/__init__.py create mode 100644 src/nepi/utils/guid_generator.py diff --git a/examples/design1.py b/examples/design1.py new file mode 100644 index 00000000..7a9cca39 --- /dev/null +++ b/examples/design1.py @@ -0,0 +1,56 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from nepi.core.experiment import ExperimentDescription + +testbed_id = "netns" +testbed_version = "0.1" +experiment = ExperimentDescription() +netns = experiment.add_testbed_description(testbed_id, testbed_version) +node1 = netns.create("Node") +node2 = netns.create("Node") +iface1 = netns.create("NodeInterface") +iface1.set_attribute_value("up", True) +node1.connector("devs").connect(iface1.connector("node")) +ip1 = iface1.add_address() +p1.set_attribute_value("Address", "10.0.0.1") +iface2 = netns.create("NodeInterface") +iface2.set_attribute_value("up", True) +node2.connector("devs").connect(iface2.connector("node")) +ip2 = iface2.add_address() +ip2.set_attribute_value("Address", "10.0.0.2") +switch = netns.create("Switch") +switch.set_attribute_value("up", True) +iface1.connector("switch").connect(switch.connector("devs")) +iface2.connector("switch").connect(switch.connector("devs")) +app = netns.create("Application") +app.set_attribute_value("command", "ping -qc10 10.0.0.2") +app.connector("node").connect(node1.connector("apps")) + +print experiment.xml_description + +description = """ + + + + + + + + + + + + + + + + + + + + + + +""" + diff --git a/src/nepi/__init__.py b/src/nepi/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/core/__init__.py b/src/nepi/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/core/attributes.py b/src/nepi/core/attributes.py index 4832cabd..2db9f0c9 100644 --- a/src/nepi/core/attributes.py +++ b/src/nepi/core/attributes.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# vim:ts=4:sw=4:et:ai:sts=4 class AttributesMap(object): """AttributesMap is the base class for every object whose attributes @@ -13,14 +12,8 @@ class AttributesMap(object): def attributes_name(self): return set(self._attributes.keys()) - def is_valid_attribute_value(self, name, value): - raise NotImplementedError - def set_attribute_value(self, name, value): - if self.is_valid_attribute_value(name, value): - self._attributes[name].value = value - return True - return False + self._attributes[name].value = value def set_attribute_readonly(self, name, value): self._attributes[name].readonly = value @@ -44,10 +37,11 @@ class AttributesMap(object): return self._attributes[name].readonly def add_attribute(self, name, help, type, value = None, range = None, - allowed = None, readonly = False): + allowed = None, readonly = False, validation_function = None): if name in self._attributes: raise AttributeError('Attribute %s already exists' % name)) - attribute = Attribute(name, help, type, value, range, allowed, readonly) + attribute = Attribute(name, help, type, value, range, allowed, readonly, + validation_function) self._attributes[name] = attribute def del_attribute(self, name): @@ -66,13 +60,14 @@ class Attribute(object): types = [STRING, BOOL, ENUM, DOUBLE, INTEGER, ENDPOINT, TIME] def __init__(self, name, help, type, value = None, range = None, - allowed = None, readonly = False): + allowed = None, readonly = False, validation_function = None): if not type in Attribute.types: raise AttributeError("invalid type %s " % type) self.name = name - self.value = value self.type = type self.help = help + self._value = value + self._validation_function = validation_function self.readonly = (readonly == True) self.modified = False # range: max and min possible values @@ -80,3 +75,16 @@ class Attribute(object): # list of possible values self.allowed = allowed + def get_value(self): + return self._value + + def set_value(self, value): + func = self._validation_function + if not func or func(value): + self._value = value + else: + raise RuntimeError("Invalid value %s for attribute %s" % + (str(value), self.name)) + + value = property(get_value, set_value) + diff --git a/src/nepi/core/description.py b/src/nepi/core/description.py new file mode 100644 index 00000000..d3837d08 --- /dev/null +++ b/src/nepi/core/description.py @@ -0,0 +1,293 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from nepi.core.attributes import AttributesMap + +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): + 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') + 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 + self._name = name + self._max = max + self._min = min + self._allowed_connections = list() + + @property + def connector_type_id(self): + return self._connector_type_id + + @property + def help(self): + return self._help + + @property + def name(self): + return self._name + + @property + def max(self): + return self._max + + @property + def min(self): + return self._min + + def add_allowed_connection(self, connector_type_id): + self._allowed_connections.append(connector_type_id) + + def can_connect(self, connector_type_id): + return connector_type_id in self._allowed_connections + +class Connector(object): + """A Connector sepcifies the connection points in an Object""" + def __init__(self, element, connector_type): + super(Connector, self).__init__() + self._element = element + self._connector_type = connector_type + self._connections = dict() + + @property + def element(self): + return self._element + + @property + def connector_type(self): + return self._connector_type + + @property + def connections(self): + return self._connections.values() + + def is_full(self): + """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 len(self.connections) >= self.connector_type.min + + 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 + + def disconnect(self, connector): + if connector._key not in self._connections or + self._key not in connector._connections: + raise RuntimeError("Could not disconnect.") + del self._connections[connector._key] + del connector._connections[self._key] + + def can_connect(self, connector): + connector_type_id = connector.connector_type.connector_type_id + self.connector_type.can_connect(connector_type_id) + + def destroy(self): + for connector in self._connections: + self.disconnect(connector) + self._element = self._connectors = None + + @property + def _key(self): + return "%d_%s" % (self.element.guid, + self.connector_type.connector_type_id) + +class Trace(AttributesMap): + def __init__(self, name, help, enabled=False): + super(Trace, self).__init__() + self._name = name + self._help = help + self._enabled = enabled + + @property + def name(self): + return self._name + + @property + def help(self): + return self._help + + @property + def is_enabled(self): + return self._enabled + + def enable(self): + self._enabled = True + + def disable(self): + self._enabled = False + +class Element(AttributesMap): + def __init__(self, guid, testbed_id, factory, container = None): + super(Element, self).__init__() + # general unique id + self._guid = guid + # factory identifier or name + self._factory_id = factory.factory_id + # elements can be nested inside other 'container' elements + self._container = container + # traces for the element + self._traces = dict() + # connectors for the element + self._connectors = dict() + for connector_type in factory.connector_types: + connector = Connector(self, connector_type) + self._connectors[connector_type.connector_id] = connector + + @property + def guid(self): + return self._guid + + @property + def factory_id(self): + return self._factory_id + + @property + def container(self): + return self._container + + @property + def connectors(self): + return self._connectors.values() + + @property + def traces(self): + return self._traces.values() + + def connector(self, name): + return self._connectors[name] + + def trace(self, name): + return self._traces[name] + + def destroy(self): + super(Element, self).destroy() + for c in self.connectors: + c.destroy() + for t in self.traces: + t.destroy() + self._connectors = self._traces = None + +class Factory(AttributesMap): + def __init__(self, factory_id, help = None, category = None): + super(Factory, self).__init__() + self._factory_id = factory_id + self._help = help + self._category = category + self._connector_types = set() + + @property + def factory_id(self): + return self._factory_id + + @property + def help(self): + return self._help + + @property + def category(self): + return self._category + + @property + def connector_types(self): + return self._connector_types + + def add_connector_type(self, connector_id, help, name, max = -1, min = 0): + connector_type = ConnectorType(connector_id, help, name, max, min) + self._connector_types.add(connector_type) + + def create(self, guid, testbed_design, container = None): + raise NotImplementedError + + def destroy(self): + super(Factory, self).destroy() + self._connector_types = None + +#TODO: Provide some way to identify that the providers and the factories +# belong to a specific testbed version +class Provider(object): + def __init__(self): + super(Provider, self).__init__() + self._factories = dict() + + def factory(self, factory_id): + return self._factories[factory_id] + + def add_factory(self, factory): + self._factories[factory.factory_id] = factory + + def remove_factory(self, factory_id): + del self._factories[factory_id] + + def list_factories(self): + return self._factories.keys() + +class TestbedDescription(AttributeMap): + def __init__(self, guid_generator, testbed_id, testbed_version, provider): + super(TestbedDescription, self).__init__() + self._guid_generator = guid_generator + self._guid = guid_generator.next() + self._testbed_id = testbed_id + self._testbed_version = testbed_version + self._provider = provider + self._elements = dict() + + @property + def guid(self): + return self._guid + + @property + def testbed_id(self): + return self._testbed_id + + @property + def testbed_version(self): + return self._testbed_version + + @property + def provider(self): + return provider + + @property + def elements(self): + return self._elements.values() + + def create(self, factory_id): + guid = self.guid_generator.next() + factory = self._provider.factories(factory_id) + element = factory.create(guid, self) + self._elements[guid] = element + + def delete(self, guid): + element = self._elements[guid] + del self._elements[guid] + element.destroy() + + def destroy(self): + for guid, element in self._elements.iteitems(): + del self._elements[guid] + element.destroy() + self._elements = None + diff --git a/src/nepi/core/experiment.py b/src/nepi/core/experiment.py index e50c6fb9..19825e49 100644 --- a/src/nepi/core/experiment.py +++ b/src/nepi/core/experiment.py @@ -1,17 +1,44 @@ +#!/usr/bin/env python # -*- coding: utf-8 -*- -# vim:ts=4:sw=4:et:ai:sts=4 -class Experiment(object): - def __init__(self): - self._backends = dict() +from nepi.utils.guid import GuidGenerator +import sys - def add_backend(self, backend): - self._backends[backend.guid] = backend +class ExperimentDescription(object): + def __init__(self, guid = 0): + self._guid_generator = GuidGenerator(guid) + # testbed design instances + self._testbed_descriptions = dict() + self._testbed_providers = dict() - def remove_backend(self, backend): - del self._backends[backend.guid] + @property + def xml_description(self): + raise NotImplementedError - def instructions(self): - #TODO - pass + def add_testbed_description(self, testbed_id, testbed_version): + testbed_module = self._testbed_module(testbed_id) + testbed_provider = self._testbed_provider(testbed_id, testbed_version) + testbed_description = testbed_module.create_description_instance( + self._guid_generator, tesbed_id, testbed_version, + testbed_provider) + guid = testbed_description.guid + self._testbed_descriptions[guid] = testbed_description + + def remove_testbed_description(self, testbed_description): + guid = testbed_description.guid + del self._testbed_descriptions[guid] + + def _testbed_module(self, testbed_id): + mod_name = 'nepi.testbeds.%s' % testbed_id + if not mod_name in sys.modules: + __import__(mod_name) + return sys.modules[mod_name] + + def _testbed_provider(self, testbed_id, testbed_version): + key = "%s_%s" % (testbed_id, testbed_version) + if key not in self._testbed_providers: + testbed_module = self._testbed_module(testbed_id) + testbed_provider = testbed_module.create_provider(testbed_version) + self._testbed_providers[key] = testbed_provider + return self._testbed_providers[key] diff --git a/src/nepi/core/protocol.py b/src/nepi/core/protocol.py index c650a324..51559202 100644 --- a/src/nepi/core/protocol.py +++ b/src/nepi/core/protocol.py @@ -1,6 +1,5 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# vim:ts=4:sw=4:et:ai:sts=4 # PROTOCOL MESSAGES CREATE = 0 diff --git a/src/nepi/core/testbed.py b/src/nepi/core/testbed.py index 655f7e4c..aaf7434e 100644 --- a/src/nepi/core/testbed.py +++ b/src/nepi/core/testbed.py @@ -1,290 +1,7 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# vim:ts=4:sw=4:et:ai:sts=4 from nepi.core.attributes import AttributesMap -class ConnectorType(object): - """A ConnectorType defines a kind of connector that can be used in an Object. - """ - def __init__(self, connector_id, help, name, max = -1, min = 0): - super(ConnectorType, self).__init__() - """ - ConnectorType(name, help, display_name, max, min): - - connector_id: (unique) identifier for this type - - 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') - if min < 0: - raise RuntimeError( - 'The minimum number of connections allowed needs to be at least 0') - self._connector_id = connector_id - self._help = help - self._name = name - self._max = max - self._min = min - - @property - def connector_id(self): - return self._connector_id - - @property - def help(self): - return self._help - - @property - def name(self): - return self._name - - @property - def max(self): - return self._max - - @property - def min(self): - return self._min - -class Connector(object): - """A Connector sepcifies the connection points in an Object""" - def __init__(self, element, connector_type): - super(Connector, self).__init__() - self._element = element - self._connector_type = connector_type - self._connections = dict() - - @property - def element(self): - return self._element - - @property - def connector_type(self): - return self._connector_type - - @property - def connections(self): - return self._connections.values() - - def is_full(self): - """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 len(self.connection) >= self.connector_type.min - - 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 - - def disconnect(self, connector): - if connector._key not in self._connections or - self._key not in connector._connections: - raise RuntimeError("Could not disconnect.") - del self._connections[connector._key] - del connector._connections[self._key] - - def can_connect(self, connector): - raise NotImplementedError - - def destroy(self): - for connector in self._connections: - self.disconnect(connector) - self._element = self._connectors = None - - @property - def _key(self): - return "%d_%s" % (self.element.guid, self.connector_type.connector_id) - -class Trace(AttributesMap): - def __init__(self, name, help, enabled=False): - super(Trace, self).__init__() - self._name = name - self._help = help - self._enabled = enabled - - @property - def name(self): - return self._name - - @property - def help(self): - return self._help - - @property - def is_enabled(self): - return self._enabled - - def read_trace(self): - raise NotImplementedError - - def enable(self): - self._enabled = True - - def disable(self): - self._enabled = False - -class Element(AttributesMap): - def __init__(self, guid, testbed_id, factory, container = None): - super(Element, self).__init__() - # general unique id - self._guid = guid - # factory identifier or name - self._factory_id = factory.factory_id - # testbed identifier - self._tesbed_id = testbed_id - # elements can be nested inside other 'container' elements - self._container = container - # traces for the element - self._traces = dict() - # connectors for the element - self._connectors = dict() - for connector_type in factory.connector_types: - connector = Connector(self, connector_type) - self._connectors[connector_type.connector_id] = connector - - @property - def guid(self): - return self._guid - - @property - def factory_id(self): - return self._factory_id - - @property - def testbed_id(self): - return self._testbed_id - - @property - def container(self): - return self._container - - @property - def connectors(self): - return self._connectors.values() - - @property - def traces(self): - return self._traces.values() - - def connector(self, name): - return self._connectors[name] - - def trace(self, name): - return self._traces[name] - - def destroy(self): - super(Element, self).destroy() - for c in self.connectors: - c.destroy() - for t in self.traces: - t.destroy() - self._connectors = self._traces = None - -class Factory(AttributesMap): - def __init__(self, factory_id, help = None, category = None): - super(Factory, self).__init__() - self._factory_id = factory_id - self._help = help - self._category = category - self._connector_types = set() - - @property - def factory_id(self): - return self._factory_id - - @property - def help(self): - return self._help - - @property - def category(self): - return self._category - - @property - def connector_types(self): - return self._connector_types - - def add_connector_type(self, connector_id, help, name, max = -1, min = 0): - connector_type = ConnectorType(connector_id, help, name, max, min) - self._connector_types.add(connector_type) - - def create(self, guid, testbed_design, container = None): - raise NotImplementedError - - def destroy(self): - super(Factory, self).destroy() - self._connector_types = None - -#TODO: Provide some way to identify that the providers and the factories -# belong to a specific testbed version -class Provider(object): - def __init__(self): - super(Provider, self).__init__() - self._factories = dict() - - def factory(self, factory_id): - return self._factories[factory_id] - - def add_factory(self, factory): - self._factories[factory.factory_id] = factory - - def remove_factory(self, factory_id): - del self._factories[factory_id] - - def list_factories(self): - return self._factories.keys() - -class TestbedDesignInstance(AttributeMap): - def __init__(self, guid_generator, testbed_id, provider): - super(TestbedInstance, self).__init__() - self._guid_generator = guid_generator - self._guid = guid_generator.next() - self._testbed_id = testbed_id - self._provider = provider - self._elements = dict() - - @property - def guid(self): - return self._guid - - @property - def testbed_id(self): - return self._testbed_id - - @property - def elements(self): - return self._elements.values() - - def create(self, factory_id): - guid = self.guid_generator.next() - factory = self._provider.factories(factory_id) - element = factory.create(guid, self) - self._elements[guid] = element - - def delete(self, guid): - element = self._elements[guid] - del self._elements[guid] - element.destroy() - - def instructions(self): - raise NotImplementedError - - def destroy(self): - for guid, element in self._elements.iteitems(): - del self._elements[guid] - element.destroy() - self._elements = None - class TestbedConfiguration(AttributesMap): pass @@ -292,50 +9,54 @@ class TestbedInstance(object): def __init__(self, configuration): pass - def execute(self, instruction): - #TODO: - pass + def create(self, guid, factory_id, parameters): + raise NotImplementedError - def execute_batch(self, batch): + def do_create(self): raise NotImplementedError - def create(self, time, guid, factory_id, parameters): + def connect(self, object1_guid, object2_guid, connect_code): raise NotImplementedError - def connect(self, time, connection_guid, - object1_guid, object2_guid, connetor1_id, connector2_id): + def do_connect(self): raise NotImplementedError - def set(self, time, guid, name, value): + def enable_trace(self, guid, trace_id): raise NotImplementedError - def get(self, time, guid, name): + def add_adddress(self, guid): + #TODO + raise NotImplementedError + + def add_route(self, guid): + #TODO raise NotImplementedError - def start(self, time, guid): + def do_configure(self): raise NotImplementedError - def stop(self, time, guid): + def cross_connect(self, guid, connect_code, paremeters): raise NotImplementedError - def status(self, time, guid): + def do_cross_connect(self): raise NotImplementedError - def enable_trace(self, time, guid, trace_id): + def set(self, time, guid, name, value): raise NotImplementedError - def disable_trace(self, time, guid, trace_id): + def get(self, time, guid, name): raise NotImplementedError - def get_trace(self, time, guid, trace_id): + def start(self, time): raise NotImplementedError - def add_adddress(self, time, guid): - #TODO + def stop(self, time): raise NotImplementedError - def add_route(self, time, guid): - #TODO + def status(self, guid): + raise NotImplementedError + + def get_trace(self, time, guid, trace_id): raise NotImplementedError def shutdown(self): diff --git a/src/nepi/testbeds/__init__.py b/src/nepi/testbeds/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/testbeds/netns/__init__.py b/src/nepi/testbeds/netns/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/utils/__init__.py b/src/nepi/utils/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/nepi/utils/guid_generator.py b/src/nepi/utils/guid_generator.py new file mode 100644 index 00000000..7e285527 --- /dev/null +++ b/src/nepi/utils/guid_generator.py @@ -0,0 +1,11 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +class GuidGenerator(object): + def __init__(self, guid = 0): + self._last_guid = guid + + def next(self): + self._last_guid += 1 + return self._last_guid + -- 2.43.0