2 # -*- coding: utf-8 -*-
8 from nepi.core.attributes import AttributesMap, Attribute
9 from nepi.core.connector import ConnectorTypeBase
10 from nepi.core.metadata import Metadata
11 from nepi.util import validation
12 from nepi.util.guid import GuidGenerator
13 from nepi.util.graphical_info import GraphicalInfo
14 from nepi.util.parser._xml import XmlExperimentParser
19 class ConnectorType(ConnectorTypeBase):
20 def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
21 super(ConnectorType, self).__init__(testbed_id, factory_id, name, max, min)
26 # allowed_connections -- keys in the dictionary correspond to the
27 # connector_type_id for possible connections. The value indicates if
28 # the connection is allowed accros different testbed instances
29 self._allowed_connections = dict()
35 def add_allowed_connection(self, testbed_id, factory_id, name, can_cross):
36 type_id = self.make_connector_type_id(testbed_id, factory_id, name)
37 self._allowed_connections[type_id] = can_cross
39 def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2):
40 for lookup_type_id in self._type_resolution_order(connector_type_id):
41 if lookup_type_id in self._allowed_connections:
42 can_cross = self._allowed_connections[lookup_type_id]
43 if can_cross or (testbed_guid1 == testbed_guid2):
48 class Connector(object):
49 """A Connector sepcifies the connection points in an Object"""
50 def __init__(self, box, connector_type):
51 super(Connector, self).__init__()
53 self._connector_type = connector_type
54 self._connections = list()
57 return "Connector(%s, %s)" % (self.box, self.connector_type)
64 def connector_type(self):
65 return self._connector_type
68 def connections(self):
69 return self._connections
72 """Return True if the connector has the maximum number of connections
74 return len(self.connections) == self.connector_type.max
76 def is_complete(self):
77 """Return True if the connector has the minimum number of connections
79 return len(self.connections) >= self.connector_type.min
81 def is_connected(self, connector):
82 return connector in self._connections
84 def connect(self, connector):
85 if not self.can_connect(connector) or not connector.can_connect(self):
86 raise RuntimeError("Could not connect. %s to %s" % (self, connector))
87 self._connections.append(connector)
88 connector._connections.append(self)
90 def disconnect(self, connector):
91 if connector not in self._connections or\
92 self not in connector._connections:
93 raise RuntimeError("Could not disconnect.")
94 self._connections.remove(connector)
95 connector._connections.remove(self)
97 def can_connect(self, connector):
98 if self.is_full() or connector.is_full():
100 if self.is_connected(connector):
102 connector_type_id = connector.connector_type.connector_type_id
103 testbed_guid1 = self.box.testbed_guid
104 testbed_guid2 = connector.box.testbed_guid
105 return self.connector_type.can_connect(connector_type_id,
106 testbed_guid1, testbed_guid2)
109 for connector in self.connections:
110 self.disconnect(connector)
111 self._box = self._connectors = None
113 class Trace(AttributesMap):
114 def __init__(self, trace_id, help, enabled = False):
115 super(Trace, self).__init__()
116 self._trace_id = trace_id
118 self.enabled = enabled
122 return self._trace_id
128 class Address(AttributesMap):
130 super(Address, self).__init__()
131 self.add_attribute(name = "AutoConfigure",
132 help = "If set, this address will automatically be assigned",
133 type = Attribute.BOOL,
135 flags = Attribute.DesignOnly,
136 validation_function = validation.is_bool)
137 self.add_attribute(name = "Address",
138 help = "Address number",
139 type = Attribute.STRING,
140 flags = Attribute.HasNoDefaultValue,
141 validation_function = validation.is_ip_address)
142 self.add_attribute(name = "NetPrefix",
143 help = "Network prefix for the address",
144 type = Attribute.INTEGER,
147 flags = Attribute.HasNoDefaultValue,
148 validation_function = validation.is_integer)
149 self.add_attribute(name = "Broadcast",
150 help = "Broadcast address",
151 type = Attribute.STRING,
152 validation_function = validation.is_ip4_address)
154 class Route(AttributesMap):
156 super(Route, self).__init__()
157 self.add_attribute(name = "Destination",
158 help = "Network destintation",
159 type = Attribute.STRING,
160 validation_function = validation.is_ip_address)
161 self.add_attribute(name = "NetPrefix",
162 help = "Network destination prefix",
163 type = Attribute.INTEGER,
166 flags = Attribute.HasNoDefaultValue,
167 validation_function = validation.is_integer)
168 self.add_attribute(name = "NextHop",
169 help = "Address for the next hop",
170 type = Attribute.STRING,
171 flags = Attribute.HasNoDefaultValue,
172 validation_function = validation.is_ip_address)
174 class Box(AttributesMap):
175 def __init__(self, guid, factory, testbed_guid, container = None):
176 super(Box, self).__init__()
177 # guid -- global unique identifier
179 # factory_id -- factory identifier or name
180 self._factory_id = factory.factory_id
181 # testbed_guid -- parent testbed guid
182 self._testbed_guid = testbed_guid
183 # container -- boxes can be nested inside other 'container' boxes
184 self._container = container
185 # traces -- list of available traces for the box
186 self._traces = dict()
187 # tags -- list of tags for the box
189 # connectors -- list of available connectors for the box
190 self._connectors = dict()
191 # factory_attributes -- factory attributes for box construction
192 self._factory_attributes = dict()
193 # graphical_info -- GUI position information
194 self.graphical_info = GraphicalInfo(str(self._guid))
196 for connector_type in factory.connector_types:
197 connector = Connector(self, connector_type)
198 self._connectors[connector_type.name] = connector
199 for trace in factory.traces:
200 tr = Trace(trace.trace_id, trace.help, trace.enabled)
201 self._traces[trace.trace_id] = tr
202 for tag_id in factory.tags:
203 self._tags.append(tag_id)
204 for attr in factory.box_attributes.attributes:
205 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
206 attr.range, attr.allowed, attr.flags,
207 attr.validation_function, attr.category)
208 for attr in factory.attributes:
210 self._factory_attributes[attr.name] = attr.value
213 return "Box(%s, %s, %s)" % (self.guid, self.factory_id, self.testbed_guid)
220 def factory_id(self):
221 return self._factory_id
224 def testbed_guid(self):
225 return self._testbed_guid
229 return self._container
232 def connectors(self):
233 return self._connectors.values()
237 return self._traces.values()
240 def traces_name(self):
241 return self._traces.keys()
244 def factory_attributes(self):
245 return self._factory_attributes
259 def trace_help(self, trace_id):
260 return self._traces[trace_id].help
262 def enable_trace(self, trace_id):
263 self._traces[trace_id].enabled = True
265 def disable_trace(self, trace_id):
266 self._traces[trace_id].enabled = False
268 def connector(self, name):
269 return self._connectors[name]
272 super(Box, self).destroy()
273 for c in self.connectors:
275 for t in self.traces:
277 self._connectors = self._traces = self._factory_attributes = None
279 class AddressableMixin(object):
280 def __init__(self, guid, factory, testbed_guid, container = None):
281 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
283 self._max_addresses = 1 # TODO: How to make this configurable!
284 self._addresses = list()
288 return self._addresses
291 def max_addresses(self):
292 return self._max_addresses
294 class UserAddressableMixin(AddressableMixin):
295 def __init__(self, guid, factory, testbed_guid, container = None):
296 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
299 def add_address(self):
300 if len(self._addresses) == self.max_addresses:
301 raise RuntimeError("Maximun number of addresses for this box reached.")
303 self._addresses.append(address)
306 def delete_address(self, address):
307 self._addresses.remove(address)
311 super(UserAddressableMixin, self).destroy()
312 for address in list(self.addresses):
313 self.delete_address(address)
314 self._addresses = None
316 class RoutableMixin(object):
317 def __init__(self, guid, factory, testbed_guid, container = None):
318 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
320 self._routes = list()
326 class UserRoutableMixin(RoutableMixin):
327 def __init__(self, guid, factory, testbed_guid, container = None):
328 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
333 self._routes.append(route)
336 def delete_route(self, route):
337 self._routes.remove(route)
341 super(UserRoutableMixin, self).destroy()
342 for route in list(self.routes):
343 self.delete_route(route)
346 def MixIn(MyClass, MixIn):
347 # Mixins are installed BEFORE "Box" because
348 # Box inherits from non-cooperative classes,
349 # so the MRO chain gets broken when it gets
353 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
356 # Somehow it doesn't work automatically
357 for name in dir(MixIn):
358 prop = getattr(MixIn,name,None)
359 if isinstance(prop, property):
360 setattr(MyClass, name, prop)
363 MyClass.__name__ = MyClass.__name__.replace(
365 MixIn.__name__.replace('MixIn','')+'Box',
368 class Factory(AttributesMap):
369 _box_class_cache = {}
371 def __init__(self, factory_id,
372 allow_addresses = False, has_addresses = False,
373 allow_routes = False, has_routes = False,
374 Help = None, category = None):
375 super(Factory, self).__init__()
376 self._factory_id = factory_id
377 self._allow_addresses = bool(allow_addresses)
378 self._allow_routes = bool(allow_routes)
379 self._has_addresses = bool(allow_addresses) or self._allow_addresses
380 self._has_routes = bool(allow_routes) or self._allow_routes
382 self._category = category
383 self._connector_types = list()
384 self._traces = list()
386 self._box_attributes = AttributesMap()
388 if not self._has_addresses and not self._has_routes:
391 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
392 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
393 key = addresses+routes
395 if key in self._box_class_cache:
396 self._factory = self._box_class_cache[key]
400 def __init__(self, guid, factory, testbed_guid, container = None):
401 super(_factory, self).__init__(guid, factory, testbed_guid, container)
403 # Add mixins, one by one
405 MixIn(_factory, UserAddressableMixin)
407 MixIn(_factory, AddressableMixin)
410 MixIn(_factory, UserRoutableMixin)
412 MixIn(_factory, RoutableMixin)
415 self._box_class_cache[key] = self._factory = _factory
418 def factory_id(self):
419 return self._factory_id
422 def allow_addresses(self):
423 return self._allow_addresses
426 def allow_routes(self):
427 return self._allow_routes
430 def has_addresses(self):
431 return self._has_addresses
434 def has_routes(self):
435 return self._has_routes
443 return self._category
446 def connector_types(self):
447 return self._connector_types
458 def box_attributes(self):
459 return self._box_attributes
461 def add_connector_type(self, connector_type):
462 self._connector_types.append(connector_type)
464 def add_trace(self, trace_id, help, enabled = False):
465 trace = Trace(trace_id, help, enabled)
466 self._traces.append(trace)
468 def add_tag(self, tag_id):
469 self._tags.append(tag_id)
471 def add_box_attribute(self, name, help, type, value = None, range = None,
472 allowed = None, flags = Attribute.NoFlags, validation_function = None,
474 self._box_attributes.add_attribute(name, help, type, value, range,
475 allowed, flags, validation_function, category)
477 def create(self, guid, testbed_description):
478 return self._factory(guid, self, testbed_description.guid)
481 super(Factory, self).destroy()
482 self._connector_types = None
484 class FactoriesProvider(object):
485 def __init__(self, testbed_id, testbed_version):
486 super(FactoriesProvider, self).__init__()
487 self._testbed_id = testbed_id
488 self._testbed_version = testbed_version
489 self._factories = dict()
491 metadata = Metadata(testbed_id, testbed_version)
492 for factory in metadata.build_design_factories():
493 self.add_factory(factory)
496 def testbed_id(self):
497 return self._testbed_id
500 def testbed_version(self):
501 return self._testbed_version
505 return self._factories.values()
507 def factory(self, factory_id):
508 return self._factories[factory_id]
510 def add_factory(self, factory):
511 self._factories[factory.factory_id] = factory
513 def remove_factory(self, factory_id):
514 del self._factories[factory_id]
516 class TestbedDescription(AttributesMap):
517 def __init__(self, guid_generator, provider):
518 super(TestbedDescription, self).__init__()
519 self._guid_generator = guid_generator
520 self._guid = guid_generator.next()
521 self._provider = provider
523 self.graphical_info = GraphicalInfo(str(self._guid))
525 metadata = Metadata(provider.testbed_id, provider.testbed_version)
526 for attr in metadata.testbed_attributes().attributes:
527 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
528 attr.range, attr.allowed, attr.flags,
529 attr.validation_function, attr.category)
537 return self._provider
541 return self._boxes.values()
544 return self._boxes[guid] if guid in self._boxes else None
546 def create(self, factory_id):
547 guid = self._guid_generator.next()
548 factory = self._provider.factory(factory_id)
549 box = factory.create(guid, self)
550 self._boxes[guid] = box
553 def delete(self, guid):
554 box = self._boxes[guid]
555 del self._boxes[guid]
559 for guid, box in self._boxes.iteritems():
563 class ExperimentDescription(object):
564 def __init__(self, guid = 0):
565 self._guid_generator = GuidGenerator(guid)
566 self._testbed_descriptions = dict()
569 def testbed_descriptions(self):
570 return self._testbed_descriptions.values()
573 parser = XmlExperimentParser()
574 return parser.to_xml(self)
576 def from_xml(self, xml):
577 parser = XmlExperimentParser()
578 parser.from_xml(self, xml)
580 def testbed_description(self, guid):
581 return self._testbed_descriptions[guid] \
582 if guid in self._testbed_descriptions else None
585 for testbed_description in self._testbed_descriptions.values():
586 box = testbed_description.box(guid)
590 def add_testbed_description(self, provider):
591 testbed_description = TestbedDescription(self._guid_generator,
593 guid = testbed_description.guid
594 self._testbed_descriptions[guid] = testbed_description
595 return testbed_description
597 def remove_testbed_description(self, testbed_description):
598 guid = testbed_description.guid
599 del self._testbed_descriptions[guid]
602 for testbed_description in self.testbed_descriptions:
603 testbed_description.destroy()
605 # TODO: When the experiment xml is passed to the controller to execute it
606 # NetReferences in the xml need to be solved
608 #targets = re.findall(r"%target:(.*?)%", command)
609 #for target in targets:
611 # (family, address, port) = resolve_netref(target, AF_INET,
612 # self.server.experiment )
613 # command = command.replace("%%target:%s%%" % target, address.address)