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
40 # testbed element instances
41 self._elements = dict()
43 self._metadata = Metadata(self._testbed_id, self._testbed_version)
44 for factory in self._metadata.build_execute_factories():
45 self._factories[factory.factory_id] = factory
46 self._attributes = self._metadata.testbed_attributes()
50 return self._create.keys()
56 def _get_factory_id(self, guid):
57 """ Returns the factory ID of the (perhaps not yet) created object """
58 return self._create.get(guid, None)
60 def defer_configure(self, name, value):
61 if not self._attributes.has_attribute(name):
62 raise AttributeError("Invalid attribute %s for testbed" % name)
64 self._attributes.set_attribute_value(name, value)
65 self._configure[name] = value
67 def defer_create(self, guid, factory_id):
68 if factory_id not in self._factories:
69 raise AttributeError("Invalid element type %s for testbed version %s" %
70 (factory_id, self._testbed_version))
71 if guid in self._create:
72 raise AttributeError("Cannot add elements with the same guid: %d" %
74 self._create[guid] = factory_id
76 def defer_create_set(self, guid, name, value):
77 if not guid in self._create:
78 raise RuntimeError("Element guid %d doesn't exist" % guid)
79 factory_id = self._create[guid]
80 factory = self._factories[factory_id]
81 if not factory.box_attributes.has_attribute(name):
82 raise AttributeError("Invalid attribute %s for element type %s" %
84 if not factory.box_attributes.is_attribute_value_valid(name, value):
85 raise AttributeError("Invalid value %s for attribute %s" % \
87 if guid not in self._create_set:
88 self._create_set[guid] = dict()
89 self._create_set[guid][name] = value
91 def defer_factory_set(self, guid, name, value):
92 if not guid in self._create:
93 raise RuntimeError("Element guid %d doesn't exist" % guid)
94 factory_id = self._create[guid]
95 factory = self._factories[factory_id]
96 if not factory.has_attribute(name):
97 raise AttributeError("Invalid attribute %s for element type %s" %
99 if not factory.is_attribute_value_valid(name, value):
100 raise AttributeError("Invalid value %s for attribute %s" % \
102 if guid not in self._factory_set:
103 self._factory_set[guid] = dict()
104 self._factory_set[guid][name] = value
106 def defer_connect(self, guid1, connector_type_name1, guid2,
107 connector_type_name2):
108 factory_id1 = self._create[guid1]
109 factory_id2 = self._create[guid2]
110 count = self._get_connection_count(guid1, connector_type_name1)
111 factory1 = self._factories[factory_id1]
112 connector_type = factory1.connector_type(connector_type_name1)
113 connector_type.can_connect(self._testbed_id, factory_id2,
114 connector_type_name2, count)
115 if not guid1 in self._connect:
116 self._connect[guid1] = dict()
117 if not connector_type_name1 in self._connect[guid1]:
118 self._connect[guid1][connector_type_name1] = dict()
119 self._connect[guid1][connector_type_name1][guid2] = \
121 if not guid2 in self._connect:
122 self._connect[guid2] = dict()
123 if not connector_type_name2 in self._connect[guid2]:
124 self._connect[guid2][connector_type_name2] = dict()
125 self._connect[guid2][connector_type_name2][guid1] = \
128 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
129 cross_testbed_id, cross_factory_id, cross_connector_type_name):
130 factory_id = self._create[guid]
131 count = self._get_connection_count(guid, connector_type_name)
132 factory = self._factories[factory_id]
133 connector_type = factory.connector_type(connector_type_name)
134 connector_type.can_connect(cross_testbed_id, cross_factory_id,
135 cross_connector_type_name, count, must_cross = True)
136 if not guid in self._cross_connect:
137 self._cross_connect[guid] = dict()
138 if not connector_type_name in self._cross_connect[guid]:
139 self._cross_connect[guid][connector_type_name] = dict()
140 self._cross_connect[guid][connector_type_name] = \
141 (cross_guid, cross_testbed_id, cross_factory_id,
142 cross_connector_type_name)
144 def defer_add_trace(self, guid, trace_id):
145 if not guid in self._create:
146 raise RuntimeError("Element guid %d doesn't exist" % guid)
147 factory_id = self._create[guid]
148 factory = self._factories[factory_id]
149 if not trace_id in factory.traces:
150 raise RuntimeError("Element type '%s' has no trace '%s'" %
151 (factory_id, trace_id))
152 if not guid in self._add_trace:
153 self._add_trace[guid] = list()
154 self._add_trace[guid].append(trace_id)
156 def defer_add_address(self, guid, address, netprefix, broadcast):
157 if not guid in self._create:
158 raise RuntimeError("Element guid %d doesn't exist" % guid)
159 factory_id = self._create[guid]
160 factory = self._factories[factory_id]
161 if not factory.allow_addresses:
162 raise RuntimeError("Element type '%s' doesn't support addresses" %
164 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
165 if guid in self._add_address:
166 count_addresses = len(self._add_address[guid])
167 if max_addresses == count_addresses:
168 raise RuntimeError("Element guid %d of type '%s' can't accept \
169 more addresses" % (guid, factory_id))
171 self._add_address[guid] = list()
172 self._add_address[guid].append((address, netprefix, broadcast))
174 def defer_add_route(self, guid, destination, netprefix, nexthop):
175 if not guid in self._create:
176 raise RuntimeError("Element guid %d doesn't exist" % guid)
177 factory_id = self._create[guid]
178 factory = self._factories[factory_id]
179 if not factory.allow_routes:
180 raise RuntimeError("Element type '%s' doesn't support routes" %
182 if not guid in self._add_route:
183 self._add_route[guid] = list()
184 self._add_route[guid].append((destination, netprefix, nexthop))
187 self._status = TESTBED_STATUS_SETUP
191 # order guids (elements) according to factory_id
192 for guid, factory_id in self._create.iteritems():
193 if not factory_id in guids:
194 guids[factory_id] = list()
195 guids[factory_id].append(guid)
196 # create elements following the factory_id order
197 for factory_id in self._metadata.create_order:
198 # omit the factories that have no element to create
199 if factory_id not in guids:
201 factory = self._factories[factory_id]
202 for guid in guids[factory_id]:
203 factory.create_function(self, guid)
204 parameters = self._get_parameters(guid)
205 for name, value in parameters.iteritems():
206 self.set(TIME_NOW, guid, name, value)
207 self._status = TESTBED_STATUS_CREATED
209 def _do_connect(self, init = True):
210 for guid1, connections in self._connect.iteritems():
211 element1 = self._elements[guid1]
212 factory_id1 = self._create[guid1]
213 factory1 = self._factories[factory_id1]
214 for connector_type_name1, connections2 in connections.iteritems():
215 connector_type1 = factory1.connector_type(connector_type_name1)
216 for guid2, connector_type_name2 in connections2.iteritems():
217 element2 = self._elements[guid2]
218 factory_id2 = self._create[guid2]
219 # Connections are executed in a "From -> To" direction only
220 # This explicitly ignores the "To -> From" (mirror)
221 # connections of every connection pair.
223 connect_code = connector_type1.connect_to_init_code(
224 self._testbed_id, factory_id2,
225 connector_type_name2)
227 connect_code = connector_type1.connect_to_compl_code(
228 self._testbed_id, factory_id2,
229 connector_type_name2)
231 connect_code(self, element1, element2)
233 def do_connect_init(self):
236 def do_connect_compl(self):
237 self._do_connect(init = False)
238 self._status = TESTBED_STATUS_CONNECTED
240 def do_preconfigure(self):
242 # order guids (elements) according to factory_id
243 for guid, factory_id in self._create.iteritems():
244 if not factory_id in guids:
245 guids[factory_id] = list()
246 guids[factory_id].append(guid)
247 # configure elements following the factory_id order
248 for factory_id in self._metadata.preconfigure_order:
249 # omit the factories that have no element to create
250 if factory_id not in guids:
252 factory = self._factories[factory_id]
253 if not factory.preconfigure_function:
255 for guid in guids[factory_id]:
256 factory.preconfigure_function(self, guid)
258 def do_configure(self):
260 # order guids (elements) according to factory_id
261 for guid, factory_id in self._create.iteritems():
262 if not factory_id in guids:
263 guids[factory_id] = list()
264 guids[factory_id].append(guid)
265 # configure elements following the factory_id order
266 for factory_id in self._metadata.configure_order:
267 # omit the factories that have no element to create
268 if factory_id not in guids:
270 factory = self._factories[factory_id]
271 if not factory.configure_function:
273 for guid in guids[factory_id]:
274 factory.configure_function(self, guid)
275 self._status = TESTBED_STATUS_CONFIGURED
277 def _do_cross_connect(self, cross_data, init = True):
278 for guid, cross_connections in self._cross_connect.iteritems():
279 element = self._elements[guid]
280 factory_id = self._create[guid]
281 factory = self._factories[factory_id]
282 for connector_type_name, cross_connection in \
283 cross_connections.iteritems():
284 connector_type = factory.connector_type(connector_type_name)
285 (cross_testbed_id, cross_factory_id,
286 cross_connector_type_name) = cross_connection
288 connect_code = connector_type.connect_to_init_code(
289 cross_testbed_id, cross_factory_id,
290 cross_conector_type_name)
292 connect_code = connector_type.connect_to_compl_code(
293 cross_testbed_id, cross_factory_id,
294 cross_conector_type_name)
296 elem_data_guid = cross_data[cross_testbed_id][cross_guid]
297 connect_code(self, element, elem_cross_data)
299 def do_cross_connect_init(self, cross_data):
300 self._do_cross_connect(cross_data)
302 def do_cross_connect_compl(self, cross_data):
303 self._do_cross_connect(cross_data, init = False)
304 self._status = TESTBED_STATUS_CROSS_CONNECTED
306 def set(self, time, guid, name, value):
307 if not guid in self._create:
308 raise RuntimeError("Element guid %d doesn't exist" % guid)
309 factory_id = self._create[guid]
310 factory = self._factories[factory_id]
311 if not factory.box_attributes.has_attribute(name):
312 raise AttributeError("Invalid attribute %s for element type %s" %
314 if self._status > TESTBED_STATUS_CREATED and \
315 factory.box_attributes.is_attribute_design_only(name):
316 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
317 if not factory.box_attributes.is_attribute_value_valid(name, value):
318 raise AttributeError("Invalid value %s for attribute %s" % \
320 if guid not in self._set:
321 self._set[guid] = dict()
322 if time not in self._set[guid]:
323 self._set[guid][time] = dict()
324 self._set[guid][time][name] = value
326 def get(self, time, guid, name):
328 Helper for subclasses, gets an attribute from box definitions
329 if available. Throws KeyError if the GUID wasn't created
330 through the defer_create interface, and AttributeError if the
331 attribute isn't available (doesn't exist or is design-only)
333 if not guid in self._create:
334 raise KeyError, "Element guid %d doesn't exist" % guid
335 factory_id = self._create[guid]
336 factory = self._factories[factory_id]
337 if not factory.box_attributes.has_attribute(name):
338 raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
339 if self._status > TESTBED_STATUS_CREATED and \
340 factory.box_attributes.is_attribute_design_only(name):
341 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
342 return factory.box_attributes.get_attribute_value(name)
344 def box_get_route(self, guid, index, attribute):
346 Helper implementation for get_route, returns information
347 given to defer_add_route.
349 Raises AttributeError if an invalid attribute is requested
350 or if the indexed routing rule does not exist.
352 Raises KeyError if the GUID has not been seen by
355 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
357 if attribute not in ATTRIBUTES:
358 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
360 attribute_index = ATTRIBUTES.index(attribute)
362 routes = self._add_route.get(guid)
364 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
366 if not (0 <= index < len(addresses)):
367 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
368 guid, self._testbed_id, index)
370 return routes[index][attribute_index]
372 def box_get_address(self, guid, index, attribute='Address'):
374 Helper implementation for get_address, returns information
375 given to defer_add_address
377 Raises AttributeError if an invalid attribute is requested
378 or if the indexed routing rule does not exist.
380 Raises KeyError if the GUID has not been seen by
383 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
385 if attribute not in ATTRIBUTES:
386 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
388 attribute_index = ATTRIBUTES.index(attribute)
390 addresses = self._add_address.get(guid)
392 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
394 if not (0 <= index < len(addresses)):
395 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
396 guid, self._testbed_id, index)
398 return addresses[index][attribute_index]
400 def get_attribute_list(self, guid):
401 factory_id = self._create[guid]
402 factory = self._factories[factory_id]
403 attribute_list = list()
404 return factory.box_attributes.attributes_list
406 def start(self, time = TIME_NOW):
407 for guid, factory_id in self._create.iteritems():
408 factory = self._factories[factory_id]
409 start_function = factory.start_function
411 start_function(self, guid)
412 self._status = TESTBED_STATUS_STARTED
414 #action: NotImplementedError
416 def stop(self, time = TIME_NOW):
417 for guid, factory_id in self._create.iteritems():
418 factory = self._factories[factory_id]
419 stop_function = factory.stop_function
421 stop_function(self, guid)
422 self._status = TESTBED_STATUS_STOPPED
424 def status(self, guid):
425 if not guid in self._create:
426 raise RuntimeError("Element guid %d doesn't exist" % guid)
427 factory_id = self._create[guid]
428 factory = self._factories[factory_id]
429 status_function = factory.status_function
431 return status_function(self, guid)
432 return STATUS_UNDETERMINED
434 def trace(self, guid, trace_id, attribute='value'):
435 if attribute == 'value':
436 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
439 elif attribute == 'path':
440 content = self.trace_filename(guid, trace_id)
445 def trace_filename(self, guid, trace_id):
447 Return a trace's file path, for TestbedController's default
448 implementation of trace()
450 raise NotImplementedError
452 #shutdown: NotImplementedError
454 def get_connected(self, guid, connector_type_name,
455 other_connector_type_name):
456 """searchs the connected elements for the specific connector_type_name
458 if guid not in self._connect:
460 # all connections for all connectors for guid
461 all_connections = self._connect[guid]
462 if connector_type_name not in all_connections:
464 # all connections for the specific connector
465 connections = all_connections[connector_type_name]
466 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
467 in connections.iteritems() if \
468 otr_connector_type_name == other_connector_type_name]
469 return specific_connections
471 def _get_connection_count(self, guid, connection_type_name):
474 if guid in self._connect and connection_type_name in \
476 count = len(self._connect[guid][connection_type_name])
477 if guid in self._cross_connect and connection_type_name in \
478 self._cross_connect[guid]:
479 cross_count = len(self._cross_connect[guid][connection_type_name])
480 return count + cross_count
482 def _get_traces(self, guid):
483 return [] if guid not in self._add_trace else self._add_trace[guid]
485 def _get_parameters(self, guid):
486 return dict() if guid not in self._create_set else \
487 self._create_set[guid]