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 return can_cross or (testbed_guid1 == testbed_guid2)
47 class Connector(object):
48 """A Connector sepcifies the connection points in an Object"""
49 def __init__(self, box, connector_type):
50 super(Connector, self).__init__()
52 self._connector_type = connector_type
53 self._connections = list()
60 def connector_type(self):
61 return self._connector_type
64 def connections(self):
65 return self._connections
68 """Return True if the connector has the maximum number of connections
70 return len(self.connections) == self.connector_type.max
72 def is_complete(self):
73 """Return True if the connector has the minimum number of connections
75 return len(self.connections) >= self.connector_type.min
77 def is_connected(self, connector):
78 return connector in self._connections
80 def connect(self, connector):
81 if not self.can_connect(connector) or not connector.can_connect(self):
82 raise RuntimeError("Could not connect.")
83 self._connections.append(connector)
84 connector._connections.append(self)
86 def disconnect(self, connector):
87 if connector not in self._connections or\
88 self not in connector._connections:
89 raise RuntimeError("Could not disconnect.")
90 self._connections.remove(connector)
91 connector._connections.remove(self)
93 def can_connect(self, connector):
94 if self.is_full() or connector.is_full():
96 if self.is_connected(connector):
98 connector_type_id = connector.connector_type.connector_type_id
99 testbed_guid1 = self.box.testbed_guid
100 testbed_guid2 = connector.box.testbed_guid
101 return self.connector_type.can_connect(connector_type_id,
102 testbed_guid1, testbed_guid2)
105 for connector in self.connections:
106 self.disconnect(connector)
107 self._box = self._connectors = None
109 class Trace(AttributesMap):
110 def __init__(self, trace_id, help, enabled = False):
111 super(Trace, self).__init__()
112 self._trace_id = trace_id
114 self.enabled = enabled
118 return self._trace_id
124 class Address(AttributesMap):
126 super(Address, self).__init__()
127 self.add_attribute(name = "AutoConfigure",
128 help = "If set, this address will automatically be assigned",
129 type = Attribute.BOOL,
131 flags = Attribute.DesignOnly,
132 validation_function = validation.is_bool)
133 self.add_attribute(name = "Address",
134 help = "Address number",
135 type = Attribute.STRING,
136 flags = Attribute.HasNoDefaultValue,
137 validation_function = validation.is_ip_address)
138 self.add_attribute(name = "NetPrefix",
139 help = "Network prefix for the address",
140 type = Attribute.INTEGER,
143 flags = Attribute.HasNoDefaultValue,
144 validation_function = validation.is_integer)
145 self.add_attribute(name = "Broadcast",
146 help = "Broadcast address",
147 type = Attribute.STRING,
148 validation_function = validation.is_ip4_address)
150 class Route(AttributesMap):
152 super(Route, self).__init__()
153 self.add_attribute(name = "Destination",
154 help = "Network destintation",
155 type = Attribute.STRING,
156 validation_function = validation.is_ip_address)
157 self.add_attribute(name = "NetPrefix",
158 help = "Network destination prefix",
159 type = Attribute.INTEGER,
160 prefix_range = (0,128),
162 flags = Attribute.HasNoDefaultValue,
163 validation_function = validation.is_integer)
164 self.add_attribute(name = "NextHop",
165 help = "Address for the next hop",
166 type = Attribute.STRING,
167 flags = Attribute.HasNoDefaultValue,
168 validation_function = validation.is_ip_address)
170 class Box(AttributesMap):
171 def __init__(self, guid, factory, testbed_guid, container = None):
172 super(Box, self).__init__()
173 # guid -- global unique identifier
175 # factory_id -- factory identifier or name
176 self._factory_id = factory.factory_id
177 # testbed_guid -- parent testbed guid
178 self._testbed_guid = testbed_guid
179 # container -- boxes can be nested inside other 'container' boxes
180 self._container = container
181 # traces -- list of available traces for the box
182 self._traces = dict()
183 # connectors -- list of available connectors for the box
184 self._connectors = dict()
185 # factory_attributes -- factory attributes for box construction
186 self._factory_attributes = dict()
187 # graphical_info -- GUI position information
188 self.graphical_info = GraphicalInfo(str(self._guid))
190 for connector_type in factory.connector_types:
191 connector = Connector(self, connector_type)
192 self._connectors[connector_type.name] = connector
193 for trace in factory.traces:
194 tr = Trace(trace.trace_id, trace.help, trace.enabled)
195 self._traces[trace.trace_id] = tr
196 for attr in factory.box_attributes.attributes:
197 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
198 attr.range, attr.allowed, attr.flags,
199 attr.validation_function)
200 for attr in factory.attributes:
202 self._factory_attributes[attr.name] = attr.value
209 def factory_id(self):
210 return self._factory_id
213 def testbed_guid(self):
214 return self._testbed_guid
218 return self._container
221 def connectors(self):
222 return self._connectors.values()
226 return self._traces.values()
229 def traces_name(self):
230 return self._traces.keys()
233 def factory_attributes(self):
234 return self._factory_attributes
244 def trace_help(self, trace_id):
245 return self._traces[trace_id].help
247 def enable_trace(self, trace_id):
248 self._traces[trace_id].enabled = True
250 def disable_trace(self, trace_id):
251 self._traces[trace_id].enabled = False
253 def connector(self, name):
254 return self._connectors[name]
257 super(Box, self).destroy()
258 for c in self.connectors:
260 for t in self.traces:
262 self._connectors = self._traces = self._factory_attributes = None
264 class AddressableMixin(object):
265 def __init__(self, guid, factory, testbed_guid, container = None):
266 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
268 self._max_addresses = 1 # TODO: How to make this configurable!
269 self._addresses = list()
273 return self._addresses
276 def max_addresses(self):
277 return self._max_addresses
279 class UserAddressableMixin(AddressableMixin):
280 def __init__(self, guid, factory, testbed_guid, container = None):
281 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
284 def add_address(self):
285 if len(self._addresses) == self.max_addresses:
286 raise RuntimeError("Maximun number of addresses for this box reached.")
288 self._addresses.append(address)
291 def delete_address(self, address):
292 self._addresses.remove(address)
296 super(UserAddressableMixin, self).destroy()
297 for address in list(self.addresses):
298 self.delete_address(address)
299 self._addresses = None
301 class RoutableMixin(object):
302 def __init__(self, guid, factory, testbed_guid, container = None):
303 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
305 self._routes = list()
311 class UserRoutableMixin(RoutableMixin):
312 def __init__(self, guid, factory, testbed_guid, container = None):
313 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
318 self._routes.append(route)
321 def delete_route(self, route):
322 self._route.remove(route)
326 super(UserRoutableMixin, self).destroy()
327 for route in list(self.routes):
328 self.delete_route(route)
331 def MixIn(MyClass, MixIn):
332 # Mixins are installed BEFORE "Box" because
333 # Box inherits from non-cooperative classes,
334 # so the MRO chain gets broken when it gets
338 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
341 # Somehow it doesn't work automatically
342 for name in dir(MixIn):
343 prop = getattr(MixIn,name,None)
344 if isinstance(prop, property):
345 setattr(MyClass, name, prop)
348 MyClass.__name__ = MyClass.__name__.replace(
350 MixIn.__name__.replace('MixIn','')+'Box',
353 class Factory(AttributesMap):
354 _box_class_cache = {}
356 def __init__(self, factory_id,
357 allow_addresses = False, has_addresses = False,
358 allow_routes = False, has_routes = False,
359 Help = None, category = None):
360 super(Factory, self).__init__()
361 self._factory_id = factory_id
362 self._allow_addresses = bool(allow_addresses)
363 self._allow_routes = bool(allow_routes)
364 self._has_addresses = bool(allow_addresses) or self._allow_addresses
365 self._has_routes = bool(allow_routes) or self._allow_routes
367 self._category = category
368 self._connector_types = list()
369 self._traces = list()
370 self._box_attributes = AttributesMap()
372 if not self._has_addresses and not self._has_routes:
375 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
376 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
377 key = addresses+routes
379 if key in self._box_class_cache:
380 self._factory = self._box_class_cache[key]
384 def __init__(self, guid, factory, testbed_guid, container = None):
385 super(_factory, self).__init__(guid, factory, testbed_guid, container)
387 # Add mixins, one by one
389 MixIn(_factory, UserAddressableMixin)
391 MixIn(_factory, AddressableMixin)
394 MixIn(_factory, UserRoutableMixin)
396 MixIn(_factory, RoutableMixin)
399 self._box_class_cache[key] = self._factory = _factory
402 def factory_id(self):
403 return self._factory_id
406 def allow_addresses(self):
407 return self._allow_addresses
410 def allow_routes(self):
411 return self._allow_routes
414 def has_addresses(self):
415 return self._has_addresses
418 def has_routes(self):
419 return self._has_routes
427 return self._category
430 def connector_types(self):
431 return self._connector_types
438 def box_attributes(self):
439 return self._box_attributes
441 def add_connector_type(self, connector_type):
442 self._connector_types.append(connector_type)
444 def add_trace(self, trace_id, help, enabled = False):
445 trace = Trace(trace_id, help, enabled)
446 self._traces.append(trace)
448 def add_box_attribute(self, name, help, type, value = None, range = None,
449 allowed = None, flags = Attribute.NoFlags, validation_function = None):
450 self._box_attributes.add_attribute(name, help, type, value, range,
451 allowed, flags, validation_function)
453 def create(self, guid, testbed_description):
454 return self._factory(guid, self, testbed_description.guid)
457 super(Factory, self).destroy()
458 self._connector_types = None
460 class FactoriesProvider(object):
461 def __init__(self, testbed_id, testbed_version):
462 super(FactoriesProvider, self).__init__()
463 self._testbed_id = testbed_id
464 self._testbed_version = testbed_version
465 self._factories = dict()
467 metadata = Metadata(testbed_id, testbed_version)
468 for factory in metadata.build_design_factories():
469 self.add_factory(factory)
472 def testbed_id(self):
473 return self._testbed_id
476 def testbed_version(self):
477 return self._testbed_version
481 return self._factories.values()
483 def factory(self, factory_id):
484 return self._factories[factory_id]
486 def add_factory(self, factory):
487 self._factories[factory.factory_id] = factory
489 def remove_factory(self, factory_id):
490 del self._factories[factory_id]
492 class TestbedDescription(AttributesMap):
493 def __init__(self, guid_generator, provider):
494 super(TestbedDescription, self).__init__()
495 self._guid_generator = guid_generator
496 self._guid = guid_generator.next()
497 self._provider = provider
499 self.graphical_info = GraphicalInfo(str(self._guid))
501 metadata = Metadata(provider.testbed_id, provider.testbed_version)
502 for attr in metadata.testbed_attributes().attributes:
503 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
504 attr.range, attr.allowed, attr.flags,
505 attr.validation_function)
513 return self._provider
517 return self._boxes.values()
520 return self._boxes[guid] if guid in self._boxes else None
522 def create(self, factory_id):
523 guid = self._guid_generator.next()
524 factory = self._provider.factory(factory_id)
525 box = factory.create(guid, self)
526 self._boxes[guid] = box
529 def delete(self, guid):
530 box = self._boxes[guid]
531 del self._boxes[guid]
535 for guid, box in self._boxes.iteritems():
539 class ExperimentDescription(object):
540 def __init__(self, guid = 0):
541 self._guid_generator = GuidGenerator(guid)
542 self._testbed_descriptions = dict()
545 def testbed_descriptions(self):
546 return self._testbed_descriptions.values()
549 parser = XmlExperimentParser()
550 return parser.to_xml(self)
552 def from_xml(self, xml):
553 parser = XmlExperimentParser()
554 parser.from_xml(self, xml)
556 def testbed_description(self, guid):
557 return self._testbed_descriptions[guid] \
558 if guid in self._testbed_descriptions else None
561 for testbed_description in self._testbed_descriptions.values():
562 box = testbed_description.box(guid)
566 def add_testbed_description(self, provider):
567 testbed_description = TestbedDescription(self._guid_generator,
569 guid = testbed_description.guid
570 self._testbed_descriptions[guid] = testbed_description
571 return testbed_description
573 def remove_testbed_description(self, testbed_description):
574 guid = testbed_description.guid
575 del self._testbed_descriptions[guid]
578 for testbed_description in self.testbed_descriptions:
579 testbed_description.destroy()
581 # TODO: When the experiment xml is passed to the controller to execute it
582 # NetReferences in the xml need to be solved
584 #targets = re.findall(r"%target:(.*?)%", command)
585 #for target in targets:
587 # (family, address, port) = resolve_netref(target, AF_INET,
588 # self.server.experiment )
589 # command = command.replace("%%target:%s%%" % target, address.address)