2 # -*- coding: utf-8 -*-
8 from nepi.core.attributes import AttributesMap, Attribute
9 from nepi.core.connector import ConnectorType
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 Connector(object):
18 """A Connector sepcifies the connection points in an Object"""
19 def __init__(self, box, connector_type):
20 super(Connector, self).__init__()
22 self._connector_type = connector_type
23 self._connections = list()
26 return "Connector(%s, %s)" % (self.box, self.connector_type)
33 def connector_type(self):
34 return self._connector_type
37 def connections(self):
38 return self._connections
41 """Return True if the connector has the maximum number of connections
43 return len(self.connections) == self.connector_type.max
45 def is_complete(self):
46 """Return True if the connector has the minimum number of connections
48 return len(self.connections) >= self.connector_type.min
50 def is_connected(self, connector):
51 return connector in self._connections
53 def connect(self, connector):
54 if not self.can_connect(connector) or not connector.can_connect(self):
55 raise RuntimeError("Could not connect. %s to %s" % (self, connector))
56 self._connections.append(connector)
57 connector._connections.append(self)
59 def disconnect(self, connector):
60 if connector not in self._connections or\
61 self not in connector._connections:
62 raise RuntimeError("Could not disconnect.")
63 self._connections.remove(connector)
64 connector._connections.remove(self)
66 def can_connect(self, connector):
67 # can't connect with self
68 if self.box.guid == connector.box.guid:
70 if self.is_full() or connector.is_full():
72 if self.is_connected(connector):
74 (testbed_id, factory_id, name) = connector.connector_type.connector_type_id
75 testbed_guid1 = self.box.testbed_guid
76 testbed_guid2 = connector.box.testbed_guid
77 must_cross = (testbed_guid1 != testbed_guid2)
78 return self.connector_type.can_connect(testbed_id, factory_id, name,
82 for connector in self.connections:
83 self.disconnect(connector)
84 self._box = self._connectors = None
86 class Trace(AttributesMap):
87 def __init__(self, trace_id, help, enabled = False):
88 super(Trace, self).__init__()
89 self._trace_id = trace_id
91 self._enabled = enabled
109 self._enabled = False
111 class Address(AttributesMap):
113 super(Address, self).__init__()
114 self.add_attribute(name = "Address",
115 help = "Address number",
116 type = Attribute.STRING,
117 flags = Attribute.HasNoDefaultValue,
118 validation_function = validation.is_ip_address)
119 self.add_attribute(name = "NetPrefix",
120 help = "Network prefix for the address",
121 type = Attribute.INTEGER,
124 flags = Attribute.HasNoDefaultValue,
125 validation_function = validation.is_integer)
126 self.add_attribute(name = "Broadcast",
127 help = "Broadcast address",
128 type = Attribute.STRING,
129 validation_function = validation.is_ip4_address)
131 class Route(AttributesMap):
133 super(Route, self).__init__()
134 self.add_attribute(name = "Destination",
135 help = "Network destintation",
136 type = Attribute.STRING,
137 validation_function = validation.is_ref_address)
138 self.add_attribute(name = "NetPrefix",
139 help = "Network destination prefix",
140 type = Attribute.INTEGER,
143 flags = Attribute.HasNoDefaultValue,
144 validation_function = validation.is_integer)
145 self.add_attribute(name = "NextHop",
146 help = "Address for the next hop",
147 type = Attribute.STRING,
148 flags = Attribute.HasNoDefaultValue,
149 validation_function = validation.is_ref_address)
150 self.add_attribute(name = "Metric",
151 help = "Routing metric",
152 type = Attribute.INTEGER,
154 flags = Attribute.HasNoDefaultValue,
155 validation_function = validation.is_integer)
157 class Box(AttributesMap):
158 def __init__(self, guid, factory, testbed_guid, container = None):
159 super(Box, self).__init__()
160 # guid -- global unique identifier
162 # factory_id -- factory identifier or name
163 self._factory_id = factory.factory_id
164 # testbed_guid -- parent testbed guid
165 self._testbed_guid = testbed_guid
166 # container -- boxes can be nested inside other 'container' boxes
167 self._container = container
168 # traces -- list of available traces for the box
169 self._traces = dict()
170 # tags -- list of tags for the box
172 # connectors -- list of available connectors for the box
173 self._connectors = dict()
174 # factory_attributes -- factory attributes for box construction
175 self._factory_attributes = dict()
176 # graphical_info -- GUI position information
177 self.graphical_info = GraphicalInfo()
179 for connector_type in factory.connector_types:
180 connector = Connector(self, connector_type)
181 self._connectors[connector_type.name] = connector
182 for trace in factory.traces:
183 tr = Trace(trace.trace_id, trace.help, trace.enabled)
184 self._traces[trace.trace_id] = tr
185 for tag_id in factory.tags:
186 self._tags.append(tag_id)
187 for attr in factory.box_attributes.attributes:
188 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
189 attr.range, attr.allowed, attr.flags,
190 attr.validation_function, attr.category)
191 for attr in factory.attributes:
192 if attr.modified or attr.invisible:
193 self._factory_attributes[attr.name] = attr.value
196 return "Box(%s, %s, %s)" % (self.guid, self.factory_id, self.testbed_guid)
203 def factory_id(self):
204 return self._factory_id
207 def testbed_guid(self):
208 return self._testbed_guid
212 return self._container
215 def connectors(self):
216 return self._connectors.values()
220 return self._traces.values()
223 def trace_names(self):
224 return self._traces.keys()
227 def factory_attributes(self):
228 return self._factory_attributes
234 def trace_help(self, trace_id):
235 return self._traces[trace_id].help
237 def enable_trace(self, trace_id):
238 self._traces[trace_id].enable()
240 def disable_trace(self, trace_id):
241 self._traces[trace_id].disable()
243 def is_trace_enabled(self, trace_id):
244 return self._traces[trace_id].enabled
246 def connector(self, name):
247 return self._connectors[name]
250 super(Box, self).destroy()
251 for c in self.connectors:
253 for t in self.traces:
255 self._connectors = self._traces = self._factory_attributes = None
257 class AddressableMixin(object):
258 def __init__(self, guid, factory, testbed_guid, container = None):
259 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
261 max_addr = self._factory_attributes["MaxAddresses"] \
262 if "MaxAddresses" in self._factory_attributes else 1
263 self._max_addresses = max_addr
264 self._addresses = list()
268 return self._addresses
271 def max_addresses(self):
272 return self._max_addresses
274 class UserAddressableMixin(AddressableMixin):
275 def __init__(self, guid, factory, testbed_guid, container = None):
276 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
279 def add_address(self):
280 if len(self._addresses) == self.max_addresses:
281 raise RuntimeError("Maximun number of addresses for this box reached.")
283 self._addresses.append(address)
286 def delete_address(self, address):
287 self._addresses.remove(address)
291 super(UserAddressableMixin, self).destroy()
292 for address in list(self.addresses):
293 self.delete_address(address)
294 self._addresses = None
296 class RoutableMixin(object):
297 def __init__(self, guid, factory, testbed_guid, container = None):
298 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
300 self._routes = list()
306 class UserRoutableMixin(RoutableMixin):
307 def __init__(self, guid, factory, testbed_guid, container = None):
308 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
313 self._routes.append(route)
316 def delete_route(self, route):
317 self._routes.remove(route)
321 super(UserRoutableMixin, self).destroy()
322 for route in list(self.routes):
323 self.delete_route(route)
326 def MixIn(MyClass, MixIn):
327 # Mixins are installed BEFORE "Box" because
328 # Box inherits from non-cooperative classes,
329 # so the MRO chain gets broken when it gets
333 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
336 # Somehow it doesn't work automatically
337 for name in dir(MixIn):
338 prop = getattr(MixIn,name,None)
339 if isinstance(prop, property):
340 setattr(MyClass, name, prop)
343 MyClass.__name__ = MyClass.__name__.replace(
345 MixIn.__name__.replace('MixIn','')+'Box',
348 class Factory(AttributesMap):
349 _box_class_cache = {}
351 def __init__(self, factory_id,
352 allow_addresses = False, has_addresses = False,
353 allow_routes = False, has_routes = False,
354 Help = None, category = None):
355 super(Factory, self).__init__()
356 self._factory_id = factory_id
357 self._allow_addresses = bool(allow_addresses)
358 self._allow_routes = bool(allow_routes)
359 self._has_addresses = bool(allow_addresses) or self._allow_addresses
360 self._has_routes = bool(allow_routes) or self._allow_routes
362 self._category = category
363 self._connector_types = list()
364 self._traces = list()
366 self._box_attributes = AttributesMap()
368 if not self._has_addresses and not self._has_routes:
371 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
372 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
373 key = addresses+routes
375 if key in self._box_class_cache:
376 self._factory = self._box_class_cache[key]
380 def __init__(self, guid, factory, testbed_guid, container = None):
381 super(_factory, self).__init__(guid, factory, testbed_guid, container)
383 # Add mixins, one by one
385 MixIn(_factory, UserAddressableMixin)
387 MixIn(_factory, AddressableMixin)
390 MixIn(_factory, UserRoutableMixin)
392 MixIn(_factory, RoutableMixin)
395 self._box_class_cache[key] = self._factory = _factory
398 def factory_id(self):
399 return self._factory_id
402 def allow_addresses(self):
403 return self._allow_addresses
406 def allow_routes(self):
407 return self._allow_routes
410 def has_addresses(self):
411 return self._has_addresses
414 def has_routes(self):
415 return self._has_routes
423 return self._category
426 def connector_types(self):
427 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_tag(self, tag_id):
449 self._tags.append(tag_id)
451 def add_box_attribute(self, name, help, type, value = None, range = None,
452 allowed = None, flags = Attribute.NoFlags, validation_function = None,
454 self._box_attributes.add_attribute(name, help, type, value, range,
455 allowed, flags, validation_function, category)
457 def create(self, guid, testbed_description):
458 return self._factory(guid, self, testbed_description.guid)
461 super(Factory, self).destroy()
462 self._connector_types = None
464 class FactoriesProvider(object):
465 def __init__(self, testbed_id, testbed_version):
466 super(FactoriesProvider, self).__init__()
467 self._testbed_id = testbed_id
468 self._testbed_version = testbed_version
469 self._factories = dict()
471 metadata = Metadata(testbed_id, testbed_version)
472 for factory in metadata.build_design_factories():
473 self.add_factory(factory)
476 def testbed_id(self):
477 return self._testbed_id
480 def testbed_version(self):
481 return self._testbed_version
485 return self._factories.values()
487 def factory(self, factory_id):
488 return self._factories[factory_id]
490 def add_factory(self, factory):
491 self._factories[factory.factory_id] = factory
493 def remove_factory(self, factory_id):
494 del self._factories[factory_id]
496 class TestbedDescription(AttributesMap):
497 def __init__(self, guid_generator, provider, guid = None):
498 super(TestbedDescription, self).__init__()
499 self._guid_generator = guid_generator
500 self._guid = guid_generator.next(guid)
501 self._provider = provider
503 self.graphical_info = GraphicalInfo()
505 metadata = Metadata(provider.testbed_id, provider.testbed_version)
506 for attr in metadata.testbed_attributes().attributes:
507 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
508 attr.range, attr.allowed, attr.flags,
509 attr.validation_function, attr.category)
517 return self._provider
521 return self._boxes.values()
524 return self._boxes[guid] if guid in self._boxes else None
526 def create(self, factory_id, guid = None):
527 guid = self._guid_generator.next(guid)
528 factory = self._provider.factory(factory_id)
529 box = factory.create(guid, self)
530 self._boxes[guid] = box
533 def delete(self, guid):
534 box = self._boxes[guid]
535 del self._boxes[guid]
539 for guid, box in self._boxes.iteritems():
543 class ExperimentDescription(object):
545 self._guid_generator = GuidGenerator()
546 self._testbed_descriptions = dict()
549 def testbed_descriptions(self):
550 return self._testbed_descriptions.values()
553 parser = XmlExperimentParser()
554 return parser.to_xml(self)
556 def from_xml(self, xml):
557 parser = XmlExperimentParser()
558 parser.from_xml(self, xml)
560 def testbed_description(self, guid):
561 return self._testbed_descriptions[guid] \
562 if guid in self._testbed_descriptions else None
565 for testbed_description in self._testbed_descriptions.values():
566 box = testbed_description.box(guid)
570 def get_element(self, guid):
571 if guid in self._testbed_descriptions:
572 return self._testbed_descriptions[guid]
573 for testbed_description in self._testbed_descriptions.values():
574 box = testbed_description.box(guid)
578 def add_testbed_description(self, provider, guid = None):
579 testbed_description = TestbedDescription(self._guid_generator,
581 guid = testbed_description.guid
582 self._testbed_descriptions[guid] = testbed_description
583 return testbed_description
585 def remove_testbed_description(self, guid):
586 testbed_description = self._testbed_descriptions[guid]
587 del self._testbed_descriptions[guid]
588 testbed_description.destroy()
591 for testbed_description in self.testbed_descriptions:
592 testbed_description.destroy()