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.constants import AF_INET, AF_INET6
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(object):
18 def __init__(self, testbed_id, factory_id, name, help, max = -1, min = 0):
19 super(ConnectorType, self).__init__()
24 "The maximum number of connections allowed need to be more than 0")
27 "The minimum number of connections allowed needs to be at least 0")
28 # connector_type_id -- univoquely identifies a connector type
30 self._connector_type_id = (testbed_id.lower(), factory_id.lower(),
32 # name -- display name for the connector type
36 # max -- maximum amount of connections that this type support,
39 # min -- minimum amount of connections required by this type of connector
41 # allowed_connections -- keys in the dictionary correspond to the
42 # connector_type_id for possible connections. The value indicates if
43 # the connection is allowed accros different testbed instances
44 self._allowed_connections = dict()
47 def connector_type_id(self):
48 return self._connector_type_id
66 def add_allowed_connection(self, testbed_id, factory_id, name, can_cross):
67 self._allowed_connections[(testbed_id.lower(),
68 factory_id.lower(), name.lower())] = can_cross
70 def can_connect(self, connector_type_id, testbed_guid1, testbed_guid2):
71 if not connector_type_id in self._allowed_connections.keys():
73 can_cross = self._allowed_connections[connector_type_id]
74 return can_cross or (testbed_guid1 == testbed_guid2)
76 class Connector(object):
77 """A Connector sepcifies the connection points in an Object"""
78 def __init__(self, box, connector_type):
79 super(Connector, self).__init__()
81 self._connector_type = connector_type
82 self._connections = list()
89 def connector_type(self):
90 return self._connector_type
93 def connections(self):
94 return self._connections
97 """Return True if the connector has the maximum number of connections
99 return len(self.connections) == self.connector_type.max
101 def is_complete(self):
102 """Return True if the connector has the minimum number of connections
104 return len(self.connections) >= self.connector_type.min
106 def is_connected(self, connector):
107 return connector in self._connections
109 def connect(self, connector):
110 if not self.can_connect(connector) or not connector.can_connect(self):
111 raise RuntimeError("Could not connect.")
112 self._connections.append(connector)
113 connector._connections.append(self)
115 def disconnect(self, connector):
116 if connector not in self._connections or\
117 self not in connector._connections:
118 raise RuntimeError("Could not disconnect.")
119 self._connections.remove(connector)
120 connector._connections.remove(self)
122 def can_connect(self, connector):
123 if self.is_full() or connector.is_full():
125 if self.is_connected(connector):
127 connector_type_id = connector.connector_type.connector_type_id
128 testbed_guid1 = self.box.testbed_guid
129 testbed_guid2 = connector.box.testbed_guid
130 return self.connector_type.can_connect(connector_type_id,
131 testbed_guid1, testbed_guid2)
134 for connector in self.connections:
135 self.disconnect(connector)
136 self._box = self._connectors = None
138 class Trace(AttributesMap):
139 def __init__(self, trace_id, help, enabled = False):
140 super(Trace, self).__init__()
141 self._trace_id = trace_id
143 self.enabled = enabled
147 return self._trace_id
153 class Address(AttributesMap):
154 def __init__(self, family):
155 super(Address, self).__init__()
156 self.add_attribute(name = "AutoConfigure",
157 help = "If set, this address will automatically be assigned",
158 type = Attribute.BOOL,
160 flags = Attribute.DesignOnly,
161 validation_function = validation.is_bool)
162 self.add_attribute(name = "Family",
163 help = "Address family type: AF_INET, AFT_INET6",
164 type = Attribute.INTEGER,
166 flags = Attribute.ReadOnly | Attribute.HasNoDefaultValue,
167 validation_function = validation.is_integer)
168 address_validation = validation.is_ip4_address if family == AF_INET \
169 else validation.is_ip6_address
170 self.add_attribute(name = "Address",
171 help = "Address number",
172 type = Attribute.STRING,
173 flags = Attribute.HasNoDefaultValue,
174 validation_function = address_validation)
175 prefix_range = (0, 32) if family == AF_INET else (0, 128)
176 self.add_attribute(name = "NetPrefix",
177 help = "Network prefix for the address",
178 type = Attribute.INTEGER,
179 range = prefix_range,
180 value = 24 if family == AF_INET else 64,
181 flags = Attribute.HasNoDefaultValue,
182 validation_function = validation.is_integer)
183 if family == AF_INET:
184 self.add_attribute(name = "Broadcast",
185 help = "Broadcast address",
186 type = Attribute.STRING,
187 validation_function = validation.is_ip4_address)
189 class Route(AttributesMap):
190 def __init__(self, family):
191 super(Route, self).__init__()
192 self.add_attribute(name = "Family",
193 help = "Address family type: AF_INET, AFT_INET6",
194 type = Attribute.INTEGER,
196 flags = Attribute.ReadOnly | Attribute.HasNoDefaultValue,
197 validation_function = validation.is_integer)
198 address_validation = validation.is_ip4_address if family == AF_INET \
199 else validation.is_ip6_address
200 self.add_attribute(name = "Destination",
201 help = "Network destintation",
202 type = Attribute.STRING,
203 validation_function = address_validation)
204 prefix_range = (0, 32) if family == AF_INET else (0, 128)
205 self.add_attribute(name = "NetPrefix",
206 help = "Network destination prefix",
207 type = Attribute.INTEGER,
208 flags = Attribute.HasNoDefaultValue,
209 prefix_range = prefix_range,
210 validation_function = validation.is_integer)
211 self.add_attribute(name = "NextHop",
212 help = "Address for the next hop",
213 type = Attribute.STRING,
214 flags = Attribute.HasNoDefaultValue,
215 validation_function = address_validation)
217 class Box(AttributesMap):
218 def __init__(self, guid, factory, testbed_guid, container = None):
219 super(Box, self).__init__()
220 # guid -- global unique identifier
222 # factory_id -- factory identifier or name
223 self._factory_id = factory.factory_id
224 # testbed_guid -- parent testbed guid
225 self._testbed_guid = testbed_guid
226 # container -- boxes can be nested inside other 'container' boxes
227 self._container = container
228 # traces -- list of available traces for the box
229 self._traces = dict()
230 # connectors -- list of available connectors for the box
231 self._connectors = dict()
232 # factory_attributes -- factory attributes for box construction
233 self._factory_attributes = dict()
234 # graphical_info -- GUI position information
235 self.graphical_info = GraphicalInfo(str(self._guid))
237 for connector_type in factory.connector_types:
238 connector = Connector(self, connector_type)
239 self._connectors[connector_type.name] = connector
240 for trace in factory.traces:
241 tr = Trace(trace.trace_id, trace.help, trace.enabled)
242 self._traces[trace.trace_id] = tr
243 for attr in factory.box_attributes.attributes:
244 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
245 attr.range, attr.allowed, attr.flags,
246 attr.validation_function)
247 for attr in factory.attributes:
249 self._factory_attributes[attr.name] = attr.value
256 def factory_id(self):
257 return self._factory_id
260 def testbed_guid(self):
261 return self._testbed_guid
265 return self._container
268 def connectors(self):
269 return self._connectors.values()
273 return self._traces.values()
276 def traces_name(self):
277 return self._traces.keys()
280 def factory_attributes(self):
281 return self._factory_attributes
291 def trace_help(self, trace_id):
292 return self._traces[trace_id].help
294 def enable_trace(self, trace_id):
295 self._traces[trace_id].enabled = True
297 def disable_trace(self, trace_id):
298 self._traces[trace_id].enabled = False
300 def connector(self, name):
301 return self._connectors[name]
304 super(Box, self).destroy()
305 for c in self.connectors:
307 for t in self.traces:
309 self._connectors = self._traces = self._factory_attributes = None
311 class AddressableBox(Box):
312 def __init__(self, guid, factory, testbed_guid, container = None):
313 super(AddressableBox, self).__init__(guid, factory, testbed_guid,
315 self._family = factory.get_attribute_value("Family")
316 # maximum number of addresses this box can have
317 self._max_addresses = factory.get_attribute_value("MaxAddresses")
318 self._addresses = list()
322 return self._addresses
325 def max_addresses(self):
326 return self._max_addresses
328 def add_address(self):
329 if len(self._addresses) == self.max_addresses:
330 raise RuntimeError("Maximun number of addresses for this box reached.")
331 address = Address(family = self._family)
332 self._addresses.append(address)
335 def delete_address(self, address):
336 self._addresses.remove(address)
340 super(AddressableBox, self).destroy()
341 for address in self.addresses:
342 self.delete_address(address)
343 self._addresses = None
345 class RoutingTableBox(Box):
346 def __init__(self, guid, factory, container = None):
347 super(RoutingTableBox, self).__init__(guid, factory, container)
348 self._routes = list()
354 def add_route(self, family):
355 route = Route(family = family)
356 self._routes.append(route)
359 def delete_route(self, route):
360 self._route.remove(route)
364 super(RoutingTableBox, self).destroy()
365 for route in self.routes:
366 self.delete_route(route)
369 class Factory(AttributesMap):
370 def __init__(self, factory_id, allow_addresses = False,
371 allow_routes = False, Help = None, category = None):
372 super(Factory, self).__init__()
373 self._factory_id = factory_id
374 self._allow_addresses = (allow_addresses == True)
375 self._allow_routes = (allow_routes == True)
377 self._category = category
378 self._connector_types = list()
379 self._traces = list()
380 self._box_attributes = AttributesMap()
383 def factory_id(self):
384 return self._factory_id
387 def allow_addresses(self):
388 return self._allow_addresses
391 def allow_routes(self):
392 return self._allow_routes
400 return self._category
403 def connector_types(self):
404 return self._connector_types
411 def box_attributes(self):
412 return self._box_attributes
414 def add_connector_type(self, connector_type):
415 self._connector_types.append(connector_type)
417 def add_trace(self, trace_id, help, enabled = False):
418 trace = Trace(trace_id, help, enabled)
419 self._traces.append(trace)
421 def add_box_attribute(self, name, help, type, value = None, range = None,
422 allowed = None, flags = Attribute.NoFlags, validation_function = None):
423 self._box_attributes.add_attribute(name, help, type, value, range,
424 allowed, flags, validation_function)
426 def create(self, guid, testbed_description):
427 if self._allow_addresses:
428 return AddressableBox(guid, self, testbed_description.guid)
429 elif self._allow_routes:
430 return RoutingTableBox(guid, self, testbed_description.guid)
432 return Box(guid, self, testbed_description.guid)
435 super(Factory, self).destroy()
436 self._connector_types = None
438 class FactoriesProvider(object):
439 def __init__(self, testbed_id, testbed_version):
440 super(FactoriesProvider, self).__init__()
441 self._testbed_id = testbed_id
442 self._testbed_version = testbed_version
443 self._factories = dict()
445 metadata = Metadata(testbed_id, testbed_version)
446 for factory in metadata.build_design_factories():
447 self.add_factory(factory)
450 def testbed_id(self):
451 return self._testbed_id
454 def testbed_version(self):
455 return self._testbed_version
459 return self._factories.values()
461 def factory(self, factory_id):
462 return self._factories[factory_id]
464 def add_factory(self, factory):
465 self._factories[factory.factory_id] = factory
467 def remove_factory(self, factory_id):
468 del self._factories[factory_id]
470 class TestbedDescription(AttributesMap):
471 def __init__(self, guid_generator, provider):
472 super(TestbedDescription, self).__init__()
473 self._guid_generator = guid_generator
474 self._guid = guid_generator.next()
475 self._provider = provider
477 self.graphical_info = GraphicalInfo(str(self._guid))
479 metadata = Metadata(provider.testbed_id, provider.testbed_version)
480 for attr in metadata.testbed_attributes().attributes:
481 self.add_attribute(attr.name, attr.help, attr.type, attr.value,
482 attr.range, attr.allowed, attr.flags,
483 attr.validation_function)
491 return self._provider
495 return self._boxes.values()
498 return self._boxes[guid] if guid in self._boxes else None
500 def create(self, factory_id):
501 guid = self._guid_generator.next()
502 factory = self._provider.factory(factory_id)
503 box = factory.create(guid, self)
504 self._boxes[guid] = box
507 def delete(self, guid):
508 box = self._boxes[guid]
509 del self._boxes[guid]
513 for guid, box in self._boxes.iteritems():
517 class ExperimentDescription(object):
518 def __init__(self, guid = 0):
519 self._guid_generator = GuidGenerator(guid)
520 self._testbed_descriptions = dict()
523 def testbed_descriptions(self):
524 return self._testbed_descriptions.values()
527 parser = XmlExperimentParser()
528 return parser.to_xml(self)
530 def from_xml(self, xml):
531 parser = XmlExperimentParser()
532 parser.from_xml(self, xml)
534 def testbed_description(self, guid):
535 return self._testbed_descriptions[guid] \
536 if guid in self._testbed_descriptions else None
539 for testbed_description in self._testbed_descriptions.values():
540 box = testbed_description.box(guid)
544 def add_testbed_description(self, provider):
545 testbed_description = TestbedDescription(self._guid_generator,
547 guid = testbed_description.guid
548 self._testbed_descriptions[guid] = testbed_description
549 return testbed_description
551 def remove_testbed_description(self, testbed_description):
552 guid = testbed_description.guid
553 del self._testbed_descriptions[guid]
556 for testbed_description in self.testbed_descriptions:
557 testbed_description.destroy()
559 # TODO: When the experiment xml is passed to the controller to execute it
560 # NetReferences in the xml need to be solved
562 #targets = re.findall(r"%target:(.*?)%", command)
563 #for target in targets:
565 # (family, address, port) = resolve_netref(target, AF_INET,
566 # self.server.experiment )
567 # command = command.replace("%%target:%s%%" % target, address.address)