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 = self._get_factory(guid)
82 if not factory.box_attributes.has_attribute(name):
83 raise AttributeError("Invalid attribute %s for element type %s" %
84 (name, factory.factory_id))
85 if not factory.box_attributes.is_attribute_value_valid(name, value):
86 raise AttributeError("Invalid value %s for attribute %s" % \
88 if guid not in self._create_set:
89 self._create_set[guid] = dict()
90 self._create_set[guid][name] = value
92 def defer_factory_set(self, guid, name, value):
93 if not guid in self._create:
94 raise RuntimeError("Element guid %d doesn't exist" % guid)
95 factory = self._get_factory(guid)
96 if not factory.has_attribute(name):
97 raise AttributeError("Invalid attribute %s for element type %s" %
98 (name, factory.factory_id))
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 factory1 = self._get_factory(guid1)
109 factory_id2 = self._create[guid2]
110 count = self._get_connection_count(guid1, connector_type_name1)
111 connector_type = factory1.connector_type(connector_type_name1)
112 connector_type.can_connect(self._testbed_id, factory_id2,
113 connector_type_name2, count)
114 if not guid1 in self._connect:
115 self._connect[guid1] = dict()
116 if not connector_type_name1 in self._connect[guid1]:
117 self._connect[guid1][connector_type_name1] = dict()
118 self._connect[guid1][connector_type_name1][guid2] = \
120 if not guid2 in self._connect:
121 self._connect[guid2] = dict()
122 if not connector_type_name2 in self._connect[guid2]:
123 self._connect[guid2][connector_type_name2] = dict()
124 self._connect[guid2][connector_type_name2][guid1] = \
127 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
128 cross_testbed_id, cross_factory_id, cross_connector_type_name):
129 factory = self._get_factory(guid)
130 count = self._get_connection_count(guid, connector_type_name)
131 connector_type = factory.connector_type(connector_type_name)
132 connector_type.can_connect(cross_testbed_id, cross_factory_id,
133 cross_connector_type_name, count, must_cross = True)
134 if not guid in self._cross_connect:
135 self._cross_connect[guid] = dict()
136 if not connector_type_name in self._cross_connect[guid]:
137 self._cross_connect[guid][connector_type_name] = dict()
138 self._cross_connect[guid][connector_type_name] = \
139 (cross_guid, cross_testbed_id, cross_factory_id,
140 cross_connector_type_name)
142 def defer_add_trace(self, guid, trace_id):
143 if not guid in self._create:
144 raise RuntimeError("Element guid %d doesn't exist" % guid)
145 factory = self._get_factory(guid)
146 if not trace_id in factory.traces:
147 raise RuntimeError("Element type '%s' has no trace '%s'" %
148 (factory.factory_id, trace_id))
149 if not guid in self._add_trace:
150 self._add_trace[guid] = list()
151 self._add_trace[guid].append(trace_id)
153 def defer_add_address(self, guid, address, netprefix, broadcast):
154 if not guid in self._create:
155 raise RuntimeError("Element guid %d doesn't exist" % guid)
156 factory = self._get_factory(guid)
157 if not factory.allow_addresses:
158 raise RuntimeError("Element type '%s' doesn't support addresses" %
160 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
161 if guid in self._add_address:
162 count_addresses = len(self._add_address[guid])
163 if max_addresses == count_addresses:
164 raise RuntimeError("Element guid %d of type '%s' can't accept \
165 more addresses" % (guid, factory.factory_id))
167 self._add_address[guid] = list()
168 self._add_address[guid].append((address, netprefix, broadcast))
170 def defer_add_route(self, guid, destination, netprefix, nexthop):
171 if not guid in self._create:
172 raise RuntimeError("Element guid %d doesn't exist" % guid)
173 factory = self._get_factory(guid)
174 if not factory.allow_routes:
175 raise RuntimeError("Element type '%s' doesn't support routes" %
177 if not guid in self._add_route:
178 self._add_route[guid] = list()
179 self._add_route[guid].append((destination, netprefix, nexthop))
182 self._status = TESTBED_STATUS_SETUP
186 # order guids (elements) according to factory_id
187 for guid, factory_id in self._create.iteritems():
188 if not factory_id in guids:
189 guids[factory_id] = list()
190 guids[factory_id].append(guid)
191 # create elements following the factory_id order
192 for factory_id in self._metadata.create_order:
193 # omit the factories that have no element to create
194 if factory_id not in guids:
196 factory = self._factories[factory_id]
197 for guid in guids[factory_id]:
198 factory.create_function(self, guid)
199 parameters = self._get_parameters(guid)
200 for name, value in parameters.iteritems():
201 self.set(TIME_NOW, guid, name, value)
202 self._status = TESTBED_STATUS_CREATED
204 def _do_connect(self, init = True):
205 for guid1, connections in self._connect.iteritems():
206 element1 = self._elements[guid1]
207 factory1 = self._get_factory(guid1)
208 for connector_type_name1, connections2 in connections.iteritems():
209 connector_type1 = factory1.connector_type(connector_type_name1)
210 for guid2, connector_type_name2 in connections2.iteritems():
211 element2 = self._elements[guid2]
212 factory_id2 = self._create[guid2]
213 # Connections are executed in a "From -> To" direction only
214 # This explicitly ignores the "To -> From" (mirror)
215 # connections of every connection pair.
217 connect_code = connector_type1.connect_to_init_code(
218 self._testbed_id, factory_id2,
219 connector_type_name2)
221 connect_code = connector_type1.connect_to_compl_code(
222 self._testbed_id, factory_id2,
223 connector_type_name2)
225 connect_code(self, element1, element2)
227 def do_connect_init(self):
230 def do_connect_compl(self):
231 self._do_connect(init = False)
232 self._status = TESTBED_STATUS_CONNECTED
234 def do_preconfigure(self):
236 # order guids (elements) according to factory_id
237 for guid, factory_id in self._create.iteritems():
238 if not factory_id in guids:
239 guids[factory_id] = list()
240 guids[factory_id].append(guid)
241 # configure elements following the factory_id order
242 for factory_id in self._metadata.preconfigure_order:
243 # omit the factories that have no element to create
244 if factory_id not in guids:
246 factory = self._factories[factory_id]
247 if not factory.preconfigure_function:
249 for guid in guids[factory_id]:
250 factory.preconfigure_function(self, guid)
252 def do_configure(self):
254 # order guids (elements) according to factory_id
255 for guid, factory_id in self._create.iteritems():
256 if not factory_id in guids:
257 guids[factory_id] = list()
258 guids[factory_id].append(guid)
259 # configure elements following the factory_id order
260 for factory_id in self._metadata.configure_order:
261 # omit the factories that have no element to create
262 if factory_id not in guids:
264 factory = self._factories[factory_id]
265 if not factory.configure_function:
267 for guid in guids[factory_id]:
268 factory.configure_function(self, guid)
269 self._status = TESTBED_STATUS_CONFIGURED
271 def _do_cross_connect(self, cross_data, init = True):
272 for guid, cross_connections in self._cross_connect.iteritems():
273 element = self._elements[guid]
274 factory = self._get_factory(guid)
275 for connector_type_name, cross_connection in \
276 cross_connections.iteritems():
277 connector_type = factory.connector_type(connector_type_name)
278 (cross_testbed_id, cross_factory_id,
279 cross_connector_type_name) = cross_connection
281 connect_code = connector_type.connect_to_init_code(
282 cross_testbed_id, cross_factory_id,
283 cross_conector_type_name)
285 connect_code = connector_type.connect_to_compl_code(
286 cross_testbed_id, cross_factory_id,
287 cross_conector_type_name)
289 elem_data_guid = cross_data[cross_testbed_id][cross_guid]
290 connect_code(self, element, elem_cross_data)
292 def do_cross_connect_init(self, cross_data):
293 self._do_cross_connect(cross_data)
295 def do_cross_connect_compl(self, cross_data):
296 self._do_cross_connect(cross_data, init = False)
297 self._status = TESTBED_STATUS_CROSS_CONNECTED
299 def set(self, time, guid, name, value):
300 if not guid in self._create:
301 raise RuntimeError("Element guid %d doesn't exist" % guid)
302 factory = self._get_factory(guid)
303 if not factory.box_attributes.has_attribute(name):
304 raise AttributeError("Invalid attribute %s for element type %s" %
305 (name, factory.factory_id))
306 if self._status > TESTBED_STATUS_CREATED and \
307 factory.box_attributes.is_attribute_design_only(name):
308 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
309 if not factory.box_attributes.is_attribute_value_valid(name, value):
310 raise AttributeError("Invalid value %s for attribute %s" % \
312 if guid not in self._set:
313 self._set[guid] = dict()
314 self._setlog[guid] = dict()
315 if time not in self._setlog[guid]:
316 self._setlog[guid][time] = dict()
317 self._setlog[guid][time][name] = value
318 self._set[guid][name] = value
320 def get(self, time, guid, name):
322 gets an attribute from box definitions if available.
323 Throws KeyError if the GUID wasn't created
324 through the defer_create interface, and AttributeError if the
325 attribute isn't available (doesn't exist or is design-only)
327 if not guid in self._create:
328 raise KeyError, "Element guid %d doesn't exist" % guid
329 factory = self._get_factory(guid)
330 if not factory.box_attributes.has_attribute(name):
331 raise AttributeError, "Invalid attribute %s for element type %s" % \
332 (name, factory.factory_id)
333 if self._status > TESTBED_STATUS_CREATED and \
334 factory.box_attributes.is_attribute_design_only(name):
335 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
336 if guid in self._set and name in self._set[guid]:
337 return self._set[guid][name]
338 if guid in self._create_set and name in self._create_set[guid]:
339 return self._create_set[guid][name]
340 return factory.box_attributes.get_attribute_value(name)
342 def get_route(self, guid, index, attribute):
344 returns information given to defer_add_route.
346 Raises AttributeError if an invalid attribute is requested
347 or if the indexed routing rule does not exist.
349 Raises KeyError if the GUID has not been seen by
352 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
354 if attribute not in ATTRIBUTES:
355 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
357 attribute_index = ATTRIBUTES.index(attribute)
359 routes = self._add_route.get(guid)
361 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
364 if not (0 <= index < len(addresses)):
365 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
366 guid, self._testbed_id, index)
368 return routes[index][attribute_index]
370 def get_address(self, guid, index, attribute='Address'):
372 returns information given to defer_add_address
374 Raises AttributeError if an invalid attribute is requested
375 or if the indexed routing rule does not exist.
377 Raises KeyError if the GUID has not been seen by
380 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
382 if attribute not in ATTRIBUTES:
383 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
385 attribute_index = ATTRIBUTES.index(attribute)
387 addresses = self._add_address.get(guid)
389 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
392 if not (0 <= index < len(addresses)):
393 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
394 guid, self._testbed_id, index)
396 return addresses[index][attribute_index]
398 def get_attribute_list(self, guid):
399 factory = self._get_factory(guid)
400 attribute_list = list()
401 return factory.box_attributes.attributes_list
403 def start(self, time = TIME_NOW):
404 for guid, factory_id in self._create.iteritems():
405 factory = self._factories[factory_id]
406 start_function = factory.start_function
408 start_function(self, guid)
409 self._status = TESTBED_STATUS_STARTED
411 #action: NotImplementedError
413 def stop(self, time = TIME_NOW):
414 for guid, factory_id in self._create.iteritems():
415 factory = self._factories[factory_id]
416 stop_function = factory.stop_function
418 stop_function(self, guid)
419 self._status = TESTBED_STATUS_STOPPED
421 def status(self, guid = None):
424 if not guid in self._create:
425 raise RuntimeError("Element guid %d doesn't exist" % guid)
426 factory = self._get_factory(guid)
427 status_function = factory.status_function
429 return status_function(self, guid)
430 return STATUS_UNDETERMINED
432 def trace(self, guid, trace_id, attribute='value'):
433 if attribute == 'value':
434 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
437 elif attribute == 'path':
438 content = self.trace_filename(guid, trace_id)
443 def trace_filename(self, guid, trace_id):
445 Return a trace's file path, for TestbedController's default
446 implementation of trace()
448 raise NotImplementedError
450 #shutdown: NotImplementedError
452 def get_connected(self, guid, connector_type_name,
453 other_connector_type_name):
454 """searchs the connected elements for the specific connector_type_name
456 if guid not in self._connect:
458 # all connections for all connectors for guid
459 all_connections = self._connect[guid]
460 if connector_type_name not in all_connections:
462 # all connections for the specific connector
463 connections = all_connections[connector_type_name]
464 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
465 in connections.iteritems() if \
466 otr_connector_type_name == other_connector_type_name]
467 return specific_connections
469 def _get_connection_count(self, guid, connection_type_name):
472 if guid in self._connect and connection_type_name in \
474 count = len(self._connect[guid][connection_type_name])
475 if guid in self._cross_connect and connection_type_name in \
476 self._cross_connect[guid]:
477 cross_count = len(self._cross_connect[guid][connection_type_name])
478 return count + cross_count
480 def _get_traces(self, guid):
481 return [] if guid not in self._add_trace else self._add_trace[guid]
483 def _get_parameters(self, guid):
484 return dict() if guid not in self._create_set else \
485 self._create_set[guid]
487 def _get_factory(self, guid):
488 factory_id = self._create[guid]
489 return self._factories[factory_id]