Added routes to OMF nodes
[nepi.git] / src / nepi / core / design.py
index f479da9..7ad5509 100644 (file)
@@ -1,4 +1,3 @@
-#!/usr/bin/env python
 # -*- coding: utf-8 -*-
 
 """
@@ -8,71 +7,12 @@ 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
+from nepi.util.tags import Taggable
 import sys
 
-class ConnectorType(object):
-    def __init__(self, testbed_id, factory_id, name, help, 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
-        # 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
-        # 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):
-        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, 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, 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"""
     def __init__(self, box, connector_type):
@@ -81,6 +21,9 @@ class Connector(object):
         self._connector_type = connector_type
         self._connections = list()
 
+    def __str__(self):
+        return "Connector(%s, %s)" % (self.box, self.connector_type)
+
     @property
     def box(self):
         return self._box
@@ -108,10 +51,15 @@ class Connector(object):
 
     def connect(self, connector):
         if not self.can_connect(connector) or not connector.can_connect(self):
-            raise RuntimeError("Could not connect.")
+            raise RuntimeError("Could not connect. %s to %s" % (self, connector))
         self._connections.append(connector)
         connector._connections.append(self)
 
+    def get_connected_box(self, idx = 0):
+        if len(self._connections) == 0:
+            return None
+        return self._connections[idx].box
+
     def disconnect(self, connector):
         if connector not in self._connections or\
                 self not in connector._connections:
@@ -120,15 +68,19 @@ class Connector(object):
         connector._connections.remove(self)
 
     def can_connect(self, connector):
+        # can't connect with self
+        if self.box.guid == connector.box.guid:
+            return False
         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
+        (testbed_id, factory_id, name) = connector.connector_type.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)
+        must_cross = (testbed_guid1 != testbed_guid2)
+        return self.connector_type.can_connect(testbed_id, factory_id, name,
+                must_cross)
 
     def destroy(self):
         for connector in self.connections:
@@ -136,78 +88,83 @@ class Connector(object):
         self._box = self._connectors = None
 
 class Trace(AttributesMap):
-    def __init__(self, trace_id, help, enabled = False):
+    def __init__(self, name, help, enabled = False):
         super(Trace, self).__init__()
-        self._trace_id = trace_id
+        self._name = name
         self._help = help       
-        self.enabled = enabled
+        self._enabled = enabled
     
     @property
-    def trace_id(self):
-        return self._trace_id
+    def name(self):
+        return self._name
 
     @property
     def help(self):
         return self._help
 
+    @property
+    def enabled(self):
+        return self._enabled
+
+    def enable(self):
+        self._enabled = True
+
+    def disable(self):
+        self._enabled = False
+
 class Address(AttributesMap):
-    def __init__(self, family):
+    def __init__(self):
         super(Address, self).__init__()
-        self.add_attribute(name = "AutoConfigure", 
-                help = "If set, this address will automatically be assigned", 
-                type = Attribute.BOOL,
-                value = False,
-                validation_function = validation.is_bool)
-        self.add_attribute(name = "Family",
-                help = "Address family type: AF_INET, AFT_INET6", 
-                type = Attribute.INTEGER, 
-                value = family,
-                readonly = True)
-        address_validation = validation.is_ip4_address if family == AF_INET \
-                        else validation.is_ip6_address
         self.add_attribute(name = "Address",
                 help = "Address number", 
                 type = Attribute.STRING,
-                validation_function = address_validation)
-        prefix_range = (0, 32) if family == AF_INET else (0, 128)
+                flags = Attribute.NoDefaultValue,
+                validation_function = validation.is_ip_address)
         self.add_attribute(name = "NetPrefix",
                 help = "Network prefix for the address", 
                 type = Attribute.INTEGER, 
-                range = prefix_range,
-                value = 24 if family == AF_INET else 64,
+                range = (0, 128),
+                value = 24,
+                flags = Attribute.NoDefaultValue,
                 validation_function = validation.is_integer)
-        if family == AF_INET:
-            self.add_attribute(name = "Broadcast",
-                    help = "Broadcast address", 
-                    type = Attribute.STRING,
-                    validation_function = validation.is_ip4_address)
+        self.add_attribute(name = "Broadcast",
+                help = "Broadcast address", 
+                type = Attribute.STRING,
+                validation_function = validation.is_ip4_address)
                 
 class Route(AttributesMap):
-    def __init__(self, family):
+    def __init__(self):
         super(Route, self).__init__()
