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
19 class TestbedController(execute.TestbedController):
20 def __init__(self, testbed_id, testbed_version):
21 super(TestbedController, self).__init__(testbed_id, testbed_version)
22 self._status = TESTBED_STATUS_ZERO
23 # testbed attributes for validation
24 self._attributes = None
25 # element factories for validation
26 self._factories = dict()
28 # experiment construction instructions
30 self._create_set = dict()
31 self._factory_set = dict()
32 self._connect = dict()
33 self._cross_connect = dict()
34 self._add_trace = dict()
35 self._add_address = dict()
36 self._add_route = dict()
37 self._configure = dict()
39 # log of set operations
44 # testbed element instances
45 self._elements = dict()
47 self._metadata = Metadata(self._testbed_id, self._testbed_version)
48 for factory in self._metadata.build_execute_factories():
49 self._factories[factory.factory_id] = factory
50 self._attributes = self._metadata.testbed_attributes()
51 self._root_directory = None
54 def root_directory(self):
55 return self._root_directory
59 return self._create.keys()
65 def _get_factory_id(self, guid):
66 """ Returns the factory ID of the (perhaps not yet) created object """
67 return self._create.get(guid, None)
69 def defer_configure(self, name, value):
70 if not self._attributes.has_attribute(name):
71 raise AttributeError("Invalid attribute %s for testbed" % name)
73 self._attributes.set_attribute_value(name, value)
74 self._configure[name] = value
76 def defer_create(self, guid, factory_id):
77 if factory_id not in self._factories:
78 raise AttributeError("Invalid element type %s for testbed version %s" %
79 (factory_id, self._testbed_version))
80 if guid in self._create:
81 raise AttributeError("Cannot add elements with the same guid: %d" %
83 self._create[guid] = factory_id
85 def defer_create_set(self, guid, name, value):
86 if not guid in self._create:
87 raise RuntimeError("Element guid %d doesn't exist" % guid)
88 factory = self._get_factory(guid)
89 if not factory.box_attributes.has_attribute(name):
90 raise AttributeError("Invalid attribute %s for element type %s" %
91 (name, factory.factory_id))
92 if not factory.box_attributes.is_attribute_value_valid(name, value):
93 raise AttributeError("Invalid value %s for attribute %s" % \
95 if guid not in self._create_set:
96 self._create_set[guid] = dict()
97 self._create_set[guid][name] = value
99 def defer_factory_set(self, guid, name, value):
100 if not guid in self._create:
101 raise RuntimeError("Element guid %d doesn't exist" % guid)
102 factory = self._get_factory(guid)
103 if not factory.has_attribute(name):
104 raise AttributeError("Invalid attribute %s for element type %s" %
105 (name, factory.factory_id))
106 if not factory.is_attribute_value_valid(name, value):
107 raise AttributeError("Invalid value %s for attribute %s" % \
109 if guid not in self._factory_set:
110 self._factory_set[guid] = dict()
111 self._factory_set[guid][name] = value
113 def defer_connect(self, guid1, connector_type_name1, guid2,
114 connector_type_name2):
115 factory1 = self._get_factory(guid1)
116 factory_id2 = self._create[guid2]
117 count = self._get_connection_count(guid1, connector_type_name1)
118 connector_type = factory1.connector_type(connector_type_name1)
119 connector_type.can_connect(self._testbed_id, factory_id2,
120 connector_type_name2, count, False)
121 if not guid1 in self._connect:
122 self._connect[guid1] = dict()
123 if not connector_type_name1 in self._connect[guid1]:
124 self._connect[guid1][connector_type_name1] = dict()
125 self._connect[guid1][connector_type_name1][guid2] = \
127 if not guid2 in self._connect:
128 self._connect[guid2] = dict()
129 if not connector_type_name2 in self._connect[guid2]:
130 self._connect[guid2][connector_type_name2] = dict()
131 self._connect[guid2][connector_type_name2][guid1] = \
134 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
135 cross_testbed_guid, cross_testbed_id, cross_factory_id,
136 cross_connector_type_name):
137 factory = self._get_factory(guid)
138 count = self._get_connection_count(guid, connector_type_name)
139 connector_type = factory.connector_type(connector_type_name)
140 connector_type.can_connect(cross_testbed_id, cross_factory_id,
141 cross_connector_type_name, count, True)
142 if not guid in self._cross_connect:
143 self._cross_connect[guid] = dict()
144 if not connector_type_name in self._cross_connect[guid]:
145 self._cross_connect[guid][connector_type_name] = dict()
146 self._cross_connect[guid][connector_type_name] = \
147 (cross_guid, cross_testbed_guid, cross_testbed_id,
148 cross_factory_id, cross_connector_type_name)
150 def defer_add_trace(self, guid, trace_id):
151 if not guid in self._create:
152 raise RuntimeError("Element guid %d doesn't exist" % guid)
153 factory = self._get_factory(guid)
154 if not trace_id in factory.traces:
155 raise RuntimeError("Element type '%s' has no trace '%s'" %
156 (factory.factory_id, trace_id))
157 if not guid in self._add_trace:
158 self._add_trace[guid] = list()
159 self._add_trace[guid].append(trace_id)
161 def defer_add_address(self, guid, address, netprefix, broadcast):
162 if not guid in self._create:
163 raise RuntimeError("Element guid %d doesn't exist" % guid)
164 factory = self._get_factory(guid)
165 if not factory.allow_addresses:
166 raise RuntimeError("Element type '%s' doesn't support addresses" %
168 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
169 if guid in self._add_address:
170 count_addresses = len(self._add_address[guid])
171 if max_addresses == count_addresses:
172 raise RuntimeError("Element guid %d of type '%s' can't accept \
173 more addresses" % (guid, factory.factory_id))
175 self._add_address[guid] = list()
176 self._add_address[guid].append((address, netprefix, broadcast))
178 def defer_add_route(self, guid, destination, netprefix, nexthop):
179 if not guid in self._create:
180 raise RuntimeError("Element guid %d doesn't exist" % guid)
181 factory = self._get_factory(guid)
182 if not factory.allow_routes:
183 raise RuntimeError("Element type '%s' doesn't support routes" %
185 if not guid in self._add_route:
186 self._add_route[guid] = list()
187 self._add_route[guid].append((destination, netprefix, nexthop))
190 self._root_directory = self._attributes.\
191 get_attribute_value("rootDirectory")
192 self._status = TESTBED_STATUS_SETUP
195 def set_params(self, guid):
196 parameters = self._get_parameters(guid)
197 for name, value in parameters.iteritems():
198 self.set(guid, name, value)
200 self._do_in_factory_order(
202 self._metadata.create_order,
203 postaction = set_params )
204 self._status = TESTBED_STATUS_CREATED
206 def _do_connect(self, init = True):
207 for guid1, connections in self._connect.iteritems():
208 factory1 = self._get_factory(guid1)
209 for connector_type_name1, connections2 in connections.iteritems():
210 connector_type1 = factory1.connector_type(connector_type_name1)
211 for guid2, connector_type_name2 in connections2.iteritems():
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,
222 connect_code = connector_type1.connect_to_compl_code(
223 self._testbed_id, factory_id2,
224 connector_type_name2,
227 connect_code(self, guid1, guid2)
229 def do_connect_init(self):
232 def do_connect_compl(self):
233 self._do_connect(init = False)
234 self._status = TESTBED_STATUS_CONNECTED
236 def _do_in_factory_order(self, action, order, postaction = None):
237 guids = collections.defaultdict(list)
238 # order guids (elements) according to factory_id
239 for guid, factory_id in self._create.iteritems():
240 guids[factory_id].append(guid)
241 # configure elements following the factory_id order
242 for factory_id in 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 getattr(factory, action):
249 for guid in guids[factory_id]:
250 getattr(factory, action)(self, guid)
252 postaction(self, guid)
254 def do_preconfigure(self):
255 self._do_in_factory_order(
256 'preconfigure_function',
257 self._metadata.preconfigure_order )
259 def do_configure(self):
260 self._do_in_factory_order(
261 'configure_function',
262 self._metadata.configure_order )
263 self._status = TESTBED_STATUS_CONFIGURED
265 def do_prestart(self):
266 self._do_in_factory_order(
268 self._metadata.prestart_order )
270 def _do_cross_connect(self, cross_data, init = True):
271 for guid, cross_connections in self._cross_connect.iteritems():
272 factory = self._get_factory(guid)
273 for connector_type_name, cross_connection in \
274 cross_connections.iteritems():
275 connector_type = factory.connector_type(connector_type_name)
276 (cross_guid, cross_testbed_guid, cross_testbed_id,
277 cross_factory_id, cross_connector_type_name) = cross_connection
279 connect_code = connector_type.connect_to_init_code(
280 cross_testbed_id, cross_factory_id,
281 cross_connector_type_name,
284 connect_code = connector_type.connect_to_compl_code(
285 cross_testbed_id, cross_factory_id,
286 cross_connector_type_name,
289 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
290 connect_code(self, guid, 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, guid, name, value, time = TIME_NOW):
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_STARTED 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, guid, name, time = TIME_NOW):
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 guid in self._set and name in self._set[guid]:
334 return self._set[guid][name]
335 if guid in self._create_set and name in self._create_set[guid]:
336 return self._create_set[guid][name]
337 return factory.box_attributes.get_attribute_value(name)
339 def get_route(self, guid, index, attribute):
341 returns information given to defer_add_route.
343 Raises AttributeError if an invalid attribute is requested
344 or if the indexed routing rule does not exist.
346 Raises KeyError if the GUID has not been seen by
349 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
351 if attribute not in ATTRIBUTES:
352 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
354 attribute_index = ATTRIBUTES.index(attribute)
356 routes = self._add_route.get(guid)
358 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
361 if not (0 <= index < len(addresses)):
362 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
363 guid, self._testbed_id, index)
365 return routes[index][attribute_index]
367 def get_address(self, guid, index, attribute='Address'):
369 returns information given to defer_add_address
371 Raises AttributeError if an invalid attribute is requested
372 or if the indexed routing rule does not exist.
374 Raises KeyError if the GUID has not been seen by
377 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
379 if attribute not in ATTRIBUTES:
380 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
382 attribute_index = ATTRIBUTES.index(attribute)
384 addresses = self._add_address.get(guid)
386 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
389 if not (0 <= index < len(addresses)):
390 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
391 guid, self._testbed_id, index)
393 return addresses[index][attribute_index]
395 def get_tags(self, guid):
396 factory = self._get_factory(guid)
399 def get_attribute_list(self, guid):
400 factory = self._get_factory(guid)
401 attribute_list = list()
402 return factory.box_attributes.attributes_list
404 def start(self, time = TIME_NOW):
405 self._do_in_factory_order(
407 self._metadata.start_order )
408 self._status = TESTBED_STATUS_STARTED
410 #action: NotImplementedError
412 def stop(self, time = TIME_NOW):
413 self._do_in_factory_order(
415 reversed(self._metadata.start_order) )
416 self._status = TESTBED_STATUS_STOPPED
418 def status(self, guid = None):
421 if not guid in self._create:
422 raise RuntimeError("Element guid %d doesn't exist" % guid)
423 factory = self._get_factory(guid)
424 status_function = factory.status_function
426 return status_function(self, guid)
427 return STATUS_UNDETERMINED
429 def trace(self, guid, trace_id, attribute='value'):
430 if attribute == 'value':
431 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
434 elif attribute == 'path':
435 content = self.trace_filename(guid, trace_id)
440 def trace_filename(self, guid, trace_id):
442 Return a trace's file path, for TestbedController's default
443 implementation of trace()
445 raise NotImplementedError
447 #shutdown: NotImplementedError
449 def get_connected(self, guid, connector_type_name,
450 other_connector_type_name):
451 """searchs the connected elements for the specific connector_type_name
453 if guid not in self._connect:
455 # all connections for all connectors for guid
456 all_connections = self._connect[guid]
457 if connector_type_name not in all_connections:
459 # all connections for the specific connector
460 connections = all_connections[connector_type_name]
461 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
462 in connections.iteritems() if \
463 otr_connector_type_name == other_connector_type_name]
464 return specific_connections
466 def _get_connection_count(self, guid, connection_type_name):
469 if guid in self._connect and connection_type_name in \
471 count = len(self._connect[guid][connection_type_name])
472 if guid in self._cross_connect and connection_type_name in \
473 self._cross_connect[guid]:
474 cross_count = len(self._cross_connect[guid][connection_type_name])
475 return count + cross_count
477 def _get_traces(self, guid):
478 return [] if guid not in self._add_trace else self._add_trace[guid]
480 def _get_parameters(self, guid):
481 return dict() if guid not in self._create_set else \
482 self._create_set[guid]
484 def _get_factory(self, guid):
485 factory_id = self._create[guid]
486 return self._factories[factory_id]