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,\
21 class TestbedController(execute.TestbedController):
22 def __init__(self, testbed_id, testbed_version):
23 super(TestbedController, self).__init__(testbed_id, testbed_version)
24 self._status = TESTBED_STATUS_ZERO
25 # testbed attributes for validation
26 self._attributes = None
27 # element factories for validation
28 self._factories = dict()
30 # experiment construction instructions
32 self._create_set = dict()
33 self._factory_set = dict()
34 self._connect = dict()
35 self._cross_connect = dict()
36 self._add_trace = dict()
37 self._add_address = dict()
38 self._add_route = dict()
39 self._configure = dict()
41 # log of set operations
46 # testbed element instances
47 self._elements = dict()
49 self._metadata = Metadata(self._testbed_id, self._testbed_version)
50 for factory in self._metadata.build_execute_factories():
51 self._factories[factory.factory_id] = factory
52 self._attributes = self._metadata.testbed_attributes()
53 self._root_directory = None
56 def root_directory(self):
57 return self._root_directory
61 return self._create.keys()
67 def _get_factory_id(self, guid):
68 """ Returns the factory ID of the (perhaps not yet) created object """
69 return self._create.get(guid, None)
71 def defer_configure(self, name, value):
72 if not self._attributes.has_attribute(name):
73 raise AttributeError("Invalid attribute %s for testbed" % name)
75 self._attributes.set_attribute_value(name, value)
76 self._configure[name] = value
78 def defer_create(self, guid, factory_id):
79 if factory_id not in self._factories:
80 raise AttributeError("Invalid element type %s for testbed version %s" %
81 (factory_id, self._testbed_version))
82 if guid in self._create:
83 raise AttributeError("Cannot add elements with the same guid: %d" %
85 self._create[guid] = factory_id
87 def defer_create_set(self, guid, name, value):
88 if not guid in self._create:
89 raise RuntimeError("Element guid %d doesn't exist" % guid)
90 factory = self._get_factory(guid)
91 if not factory.box_attributes.has_attribute(name):
92 raise AttributeError("Invalid attribute %s for element type %s" %
93 (name, factory.factory_id))
94 if not factory.box_attributes.is_attribute_value_valid(name, value):
95 raise AttributeError("Invalid value %s for attribute %s" % \
97 if guid not in self._create_set:
98 self._create_set[guid] = dict()
99 self._create_set[guid][name] = value
101 def defer_factory_set(self, guid, name, value):
102 if not guid in self._create:
103 raise RuntimeError("Element guid %d doesn't exist" % guid)
104 factory = self._get_factory(guid)
105 if not factory.has_attribute(name):
106 raise AttributeError("Invalid attribute %s for element type %s" %
107 (name, factory.factory_id))
108 if not factory.is_attribute_value_valid(name, value):
109 raise AttributeError("Invalid value %s for attribute %s" % \
111 if guid not in self._factory_set:
112 self._factory_set[guid] = dict()
113 self._factory_set[guid][name] = value
115 def defer_connect(self, guid1, connector_type_name1, guid2,
116 connector_type_name2):
117 factory1 = self._get_factory(guid1)
118 factory_id2 = self._create[guid2]
119 count = self._get_connection_count(guid1, connector_type_name1)
120 connector_type = factory1.connector_type(connector_type_name1)
121 connector_type.can_connect(self._testbed_id, factory_id2,
122 connector_type_name2, count, False)
123 if not guid1 in self._connect:
124 self._connect[guid1] = dict()
125 if not connector_type_name1 in self._connect[guid1]:
126 self._connect[guid1][connector_type_name1] = dict()
127 self._connect[guid1][connector_type_name1][guid2] = \
129 if not guid2 in self._connect:
130 self._connect[guid2] = dict()
131 if not connector_type_name2 in self._connect[guid2]:
132 self._connect[guid2][connector_type_name2] = dict()
133 self._connect[guid2][connector_type_name2][guid1] = \
136 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
137 cross_testbed_guid, cross_testbed_id, cross_factory_id,
138 cross_connector_type_name):
139 factory = self._get_factory(guid)
140 count = self._get_connection_count(guid, connector_type_name)
141 connector_type = factory.connector_type(connector_type_name)
142 connector_type.can_connect(cross_testbed_id, cross_factory_id,
143 cross_connector_type_name, count, True)
144 if not guid in self._cross_connect:
145 self._cross_connect[guid] = dict()
146 if not connector_type_name in self._cross_connect[guid]:
147 self._cross_connect[guid][connector_type_name] = dict()
148 self._cross_connect[guid][connector_type_name] = \
149 (cross_guid, cross_testbed_guid, cross_testbed_id,
150 cross_factory_id, cross_connector_type_name)
152 def defer_add_trace(self, guid, trace_id):
153 if not guid in self._create:
154 raise RuntimeError("Element guid %d doesn't exist" % guid)
155 factory = self._get_factory(guid)
156 if not trace_id in factory.traces:
157 raise RuntimeError("Element type '%s' has no trace '%s'" %
158 (factory.factory_id, trace_id))
159 if not guid in self._add_trace:
160 self._add_trace[guid] = list()
161 self._add_trace[guid].append(trace_id)
163 def defer_add_address(self, guid, address, netprefix, broadcast):
164 if not guid in self._create:
165 raise RuntimeError("Element guid %d doesn't exist" % guid)
166 factory = self._get_factory(guid)
167 if not factory.allow_addresses:
168 raise RuntimeError("Element type '%s' doesn't support addresses" %
170 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
171 if guid in self._add_address:
172 count_addresses = len(self._add_address[guid])
173 if max_addresses == count_addresses:
174 raise RuntimeError("Element guid %d of type '%s' can't accept \
175 more addresses" % (guid, factory.factory_id))
177 self._add_address[guid] = list()
178 self._add_address[guid].append((address, netprefix, broadcast))
180 def defer_add_route(self, guid, destination, netprefix, nexthop):
181 if not guid in self._create:
182 raise RuntimeError("Element guid %d doesn't exist" % guid)
183 factory = self._get_factory(guid)
184 if not factory.allow_routes:
185 raise RuntimeError("Element type '%s' doesn't support routes" %
187 if not guid in self._add_route:
188 self._add_route[guid] = list()
189 self._add_route[guid].append((destination, netprefix, nexthop))
192 self._root_directory = self._attributes.\
193 get_attribute_value("rootDirectory")
194 self._status = TESTBED_STATUS_SETUP
197 def set_params(self, guid):
198 parameters = self._get_parameters(guid)
199 for name, value in parameters.iteritems():
200 self.set(guid, name, value)
202 self._do_in_factory_order(
204 self._metadata.create_order,
205 postaction = set_params )
206 self._status = TESTBED_STATUS_CREATED
208 def _do_connect(self, init = True):
209 unconnected = copy.deepcopy(self._connect)
212 for guid1, connections in unconnected.items():
213 factory1 = self._get_factory(guid1)
214 for connector_type_name1, connections2 in connections.items():
215 connector_type1 = factory1.connector_type(connector_type_name1)
216 for guid2, connector_type_name2 in connections2.items():
217 factory_id2 = self._create[guid2]
218 # Connections are executed in a "From -> To" direction only
219 # This explicitly ignores the "To -> From" (mirror)
220 # connections of every connection pair.
222 connect_code = connector_type1.connect_to_init_code(
223 self._testbed_id, factory_id2,
224 connector_type_name2,
227 connect_code = connector_type1.connect_to_compl_code(
228 self._testbed_id, factory_id2,
229 connector_type_name2,
233 delay = connect_code(self, guid1, guid2)
235 if delay is not CONNECTION_DELAY:
236 del unconnected[guid1][connector_type_name1][guid2]
237 if not unconnected[guid1][connector_type_name1]:
238 del unconnected[guid1][connector_type_name1]
239 if not unconnected[guid1]:
240 del unconnected[guid1]
242 def do_connect_init(self):
245 def do_connect_compl(self):
246 self._do_connect(init = False)
247 self._status = TESTBED_STATUS_CONNECTED
249 def _do_in_factory_order(self, action, order, postaction = None, poststep = None):
250 guids = collections.defaultdict(list)
251 # order guids (elements) according to factory_id
252 for guid, factory_id in self._create.iteritems():
253 guids[factory_id].append(guid)
254 # configure elements following the factory_id order
255 for factory_id in order:
256 # omit the factories that have no element to create
257 if factory_id not in guids:
259 factory = self._factories[factory_id]
260 if not getattr(factory, action):
262 for guid in guids[factory_id]:
263 getattr(factory, action)(self, guid)
265 postaction(self, guid)
267 for guid in guids[factory_id]:
271 def do_poststep_preconfigure(self, guid):
272 # dummy hook for implementations interested in
273 # two-phase configuration
276 def do_preconfigure(self):
277 self._do_in_factory_order(
278 'preconfigure_function',
279 self._metadata.preconfigure_order,
280 poststep = self.do_poststep_preconfigure )
283 def do_poststep_configure(self, guid):
284 # dummy hook for implementations interested in
285 # two-phase configuration
288 def do_configure(self):
289 self._do_in_factory_order(
290 'configure_function',
291 self._metadata.configure_order,
292 poststep = self.do_poststep_configure )
293 self._status = TESTBED_STATUS_CONFIGURED
295 def do_prestart(self):
296 self._do_in_factory_order(
298 self._metadata.prestart_order )
300 def _do_cross_connect(self, cross_data, init = True):
301 for guid, cross_connections in self._cross_connect.iteritems():
302 factory = self._get_factory(guid)
303 for connector_type_name, cross_connection in \
304 cross_connections.iteritems():
305 connector_type = factory.connector_type(connector_type_name)
306 (cross_guid, cross_testbed_guid, cross_testbed_id,
307 cross_factory_id, cross_connector_type_name) = cross_connection
309 connect_code = connector_type.connect_to_init_code(
310 cross_testbed_id, cross_factory_id,
311 cross_connector_type_name,
314 connect_code = connector_type.connect_to_compl_code(
315 cross_testbed_id, cross_factory_id,
316 cross_connector_type_name,
319 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
320 connect_code(self, guid, elem_cross_data)
322 def do_cross_connect_init(self, cross_data):
323 self._do_cross_connect(cross_data)
325 def do_cross_connect_compl(self, cross_data):
326 self._do_cross_connect(cross_data, init = False)
327 self._status = TESTBED_STATUS_CROSS_CONNECTED
329 def set(self, guid, name, value, time = TIME_NOW):
330 if not guid in self._create:
331 raise RuntimeError("Element guid %d doesn't exist" % guid)
332 factory = self._get_factory(guid)
333 if not factory.box_attributes.has_attribute(name):
334 raise AttributeError("Invalid attribute %s for element type %s" %
335 (name, factory.factory_id))
336 if self._status > TESTBED_STATUS_STARTED and \
337 factory.box_attributes.is_attribute_design_only(name):
338 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
339 if not factory.box_attributes.is_attribute_value_valid(name, value):
340 raise AttributeError("Invalid value %s for attribute %s" % \
342 if guid not in self._set:
343 self._set[guid] = dict()
344 self._setlog[guid] = dict()
345 if time not in self._setlog[guid]:
346 self._setlog[guid][time] = dict()
347 self._setlog[guid][time][name] = value
348 self._set[guid][name] = value
350 def get(self, guid, name, time = TIME_NOW):
352 gets an attribute from box definitions if available.
353 Throws KeyError if the GUID wasn't created
354 through the defer_create interface, and AttributeError if the
355 attribute isn't available (doesn't exist or is design-only)
357 if not guid in self._create:
358 raise KeyError, "Element guid %d doesn't exist" % guid
359 factory = self._get_factory(guid)
360 if not factory.box_attributes.has_attribute(name):
361 raise AttributeError, "Invalid attribute %s for element type %s" % \
362 (name, factory.factory_id)
363 if guid in self._set and name in self._set[guid]:
364 return self._set[guid][name]
365 if guid in self._create_set and name in self._create_set[guid]:
366 return self._create_set[guid][name]
367 return factory.box_attributes.get_attribute_value(name)
369 def get_route(self, guid, index, attribute):
371 returns information given to defer_add_route.
373 Raises AttributeError if an invalid attribute is requested
374 or if the indexed routing rule does not exist.
376 Raises KeyError if the GUID has not been seen by
379 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
381 if attribute not in ATTRIBUTES:
382 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
384 attribute_index = ATTRIBUTES.index(attribute)
386 routes = self._add_route.get(guid)
388 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
391 if not (0 <= index < len(addresses)):
392 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
393 guid, self._testbed_id, index)
395 return routes[index][attribute_index]
397 def get_address(self, guid, index, attribute='Address'):
399 returns information given to defer_add_address
401 Raises AttributeError if an invalid attribute is requested
402 or if the indexed routing rule does not exist.
404 Raises KeyError if the GUID has not been seen by
407 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
409 if attribute not in ATTRIBUTES:
410 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
412 attribute_index = ATTRIBUTES.index(attribute)
414 addresses = self._add_address.get(guid)
416 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
419 if not (0 <= index < len(addresses)):
420 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
421 guid, self._testbed_id, index)
423 return addresses[index][attribute_index]
425 def get_attribute_list(self, guid):
426 factory = self._get_factory(guid)
427 attribute_list = list()
428 return factory.box_attributes.attributes_list
430 def get_factory_id(self, guid):
431 factory = self._get_factory(guid)
432 return factory.factory_id
434 def start(self, time = TIME_NOW):
435 self._do_in_factory_order(
437 self._metadata.start_order )
438 self._status = TESTBED_STATUS_STARTED
440 #action: NotImplementedError
442 def stop(self, time = TIME_NOW):
443 self._do_in_factory_order(
445 reversed(self._metadata.start_order) )
446 self._status = TESTBED_STATUS_STOPPED
448 def status(self, guid = None):
451 if not guid in self._create:
452 raise RuntimeError("Element guid %d doesn't exist" % guid)
453 factory = self._get_factory(guid)
454 status_function = factory.status_function
456 return status_function(self, guid)
457 return STATUS_UNDETERMINED
459 def trace(self, guid, trace_id, attribute='value'):
460 if attribute == 'value':
461 fd = open("%s" % self.trace_filepath(guid, trace_id), "r")
464 elif attribute == 'path':
465 content = self.trace_filepath(guid, trace_id)
466 elif attribute == 'size':
467 content = str(self.traces_filesize(guid, trace_id))
472 def traces_info(self):
474 host = self._attributes.get_attribute_value("deployment_host")
475 for guid, trace_list in self._add_trace.iteritems():
476 traces_info[guid] = dict()
477 for trace_id in trace_list:
478 traces_info[guid][trace_id] = dict()
479 filepath = self.trace(guid, trace_id, attribute = "path")
481 # filesize = self.trace(guid, trace_id)
483 traces_info[guid][trace_id]["host"] = host
484 traces_info[guid][trace_id]["filepath"] = filepath
485 traces_info[guid][trace_id]["filesize"] = str(filesize)
488 def trace_filepath(self, guid, trace_id):
490 Return a trace's file path, for TestbedController's default
491 implementation of trace()
493 raise NotImplementedError
495 def trace_filesize(self, guid, trace_id):
497 Return a trace's filesize in bytes
499 raise NotImplementedError
501 #shutdown: NotImplementedError
503 def get_connected(self, guid, connector_type_name,
504 other_connector_type_name):
505 """searchs the connected elements for the specific connector_type_name
507 if guid not in self._connect:
509 # all connections for all connectors for guid
510 all_connections = self._connect[guid]
511 if connector_type_name not in all_connections:
513 # all connections for the specific connector
514 connections = all_connections[connector_type_name]
515 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
516 in connections.iteritems() if \
517 otr_connector_type_name == other_connector_type_name]
518 return specific_connections
520 def _get_connection_count(self, guid, connection_type_name):
523 if guid in self._connect and connection_type_name in \
525 count = len(self._connect[guid][connection_type_name])
526 if guid in self._cross_connect and connection_type_name in \
527 self._cross_connect[guid]:
528 cross_count = len(self._cross_connect[guid][connection_type_name])
529 return count + cross_count
531 def _get_traces(self, guid):
532 return [] if guid not in self._add_trace else self._add_trace[guid]
534 def _get_parameters(self, guid):
535 return dict() if guid not in self._create_set else \
536 self._create_set[guid]
538 def _get_factory(self, guid):
539 factory_id = self._create[guid]
540 return self._factories[factory_id]