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()
49 self._root_directory = None
52 def root_directory(self):
53 return self._root_directory
57 return self._create.keys()
63 def _get_factory_id(self, guid):
64 """ Returns the factory ID of the (perhaps not yet) created object """
65 return self._create.get(guid, None)
67 def defer_configure(self, name, value):
68 if not self._attributes.has_attribute(name):
69 raise AttributeError("Invalid attribute %s for testbed" % name)
71 self._attributes.set_attribute_value(name, value)
72 self._configure[name] = value
74 def defer_create(self, guid, factory_id):
75 if factory_id not in self._factories:
76 raise AttributeError("Invalid element type %s for testbed version %s" %
77 (factory_id, self._testbed_version))
78 if guid in self._create:
79 raise AttributeError("Cannot add elements with the same guid: %d" %
81 self._create[guid] = factory_id
83 def defer_create_set(self, guid, name, value):
84 if not guid in self._create:
85 raise RuntimeError("Element guid %d doesn't exist" % guid)
86 factory = self._get_factory(guid)
87 if not factory.box_attributes.has_attribute(name):
88 raise AttributeError("Invalid attribute %s for element type %s" %
89 (name, factory.factory_id))
90 if not factory.box_attributes.is_attribute_value_valid(name, value):
91 raise AttributeError("Invalid value %s for attribute %s" % \
93 if guid not in self._create_set:
94 self._create_set[guid] = dict()
95 self._create_set[guid][name] = value
97 def defer_factory_set(self, guid, name, value):
98 if not guid in self._create:
99 raise RuntimeError("Element guid %d doesn't exist" % guid)
100 factory = self._get_factory(guid)
101 if not factory.has_attribute(name):
102 raise AttributeError("Invalid attribute %s for element type %s" %
103 (name, factory.factory_id))
104 if not factory.is_attribute_value_valid(name, value):
105 raise AttributeError("Invalid value %s for attribute %s" % \
107 if guid not in self._factory_set:
108 self._factory_set[guid] = dict()
109 self._factory_set[guid][name] = value
111 def defer_connect(self, guid1, connector_type_name1, guid2,
112 connector_type_name2):
113 factory1 = self._get_factory(guid1)
114 factory_id2 = self._create[guid2]
115 count = self._get_connection_count(guid1, connector_type_name1)
116 connector_type = factory1.connector_type(connector_type_name1)
117 connector_type.can_connect(self._testbed_id, factory_id2,
118 connector_type_name2, count)
119 if not guid1 in self._connect:
120 self._connect[guid1] = dict()
121 if not connector_type_name1 in self._connect[guid1]:
122 self._connect[guid1][connector_type_name1] = dict()
123 self._connect[guid1][connector_type_name1][guid2] = \
125 if not guid2 in self._connect:
126 self._connect[guid2] = dict()
127 if not connector_type_name2 in self._connect[guid2]:
128 self._connect[guid2][connector_type_name2] = dict()
129 self._connect[guid2][connector_type_name2][guid1] = \
132 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
133 cross_testbed_guid, cross_testbed_id, cross_factory_id,
134 cross_connector_type_name):
135 factory = self._get_factory(guid)
136 count = self._get_connection_count(guid, connector_type_name)
137 connector_type = factory.connector_type(connector_type_name)
138 connector_type.can_connect(cross_testbed_id, cross_factory_id,
139 cross_connector_type_name, count, must_cross = True)
140 if not guid in self._cross_connect:
141 self._cross_connect[guid] = dict()
142 if not connector_type_name in self._cross_connect[guid]:
143 self._cross_connect[guid][connector_type_name] = dict()
144 self._cross_connect[guid][connector_type_name] = \
145 (cross_guid, cross_testbed_guid, cross_testbed_id,
146 cross_factory_id, cross_connector_type_name)
148 def defer_add_trace(self, guid, trace_id):
149 if not guid in self._create:
150 raise RuntimeError("Element guid %d doesn't exist" % guid)
151 factory = self._get_factory(guid)
152 if not trace_id in factory.traces:
153 raise RuntimeError("Element type '%s' has no trace '%s'" %
154 (factory.factory_id, trace_id))
155 if not guid in self._add_trace:
156 self._add_trace[guid] = list()
157 self._add_trace[guid].append(trace_id)
159 def defer_add_address(self, guid, address, netprefix, broadcast):
160 if not guid in self._create:
161 raise RuntimeError("Element guid %d doesn't exist" % guid)
162 factory = self._get_factory(guid)
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.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 = self._get_factory(guid)
180 if not factory.allow_routes:
181 raise RuntimeError("Element type '%s' doesn't support routes" %
183 if not guid in self._add_route:
184 self._add_route[guid] = list()
185 self._add_route[guid].append((destination, netprefix, nexthop))
188 self._root_directory = self._attributes.\
189 get_attribute_value("rootDirectory")
190 self._status = TESTBED_STATUS_SETUP
194 # order guids (elements) according to factory_id
195 for guid, factory_id in self._create.iteritems():
196 if not factory_id in guids:
197 guids[factory_id] = list()
198 guids[factory_id].append(guid)
199 # create elements following the factory_id order
200 for factory_id in self._metadata.create_order:
201 # omit the factories that have no element to create
202 if factory_id not in guids:
204 factory = self._factories[factory_id]
205 for guid in guids[factory_id]:
206 factory.create_function(self, guid)
207 parameters = self._get_parameters(guid)
208 for name, value in parameters.iteritems():
209 self.set(guid, name, value)
210 self._status = TESTBED_STATUS_CREATED
212 def _do_connect(self, init = True):
213 for guid1, connections in self._connect.iteritems():
214 factory1 = self._get_factory(guid1)
215 for connector_type_name1, connections2 in connections.iteritems():
216 connector_type1 = factory1.connector_type(connector_type_name1)
217 for guid2, connector_type_name2 in connections2.iteritems():
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, guid1, guid2)
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 factory = self._get_factory(guid)
280 for connector_type_name, cross_connection in \
281 cross_connections.iteritems():
282 connector_type = factory.connector_type(connector_type_name)
283 (cross_guid, cross_testbed_guid, cross_testbed_id,
284 cross_factory_id, cross_connector_type_name) = cross_connection
286 connect_code = connector_type.connect_to_init_code(
287 cross_testbed_id, cross_factory_id,
288 cross_connector_type_name)
290 connect_code = connector_type.connect_to_compl_code(
291 cross_testbed_id, cross_factory_id,
292 cross_connector_type_name)
294 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
295 connect_code(self, guid, elem_cross_data)
297 def do_cross_connect_init(self, cross_data):
298 self._do_cross_connect(cross_data)
300 def do_cross_connect_compl(self, cross_data):
301 self._do_cross_connect(cross_data, init = False)
302 self._status = TESTBED_STATUS_CROSS_CONNECTED
304 def set(self, guid, name, value, time = TIME_NOW):
305 if not guid in self._create:
306 raise RuntimeError("Element guid %d doesn't exist" % guid)
307 factory = self._get_factory(guid)
308 if not factory.box_attributes.has_attribute(name):
309 raise AttributeError("Invalid attribute %s for element type %s" %
310 (name, factory.factory_id))
311 if self._status > TESTBED_STATUS_STARTED and \
312 factory.box_attributes.is_attribute_design_only(name):
313 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
314 if not factory.box_attributes.is_attribute_value_valid(name, value):
315 raise AttributeError("Invalid value %s for attribute %s" % \
317 if guid not in self._set:
318 self._set[guid] = dict()
319 self._setlog[guid] = dict()
320 if time not in self._setlog[guid]:
321 self._setlog[guid][time] = dict()
322 self._setlog[guid][time][name] = value
323 self._set[guid][name] = value
325 def get(self, guid, name, time = TIME_NOW):
327 gets an attribute from box definitions if available.
328 Throws KeyError if the GUID wasn't created
329 through the defer_create interface, and AttributeError if the
330 attribute isn't available (doesn't exist or is design-only)
332 if not guid in self._create:
333 raise KeyError, "Element guid %d doesn't exist" % guid
334 factory = self._get_factory(guid)
335 if not factory.box_attributes.has_attribute(name):
336 raise AttributeError, "Invalid attribute %s for element type %s" % \
337 (name, factory.factory_id)
338 if guid in self._set and name in self._set[guid]:
339 return self._set[guid][name]
340 if guid in self._create_set and name in self._create_set[guid]:
341 return self._create_set[guid][name]
342 return factory.box_attributes.get_attribute_value(name)
344 def get_route(self, guid, index, attribute):
346 returns information given to defer_add_route.
348 Raises AttributeError if an invalid attribute is requested
349 or if the indexed routing rule does not exist.
351 Raises KeyError if the GUID has not been seen by
354 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
356 if attribute not in ATTRIBUTES:
357 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
359 attribute_index = ATTRIBUTES.index(attribute)
361 routes = self._add_route.get(guid)
363 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 get_address(self, guid, index, attribute='Address'):
374 returns information given to defer_add_address
376 Raises AttributeError if an invalid attribute is requested
377 or if the indexed routing rule does not exist.
379 Raises KeyError if the GUID has not been seen by
382 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
384 if attribute not in ATTRIBUTES:
385 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
387 attribute_index = ATTRIBUTES.index(attribute)
389 addresses = self._add_address.get(guid)
391 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 = self._get_factory(guid)
402 attribute_list = list()
403 return factory.box_attributes.attributes_list
405 def start(self, time = TIME_NOW):
406 for guid, factory_id in self._create.iteritems():
407 factory = self._factories[factory_id]
408 start_function = factory.start_function
410 start_function(self, guid)
411 self._status = TESTBED_STATUS_STARTED
413 #action: NotImplementedError
415 def stop(self, time = TIME_NOW):
416 for guid, factory_id in self._create.iteritems():
417 factory = self._factories[factory_id]
418 stop_function = factory.stop_function
420 stop_function(self, guid)
421 self._status = TESTBED_STATUS_STOPPED
423 def status(self, guid = None):
426 if not guid in self._create:
427 raise RuntimeError("Element guid %d doesn't exist" % guid)
428 factory = self._get_factory(guid)
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]
489 def _get_factory(self, guid):
490 factory_id = self._create[guid]
491 return self._factories[factory_id]