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()
61 def connector_type(self):
62 return self._connector_type
65 def connections(self):
66 return self._connections
69 """Return True if the connector has the maximum number of connections
71 return len(self.connections) == self.connector_type.max
73 def is_complete(self):
74 """Return True if the connector has the minimum number of connections
76 return len(self.connections) >= self.connector_type.min
78 def is_connected(self, connector):
79 return connector in self._connections
81 def connect(self, connector):
82 if not self.can_connect(connector) or not connector.can_connect(self):
83 raise RuntimeError("Could not connect.")
84 self._connections.append(connector)
85 connector._connections.append(self)
87 def disconnect(self, connector):
88 if connector not in self._connections or\
89 self not in connector._connections:
90 raise RuntimeError("Could not disconnect.")
91 self._connections.remove(connector)
92 connector._connections.remove(self)
94 def can_connect(self, connector):
95 if self.is_full() or connector.is_full():
97 if self.is_connected(connector):
99 connector_type_id = connector.connector_type.connector_type_id
100 testbed_guid1 = self.box.testbed_guid
101 testbed_guid2 = connector.box.testbed_guid
102 return self.connector_type.can_connect(connector_type_id,
103 testbed_guid1, testbed_guid2)
106 for connector in self.connections:
107 self.disconnect(connector)
108 self._box = self._connectors = None
110 class Trace(AttributesMap):
111 def __init__(self, trace_id, help, enabled = False):
112 super(Trace, self).__init__()
113 self._trace_id = trace_id
115 self.enabled = enabled
119 return self._trace_id
125 class Address(AttributesMap):
127 super(Address, self).__init__()
128 self.add_attribute(name = "AutoConfigure",
129 help = "If set, this address will automatically be assigned",
130 type = Attribute.BOOL,
132 flags = Attribute.DesignOnly,
133 validation_function = validation.is_bool)
134 self.add_attribute(name = "Address",
135 help = "Address number",
136 type = Attribute.STRING,
137 flags = Attribute.HasNoDefaultValue,
138 validation_function = validation.is_ip_address)
139 self.add_attribute(name = "NetPrefix",
140 help = "Network prefix for the address",
141 type = Attribute.INTEGER,
144 flags = Attribute.HasNoDefaultValue,
145 validation_function = validation.is_integer)
146 self.add_attribute(name = "Broadcast",
147 help = "Broadcast address",
148 type = Attribute.STRING,
149 validation_function = validation.is_ip4_address)
151 class Route(AttributesMap):
153 super(Route, self).__init__()
154 self.add_attribute(name = "Destination",
155 help = "Network destintation",
156 type = Attribute.STRING,
157 validation_function = validation.is_ip_address)
158 self.add_attribute(name = "NetPrefix",
159 help = "Network destination prefix",
160 type = Attribute.INTEGER,
163 flags = Attribute.HasNoDefaultValue,
164 validation_function = validation.is_integer)
165 self.add_attribute(name = "NextHop",
166 help = "Address for the next hop",
167 type = Attribute.STRING,
168 flags = Attribute.HasNoDefaultValue,
169 validation_function = validation.is_ip_address)
171 class Box(AttributesMap):
172 def __init__(self, guid, factory, testbed_guid, container = None):
173 super(Box, self).__init__()
174 # guid -- global unique identifier
176 # factory_id -- factory identifier or name
177 self._factory_id = factory.factory_id
178 # testbed_guid -- parent testbed guid
179 self._testbed_guid = testbed_guid
180 # container -- boxes can be nested inside other 'container' boxes
181 self._container = container
182 # traces -- list of available traces for the box
183 self._traces = dict()
184 # connectors -- list of available connectors for the box
185 self._connectors = dict()
186 # factory_attributes -- factory attributes for box construction
187 self._factory_attributes = dict()
188 # graphical_info -- GUI position information
189 self.graphical_info = GraphicalInfo(str(self._guid))
191 for connector_type in factory.connector_types:
192 connector = Connector(self, connector_type)
193 self._connectors[connector_type.name] = connector
194 for trace in factory.traces:
195 tr = Trace(trace.trace_id, trace.help, trace.enabled)
196 self._traces[trace.trace_id] = tr
197 for attr in factory.box_attributes.attributes:
198 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
199 attr.range, attr.allowed, attr.flags,
200 attr.validation_function)
201 for attr in factory.attributes:
203 self._factory_attributes[attr.name] = attr.value
210 def factory_id(self):
211 return self._factory_id
214 def testbed_guid(self):
215 return self._testbed_guid
219 return self._container
222 def connectors(self):
223 return self._connectors.values()
227 return self._traces.values()
230 def traces_name(self):
231 return self._traces.keys()
234 def factory_attributes(self):
235 return self._factory_attributes
245 def trace_help(self, trace_id):
246 return self._traces[trace_id].help
248 def enable_trace(self, trace_id):
249 self._traces[trace_id].enabled = True
251 def disable_trace(self, trace_id):
252 self._traces[trace_id].enabled = False
254 def connector(self, name):
255 return self._connectors[name]
258 super(Box, self).destroy()
259 for c in self.connectors:
261 for t in self.traces:
263 self._connectors = self._traces = self._factory_attributes = None
265 class AddressableMixin(object):
266 def __init__(self, guid, factory, testbed_guid, container = None):
267 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
269 self._max_addresses = 1 # TODO: How to make this configurable!
270 self._addresses = list()
274 return self._addresses
277 def max_addresses(self):
278 return self._max_addresses
280 class UserAddressableMixin(AddressableMixin):
281 def __init__(self, guid, factory, testbed_guid, container = None):
282 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
285 def add_address(self):
286 if len(self._addresses) == self.max_addresses:
287 raise RuntimeError("Maximun number of addresses for this box reached.")
289 self._addresses.append(address)
292 def delete_address(self, address):
293 self._addresses.remove(address)
297 super(UserAddressableMixin, self).destroy()
298 for address in list(self.addresses):
299 self.delete_address(address)
300 self._addresses = None
302 class RoutableMixin(object):
303 def __init__(self, guid, factory, testbed_guid, container = None):
304 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
306 self._routes = list()
312 class UserRoutableMixin(RoutableMixin):
313 def __init__(self, guid, factory, testbed_guid, container = None):
314 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
319 self._routes.append(route)
322 def delete_route(self, route):
323 self._route.remove(route)
327 super(UserRoutableMixin, self).destroy()
328 for route in list(self.routes):
329 self.delete_route(route)
332 def MixIn(MyClass, MixIn):
333 # Mixins are installed BEFORE "Box" because
334 # Box inherits from non-cooperative classes,
335 # so the MRO chain gets broken when it gets
339 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
342 # Somehow it doesn't work automatically
343 for name in dir(MixIn):
344 prop = getattr(MixIn,name,None)
345 if isinstance(prop, property):
346 setattr(MyClass, name, prop)
349 MyClass.__name__ = MyClass.__name__.replace(
351 MixIn.__name__.replace('MixIn','')+'Box',
354 class Factory(AttributesMap):
355 _box_class_cache = {}
357 def __init__(self, factory_id,
358 allow_addresses = False, has_addresses = False,
359 allow_routes = False, has_routes = False,
360 Help = None, category = None):
361 super(Factory, self).__init__()
362 self._factory_id = factory_id
363 self._allow_addresses = bool(allow_addresses)
364 self._allow_routes = bool(allow_routes)
365 self._has_addresses = bool(allow_addresses) or self._allow_addresses
366 self._has_routes = bool(allow_routes) or self._allow_routes
368 self._category = category
369 self._connector_types = list()
370 self._traces = list()
371 self._box_attributes = AttributesMap()
373 if not self._has_addresses and not self._has_routes:
376 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
377 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
378 key = addresses+routes
380 if key in self._box_class_cache:
381 self._factory = self._box_class_cache[key]
385 def __init__(self, guid, factory, testbed_guid, container = None):
386 super(_factory, self).__init__(guid, factory, testbed_guid, container)
388 # Add mixins, one by one
390 MixIn(_factory, UserAddressableMixin)
392 MixIn(_factory, AddressableMixin)
395 MixIn(_factory, UserRoutableMixin)
397 MixIn(_factory, RoutableMixin)
400 self._box_class_cache[key] = self._factory = _factory
403 def factory_id(self):
404 return self._factory_id
407 def allow_addresses(self):
408 return self._allow_addresses
411 def allow_routes(self):
412 return self._allow_routes
415 def has_addresses(self):
416 return self._has_addresses
419 def has_routes(self):
420 return self._has_routes
428 return self._category
431 def connector_types(self):
432 return self._connector_types
439 def box_attributes(self):
440 return self._box_attributes
442 def add_connector_type(self, connector_type):
443 self._connector_types.append(connector_type)
445 def add_trace(self, trace_id, help, enabled = False):
446 trace = Trace(trace_id, help, enabled)
447 self._traces.append(trace)
449 def add_box_attribute(self, name, help, type, value = None, range = None,
450 allowed = None, flags = Attribute.NoFlags, validation_function = None):
451 self._box_attributes.add_attribute(name, help, type, value, range,
452 allowed, flags, validation_function)
454 def create(self, guid, testbed_description):
455 return self._factory(guid, self, testbed_description.guid)
458 super(Factory, self).destroy()
459 self._connector_types = None
461 class FactoriesProvider(object):
462 def __init__(self, testbed_id, testbed_version):
463 super(FactoriesProvider, self).__init__()
464 self._testbed_id = testbed_id
465 self._testbed_version = testbed_version
466 self._factories = dict()
468 metadata = Metadata(testbed_id, testbed_version)
469 for factory in metadata.build_design_factories():
470 self.add_factory(factory)
473 def testbed_id(self):
474 return self._testbed_id
477 def testbed_version(self):
478 return self._testbed_version
482 return self._factories.values()
484 def factory(self, factory_id):
485 return self._factories[factory_id]
487 def add_factory(self, factory):
488 self._factories[factory.factory_id] = factory
490 def remove_factory(self, factory_id):
491 del self._factories[factory_id]
493 class TestbedDescription(AttributesMap):
494 def __init__(self, guid_generator, provider):
495 super(TestbedDescription, self).__init__()
496 self._guid_generator = guid_generator
497 self._guid = guid_generator.next()
498 self._provider = provider
500 self.graphical_info = GraphicalInfo(str(self._guid))
502 metadata = Metadata(provider.testbed_id, provider.testbed_version)
503 for attr in metadata.testbed_attributes().attributes:
504 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
505 attr.range, attr.allowed, attr.flags,
506 attr.validation_function)
514 return self._provider
518 return self._boxes.values()
521 return self._boxes[guid] if guid in self._boxes else None
523 def create(self, factory_id):
524 guid = self._guid_generator.next()
525 factory = self._provider.factory(factory_id)
526 box = factory.create(guid, self)
527 self._boxes[guid] = box
530 def delete(self, guid):
531 box = self._boxes[guid]
532 del self._boxes[guid]
536 for guid, box in self._boxes.iteritems():
540 class ExperimentDescription(object):
541 def __init__(self, guid = 0):
542 self._guid_generator = GuidGenerator(guid)
543 self._testbed_descriptions = dict()
546 def testbed_descriptions(self):
547 return self._testbed_descriptions.values()
550 parser = XmlExperimentParser()
551 return parser.to_xml(self)
553 def from_xml(self, xml):
554 parser = XmlExperimentParser()
555 parser.from_xml(self, xml)
557 def testbed_description(self, guid):
558 return self._testbed_descriptions[guid] \
559 if guid in self._testbed_descriptions else None
562 for testbed_description in self._testbed_descriptions.values():
563 box = testbed_description.box(guid)
567 def add_testbed_description(self, provider):
568 testbed_description = TestbedDescription(self._guid_generator,
570 guid = testbed_description.guid
571 self._testbed_descriptions[guid] = testbed_description
572 return testbed_description
574 def remove_testbed_description(self, testbed_description):
575 guid = testbed_description.guid
576 del self._testbed_descriptions[guid]
579 for testbed_description in self.testbed_descriptions:
580 testbed_description.destroy()
582 # TODO: When the experiment xml is passed to the controller to execute it
583 # NetReferences in the xml need to be solved
585 #targets = re.findall(r"%target:(.*?)%", command)
586 #for target in targets:
588 # (family, address, port) = resolve_netref(target, AF_INET,
589 # self.server.experiment )
590 # command = command.replace("%%target:%s%%" % target, address.address)