2 # -*- coding: utf-8 -*-
8 from nepi.core.attributes import AttributesMap, Attribute
9 from nepi.core.metadata import Metadata
10 from nepi.util import validation
11 from nepi.util.guid import GuidGenerator
12 from nepi.util.graphical_info import GraphicalInfo
13 from nepi.util.parser._xml import XmlExperimentParser
16 class ConnectorType(object):
17 def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
18 super(ConnectorType, self).__init__()
23 "The maximum number of connections allowed need to be more than 0")
26 "The minimum number of connections allowed needs to be at least 0")
27 # connector_type_id -- univoquely identifies a connector type
29 self._connector_type_id = (testbed_id.lower(), factory_id.lower(),
31 # name -- display name for the connector type
35 # max -- maximum amount of connections that this type support,
38 # min -- minimum amount of connections required by this type of connector
40 # allowed_connections -- keys in the dictionary correspond to the
41 # connector_type_id for possible connections. The value indicates if
42 # the connection is allowed accros different testbed instances
43 self._allowed_connections = dict()
46 def connector_type_id(self):
47 return self._connector_type_id
65 def add_allowed_connection(self, testbed_id, factory_id, name, can_cross):
66 self._allowed_connections[(testbed_id.lower(),
67 factory_id.lower(), name.lower())] = can_cross
69 def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2):
70 if not connector_type_id in self._allowed_connections.keys():
72 can_cross = self._allowed_connections[connector_type_id]
73 return can_cross or (testbed_guid1 == testbed_guid2)
75 class Connector(object):
76 """A Connector sepcifies the connection points in an Object"""
77 def __init__(self, box, connector_type):
78 super(Connector, self).__init__()
80 self._connector_type = connector_type
81 self._connections = list()
88 def connector_type(self):
89 return self._connector_type
92 def connections(self):
93 return self._connections
96 """Return True if the connector has the maximum number of connections
98 return len(self.connections) == self.connector_type.max
100 def is_complete(self):
101 """Return True if the connector has the minimum number of connections
103 return len(self.connections) >= self.connector_type.min
105 def is_connected(self, connector):
106 return connector in self._connections
108 def connect(self, connector):
109 if not self.can_connect(connector) or not connector.can_connect(self):
110 raise RuntimeError("Could not connect.")
111 self._connections.append(connector)
112 connector._connections.append(self)
114 def disconnect(self, connector):
115 if connector not in self._connections or\
116 self not in connector._connections:
117 raise RuntimeError("Could not disconnect.")
118 self._connections.remove(connector)
119 connector._connections.remove(self)
121 def can_connect(self, connector):
122 if self.is_full() or connector.is_full():
124 if self.is_connected(connector):
126 connector_type_id = connector.connector_type.connector_type_id
127 testbed_guid1 = self.box.testbed_guid
128 testbed_guid2 = connector.box.testbed_guid
129 return self.connector_type.can_connect(connector_type_id,
130 testbed_guid1, testbed_guid2)
133 for connector in self.connections:
134 self.disconnect(connector)
135 self._box = self._connectors = None
137 class Trace(AttributesMap):
138 def __init__(self, trace_id, help, enabled = False):
139 super(Trace, self).__init__()
140 self._trace_id = trace_id
142 self.enabled = enabled
146 return self._trace_id
152 class Address(AttributesMap):
154 super(Address, self).__init__()
155 self.add_attribute(name = "AutoConfigure",
156 help = "If set, this address will automatically be assigned",
157 type = Attribute.BOOL,
159 flags = Attribute.DesignOnly,
160 validation_function = validation.is_bool)
161 self.add_attribute(name = "Address",
162 help = "Address number",
163 type = Attribute.STRING,
164 flags = Attribute.HasNoDefaultValue,
165 validation_function = validation.is_ip_address)
166 self.add_attribute(name = "NetPrefix",
167 help = "Network prefix for the address",
168 type = Attribute.INTEGER,
171 flags = Attribute.HasNoDefaultValue,
172 validation_function = validation.is_integer)
173 self.add_attribute(name = "Broadcast",
174 help = "Broadcast address",
175 type = Attribute.STRING,
176 validation_function = validation.is_ip4_address)
178 class Route(AttributesMap):
180 super(Route, self).__init__()
181 self.add_attribute(name = "Destination",
182 help = "Network destintation",
183 type = Attribute.STRING,
184 validation_function = validation.is_ip_address)
185 self.add_attribute(name = "NetPrefix",
186 help = "Network destination prefix",
187 type = Attribute.INTEGER,
188 prefix_range = (0,128),
190 flags = Attribute.HasNoDefaultValue,
191 validation_function = validation.is_integer)
192 self.add_attribute(name = "NextHop",
193 help = "Address for the next hop",
194 type = Attribute.STRING,
195 flags = Attribute.HasNoDefaultValue,
196 validation_function = validation.is_ip_address)
198 class Box(AttributesMap):
199 def __init__(self, guid, factory, testbed_guid, container = None):
200 super(Box, self).__init__()
201 # guid -- global unique identifier
203 # factory_id -- factory identifier or name
204 self._factory_id = factory.factory_id
205 # testbed_guid -- parent testbed guid
206 self._testbed_guid = testbed_guid
207 # container -- boxes can be nested inside other 'container' boxes
208 self._container = container
209 # traces -- list of available traces for the box
210 self._traces = dict()
211 # connectors -- list of available connectors for the box
212 self._connectors = dict()
213 # factory_attributes -- factory attributes for box construction
214 self._factory_attributes = dict()
215 # graphical_info -- GUI position information
216 self.graphical_info = GraphicalInfo(str(self._guid))
218 for connector_type in factory.connector_types:
219 connector = Connector(self, connector_type)
220 self._connectors[connector_type.name] = connector
221 for trace in factory.traces:
222 tr = Trace(trace.trace_id, trace.help, trace.enabled)
223 self._traces[trace.trace_id] = tr
224 for attr in factory.box_attributes.attributes:
225 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
226 attr.range, attr.allowed, attr.flags,
227 attr.validation_function)
228 for attr in factory.attributes:
230 self._factory_attributes[attr.name] = attr.value
237 def factory_id(self):
238 return self._factory_id
241 def testbed_guid(self):
242 return self._testbed_guid
246 return self._container
249 def connectors(self):
250 return self._connectors.values()
254 return self._traces.values()
257 def traces_name(self):
258 return self._traces.keys()
261 def factory_attributes(self):
262 return self._factory_attributes
272 def trace_help(self, trace_id):
273 return self._traces[trace_id].help
275 def enable_trace(self, trace_id):
276 self._traces[trace_id].enabled = True
278 def disable_trace(self, trace_id):
279 self._traces[trace_id].enabled = False
281 def connector(self, name):
282 return self._connectors[name]
285 super(Box, self).destroy()
286 for c in self.connectors:
288 for t in self.traces:
290 self._connectors = self._traces = self._factory_attributes = None
292 class AddressableMixin(object):
293 def __init__(self, guid, factory, testbed_guid, container = None):
294 super(AddressableMixin, self).__init__(guid, factory, testbed_guid,
296 self._max_addresses = 1 # TODO: How to make this configurable!
297 self._addresses = list()
301 return self._addresses
304 def max_addresses(self):
305 return self._max_addresses
307 class UserAddressableMixin(AddressableMixin):
308 def __init__(self, guid, factory, testbed_guid, container = None):
309 super(UserAddressableMixin, self).__init__(guid, factory, testbed_guid,
312 def add_address(self):
313 if len(self._addresses) == self.max_addresses:
314 raise RuntimeError("Maximun number of addresses for this box reached.")
316 self._addresses.append(address)
319 def delete_address(self, address):
320 self._addresses.remove(address)
324 super(UserAddressableMixin, self).destroy()
325 for address in list(self.addresses):
326 self.delete_address(address)
327 self._addresses = None
329 class RoutableMixin(object):
330 def __init__(self, guid, factory, testbed_guid, container = None):
331 super(RoutableMixin, self).__init__(guid, factory, testbed_guid,
333 self._routes = list()
339 class UserRoutableMixin(RoutableMixin):
340 def __init__(self, guid, factory, testbed_guid, container = None):
341 super(UserRoutableMixin, self).__init__(guid, factory, testbed_guid,
346 self._routes.append(route)
349 def delete_route(self, route):
350 self._route.remove(route)
354 super(UserRoutableMixin, self).destroy()
355 for route in list(self.routes):
356 self.delete_route(route)
359 def MixIn(MyClass, MixIn):
360 # Mixins are installed BEFORE "Box" because
361 # Box inherits from non-cooperative classes,
362 # so the MRO chain gets broken when it gets
366 MyClass.__bases__ = (MixIn,) + MyClass.__bases__
369 # Somehow it doesn't work automatically
370 for name in dir(MixIn):
371 prop = getattr(MixIn,name,None)
372 if isinstance(prop, property):
373 setattr(MyClass, name, prop)
376 MyClass.__name__ = MyClass.__name__.replace(
378 MixIn.__name__.replace('MixIn','')+'Box',
381 class Factory(AttributesMap):
382 _box_class_cache = {}
384 def __init__(self, factory_id,
385 allow_addresses = False, has_addresses = False,
386 allow_routes = False, has_routes = False,
387 Help = None, category = None):
388 super(Factory, self).__init__()
389 self._factory_id = factory_id
390 self._allow_addresses = bool(allow_addresses)
391 self._allow_routes = bool(allow_routes)
392 self._has_addresses = bool(allow_addresses) or self._allow_addresses
393 self._has_routes = bool(allow_routes) or self._allow_routes
395 self._category = category
396 self._connector_types = list()
397 self._traces = list()
398 self._box_attributes = AttributesMap()
400 if not self._has_addresses and not self._has_routes:
403 addresses = 'w' if self._allow_addresses else ('r' if self._has_addresses else '-')
404 routes = 'w' if self._allow_routes else ('r' if self._has_routes else '-')
405 key = addresses+routes
407 if key in self._box_class_cache:
408 self._factory = self._box_class_cache[key]
412 def __init__(self, guid, factory, testbed_guid, container = None):
413 super(_factory, self).__init__(guid, factory, testbed_guid, container)
415 # Add mixins, one by one
417 MixIn(_factory, UserAddressableMixin)
419 MixIn(_factory, AddressableMixin)
422 MixIn(_factory, UserRoutableMixin)
424 MixIn(_factory, RoutableMixin)
427 self._box_class_cache[key] = self._factory = _factory
430 def factory_id(self):
431 return self._factory_id
434 def allow_addresses(self):
435 return self._allow_addresses
438 def allow_routes(self):
439 return self._allow_routes
442 def has_addresses(self):
443 return self._has_addresses
446 def has_routes(self):
447 return self._has_routes
455 return self._category
458 def connector_types(self):
459 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_box_attribute(self, name, help, type, value = None, range = None,
477 allowed = None, flags = Attribute.NoFlags, validation_function = None):
478 self._box_attributes.add_attribute(name, help, type, value, range,
479 allowed, flags, validation_function)
481 def create(self, guid, testbed_description):
482 return self._factory(guid, self, testbed_description.guid)
485 super(Factory, self).destroy()
486 self._connector_types = None
488 class FactoriesProvider(object):
489 def __init__(self, testbed_id, testbed_version):
490 super(FactoriesProvider, self).__init__()
491 self._testbed_id = testbed_id
492 self._testbed_version = testbed_version
493 self._factories = dict()
495 metadata = Metadata(testbed_id, testbed_version)
496 for factory in metadata.build_design_factories():
497 self.add_factory(factory)
500 def testbed_id(self):
501 return self._testbed_id
504 def testbed_version(self):
505 return self._testbed_version
509 return self._factories.values()
511 def factory(self, factory_id):
512 return self._factories[factory_id]
514 def add_factory(self, factory):
515 self._factories[factory.factory_id] = factory
517 def remove_factory(self, factory_id):
518 del self._factories[factory_id]
520 class TestbedDescription(AttributesMap):
521 def __init__(self, guid_generator, provider):
522 super(TestbedDescription, self).__init__()
523 self._guid_generator = guid_generator
524 self._guid = guid_generator.next()
525 self._provider = provider
527 self.graphical_info = GraphicalInfo(str(self._guid))
529 metadata = Metadata(provider.testbed_id, provider.testbed_version)
530 for attr in metadata.testbed_attributes().attributes:
531 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
532 attr.range, attr.allowed, attr.flags,
533 attr.validation_function)
541 return self._provider
545 return self._boxes.values()
548 return self._boxes[guid] if guid in self._boxes else None
550 def create(self, factory_id):
551 guid = self._guid_generator.next()
552 factory = self._provider.factory(factory_id)
553 box = factory.create(guid, self)
554 self._boxes[guid] = box
557 def delete(self, guid):
558 box = self._boxes[guid]
559 del self._boxes[guid]
563 for guid, box in self._boxes.iteritems():
567 class ExperimentDescription(object):
568 def __init__(self, guid = 0):
569 self._guid_generator = GuidGenerator(guid)
570 self._testbed_descriptions = dict()
573 def testbed_descriptions(self):
574 return self._testbed_descriptions.values()
577 parser = XmlExperimentParser()
578 return parser.to_xml(self)
580 def from_xml(self, xml):
581 parser = XmlExperimentParser()
582 parser.from_xml(self, xml)
584 def testbed_description(self, guid):
585 return self._testbed_descriptions[guid] \
586 if guid in self._testbed_descriptions else None
589 for testbed_description in self._testbed_descriptions.values():
590 box = testbed_description.box(guid)
594 def add_testbed_description(self, provider):
595 testbed_description = TestbedDescription(self._guid_generator,
597 guid = testbed_description.guid
598 self._testbed_descriptions[guid] = testbed_description
599 return testbed_description
601 def remove_testbed_description(self, testbed_description):
602 guid = testbed_description.guid
603 del self._testbed_descriptions[guid]
606 for testbed_description in self.testbed_descriptions:
607 testbed_description.destroy()
609 # TODO: When the experiment xml is passed to the controller to execute it
610 # NetReferences in the xml need to be solved
612 #targets = re.findall(r"%target:(.*?)%", command)
613 #for target in targets:
615 # (family, address, port) = resolve_netref(target, AF_INET,
616 # self.server.experiment )
617 # command = command.replace("%%target:%s%%" % target, address.address)