-        self.add_attribute(name = "Family",
-                help = "Address family type: AF_INET, AFT_INET6", 
-                type = Attribute.INTEGER, 
-                value = family,
-                readonly = True)
-        address_validation = validation.is_ip4_address if family == AF_INET \
-                        else validation.is_ip6_address
         self.add_attribute(name = "Destination", 
                 help = "Network destintation",
                 type = Attribute.STRING, 
-                validation_function = address_validation)
-        prefix_range = (0, 32) if family == AF_INET else (0, 128)
+                validation_function = validation.is_ref_address)
         self.add_attribute(name = "NetPrefix",
                 help = "Network destination prefix", 
                 type = Attribute.INTEGER, 
-                prefix_range = prefix_range,
+                range = (0, 128),
+                value = 24,
+                flags = Attribute.NoDefaultValue,
                 validation_function = validation.is_integer)
         self.add_attribute(name = "NextHop",
                 help = "Address for the next hop", 
                 type = Attribute.STRING,
-                validation_function = address_validation)
+                flags = Attribute.NoDefaultValue,
+                validation_function = validation.is_ref_address)
+        self.add_attribute(name = "Metric",
+                help = "Routing metric", 
+                type = Attribute.INTEGER,
+                value = 0,
+                flags = Attribute.NoDefaultValue,
+                validation_function = validation.is_integer)
+        self.add_attribute(name = "Device",
+                help = "Device name", 
+                type = Attribute.STRING,
+                value = None,
+                flags = Attribute.NoDefaultValue,
+                validation_function = validation.is_string)
 
-class Box(AttributesMap):
+class Box(AttributesMap, Taggable):
     def __init__(self, guid, factory, testbed_guid, container = None):
         super(Box, self).__init__()
         # guid -- global unique identifier
@@ -225,22 +182,27 @@ class Box(AttributesMap):
         # factory_attributes -- factory attributes for box construction
         self._factory_attributes = dict()
         # graphical_info -- GUI position information
-        self.graphical_info = GraphicalInfo(str(self._guid))
+        self.graphical_info = GraphicalInfo()
 
         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.trace_id, trace.help, trace.enabled)
-            self._traces[trace.trace_id] = tr
-        for attr in factory.box_attributes:
+        for (name, help, enabled) in factory.traces:
+            trace = Trace(name, help, enabled)
+            self._traces[name] = trace
+        for tag_id in factory.tags:
+            self.add_tag(tag_id)
+        for attr in factory.box_attributes.attributes:
             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
-                    attr.range, attr.allowed, attr.readonly, attr.visible
-                    attr.validation_function)
+                    attr.range, attr.allowed, attr.flags
+                    attr.validation_function, attr.category)
         for attr in factory.attributes:
-            if attr.modified:
+            if attr.modified or attr.is_metadata:
                 self._factory_attributes[attr.name] = attr.value
 
+    def __str__(self):
+        return "Box(%s, %s, %s)" % (self.guid, self.factory_id, self.testbed_guid)
+
     @property
     def guid(self):
         return self._guid
@@ -266,29 +228,24 @@ class Box(AttributesMap):
         return self._traces.values()
 
     @property
-    def traces_name(self):
+    def traces_list(self):
         return self._traces.keys()
 
     @property
     def factory_attributes(self):
         return self._factory_attributes
 
-    @property
-    def addresses(self):
-        return []
-
-    @property
-    def routes(self):
-        return []
-
     def trace_help(self, trace_id):
         return self._traces[trace_id].help
 
     def enable_trace(self, trace_id):
-        self._traces[trace_id].enabled = True
+        self._traces[trace_id].enable()
 
     def disable_trace(self, trace_id):
-        self._traces[trace_id].enabled = False
+        self._traces[trace_id].disable()
+
+    def is_trace_enabled(self, trace_id):
+        return self._traces[trace_id].enabled
 
     def connector(self, name):
         return self._connectors[name]
@@ -301,146 +258,18 @@ class Box(AttributesMap):
             t.destroy()
         self._connectors = self._traces = self._factory_attributes = None
 
