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):
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 def do_preconfigure(self):
268 self._do_in_factory_order(
269 'preconfigure_function',
270 self._metadata.preconfigure_order )
272 def do_configure(self):
273 self._do_in_factory_order(
274 'configure_function',
275 self._metadata.configure_order )
276 self._status = TESTBED_STATUS_CONFIGURED
278 def do_prestart(self):
279 self._do_in_factory_order(
281 self._metadata.prestart_order )
283 def _do_cross_connect(self, cross_data, init = True):
284 for guid, cross_connections in self._cross_connect.iteritems():
285 factory = self._get_factory(guid)
286 for connector_type_name, cross_connection in \
287 cross_connections.iteritems():
288 connector_type = factory.connector_type(connector_type_name)
289 (cross_guid, cross_testbed_guid, cross_testbed_id,
290 cross_factory_id, cross_connector_type_name) = cross_connection
292 connect_code = connector_type.connect_to_init_code(
293 cross_testbed_id, cross_factory_id,
294 cross_connector_type_name,
297 connect_code = connector_type.connect_to_compl_code(
298 cross_testbed_id, cross_factory_id,
299 cross_connector_type_name,
302 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
303 connect_code(self, guid, elem_cross_data)
305 def do_cross_connect_init(self, cross_data):
306 self._do_cross_connect(cross_data)
308 def do_cross_connect_compl(self, cross_data):
309 self._do_cross_connect(cross_data, init = False)
310 self._status = TESTBED_STATUS_CROSS_CONNECTED
312 def set(self, guid, name, value, time = TIME_NOW):
313 if not guid in self._create:
314 raise RuntimeError("Element guid %d doesn't exist" % guid)
315 factory = self._get_factory(guid)
316 if not factory.box_attributes.has_attribute(name):
317 raise AttributeError("Invalid attribute %s for element type %s" %
318 (name, factory.factory_id))
319 if self._status > TESTBED_STATUS_STARTED and \
320 factory.box_attributes.is_attribute_design_only(name):
321 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
322 if not factory.box_attributes.is_attribute_value_valid(name, value):
323 raise AttributeError("Invalid value %s for attribute %s" % \
325 if guid not in self._set:
326 self._set[guid] = dict()
327 self._setlog[guid] = dict()
328 if time not in self._setlog[guid]:
329 self._setlog[guid][time] = dict()
330 self._setlog[guid][time][name] = value
331 self._set[guid][name] = value
333 def get(self, guid, name, time = TIME_NOW):
335 gets an attribute from box definitions if available.
336 Throws KeyError if the GUID wasn't created
337 through the defer_create interface, and AttributeError if the
338 attribute isn't available (doesn't exist or is design-only)
340 if not guid in self._create:
341 raise KeyError, "Element guid %d doesn't exist" % guid
342 factory = self._get_factory(guid)
343 if not factory.box_attributes.has_attribute(name):
344 raise AttributeError, "Invalid attribute %s for element type %s" % \
345 (name, factory.factory_id)
346 if guid in self._set and name in self._set[guid]:
347 return self._set[guid][name]
348 if guid in self._create_set and name in self._create_set[guid]:
349 return self._create_set[guid][name]
350 return factory.box_attributes.get_attribute_value(name)
352 def get_route(self, guid, index, attribute):
354 returns information given to defer_add_route.
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 = ['Destination', 'NetPrefix', 'NextHop']
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 routes = self._add_route.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 a routing entry #%s" % (
376 guid, self._testbed_id, index)
378 return routes[index][attribute_index]
380 def get_address(self, guid, index, attribute='Address'):
382 returns information given to defer_add_address
384 Raises AttributeError if an invalid attribute is requested
385 or if the indexed routing rule does not exist.
387 Raises KeyError if the GUID has not been seen by
390 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
392 if attribute not in ATTRIBUTES:
393 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
395 attribute_index = ATTRIBUTES.index(attribute)
397 addresses = self._add_address.get(guid)
399 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
402 if not (0 <= index < len(addresses)):
403 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
404 guid, self._testbed_id, index)
406 return addresses[index][attribute_index]
408 def get_attribute_list(self, guid):
409 factory = self._get_factory(guid)
410 attribute_list = list()
411 return factory.box_attributes.attributes_list
413 def get_factory_id(self, guid):
414 factory = self._get_factory(guid)
415 return factory.factory_id
417 def start(self, time = TIME_NOW):
418 self._do_in_factory_order(
420 self._metadata.start_order )
421 self._status = TESTBED_STATUS_STARTED
423 #action: NotImplementedError
425 def stop(self, time = TIME_NOW):
426 self._do_in_factory_order(
428 reversed(self._metadata.start_order) )
429 self._status = TESTBED_STATUS_STOPPED
431 def status(self, guid = None):
434 if not guid in self._create:
435 raise RuntimeError("Element guid %d doesn't exist" % guid)
436 factory = self._get_factory(guid)
437 status_function = factory.status_function
439 return status_function(self, guid)
440 return STATUS_UNDETERMINED
442 def trace(self, guid, trace_id, attribute='value'):
443 if attribute == 'value':
444 fd = open("%s" % self.trace_filepath(guid, trace_id), "r")
447 elif attribute == 'path':
448 content = self.trace_filepath(guid, trace_id)
449 elif attribute == 'size':
450 content = str(self.traces_filesize(guid, trace_id))
455 def traces_info(self):
457 host = self._attributes.get_attribute_value("deployment_host")
458 for guid, trace_list in self._add_trace.iteritems():
459 traces_info[guid] = dict()
460 for trace_id in trace_list:
461 traces_info[guid][trace_id] = dict()
462 filepath = self.trace(guid, trace_id, attribute = "path")
464 # filesize = self.trace(guid, trace_id)
466 traces_info[guid][trace_id]["host"] = host
467 traces_info[guid][trace_id]["filepath"] = filepath
468 traces_info[guid][trace_id]["filesize"] = str(filesize)
471 def trace_filepath(self, guid, trace_id):
473 Return a trace's file path, for TestbedController's default
474 implementation of trace()
476 raise NotImplementedError
478 def trace_filesize(self, guid, trace_id):
480 Return a trace's filesize in bytes
482 raise NotImplementedError
484 #shutdown: NotImplementedError
486 def get_connected(self, guid, connector_type_name,
487 other_connector_type_name):
488 """searchs the connected elements for the specific connector_type_name
490 if guid not in self._connect:
492 # all connections for all connectors for guid
493 all_connections = self._connect[guid]
494 if connector_type_name not in all_connections:
496 # all connections for the specific connector
497 connections = all_connections[connector_type_name]
498 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
499 in connections.iteritems() if \
500 otr_connector_type_name == other_connector_type_name]
501 return specific_connections
503 def _get_connection_count(self, guid, connection_type_name):
506 if guid in self._connect and connection_type_name in \
508 count = len(self._connect[guid][connection_type_name])
509 if guid in self._cross_connect and connection_type_name in \
510 self._cross_connect[guid]:
511 cross_count = len(self._cross_connect[guid][connection_type_name])
512 return count + cross_count
514 def _get_traces(self, guid):
515 return [] if guid not in self._add_trace else self._add_trace[guid]
517 def _get_parameters(self, guid):
518 return dict() if guid not in self._create_set else \
519 self._create_set[guid]
521 def _get_factory(self, guid):
522 factory_id = self._create[guid]
523 return self._factories[factory_id]