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_guid, cross_testbed_id, cross_factory_id,
129 cross_connector_type_name):
130 factory = self._get_factory(guid)
131 count = self._get_connection_count(guid, connector_type_name)
132 connector_type = factory.connector_type(connector_type_name)
133 connector_type.can_connect(cross_testbed_id, cross_factory_id,
134 cross_connector_type_name, count, must_cross = True)
135 if not guid in self._cross_connect:
136 self._cross_connect[guid] = dict()
137 if not connector_type_name in self._cross_connect[guid]:
138 self._cross_connect[guid][connector_type_name] = dict()
139 self._cross_connect[guid][connector_type_name] = \
140 (cross_guid, cross_testbed_guid, cross_testbed_id,
141 cross_factory_id, cross_connector_type_name)
143 def defer_add_trace(self, guid, trace_id):
144 if not guid in self._create:
145 raise RuntimeError("Element guid %d doesn't exist" % guid)
146 factory = self._get_factory(guid)
147 if not trace_id in factory.traces:
148 raise RuntimeError("Element type '%s' has no trace '%s'" %
149 (factory.factory_id, trace_id))
150 if not guid in self._add_trace:
151 self._add_trace[guid] = list()
152 self._add_trace[guid].append(trace_id)
154 def defer_add_address(self, guid, address, netprefix, broadcast):
155 if not guid in self._create:
156 raise RuntimeError("Element guid %d doesn't exist" % guid)
157 factory = self._get_factory(guid)
158 if not factory.allow_addresses:
159 raise RuntimeError("Element type '%s' doesn't support addresses" %
161 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
162 if guid in self._add_address:
163 count_addresses = len(self._add_address[guid])
164 if max_addresses == count_addresses:
165 raise RuntimeError("Element guid %d of type '%s' can't accept \
166 more addresses" % (guid, factory.factory_id))
168 self._add_address[guid] = list()
169 self._add_address[guid].append((address, netprefix, broadcast))
171 def defer_add_route(self, guid, destination, netprefix, nexthop):
172 if not guid in self._create:
173 raise RuntimeError("Element guid %d doesn't exist" % guid)
174 factory = self._get_factory(guid)
175 if not factory.allow_routes:
176 raise RuntimeError("Element type '%s' doesn't support routes" %
178 if not guid in self._add_route:
179 self._add_route[guid] = list()
180 self._add_route[guid].append((destination, netprefix, nexthop))
183 self._status = TESTBED_STATUS_SETUP
187 # order guids (elements) according to factory_id
188 for guid, factory_id in self._create.iteritems():
189 if not factory_id in guids:
190 guids[factory_id] = list()
191 guids[factory_id].append(guid)
192 # create elements following the factory_id order
193 for factory_id in self._metadata.create_order:
194 # omit the factories that have no element to create
195 if factory_id not in guids:
197 factory = self._factories[factory_id]
198 for guid in guids[factory_id]:
199 factory.create_function(self, guid)
200 parameters = self._get_parameters(guid)
201 for name, value in parameters.iteritems():
202 self.set(guid, name, value)
203 self._status = TESTBED_STATUS_CREATED
205 def _do_connect(self, init = True):
206 for guid1, connections in self._connect.iteritems():
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 factory_id2 = self._create[guid2]
212 # Connections are executed in a "From -> To" direction only
213 # This explicitly ignores the "To -> From" (mirror)
214 # connections of every connection pair.
216 connect_code = connector_type1.connect_to_init_code(
217 self._testbed_id, factory_id2,
218 connector_type_name2)
220 connect_code = connector_type1.connect_to_compl_code(
221 self._testbed_id, factory_id2,
222 connector_type_name2)
224 connect_code(self, guid1, guid2)
226 def do_connect_init(self):
229 def do_connect_compl(self):
230 self._do_connect(init = False)
231 self._status = TESTBED_STATUS_CONNECTED
233 def do_preconfigure(self):
235 # order guids (elements) according to factory_id
236 for guid, factory_id in self._create.iteritems():
237 if not factory_id in guids:
238 guids[factory_id] = list()
239 guids[factory_id].append(guid)
240 # configure elements following the factory_id order
241 for factory_id in self._metadata.preconfigure_order:
242 # omit the factories that have no element to create
243 if factory_id not in guids:
245 factory = self._factories[factory_id]
246 if not factory.preconfigure_function:
248 for guid in guids[factory_id]:
249 factory.preconfigure_function(self, guid)
251 def do_configure(self):
253 # order guids (elements) according to factory_id
254 for guid, factory_id in self._create.iteritems():
255 if not factory_id in guids:
256 guids[factory_id] = list()
257 guids[factory_id].append(guid)
258 # configure elements following the factory_id order
259 for factory_id in self._metadata.configure_order:
260 # omit the factories that have no element to create
261 if factory_id not in guids:
263 factory = self._factories[factory_id]
264 if not factory.configure_function:
266 for guid in guids[factory_id]:
267 factory.configure_function(self, guid)
268 self._status = TESTBED_STATUS_CONFIGURED
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)
283 connect_code = connector_type.connect_to_compl_code(
284 cross_testbed_id, cross_factory_id,
285 cross_connector_type_name)
287 elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
288 connect_code(self, guid, elem_cross_data)
290 def do_cross_connect_init(self, cross_data):
291 self._do_cross_connect(cross_data)
293 def do_cross_connect_compl(self, cross_data):
294 self._do_cross_connect(cross_data, init = False)
295 self._status = TESTBED_STATUS_CROSS_CONNECTED
297 def set(self, guid, name, value, time = TIME_NOW):
298 if not guid in self._create:
299 raise RuntimeError("Element guid %d doesn't exist" % guid)
300 factory = self._get_factory(guid)
301 if not factory.box_attributes.has_attribute(name):
302 raise AttributeError("Invalid attribute %s for element type %s" %
303 (name, factory.factory_id))
304 if self._status > TESTBED_STATUS_CREATED and \
305 factory.box_attributes.is_attribute_design_only(name):
306 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
307 if not factory.box_attributes.is_attribute_value_valid(name, value):
308 raise AttributeError("Invalid value %s for attribute %s" % \
310 if guid not in self._set:
311 self._set[guid] = dict()
312 self._setlog[guid] = dict()
313 if time not in self._setlog[guid]:
314 self._setlog[guid][time] = dict()
315 self._setlog[guid][time][name] = value
316 self._set[guid][name] = value
318 def get(self, guid, name, time = TIME_NOW):
320 gets an attribute from box definitions if available.
321 Throws KeyError if the GUID wasn't created
322 through the defer_create interface, and AttributeError if the
323 attribute isn't available (doesn't exist or is design-only)
325 if not guid in self._create:
326 raise KeyError, "Element guid %d doesn't exist" % guid
327 factory = self._get_factory(guid)
328 if not factory.box_attributes.has_attribute(name):
329 raise AttributeError, "Invalid attribute %s for element type %s" % \
330 (name, factory.factory_id)
331 if guid in self._set and name in self._set[guid]:
332 return self._set[guid][name]
333 if guid in self._create_set and name in self._create_set[guid]:
334 return self._create_set[guid][name]
335 return factory.box_attributes.get_attribute_value(name)
337 def get_route(self, guid, index, attribute):
339 returns information given to defer_add_route.
341 Raises AttributeError if an invalid attribute is requested
342 or if the indexed routing rule does not exist.
344 Raises KeyError if the GUID has not been seen by
347 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
349 if attribute not in ATTRIBUTES:
350 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
352 attribute_index = ATTRIBUTES.index(attribute)
354 routes = self._add_route.get(guid)
356 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
359 if not (0 <= index < len(addresses)):
360 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
361 guid, self._testbed_id, index)
363 return routes[index][attribute_index]
365 def get_address(self, guid, index, attribute='Address'):
367 returns information given to defer_add_address
369 Raises AttributeError if an invalid attribute is requested
370 or if the indexed routing rule does not exist.
372 Raises KeyError if the GUID has not been seen by
375 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
377 if attribute not in ATTRIBUTES:
378 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
380 attribute_index = ATTRIBUTES.index(attribute)
382 addresses = self._add_address.get(guid)
384 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
387 if not (0 <= index < len(addresses)):
388 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
389 guid, self._testbed_id, index)
391 return addresses[index][attribute_index]
393 def get_attribute_list(self, guid):
394 factory = self._get_factory(guid)
395 attribute_list = list()
396 return factory.box_attributes.attributes_list
398 def start(self, time = TIME_NOW):
399 for guid, factory_id in self._create.iteritems():
400 factory = self._factories[factory_id]
401 start_function = factory.start_function
403 start_function(self, guid)
404 self._status = TESTBED_STATUS_STARTED
406 #action: NotImplementedError
408 def stop(self, time = TIME_NOW):
409 for guid, factory_id in self._create.iteritems():
410 factory = self._factories[factory_id]
411 stop_function = factory.stop_function
413 stop_function(self, guid)
414 self._status = TESTBED_STATUS_STOPPED
416 def status(self, guid = None):
419 if not guid in self._create:
420 raise RuntimeError("Element guid %d doesn't exist" % guid)
421 factory = self._get_factory(guid)
422 status_function = factory.status_function
424 return status_function(self, guid)
425 return STATUS_UNDETERMINED
427 def trace(self, guid, trace_id, attribute='value'):
428 if attribute == 'value':
429 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
432 elif attribute == 'path':
433 content = self.trace_filename(guid, trace_id)
438 def trace_filename(self, guid, trace_id):
440 Return a trace's file path, for TestbedController's default
441 implementation of trace()
443 raise NotImplementedError
445 #shutdown: NotImplementedError
447 def get_connected(self, guid, connector_type_name,
448 other_connector_type_name):
449 """searchs the connected elements for the specific connector_type_name
451 if guid not in self._connect:
453 # all connections for all connectors for guid
454 all_connections = self._connect[guid]
455 if connector_type_name not in all_connections:
457 # all connections for the specific connector
458 connections = all_connections[connector_type_name]
459 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
460 in connections.iteritems() if \
461 otr_connector_type_name == other_connector_type_name]
462 return specific_connections
464 def _get_connection_count(self, guid, connection_type_name):
467 if guid in self._connect and connection_type_name in \
469 count = len(self._connect[guid][connection_type_name])
470 if guid in self._cross_connect and connection_type_name in \
471 self._cross_connect[guid]:
472 cross_count = len(self._cross_connect[guid][connection_type_name])
473 return count + cross_count
475 def _get_traces(self, guid):
476 return [] if guid not in self._add_trace else self._add_trace[guid]
478 def _get_parameters(self, guid):
479 return dict() if guid not in self._create_set else \
480 self._create_set[guid]
482 def _get_factory(self, guid):
483 factory_id = self._create[guid]
484 return self._factories[factory_id]