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 TIME_NOW, \
8 ApplicationStatus as AS, \
15 class TestbedController(execute.TestbedController):
16 def __init__(self, testbed_id, testbed_version):
17 super(TestbedController, self).__init__(testbed_id, testbed_version)
18 self._status = TS.STATUS_ZERO
19 # testbed attributes for validation
20 self._attributes = None
21 # element factories for validation
22 self._factories = dict()
24 # experiment construction instructions
26 self._create_set = dict()
27 self._factory_set = dict()
28 self._connect = dict()
29 self._cross_connect = dict()
30 self._add_trace = dict()
31 self._add_address = dict()
32 self._add_route = dict()
33 self._configure = dict()
35 # log of set operations
40 # testbed element instances
41 self._elements = dict()
43 self._metadata = Metadata(self._testbed_id)
44 if self._metadata.testbed_version != testbed_version:
45 raise RuntimeError("Bad testbed version on testbed %s. Asked for %s, got %s" % \
46 (testbed_id, testbed_version, self._metadata.testbed_version))
47 for factory in self._metadata.build_factories():
48 self._factories[factory.factory_id] = factory
49 self._attributes = self._metadata.testbed_attributes()
50 self._root_directory = None
53 def root_directory(self):
54 return self._root_directory
58 return self._create.keys()
64 def defer_configure(self, name, value):
65 self._validate_testbed_attribute(name)
66 self._validate_testbed_value(name, value)
67 self._attributes.set_attribute_value(name, value)
68 self._configure[name] = value
70 def defer_create(self, guid, factory_id):
71 self._validate_factory_id(factory_id)
72 self._validate_not_guid(guid)
73 self._create[guid] = factory_id
75 def defer_create_set(self, guid, name, value):
76 self._validate_guid(guid)
77 self._validate_box_attribute(guid, name)
78 self._validate_box_value(guid, name, value)
79 if guid not in self._create_set:
80 self._create_set[guid] = dict()
81 self._create_set[guid][name] = value
83 def defer_factory_set(self, guid, name, value):
84 self._validate_guid(guid)
85 self._validate_factory_attribute(guid, name)
86 self._validate_factory_value(guid, name, value)
87 if guid not in self._factory_set:
88 self._factory_set[guid] = dict()
89 self._factory_set[guid][name] = value
91 def defer_connect(self, guid1, connector_type_name1, guid2,
92 connector_type_name2):
93 self._validate_guid(guid1)
94 self._validate_guid(guid2)
95 factory1 = self._get_factory(guid1)
96 factory_id2 = self._create[guid2]
97 connector_type = factory1.connector_type(connector_type_name1)
98 connector_type.can_connect(self._testbed_id, factory_id2,
99 connector_type_name2, False)
100 self._validate_connection(guid1, connector_type_name1, guid2,
101 connector_type_name2)
103 if not guid1 in self._connect:
104 self._connect[guid1] = dict()
105 if not connector_type_name1 in self._connect[guid1]:
106 self._connect[guid1][connector_type_name1] = dict()
107 self._connect[guid1][connector_type_name1][guid2] = \
109 if not guid2 in self._connect:
110 self._connect[guid2] = dict()
111 if not connector_type_name2 in self._connect[guid2]:
112 self._connect[guid2][connector_type_name2] = dict()
113 self._connect[guid2][connector_type_name2][guid1] = \
116 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
117 cross_testbed_guid, cross_testbed_id, cross_factory_id,
118 cross_connector_type_name):
119 self._validate_guid(guid)
120 factory = self._get_factory(guid)
121 connector_type = factory.connector_type(connector_type_name)
122 connector_type.can_connect(cross_testbed_id, cross_factory_id,
123 cross_connector_type_name, True)
124 self._validate_connection(guid, connector_type_name, cross_guid,
125 cross_connector_type_name)
127 if not guid in self._cross_connect:
128 self._cross_connect[guid] = dict()
129 if not connector_type_name in self._cross_connect[guid]:
130 self._cross_connect[guid][connector_type_name] = dict()
131 self._cross_connect[guid][connector_type_name] = \
132 (cross_guid, cross_testbed_guid, cross_testbed_id,
133 cross_factory_id, cross_connector_type_name)
135 def defer_add_trace(self, guid, trace_name):
136 self._validate_guid(guid)
137 self._validate_trace(guid, trace_name)
138 if not guid in self._add_trace:
139 self._add_trace[guid] = list()
140 self._add_trace[guid].append(trace_name)
142 def defer_add_address(self, guid, address, netprefix, broadcast):
143 self._validate_guid(guid)
144 self._validate_allow_addresses(guid)
145 if guid not in self._add_address:
146 self._add_address[guid] = list()
147 self._add_address[guid].append((address, netprefix, broadcast))
149 def defer_add_route(self, guid, destination, netprefix, nexthop, metric = 0):
150 self._validate_guid(guid)
151 self._validate_allow_routes(guid)
152 if not guid in self._add_route:
153 self._add_route[guid] = list()
154 self._add_route[guid].append((destination, netprefix, nexthop, metric))
157 self._root_directory = self._attributes.\
158 get_attribute_value("rootDirectory")
159 self._status = TS.STATUS_SETUP
162 def set_params(self, guid):
163 parameters = self._get_parameters(guid)
164 for name, value in parameters.iteritems():
165 self.set(guid, name, value)
167 self._do_in_factory_order(
169 self._metadata.create_order,
170 postaction = set_params )
171 self._status = TS.STATUS_CREATED
173 def _do_connect(self, init = True):
174 unconnected = copy.deepcopy(self._connect)
177 for guid1, connections in unconnected.items():
178 factory1 = self._get_factory(guid1)
179 for connector_type_name1, connections2 in connections.items():
180 connector_type1 = factory1.connector_type(connector_type_name1)
181 for guid2, connector_type_name2 in connections2.items():
182 factory_id2 = self._create[guid2]
183 # Connections are executed in a "From -> To" direction only
184 # This explicitly ignores the "To -> From" (mirror)
185 # connections of every connection pair.
187 connect_code = connector_type1.connect_to_init_code(
188 self._testbed_id, factory_id2,
189 connector_type_name2,
192 connect_code = connector_type1.connect_to_compl_code(
193 self._testbed_id, factory_id2,
194 connector_type_name2,
198 delay = connect_code(self, guid1, guid2)
200 if delay is not CONNECTION_DELAY:
201 del unconnected[guid1][connector_type_name1][guid2]
202 if not unconnected[guid1][connector_type_name1]:
203 del unconnected[guid1][connector_type_name1]
204 if not unconnected[guid1]:
205 del unconnected[guid1]
207 def do_connect_init(self):
210 def do_connect_compl(self):
211 self._do_connect(init = False)
212 self._status = TS.STATUS_CONNECTED
214 def _do_in_factory_order(self, action, order, postaction = None, poststep = None):
215 guids = collections.defaultdict(list)
216 # order guids (elements) according to factory_id
217 for guid, factory_id in self._create.iteritems():
218 guids[factory_id].append(guid)
219 # configure elements following the factory_id order
220 for factory_id in order:
221 # omit the factories that have no element to create
222 if factory_id not in guids:
224 factory = self._factories[factory_id]
225 if not getattr(factory, action):
227 for guid in guids[factory_id]:
228 getattr(factory, action)(self, guid)
230 postaction(self, guid)
232 for guid in guids[factory_id]:
236 def do_poststep_preconfigure(self, guid):
237 # dummy hook for implementations interested in
238 # two-phase configuration
241 def do_preconfigure(self):
242 self._do_in_factory_order(
243 'preconfigure_function',
244 self._metadata.preconfigure_order,
245 poststep = self.do_poststep_preconfigure )
248 def do_poststep_configure(self, guid):
249 # dummy hook for implementations interested in
250 # two-phase configuration
253 def do_configure(self):
254 self._do_in_factory_order(
255 'configure_function',
256 self._metadata.configure_order,
257 poststep = self.do_poststep_configure )
258 self._status = TS.STATUS_CONFIGURED
260 def do_prestart(self):
261 self._do_in_factory_order(
263 self._metadata.prestart_order )
265 def _do_cross_connect(self, cross_data, init = True):
266 for guid, cross_connections in self._cross_connect.iteritems():
267 factory = self._get_factory(guid)
268 for connector_type_name, cross_connection in \
269 cross_connections.iteritems():
270 connector_type = factory.connector_type(connector_type_name)
271 (cross_guid, cross_testbed_guid, cross_testbed_id,
272 cross_factory_id, cross_connector_type_name) = cross_connection
274 connect_code = connector_type.connect_to_init_code(
275 cross_testbed_id, cross_factory_id,
276 cross_connector_type_name,
279 connect_code = connector_type.connect_to_compl_code(
280 cross_testbed_id, cross_factory_id,
281 cross_connector_type_name,
284 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
285 connect_code(self, guid, elem_cross_data)
287 def do_cross_connect_init(self, cross_data):
288 self._do_cross_connect(cross_data)
290 def do_cross_connect_compl(self, cross_data):
291 self._do_cross_connect(cross_data, init = False)
292 self._status = TS.STATUS_CROSS_CONNECTED
294 def set(self, guid, name, value, time = TIME_NOW):
295 self._validate_guid(guid)
296 self._validate_box_attribute(guid, name)
297 self._validate_box_value(guid, name, value)
298 self._validate_modify_box_value(guid, name)
299 if guid not in self._set:
300 self._set[guid] = dict()
301 self._setlog[guid] = dict()
302 if time not in self._setlog[guid]:
303 self._setlog[guid][time] = dict()
304 self._setlog[guid][time][name] = value
305 self._set[guid][name] = value
307 def get(self, guid, name, time = TIME_NOW):
309 gets an attribute from box definitions if available.
310 Throws KeyError if the GUID wasn't created
311 through the defer_create interface, and AttributeError if the
312 attribute isn't available (doesn't exist or is design-only)
314 self._validate_guid(guid)
315 self._validate_box_attribute(guid, name)
316 if guid in self._set and name in self._set[guid]:
317 return self._set[guid][name]
318 if guid in self._create_set and name in self._create_set[guid]:
319 return self._create_set[guid][name]
320 # if nothing else found, returns the factory default value
321 factory = self._get_factory(guid)
322 return factory.box_attributes.get_attribute_value(name)
324 def get_route(self, guid, index, attribute):
326 returns information given to defer_add_route.
328 Raises AttributeError if an invalid attribute is requested
329 or if the indexed routing rule does not exist.
331 Raises KeyError if the GUID has not been seen by
334 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
336 if attribute not in ATTRIBUTES:
337 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
339 attribute_index = ATTRIBUTES.index(attribute)
341 routes = self._add_route.get(guid)
343 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
346 if not (0 <= index < len(addresses)):
347 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
348 guid, self._testbed_id, index)
350 return routes[index][attribute_index]
352 def get_address(self, guid, index, attribute='Address'):
354 returns information given to defer_add_address
356 Raises AttributeError if an invalid attribute is requested
357 or if the indexed routing rule does not exist.
359 Raises KeyError if the GUID has not been seen by
362 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
364 if attribute not in ATTRIBUTES:
365 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
367 attribute_index = ATTRIBUTES.index(attribute)
369 addresses = self._add_address.get(guid)
371 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
374 if not (0 <= index < len(addresses)):
375 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
376 guid, self._testbed_id, index)
378 return addresses[index][attribute_index]
380 def get_attribute_list(self, guid, filter_flags = None, exclude = False):
381 factory = self._get_factory(guid)
382 attribute_list = list()
383 return factory.box_attributes.get_attribute_list(filter_flags, exclude)
385 def get_factory_id(self, guid):
386 factory = self._get_factory(guid)
387 return factory.factory_id
389 def start(self, time = TIME_NOW):
390 self._do_in_factory_order(
392 self._metadata.start_order )
393 self._status = TS.STATUS_STARTED
395 #action: NotImplementedError
397 def stop(self, time = TIME_NOW):
398 self._do_in_factory_order(
400 reversed(self._metadata.start_order) )
401 self._status = TS.STATUS_STOPPED
403 def status(self, guid = None):
406 self._validate_guid(guid)
407 factory = self._get_factory(guid)
408 status_function = factory.status_function
410 return status_function(self, guid)
411 return AS.STATUS_UNDETERMINED
413 def trace(self, guid, trace_id, attribute='value'):
414 if attribute == 'value':
415 fd = open("%s" % self.trace_filepath(guid, trace_id), "r")
418 elif attribute == 'path':
419 content = self.trace_filepath(guid, trace_id)
424 def traces_info(self):
426 host = self._attributes.get_attribute_value("deployment_host")
427 user = self._attributes.get_attribute_value("deployment_user")
428 for guid, trace_list in self._add_trace.iteritems():
429 traces_info[guid] = dict()
430 for trace_id in trace_list:
431 traces_info[guid][trace_id] = dict()
432 filepath = self.trace(guid, trace_id, attribute = "path")
433 traces_info[guid][trace_id]["host"] = host
434 traces_info[guid][trace_id]["user"] = user
435 traces_info[guid][trace_id]["filepath"] = filepath
438 def trace_filepath(self, guid, trace_id):
440 Return a trace's file path, for TestbedController's default
441 implementation of trace()
443 raise NotImplementedError
445 #shutdown: NotImplementedError
447 def get_connected(self, guid, connector_type_name,
448 other_connector_type_name):
449 """searchs the connected elements for the specific connector_type_name
451 if guid not in self._connect:
453 # all connections for all connectors for guid
454 all_connections = self._connect[guid]
455 if connector_type_name not in all_connections:
457 # all connections for the specific connector
458 connections = all_connections[connector_type_name]
459 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
460 in connections.iteritems() if \
461 otr_connector_type_name == other_connector_type_name]
462 return specific_connections
464 def _get_connection_count(self, guid, connection_type_name):
467 if guid in self._connect and connection_type_name in \
469 count = len(self._connect[guid][connection_type_name])
470 if guid in self._cross_connect and connection_type_name in \
471 self._cross_connect[guid]:
472 cross_count = len(self._cross_connect[guid][connection_type_name])
473 return count + cross_count
475 def _get_traces(self, guid):
476 return [] if guid not in self._add_trace else self._add_trace[guid]
478 def _get_parameters(self, guid):
479 return dict() if guid not in self._create_set else \
480 self._create_set[guid]
482 def _get_factory(self, guid):
483 factory_id = self._create[guid]
484 return self._factories[factory_id]
486 def _get_factory_id(self, guid):
487 """ Returns the factory ID of the (perhaps not yet) created object """
488 return self._create.get(guid, None)
490 def _validate_guid(self, guid):
491 if not guid in self._create:
492 raise RuntimeError("Element guid %d doesn't exist" % guid)
494 def _validate_not_guid(self, guid):
495 if guid in self._create:
496 raise AttributeError("Cannot add elements with the same guid: %d" %
499 def _validate_factory_id(self, factory_id):
500 if factory_id not in self._factories:
501 raise AttributeError("Invalid element type %s for testbed version %s" %
502 (factory_id, self._testbed_version))
504 def _validate_testbed_attribute(self, name):
505 if not self._attributes.has_attribute(name):
506 raise AttributeError("Invalid testbed attribute %s for testbed" % \
509 def _validate_testbed_value(self, name, value):
510 if not self._attributes.is_attribute_value_valid(name, value):
511 raise AttributeError("Invalid value %s for testbed attribute %s" % \
514 def _validate_box_attribute(self, guid, name):
515 factory = self._get_factory(guid)
516 if not factory.box_attributes.has_attribute(name):
517 raise AttributeError("Invalid attribute %s for element type %s" %
518 (name, factory.factory_id))
520 def _validate_box_value(self, guid, name, value):
521 factory = self._get_factory(guid)
522 if not factory.box_attributes.is_attribute_value_valid(name, value):
523 raise AttributeError("Invalid value %s for attribute %s" % \
526 def _validate_factory_attribute(self, guid, name):
527 factory = self._get_factory(guid)
528 if not factory.has_attribute(name):
529 raise AttributeError("Invalid attribute %s for element type %s" %
530 (name, factory.factory_id))
532 def _validate_factory_value(self, guid, name, value):
533 factory = self._get_factory(guid)
534 if not factory.is_attribute_value_valid(name, value):
535 raise AttributeError("Invalid value %s for attribute %s" % \
538 def _validate_trace(self, guid, trace_name):
539 factory = self._get_factory(guid)
540 if not trace_name in factory.traces_list:
541 raise RuntimeError("Element type '%s' has no trace '%s'" %
542 (factory.factory_id, trace_name))
544 def _validate_allow_addresses(self, guid):
545 factory = self._get_factory(guid)
546 if not factory.allow_addresses:
547 raise RuntimeError("Element type '%s' doesn't support addresses" %
549 attr_name = "maxAddresses"
550 if guid in self._create_set and attr_name in self._create_set[guid]:
551 max_addresses = self._create_set[guid][attr_name]
553 factory = self._get_factory(guid)
554 max_addresses = factory.box_attributes.get_attribute_value(attr_name)
555 if guid in self._add_address:
556 count_addresses = len(self._add_address[guid])
557 if max_addresses == count_addresses:
558 raise RuntimeError("Element guid %d of type '%s' can't accept \
559 more addresses" % (guid, factory.factory_id))
561 def _validate_allow_routes(self, guid):
562 factory = self._get_factory(guid)
563 if not factory.allow_routes:
564 raise RuntimeError("Element type '%s' doesn't support routes" %
567 def _validate_connection(self, guid1, connector_type_name1, guid2,
568 connector_type_name2, cross = False):
569 # can't connect with self
571 raise AttributeError("Can't connect guid %d to self" % \
573 # the connection is already done, so ignore
574 connected = self.get_connected(guid1, connector_type_name1,
575 connector_type_name2)
576 if guid2 in connected:
578 count1 = self._get_connection_count(guid1, connector_type_name1)
579 factory1 = self._get_factory(guid1)
580 connector_type1 = factory1.connector_type(connector_type_name1)
581 if count1 == connector_type1.max:
582 raise AttributeError("Connector %s is full for guid %d" % \
583 (connector_type_name1, guid1))
585 def _validate_modify_box_value(self, guid, name):
586 factory = self._get_factory(guid)
587 if self._status > TS.STATUS_STARTED and \
588 (factory.box_attributes.is_attribute_exec_read_only(name) or \
589 factory.box_attributes.is_attribute_exec_immutable(name)):
590 raise AttributeError("Attribute %s can only be modified during experiment design" % name)