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
17 class TestbedController(execute.TestbedController):
18 def __init__(self, testbed_id, testbed_version):
19 super(TestbedController, self).__init__(testbed_id, testbed_version)
20 self._status = TESTBED_STATUS_ZERO
21 # testbed attributes for validation
22 self._attributes = None
23 # element factories for validation
24 self._factories = dict()
26 # experiment construction instructions
28 self._create_set = dict()
29 self._factory_set = dict()
30 self._connect = dict()
31 self._cross_connect = dict()
32 self._add_trace = dict()
33 self._add_address = dict()
34 self._add_route = dict()
35 self._configure = dict()
37 # log of set operations
42 # testbed element instances
43 self._elements = dict()
45 self._metadata = Metadata(self._testbed_id, self._testbed_version)
46 for factory in self._metadata.build_execute_factories():
47 self._factories[factory.factory_id] = factory
48 self._attributes = self._metadata.testbed_attributes()
52 return self._create.keys()
58 def _get_factory_id(self, guid):
59 """ Returns the factory ID of the (perhaps not yet) created object """
60 return self._create.get(guid, None)
62 def defer_configure(self, name, value):
63 if not self._attributes.has_attribute(name):
64 raise AttributeError("Invalid attribute %s for testbed" % name)
66 self._attributes.set_attribute_value(name, value)
67 self._configure[name] = value
69 def defer_create(self, guid, factory_id):
70 if factory_id not in self._factories:
71 raise AttributeError("Invalid element type %s for testbed version %s" %
72 (factory_id, self._testbed_version))
73 if guid in self._create:
74 raise AttributeError("Cannot add elements with the same guid: %d" %
76 self._create[guid] = factory_id
78 def defer_create_set(self, guid, name, value):
79 if not guid in self._create:
80 raise RuntimeError("Element guid %d doesn't exist" % guid)
81 factory_id = self._create[guid]
82 factory = self._factories[factory_id]
83 if not factory.box_attributes.has_attribute(name):
84 raise AttributeError("Invalid attribute %s for element type %s" %
86 if not factory.box_attributes.is_attribute_value_valid(name, value):
87 raise AttributeError("Invalid value %s for attribute %s" % \
89 if guid not in self._create_set:
90 self._create_set[guid] = dict()
91 self._create_set[guid][name] = value
93 def defer_factory_set(self, guid, name, value):
94 if not guid in self._create:
95 raise RuntimeError("Element guid %d doesn't exist" % guid)
96 factory_id = self._create[guid]
97 factory = self._factories[factory_id]
98 if not factory.has_attribute(name):
99 raise AttributeError("Invalid attribute %s for element type %s" %
101 if not factory.is_attribute_value_valid(name, value):
102 raise AttributeError("Invalid value %s for attribute %s" % \
104 if guid not in self._factory_set:
105 self._factory_set[guid] = dict()
106 self._factory_set[guid][name] = value
108 def defer_connect(self, guid1, connector_type_name1, guid2,
109 connector_type_name2):
110 factory_id1 = self._create[guid1]
111 factory_id2 = self._create[guid2]
112 count = self._get_connection_count(guid1, connector_type_name1)
113 factory1 = self._factories[factory_id1]
114 connector_type = factory1.connector_type(connector_type_name1)
115 connector_type.can_connect(self._testbed_id, factory_id2,
116 connector_type_name2, count)
117 if not guid1 in self._connect:
118 self._connect[guid1] = dict()
119 if not connector_type_name1 in self._connect[guid1]:
120 self._connect[guid1][connector_type_name1] = dict()
121 self._connect[guid1][connector_type_name1][guid2] = \
123 if not guid2 in self._connect:
124 self._connect[guid2] = dict()
125 if not connector_type_name2 in self._connect[guid2]:
126 self._connect[guid2][connector_type_name2] = dict()
127 self._connect[guid2][connector_type_name2][guid1] = \
130 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
131 cross_testbed_id, cross_factory_id, cross_connector_type_name):
132 factory_id = self._create[guid]
133 count = self._get_connection_count(guid, connector_type_name)
134 factory = self._factories[factory_id]
135 connector_type = factory.connector_type(connector_type_name)
136 connector_type.can_connect(cross_testbed_id, cross_factory_id,
137 cross_connector_type_name, count, must_cross = True)
138 if not guid in self._cross_connect:
139 self._cross_connect[guid] = dict()
140 if not connector_type_name in self._cross_connect[guid]:
141 self._cross_connect[guid][connector_type_name] = dict()
142 self._cross_connect[guid][connector_type_name] = \
143 (cross_guid, cross_testbed_id, cross_factory_id,
144 cross_connector_type_name)
146 def defer_add_trace(self, guid, trace_id):
147 if not guid in self._create:
148 raise RuntimeError("Element guid %d doesn't exist" % guid)
149 factory_id = self._create[guid]
150 factory = self._factories[factory_id]
151 if not trace_id in factory.traces:
152 raise RuntimeError("Element type '%s' has no trace '%s'" %
153 (factory_id, trace_id))
154 if not guid in self._add_trace:
155 self._add_trace[guid] = list()
156 self._add_trace[guid].append(trace_id)
158 def defer_add_address(self, guid, address, netprefix, broadcast):
159 if not guid in self._create:
160 raise RuntimeError("Element guid %d doesn't exist" % guid)
161 factory_id = self._create[guid]
162 factory = self._factories[factory_id]
163 if not factory.allow_addresses:
164 raise RuntimeError("Element type '%s' doesn't support addresses" %
166 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
167 if guid in self._add_address:
168 count_addresses = len(self._add_address[guid])
169 if max_addresses == count_addresses:
170 raise RuntimeError("Element guid %d of type '%s' can't accept \
171 more addresses" % (guid, factory_id))
173 self._add_address[guid] = list()
174 self._add_address[guid].append((address, netprefix, broadcast))
176 def defer_add_route(self, guid, destination, netprefix, nexthop):
177 if not guid in self._create:
178 raise RuntimeError("Element guid %d doesn't exist" % guid)
179 factory_id = self._create[guid]
180 factory = self._factories[factory_id]
181 if not factory.allow_routes:
182 raise RuntimeError("Element type '%s' doesn't support routes" %
184 if not guid in self._add_route:
185 self._add_route[guid] = list()
186 self._add_route[guid].append((destination, netprefix, nexthop))
189 self._status = TESTBED_STATUS_SETUP
193 # order guids (elements) according to factory_id
194 for guid, factory_id in self._create.iteritems():
195 if not factory_id in guids:
196 guids[factory_id] = list()
197 guids[factory_id].append(guid)
198 # create elements following the factory_id order
199 for factory_id in self._metadata.create_order:
200 # omit the factories that have no element to create
201 if factory_id not in guids:
203 factory = self._factories[factory_id]
204 for guid in guids[factory_id]:
205 factory.create_function(self, guid)
206 parameters = self._get_parameters(guid)
207 for name, value in parameters.iteritems():
208 self.set(TIME_NOW, guid, name, value)
209 self._status = TESTBED_STATUS_CREATED
211 def _do_connect(self, init = True):
212 for guid1, connections in self._connect.iteritems():
213 element1 = self._elements[guid1]
214 factory_id1 = self._create[guid1]
215 factory1 = self._factories[factory_id1]
216 for connector_type_name1, connections2 in connections.iteritems():
217 connector_type1 = factory1.connector_type(connector_type_name1)
218 for guid2, connector_type_name2 in connections2.iteritems():
219 element2 = self._elements[guid2]
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)
229 connect_code = connector_type1.connect_to_compl_code(
230 self._testbed_id, factory_id2,
231 connector_type_name2)
233 connect_code(self, element1, element2)
235 def do_connect_init(self):
238 def do_connect_compl(self):
239 self._do_connect(init = False)
240 self._status = TESTBED_STATUS_CONNECTED
242 def do_preconfigure(self):
244 # order guids (elements) according to factory_id
245 for guid, factory_id in self._create.iteritems():
246 if not factory_id in guids:
247 guids[factory_id] = list()
248 guids[factory_id].append(guid)
249 # configure elements following the factory_id order
250 for factory_id in self._metadata.preconfigure_order:
251 # omit the factories that have no element to create
252 if factory_id not in guids:
254 factory = self._factories[factory_id]
255 if not factory.preconfigure_function:
257 for guid in guids[factory_id]:
258 factory.preconfigure_function(self, guid)
260 def do_configure(self):
262 # order guids (elements) according to factory_id
263 for guid, factory_id in self._create.iteritems():
264 if not factory_id in guids:
265 guids[factory_id] = list()
266 guids[factory_id].append(guid)
267 # configure elements following the factory_id order
268 for factory_id in self._metadata.configure_order:
269 # omit the factories that have no element to create
270 if factory_id not in guids:
272 factory = self._factories[factory_id]
273 if not factory.configure_function:
275 for guid in guids[factory_id]:
276 factory.configure_function(self, guid)
277 self._status = TESTBED_STATUS_CONFIGURED
279 def _do_cross_connect(self, cross_data, init = True):
280 for guid, cross_connections in self._cross_connect.iteritems():
281 element = self._elements[guid]
282 factory_id = self._create[guid]
283 factory = self._factories[factory_id]
284 for connector_type_name, cross_connection in \
285 cross_connections.iteritems():
286 connector_type = factory.connector_type(connector_type_name)
287 (cross_testbed_id, cross_factory_id,
288 cross_connector_type_name) = cross_connection
290 connect_code = connector_type.connect_to_init_code(
291 cross_testbed_id, cross_factory_id,
292 cross_conector_type_name)
294 connect_code = connector_type.connect_to_compl_code(
295 cross_testbed_id, cross_factory_id,
296 cross_conector_type_name)
298 elem_data_guid = cross_data[cross_testbed_id][cross_guid]
299 connect_code(self, element, elem_cross_data)
301 def do_cross_connect_init(self, cross_data):
302 self._do_cross_connect(cross_data)
304 def do_cross_connect_compl(self, cross_data):
305 self._do_cross_connect(cross_data, init = False)
306 self._status = TESTBED_STATUS_CROSS_CONNECTED
308 def set(self, time, guid, name, value):
309 if not guid in self._create:
310 raise RuntimeError("Element guid %d doesn't exist" % guid)
311 factory_id = self._create[guid]
312 factory = self._factories[factory_id]
313 if not factory.box_attributes.has_attribute(name):
314 raise AttributeError("Invalid attribute %s for element type %s" %
316 if self._status > TESTBED_STATUS_CREATED and \
317 factory.box_attributes.is_attribute_design_only(name):
318 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
319 if not factory.box_attributes.is_attribute_value_valid(name, value):
320 raise AttributeError("Invalid value %s for attribute %s" % \
322 if guid not in self._set:
323 self._set[guid] = dict()
324 self._setlog[guid] = dict()
325 if time not in self._setlog[guid]:
326 self._setlog[guid][time] = dict()
327 self._setlog[guid][time][name] = value
328 self._set[guid][name] = value
330 def get(self, time, guid, name):
332 gets an attribute from box definitions if available.
333 Throws KeyError if the GUID wasn't created
334 through the defer_create interface, and AttributeError if the
335 attribute isn't available (doesn't exist or is design-only)
337 if not guid in self._create:
338 raise KeyError, "Element guid %d doesn't exist" % guid
339 factory_id = self._create[guid]
340 factory = self._factories[factory_id]
341 if not factory.box_attributes.has_attribute(name):
342 raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
343 if self._status > TESTBED_STATUS_CREATED and \
344 factory.box_attributes.is_attribute_design_only(name):
345 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
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_id = self._create[guid]
410 factory = self._factories[factory_id]
411 attribute_list = list()
412 return factory.box_attributes.attributes_list
414 def start(self, time = TIME_NOW):
415 for guid, factory_id in self._create.iteritems():
416 factory = self._factories[factory_id]
417 start_function = factory.start_function
419 start_function(self, guid)
420 self._status = TESTBED_STATUS_STARTED
422 #action: NotImplementedError
424 def stop(self, time = TIME_NOW):
425 for guid, factory_id in self._create.iteritems():
426 factory = self._factories[factory_id]
427 stop_function = factory.stop_function
429 stop_function(self, guid)
430 self._status = TESTBED_STATUS_STOPPED
432 def status(self, guid):
433 if not guid in self._create:
434 raise RuntimeError("Element guid %d doesn't exist" % guid)
435 factory_id = self._create[guid]
436 factory = self._factories[factory_id]
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_filename(guid, trace_id), "r")
447 elif attribute == 'path':
448 content = self.trace_filename(guid, trace_id)
453 def trace_filename(self, guid, trace_id):
455 Return a trace's file path, for TestbedController's default
456 implementation of trace()
458 raise NotImplementedError
460 #shutdown: NotImplementedError
462 def get_connected(self, guid, connector_type_name,
463 other_connector_type_name):
464 """searchs the connected elements for the specific connector_type_name
466 if guid not in self._connect:
468 # all connections for all connectors for guid
469 all_connections = self._connect[guid]
470 if connector_type_name not in all_connections:
472 # all connections for the specific connector
473 connections = all_connections[connector_type_name]
474 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
475 in connections.iteritems() if \
476 otr_connector_type_name == other_connector_type_name]
477 return specific_connections
479 def _get_connection_count(self, guid, connection_type_name):
482 if guid in self._connect and connection_type_name in \
484 count = len(self._connect[guid][connection_type_name])
485 if guid in self._cross_connect and connection_type_name in \
486 self._cross_connect[guid]:
487 cross_count = len(self._cross_connect[guid][connection_type_name])
488 return count + cross_count
490 def _get_traces(self, guid):
491 return [] if guid not in self._add_trace else self._add_trace[guid]
493 def _get_parameters(self, guid):
494 return dict() if guid not in self._create_set else \
495 self._create_set[guid]