2 # -*- coding: utf-8 -*-
4 from nepi.core import execute
5 from nepi.core.metadata import Metadata
6 from nepi.util import validation
7 from nepi.util.constants import STATUS_UNDETERMINED, TIME_NOW, \
9 TESTBED_STATUS_SETUP, \
10 TESTBED_STATUS_CREATED, \
11 TESTBED_STATUS_CONNECTED, \
12 TESTBED_STATUS_CROSS_CONNECTED, \
13 TESTBED_STATUS_CONFIGURED, \
14 TESTBED_STATUS_STARTED, \
15 TESTBED_STATUS_STOPPED
19 class TestbedController(execute.TestbedController):
20 def __init__(self, testbed_id, testbed_version):
21 super(TestbedController, self).__init__(testbed_id, testbed_version)
22 self._status = TESTBED_STATUS_ZERO
23 # testbed attributes for validation
24 self._attributes = None
25 # element factories for validation
26 self._factories = dict()
28 # experiment construction instructions
30 self._create_set = dict()
31 self._factory_set = dict()
32 self._connect = dict()
33 self._cross_connect = dict()
34 self._add_trace = dict()
35 self._add_address = dict()
36 self._add_route = dict()
37 self._configure = dict()
39 # log of set operations
44 # testbed element instances
45 self._elements = dict()
47 self._metadata = Metadata(self._testbed_id, self._testbed_version)
48 for factory in self._metadata.build_execute_factories():
49 self._factories[factory.factory_id] = factory
50 self._attributes = self._metadata.testbed_attributes()
51 self._root_directory = None
54 def root_directory(self):
55 return self._root_directory
59 return self._create.keys()
65 def _get_factory_id(self, guid):
66 """ Returns the factory ID of the (perhaps not yet) created object """
67 return self._create.get(guid, None)
69 def defer_configure(self, name, value):
70 if not self._attributes.has_attribute(name):
71 raise AttributeError("Invalid attribute %s for testbed" % name)
73 self._attributes.set_attribute_value(name, value)
74 self._configure[name] = value
76 def defer_create(self, guid, factory_id):
77 if factory_id not in self._factories:
78 raise AttributeError("Invalid element type %s for testbed version %s" %
79 (factory_id, self._testbed_version))
80 if guid in self._create:
81 raise AttributeError("Cannot add elements with the same guid: %d" %
83 self._create[guid] = factory_id
85 def defer_create_set(self, guid, name, value):
86 if not guid in self._create:
87 raise RuntimeError("Element guid %d doesn't exist" % guid)
88 factory = self._get_factory(guid)
89 if not factory.box_attributes.has_attribute(name):
90 raise AttributeError("Invalid attribute %s for element type %s" %
91 (name, factory.factory_id))
92 if not factory.box_attributes.is_attribute_value_valid(name, value):
93 raise AttributeError("Invalid value %s for attribute %s" % \
95 if guid not in self._create_set:
96 self._create_set[guid] = dict()
97 self._create_set[guid][name] = value
99 def defer_factory_set(self, guid, name, value):
100 if not guid in self._create:
101 raise RuntimeError("Element guid %d doesn't exist" % guid)
102 factory = self._get_factory(guid)
103 if not factory.has_attribute(name):
104 raise AttributeError("Invalid attribute %s for element type %s" %
105 (name, factory.factory_id))
106 if not factory.is_attribute_value_valid(name, value):
107 raise AttributeError("Invalid value %s for attribute %s" % \
109 if guid not in self._factory_set:
110 self._factory_set[guid] = dict()
111 self._factory_set[guid][name] = value
113 def defer_connect(self, guid1, connector_type_name1, guid2,
114 connector_type_name2):
115 factory1 = self._get_factory(guid1)
116 factory_id2 = self._create[guid2]
117 count = self._get_connection_count(guid1, connector_type_name1)
118 connector_type = factory1.connector_type(connector_type_name1)
119 connector_type.can_connect(self._testbed_id, factory_id2,
120 connector_type_name2, count, False)
121 if not guid1 in self._connect:
122 self._connect[guid1] = dict()
123 if not connector_type_name1 in self._connect[guid1]:
124 self._connect[guid1][connector_type_name1] = dict()
125 self._connect[guid1][connector_type_name1][guid2] = \
127 if not guid2 in self._connect:
128 self._connect[guid2] = dict()
129 if not connector_type_name2 in self._connect[guid2]:
130 self._connect[guid2][connector_type_name2] = dict()
131 self._connect[guid2][connector_type_name2][guid1] = \
134 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
135 cross_testbed_guid, cross_testbed_id, cross_factory_id,
136 cross_connector_type_name):
137 factory = self._get_factory(guid)
138 count = self._get_connection_count(guid, connector_type_name)
139 connector_type = factory.connector_type(connector_type_name)
140 connector_type.can_connect(cross_testbed_id, cross_factory_id,
141 cross_connector_type_name, count, True)
142 if not guid in self._cross_connect:
143 self._cross_connect[guid] = dict()
144 if not connector_type_name in self._cross_connect[guid]:
145 self._cross_connect[guid][connector_type_name] = dict()
146 self._cross_connect[guid][connector_type_name] = \
147 (cross_guid, cross_testbed_guid, cross_testbed_id,
148 cross_factory_id, cross_connector_type_name)
150 def defer_add_trace(self, guid, trace_id):
151 if not guid in self._create:
152 raise RuntimeError("Element guid %d doesn't exist" % guid)
153 factory = self._get_factory(guid)
154 if not trace_id in factory.traces:
155 raise RuntimeError("Element type '%s' has no trace '%s'" %
156 (factory.factory_id, trace_id))
157 if not guid in self._add_trace:
158 self._add_trace[guid] = list()
159 self._add_trace[guid].append(trace_id)
161 def defer_add_address(self, guid, address, netprefix, broadcast):
162 if not guid in self._create:
163 raise RuntimeError("Element guid %d doesn't exist" % guid)
164 factory = self._get_factory(guid)
165 if not factory.allow_addresses:
166 raise RuntimeError("Element type '%s' doesn't support addresses" %
168 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
169 if guid in self._add_address:
170 count_addresses = len(self._add_address[guid])
171 if max_addresses == count_addresses:
172 raise RuntimeError("Element guid %d of type '%s' can't accept \
173 more addresses" % (guid, factory.factory_id))
175 self._add_address[guid] = list()
176 self._add_address[guid].append((address, netprefix, broadcast))
178 def defer_add_route(self, guid, destination, netprefix, nexthop):
179 if not guid in self._create:
180 raise RuntimeError("Element guid %d doesn't exist" % guid)
181 factory = self._get_factory(guid)
182 if not factory.allow_routes:
183 raise RuntimeError("Element type '%s' doesn't support routes" %
185 if not guid in self._add_route:
186 self._add_route[guid] = list()
187 self._add_route[guid].append((destination, netprefix, nexthop))
190 self._root_directory = self._attributes.\
191 get_attribute_value("rootDirectory")
192 self._status = TESTBED_STATUS_SETUP
196 # order guids (elements) according to factory_id
197 for guid, factory_id in self._create.iteritems():
198 if not factory_id in guids:
199 guids[factory_id] = list()
200 guids[factory_id].append(guid)
201 # create elements following the factory_id order
202 for factory_id in self._metadata.create_order:
203 # omit the factories that have no element to create
204 if factory_id not in guids:
206 factory = self._factories[factory_id]
207 for guid in guids[factory_id]:
208 factory.create_function(self, guid)
209 parameters = self._get_parameters(guid)
210 for name, value in parameters.iteritems():
211 self.set(guid, name, value)
212 self._status = TESTBED_STATUS_CREATED
214 def _do_connect(self, init = True):
215 for guid1, connections in self._connect.iteritems():
216 factory1 = self._get_factory(guid1)
217 for connector_type_name1, connections2 in connections.iteritems():
218 connector_type1 = factory1.connector_type(connector_type_name1)
219 for guid2, connector_type_name2 in connections2.iteritems():
220 factory_id2 = self._create[guid2]
221 # Connections are executed in a "From -> To" direction only
222 # This explicitly ignores the "To -> From" (mirror)
223 # connections of every connection pair.
225 connect_code = connector_type1.connect_to_init_code(
226 self._testbed_id, factory_id2,
227 connector_type_name2,
230 connect_code = connector_type1.connect_to_compl_code(
231 self._testbed_id, factory_id2,
232 connector_type_name2,
235 connect_code(self, guid1, guid2)
237 def do_connect_init(self):
240 def do_connect_compl(self):
241 self._do_connect(init = False)
242 self._status = TESTBED_STATUS_CONNECTED
244 def do_preconfigure(self):
246 # order guids (elements) according to factory_id
247 for guid, factory_id in self._create.iteritems():
248 if not factory_id in guids:
249 guids[factory_id] = list()
250 guids[factory_id].append(guid)
251 # configure elements following the factory_id order
252 for factory_id in self._metadata.preconfigure_order:
253 # omit the factories that have no element to create
254 if factory_id not in guids:
256 factory = self._factories[factory_id]
257 if not factory.preconfigure_function:
259 for guid in guids[factory_id]:
260 factory.preconfigure_function(self, guid)
262 def do_configure(self):
264 # order guids (elements) according to factory_id
265 for guid, factory_id in self._create.iteritems():
266 if not factory_id in guids:
267 guids[factory_id] = list()
268 guids[factory_id].append(guid)
269 # configure elements following the factory_id order
270 for factory_id in self._metadata.configure_order:
271 # omit the factories that have no element to create
272 if factory_id not in guids:
274 factory = self._factories[factory_id]
275 if not factory.configure_function:
277 for guid in guids[factory_id]:
278 factory.configure_function(self, guid)
279 self._status = TESTBED_STATUS_CONFIGURED
281 def _do_cross_connect(self, cross_data, init = True):
282 for guid, cross_connections in self._cross_connect.iteritems():
283 factory = self._get_factory(guid)
284 for connector_type_name, cross_connection in \
285 cross_connections.iteritems():
286 connector_type = factory.connector_type(connector_type_name)
287 (cross_guid, cross_testbed_guid, cross_testbed_id,
288 cross_factory_id, cross_connector_type_name) = cross_connection
290 connect_code = connector_type.connect_to_init_code(
291 cross_testbed_id, cross_factory_id,
292 cross_connector_type_name,
295 connect_code = connector_type.connect_to_compl_code(
296 cross_testbed_id, cross_factory_id,
297 cross_connector_type_name,
300 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
301 connect_code(self, guid, elem_cross_data)
303 def do_cross_connect_init(self, cross_data):
304 self._do_cross_connect(cross_data)
306 def do_cross_connect_compl(self, cross_data):
307 self._do_cross_connect(cross_data, init = False)
308 self._status = TESTBED_STATUS_CROSS_CONNECTED
310 def set(self, guid, name, value, time = TIME_NOW):
311 if not guid in self._create:
312 raise RuntimeError("Element guid %d doesn't exist" % guid)
313 factory = self._get_factory(guid)
314 if not factory.box_attributes.has_attribute(name):
315 raise AttributeError("Invalid attribute %s for element type %s" %
316 (name, factory.factory_id))
317 if self._status > TESTBED_STATUS_STARTED and \
318 factory.box_attributes.is_attribute_design_only(name):
319 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
320 if not factory.box_attributes.is_attribute_value_valid(name, value):
321 raise AttributeError("Invalid value %s for attribute %s" % \
323 if guid not in self._set:
324 self._set[guid] = dict()
325 self._setlog[guid] = dict()
326 if time not in self._setlog[guid]:
327 self._setlog[guid][time] = dict()
328 self._setlog[guid][time][name] = value
329 self._set[guid][name] = value
331 def get(self, guid, name, time = TIME_NOW):
333 gets an attribute from box definitions if available.
334 Throws KeyError if the GUID wasn't created
335 through the defer_create interface, and AttributeError if the
336 attribute isn't available (doesn't exist or is design-only)
338 if not guid in self._create:
339 raise KeyError, "Element guid %d doesn't exist" % guid
340 factory = self._get_factory(guid)
341 if not factory.box_attributes.has_attribute(name):
342 raise AttributeError, "Invalid attribute %s for element type %s" % \
343 (name, factory.factory_id)
344 if guid in self._set and name in self._set[guid]:
345 return self._set[guid][name]
346 if guid in self._create_set and name in self._create_set[guid]:
347 return self._create_set[guid][name]
348 return factory.box_attributes.get_attribute_value(name)
350 def get_route(self, guid, index, attribute):
352 returns information given to defer_add_route.
354 Raises AttributeError if an invalid attribute is requested
355 or if the indexed routing rule does not exist.
357 Raises KeyError if the GUID has not been seen by
360 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
362 if attribute not in ATTRIBUTES:
363 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
365 attribute_index = ATTRIBUTES.index(attribute)
367 routes = self._add_route.get(guid)
369 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
372 if not (0 <= index < len(addresses)):
373 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
374 guid, self._testbed_id, index)
376 return routes[index][attribute_index]
378 def get_address(self, guid, index, attribute='Address'):
380 returns information given to defer_add_address
382 Raises AttributeError if an invalid attribute is requested
383 or if the indexed routing rule does not exist.
385 Raises KeyError if the GUID has not been seen by
388 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
390 if attribute not in ATTRIBUTES:
391 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
393 attribute_index = ATTRIBUTES.index(attribute)
395 addresses = self._add_address.get(guid)
397 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
400 if not (0 <= index < len(addresses)):
401 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
402 guid, self._testbed_id, index)
404 return addresses[index][attribute_index]
406 def get_attribute_list(self, guid):
407 factory = self._get_factory(guid)
408 attribute_list = list()
409 return factory.box_attributes.attributes_list
411 def start(self, time = TIME_NOW):
413 # - group by factory_id
414 # - enqueue task callables
415 plan = collections.defaultdict(list)
417 for guid, factory_id in self._create.iteritems():
418 factory = self._factories[factory_id]
419 start_function = factory.start_function
421 plan[factory_id].append((start_function, guid))
423 # Execute plan, following the factory_id order
424 for factory_id in self._metadata.start_order:
425 if factory_id in plan:
426 for start_function, guid in plan[factory_id]:
427 start_function(self, guid)
429 self._status = TESTBED_STATUS_STARTED
431 #action: NotImplementedError
433 def stop(self, time = TIME_NOW):
434 for guid, factory_id in self._create.iteritems():
435 factory = self._factories[factory_id]
436 stop_function = factory.stop_function
438 stop_function(self, guid)
439 self._status = TESTBED_STATUS_STOPPED
441 def status(self, guid = None):
444 if not guid in self._create:
445 raise RuntimeError("Element guid %d doesn't exist" % guid)
446 factory = self._get_factory(guid)
447 status_function = factory.status_function
449 return status_function(self, guid)
450 return STATUS_UNDETERMINED
452 def trace(self, guid, trace_id, attribute='value'):
453 if attribute == 'value':
454 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
457 elif attribute == 'path':
458 content = self.trace_filename(guid, trace_id)
463 def trace_filename(self, guid, trace_id):
465 Return a trace's file path, for TestbedController's default
466 implementation of trace()
468 raise NotImplementedError
470 #shutdown: NotImplementedError
472 def get_connected(self, guid, connector_type_name,
473 other_connector_type_name):
474 """searchs the connected elements for the specific connector_type_name
476 if guid not in self._connect:
478 # all connections for all connectors for guid
479 all_connections = self._connect[guid]
480 if connector_type_name not in all_connections:
482 # all connections for the specific connector
483 connections = all_connections[connector_type_name]
484 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
485 in connections.iteritems() if \
486 otr_connector_type_name == other_connector_type_name]
487 return specific_connections
489 def _get_connection_count(self, guid, connection_type_name):
492 if guid in self._connect and connection_type_name in \
494 count = len(self._connect[guid][connection_type_name])
495 if guid in self._cross_connect and connection_type_name in \
496 self._cross_connect[guid]:
497 cross_count = len(self._cross_connect[guid][connection_type_name])
498 return count + cross_count
500 def _get_traces(self, guid):
501 return [] if guid not in self._add_trace else self._add_trace[guid]
503 def _get_parameters(self, guid):
504 return dict() if guid not in self._create_set else \
505 self._create_set[guid]
507 def _get_factory(self, guid):
508 factory_id = self._create[guid]
509 return self._factories[factory_id]