-class AddressableBox(Box):
-    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 = factory.get_attribute_value("MaxAddresses")
-        self._addresses = list()
-
-    @property
-    def addresses(self):
-        return self._addresses
-
-    @property
-    def max_addresses(self):
-        return self._max_addresses
-
-    def add_address(self):
-        if len(self._addresses) == self.max_addresses:
-            raise RuntimeError("Maximun number of addresses for this box reached.")
-        address = Address(family = self._family)
-        self._addresses.append(address)
-        return address
-
-    def delete_address(self, address):
-        self._addresses.remove(address)
-        del address
-
-    def destroy(self):
-        super(AddressableBox, self).destroy()
-        for address in self.addresses:
-            self.delete_address(address)
-        self._addresses = None
-
-class RoutingTableBox(Box):
-    def __init__(self, guid, factory, container = None):
-        super(RoutingTableBox, self).__init__(guid, factory, container)
-        self._routes = list()
-
-    @property
-    def routes(self):
-        return self._routes
-
-    def add_route(self, family):
-        route = Route(family = family)
-        self._routes.append(route)
-        return route
-
-    def delete_route(self, route):
-        self._route.remove(route)
-        del route
-
-    def destroy(self):
-        super(RoutingTableBox, self).destroy()
-        for route in self.routes:
-            self.delete_route(route)
-        self._route = None
-
-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._connector_types = list()
-        self._traces = list()
-        self._box_attributes = 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 help(self):
-        return self._help
-
-    @property
-    def category(self):
-        return self._category
-
-    @property
-    def connector_types(self):
-        return self._connector_types
-
-    @property
-    def traces(self):
-        return self._traces
-
-    @property
-    def box_attributes(self):
-        return self._box_attributes
-
-    def add_connector_type(self, connector_type):
-        self._connector_types.append(connector_type)
-
-    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, visible = True, 
-        validation_function = None):
-        attribute = Attribute(name, help, type, value, range, allowed, readonly,
-                visible, validation_function)
-        self._box_attributes.append(attribute)
-
-    def create(self, guid, testbed_description):
-        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(Factory, self).destroy()
-        self._connector_types = None
-
 class FactoriesProvider(object):
-    def __init__(self, testbed_id, testbed_version):
+    def __init__(self, testbed_id):
         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():
+        metadata = Metadata(testbed_id) 
+        for factory in metadata.build_factories():
             self.add_factory(factory)
 
+        self._testbed_version = metadata.testbed_version
+
     @property
     def testbed_id(self):
         return self._testbed_id
@@ -463,19 +292,19 @@ class FactoriesProvider(object):
         del self._factories[factory_id]
 
 class TestbedDescription(AttributesMap):
-    def __init__(self, guid_generator, provider):
+    def __init__(self, guid_generator, provider, guid = None):
         super(TestbedDescription, self).__init__()
         self._guid_generator = guid_generator
-        self._guid = guid_generator.next()
+        self._guid = guid_generator.next(guid)
         self._provider = provider
         self._boxes = dict()
-        self.graphical_info = GraphicalInfo(str(self._guid))
+        self.graphical_info = GraphicalInfo()
 
-        metadata = Metadata(provider.testbed_id, provider.testbed_version)
+        metadata = Metadata(provider.testbed_id)
         for attr in metadata.testbed_attributes().attributes:
             self.add_attribute(attr.name, attr.help, attr.type, attr.value, 
-                    attr.range, attr.allowed, attr.readonly, attr.visible
-                    attr.validation_function)
+                    attr.range, attr.allowed, attr.flags
+                    attr.validation_function, attr.category)
 
     @property
     def guid(self):
@@ -492,8 +321,8 @@ class TestbedDescription(AttributesMap):
     def box(self, guid):
         return self._boxes[guid] if guid in self._boxes else None
 
-    def create(self, factory_id):
-        guid = self._guid_generator.next()
+    def create(self, factory_id, guid = None):
+        guid = self._guid_generator.next(guid)
         factory = self._provider.factory(factory_id)
         box = factory.create(guid, self)
         self._boxes[guid] = box
@@ -510,8 +339,8 @@ class TestbedDescription(AttributesMap):
         self._boxes = None
 
 class ExperimentDescription(object):
-    def __init__(self, guid = 0):
-        self._guid_generator = GuidGenerator(guid)
+    def __init__(self):
+        self._guid_generator = GuidGenerator()
         self._testbed_descriptions = dict()
 
     @property
@@ -536,30 +365,39 @@ class ExperimentDescription(object):
             if box: return box
         return None
 
-    def add_testbed_description(self, provider):
+    def get_element(self, guid):
+        if guid in self._testbed_descriptions:
+            return self._testbed_descriptions[guid]
+        for testbed_description in self._testbed_descriptions.values():
+            box = testbed_description.box(guid)
+            if box: return box
+        return None
+
+    def get_element_by_label(self, label):
+        for tbd_desc in self._testbed_descriptions.values():
+            l = tbd_desc.get_attribute_value("label")
+            if label == l:
+                return tbd_desc
+            for box in tbd_desc.boxes:
+                l = box.get_attribute_value("label")
+                if label == l:
+                    return box
+        return None
+    
+    def add_testbed_description(self, provider, guid = None):
         testbed_description = TestbedDescription(self._guid_generator, 
-                provider)
+                provider, guid)
         guid = testbed_description.guid
         self._testbed_descriptions[guid] = testbed_description
         return testbed_description
 
-    def remove_testbed_description(self, testbed_description):
-        guid = testbed_description.guid
+    def remove_testbed_description(self, guid):
+        testbed_description = self._testbed_descriptions[guid]
         del self._testbed_descriptions[guid]
+        testbed_description.destroy()
 
     def destroy(self):
         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