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
17 class ConnectorType(ConnectorTypeBase):
18 def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
19 super(ConnectorType, self).__init__(testbed_id, factory_id, name, max, min)
24 # allowed_connections -- keys in the dictionary correspond to the
25 # connector_type_id for possible connections. The value indicates if
26 # the connection is allowed accros different testbed instances
27 self._allowed_connections = dict()
33 def add_allowed_connection(self, testbed_id, factory_id, name, can_cross):
34 type_id = self.make_connector_type_id(testbed_id, factory_id, name)
35 self._allowed_connections[type_id] = can_cross
37 def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2):
38 for lookup_type_id in self._type_resolution_order(connector_type_id):
39 if lookup_type_id in self._allowed_connections:
40 can_cross = self._allowed_connections[lookup_type_id]
41 if can_cross or (testbed_guid1 == testbed_guid2):
46 class Connector(object):
47 """A Connector sepcifies the connection points in an Object"""
48 def __init__(self, box, connector_type):
49 super(Connector, self).__init__()
51 self._connector_type = connector_type
52 self._connections = list()
55 return "Connector(%s, %s)" % (self.box, self.connector_type)
62 def connector_type(self):
63 return self._connector_type
66 def connections(self):
67 return self._connections
70 """Return True if the connector has the maximum number of connections
72 return len(self.connections) == self.connector_type.max
74 def is_complete(self):
75 """Return True if the connector has the minimum number of connections
77 return len(self.connections) >= self.connector_type.min
79 def is_connected(self, connector):
80 return connector in self._connections
82 def connect(self, connector):
83 if not self.can_connect(connector) or not connector.can_connect(self):
84 raise RuntimeError("Could not connect. %s to %s" % (self, connector))
85 self._connections.append(connector)
86 connector._connections.append(self)
88 def disconnect(self, connector):
89 if connector not in self._connections or\
90 self not in connector._connections:
91 raise RuntimeError("Could not disconnect.")
92 self._connections.remove(connector)
93 connector._connections.remove(self)
95 def can_connect(self, connector):
96 # can't connect with self
97 if self.box.guid == connector.box.guid:
99 if self.is_full() or connector.is_full():
101 if self.is_connected(connector):
103 connector_type_id = connector.connector_type.connector_type_id
104 testbed_guid1 = self.box.testbed_guid
105 testbed_guid2 = connector.box.testbed_guid
106 return self.connector_type.can_connect(connector_type_id,
107 testbed_guid1, testbed_guid2)
110 for connector in self.connections:
111 self.disconnect(connector)
112 self._box = self._connectors = None
114 class Trace(AttributesMap):
115 def __init__(self, trace_id, help, enabled = False):
116 super(Trace, self).__init__()
117 self._trace_id = trace_id
119 self._enabled = enabled
123 return self._trace_id
137 self._enabled = False
139 class Address(AttributesMap):
141 super(Address, self).__init__()
142 self.add_attribute(name = "Address",
143 help = "Address number",
144 type = Attribute.STRING,
145 flags = Attribute.HasNoDefaultValue,
146 validation_function = validation.is_ip_address)
147 self.add_attribute(name = "NetPrefix",
148 help = "Network prefix for the address",
149 type = Attribute.INTEGER,
152 flags = Attribute.HasNoDefaultValue,
153 validation_function = validation.is_integer)
154 self.add_attribute(name = "Broadcast",
155 help = "Broadcast address",
156 type = Attribute.STRING,
157 validation_function = validation.is_ip4_address)
159 class Route(AttributesMap):
161 super(Route, self).__init__()
162 self.add_attribute(name = "Destination",
163 help = "Network destintation",
164 type = Attribute.STRING,
165 validation_function = validation.is_ref_address)
166 self.add_attribute(name = "NetPrefix",
167 help = "Network destination prefix",
168 type = Attribute.INTEGER,
171 flags = Attribute.HasNoDefaultValue,
172 validation_function = validation.is_integer)
173 self.add_attribute(name = "NextHop",
174 help = "Address for the next hop",
175 type = Attribute.STRING,
176 flags = Attribute.HasNoDefaultValue,
177 validation_function = validation.is_ref_address)
178 self.add_attribute(name = "Metric",
179 help = "Routing metric",
180 type = Attribute.INTEGER,
182 flags = Attribute.HasNoDefaultValue,
183 validation_function = validation.is_integer)
185 class Box(AttributesMap):
186 def __init__(self, guid, factory, testbed_guid, container = None):
187 super(Box, self).__init__()
188 # guid -- global unique identifier
190 # factory_id -- factory identifier or name
191 self._factory_id = factory.factory_id
192 # testbed_guid -- parent testbed guid
193 self._testbed_guid = testbed_guid
194 # container -- boxes can be nested inside other 'container' boxes
195 self._container = container
196 # traces -- list of available traces for the box
197 self._traces = dict()
198 # tags -- list of tags for the box
200 # connectors -- list of available connectors for the box
201 self._connectors = dict()
202 # factory_attributes -- factory attributes for box construction
203 self._factory_attributes = dict()
204 # graphical_info -- GUI position information
205 self.graphical_info = GraphicalInfo()
207 for connector_type in factory.connector_types:
208 connector = Connector(self, connector_type)
209 self._connectors[connector_type.name] = connector
210 for trace in factory.traces:
211 tr = Trace(trace.trace_id, trace.help, trace.enabled)
212 self._traces[trace.trace_id] = tr
213 for tag_id in factory.tags:
214 self._tags.append(tag_id)
215 for attr in factory.box_attributes.attributes:
216 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
217 attr.range, attr.allowed, attr.flags,
218 attr.validation_function, attr.category)
219 for attr in factory.attributes:
220 if attr.modified or attr.invisible:
221 self._factory_attributes[attr.name] = attr.value
224 return "Box(%s, %s, %s)" % (self.guid, self.factory_id, self.testbed_guid)
231 def factory_id(self):
232 return self._factory_id
235 def testbed_guid(self):
236 return self._testbed_guid
240 return self._container
243 def connectors(self):
244 return self._connectors.values()
248 return self._traces.values()
251 def trace_names(self):
252 return self._traces.keys()
255 def factory_attributes(self):
256 return self._factory_attributes
262 def trace_help(self, trace_id):
263 return self._traces[trace_id].help
265 def enable_trace(self, trace_id):
266 self._traces[trace_id].enable()
268 def disable_trace(self, trace_id):
269 self._traces[trace_id].disable()
271 def is_trace_enabled(self, trace_id):
272 return self._traces[trace_id].enabled
274 def connector(self, name):
275 return self._connectors[name]
278 super(Box, self).destroy()
279 for c in self.connectors:
281 for t in self.traces:
283 self._connectors = self._traces = self._factory_attributes = None
285 class AddressableMixin(object):
286 def __init__(self, guid, factory, testbed_guid, container = None):
287 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
289 max_addr = self._factory_attributes["MaxAddresses"] \
290 if "MaxAddresses" in self._factory_attributes else 1
291 self._max_addresses = max_addr
292 self._addresses = list()
296 return self._addresses
299 def max_addresses(self):
300 return self._max_addresses
302 class UserAddressableMixin(AddressableMixin):
303 def __init__(self, guid, factory, testbed_guid, container = None):
304 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
307 def add_address(self):
308 if len(self._addresses) == self.max_addresses:
309 raise RuntimeError("Maximun number of addresses for this box reached.")
311 self._addresses.append(address)
314 def delete_address(self, address):
315 self._addresses.remove(address)
319 super(UserAddressableMixin, self).destroy()
320 for address in list(self.addresses):
321 self.delete_address(address)
322 self._addresses = None
324 class RoutableMixin(object):
325 def __init__(self, guid, factory, testbed_guid, container = None):
326 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
328 self._routes = list()
334 class UserRoutableMixin(RoutableMixin):
335 def __init__(self, guid, factory, testbed_guid, container = None):
336 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
341 self._routes.append(route)
344 def delete_route(self, route):
345 self._routes.remove(route)
349 super(UserRoutableMixin, self).destroy()
350 for route in list(self.routes):
351 self.delete_route(route)
354 def MixIn(MyClass, MixIn):
355 # Mixins are installed BEFORE "Box" because
356 # Box inherits from non-cooperative classes,
357 # so the MRO chain gets broken when it gets
361 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
364 # Somehow it doesn't work automatically
365 for name in dir(MixIn):
366 prop = getattr(MixIn,name,None)
367 if isinstance(prop, property):
368 setattr(MyClass, name, prop)
371 MyClass.__name__ = MyClass.__name__.replace(
373 MixIn.__name__.replace('MixIn','')+'Box',
376 class Factory(AttributesMap):
377 _box_class_cache = {}
379 def __init__(self, factory_id,
380 allow_addresses = False, has_addresses = False,
381 allow_routes = False, has_routes = False,
382 Help = None, category = None):
383 super(Factory, self).__init__()
384 self._factory_id = factory_id
385 self._allow_addresses = bool(allow_addresses)
386 self._allow_routes = bool(allow_routes)
387 self._has_addresses = bool(allow_addresses) or self._allow_addresses
388 self._has_routes = bool(allow_routes) or self._allow_routes
390 self._category = category
391 self._connector_types = list()
392 self._traces = list()
394 self._box_attributes = AttributesMap()
396 if not self._has_addresses and not self._has_routes:
399 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
400 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
401 key = addresses+routes
403 if key in self._box_class_cache:
404 self._factory = self._box_class_cache[key]
408 def __init__(self, guid, factory, testbed_guid, container = None):
409 super(_factory, self).__init__(guid, factory, testbed_guid, container)
411 # Add mixins, one by one
413 MixIn(_factory, UserAddressableMixin)
415 MixIn(_factory, AddressableMixin)
418 MixIn(_factory, UserRoutableMixin)
420 MixIn(_factory, RoutableMixin)
423 self._box_class_cache[key] = self._factory = _factory
426 def factory_id(self):
427 return self._factory_id
430 def allow_addresses(self):
431 return self._allow_addresses
434 def allow_routes(self):
435 return self._allow_routes
438 def has_addresses(self):
439 return self._has_addresses
442 def has_routes(self):
443 return self._has_routes
451 return self._category
454 def connector_types(self):
455 return self._connector_types
466 def box_attributes(self):
467 return self._box_attributes
469 def add_connector_type(self, connector_type):
470 self._connector_types.append(connector_type)
472 def add_trace(self, trace_id, help, enabled = False):
473 trace = Trace(trace_id, help, enabled)
474 self._traces.append(trace)
476 def add_tag(self, tag_id):
477 self._tags.append(tag_id)
479 def add_box_attribute(self, name, help, type, value = None, range = None,
480 allowed = None, flags = Attribute.NoFlags, validation_function = None,
482 self._box_attributes.add_attribute(name, help, type, value, range,
483 allowed, flags, validation_function, category)
485 def create(self, guid, testbed_description):
486 return self._factory(guid, self, testbed_description.guid)
489 super(Factory, self).destroy()
490 self._connector_types = None
492 class FactoriesProvider(object):
493 def __init__(self, testbed_id, testbed_version):
494 super(FactoriesProvider, self).__init__()
495 self._testbed_id = testbed_id
496 self._testbed_version = testbed_version
497 self._factories = dict()
499 metadata = Metadata(testbed_id, testbed_version)
500 for factory in metadata.build_design_factories():
501 self.add_factory(factory)
504 def testbed_id(self):
505 return self._testbed_id
508 def testbed_version(self):
509 return self._testbed_version
513 return self._factories.values()
515 def factory(self, factory_id):
516 return self._factories[factory_id]
518 def add_factory(self, factory):
519 self._factories[factory.factory_id] = factory
521 def remove_factory(self, factory_id):
522 del self._factories[factory_id]
524 class TestbedDescription(AttributesMap):
525 def __init__(self, guid_generator, provider, guid = None):
526 super(TestbedDescription, self).__init__()
527 self._guid_generator = guid_generator
528 self._guid = guid_generator.next(guid)
529 self._provider = provider
531 self.graphical_info = GraphicalInfo()
533 metadata = Metadata(provider.testbed_id, provider.testbed_version)
534 for attr in metadata.testbed_attributes().attributes:
535 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
536 attr.range, attr.allowed, attr.flags,
537 attr.validation_function, attr.category)
545 return self._provider
549 return self._boxes.values()
552 return self._boxes[guid] if guid in self._boxes else None
554 def create(self, factory_id, guid = None):
555 guid = self._guid_generator.next(guid)
556 factory = self._provider.factory(factory_id)
557 box = factory.create(guid, self)
558 self._boxes[guid] = box
561 def delete(self, guid):
562 box = self._boxes[guid]
563 del self._boxes[guid]
567 for guid, box in self._boxes.iteritems():
571 class ExperimentDescription(object):
573 self._guid_generator = GuidGenerator()
574 self._testbed_descriptions = dict()
577 def testbed_descriptions(self):
578 return self._testbed_descriptions.values()
581 parser = XmlExperimentParser()
582 return parser.to_xml(self)
584 def from_xml(self, xml):
585 parser = XmlExperimentParser()
586 parser.from_xml(self, xml)
588 def testbed_description(self, guid):
589 return self._testbed_descriptions[guid] \
590 if guid in self._testbed_descriptions else None
593 for testbed_description in self._testbed_descriptions.values():
594 box = testbed_description.box(guid)
598 def get_element(self, guid):
599 if guid in self._testbed_descriptions:
600 return self._testbed_descriptions[guid]
601 for testbed_description in self._testbed_descriptions.values():
602 box = testbed_description.box(guid)
606 def add_testbed_description(self, provider, guid = None):
607 testbed_description = TestbedDescription(self._guid_generator,
609 guid = testbed_description.guid
610 self._testbed_descriptions[guid] = testbed_description
611 return testbed_description
613 def remove_testbed_description(self, guid):
614 testbed_description = self._testbed_descriptions[guid]
615 del self._testbed_descriptions[guid]
616 testbed_description.destroy()
619 for testbed_description in self.testbed_descriptions:
620 testbed_description.destroy()