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 AF_INET, AF_INET6, STATUS_UNDETERMINED, TIME_NOW
9 class TestbedController(execute.TestbedController):
10 def __init__(self, testbed_id, testbed_version):
11 super(TestbedController, self).__init__(testbed_id, testbed_version)
13 # testbed attributes for validation
14 self._attributes = None
15 # element factories for validation
16 self._factories = dict()
18 # experiment construction instructions
20 self._create_set = dict()
21 self._factory_set = dict()
22 self._connect = dict()
23 self._cross_connect = dict()
24 self._add_trace = dict()
25 self._add_address = dict()
26 self._add_route = dict()
27 self._configure = dict()
29 # log of set operations
32 # testbed element instances
33 self._elements = dict()
35 self._metadata = Metadata(self._testbed_id, self._testbed_version)
36 for factory in self._metadata.build_execute_factories():
37 self._factories[factory.factory_id] = factory
38 self._attributes = self._metadata.testbed_attributes()
42 return self._create.keys()
48 def _get_factory_id(self, guid):
49 """ Returns the factory ID of the (perhaps not yet) created object """
50 return self._create.get(guid, None)
52 def defer_configure(self, name, value):
53 if not self._attributes.has_attribute(name):
54 raise AttributeError("Invalid attribute %s for testbed" % name)
56 self._attributes.set_attribute_value(name, value)
57 self._configure[name] = value
59 def defer_create(self, guid, factory_id):
60 if factory_id not in self._factories:
61 raise AttributeError("Invalid element type %s for testbed version %s" %
62 (factory_id, self._testbed_version))
63 if guid in self._create:
64 raise AttributeError("Cannot add elements with the same guid: %d" %
66 self._create[guid] = factory_id
68 def defer_create_set(self, guid, name, value):
69 if not guid in self._create:
70 raise RuntimeError("Element guid %d doesn't exist" % guid)
71 factory_id = self._create[guid]
72 factory = self._factories[factory_id]
73 if not factory.box_attributes.has_attribute(name):
74 raise AttributeError("Invalid attribute %s for element type %s" %
76 if not factory.box_attributes.is_attribute_value_valid(name, value):
77 raise AttributeError("Invalid value %s for attribute %s" % \
79 if guid not in self._create_set:
80 self._create_set[guid] = dict()
81 self._create_set[guid][name] = value
83 def defer_factory_set(self, guid, name, value):
84 if not guid in self._create:
85 raise RuntimeError("Element guid %d doesn't exist" % guid)
86 factory_id = self._create[guid]
87 factory = self._factories[factory_id]
88 if not factory.has_attribute(name):
89 raise AttributeError("Invalid attribute %s for element type %s" %
91 if not factory.is_attribute_value_valid(name, value):
92 raise AttributeError("Invalid value %s for attribute %s" % \
94 if guid not in self._factory_set:
95 self._factory_set[guid] = dict()
96 self._factory_set[guid][name] = value
98 def defer_connect(self, guid1, connector_type_name1, guid2,
99 connector_type_name2):
100 factory_id1 = self._create[guid1]
101 factory_id2 = self._create[guid2]
102 count = self._get_connection_count(guid1, connector_type_name1)
103 factory1 = self._factories[factory_id1]
104 connector_type = factory1.connector_type(connector_type_name1)
105 connector_type.can_connect(self._testbed_id, factory_id2,
106 connector_type_name2, count)
107 if not guid1 in self._connect:
108 self._connect[guid1] = dict()
109 if not connector_type_name1 in self._connect[guid1]:
110 self._connect[guid1][connector_type_name1] = dict()
111 self._connect[guid1][connector_type_name1][guid2] = \
113 if not guid2 in self._connect:
114 self._connect[guid2] = dict()
115 if not connector_type_name2 in self._connect[guid2]:
116 self._connect[guid2][connector_type_name2] = dict()
117 self._connect[guid2][connector_type_name2][guid1] = \
120 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
121 cross_testbed_id, cross_factory_id, cross_connector_type_name):
122 factory_id = self._create[guid]
123 count = self._get_connection_count(guid, connector_type_name)
124 factory = self._factories[factory_id]
125 connector_type = factory.connector_type(connector_type_name)
126 connector_type.can_connect(cross_testbed_id, cross_factory_id,
127 cross_connector_type_name, count, must_cross = True)
128 if not guid in self._cross_connect:
129 self._cross_connect[guid] = dict()
130 if not connector_type_name in self._cross_connect[guid]:
131 self._cross_connect[guid][connector_type_name] = dict()
132 self._cross_connect[guid][connector_type_name] = \
133 (cross_guid, cross_testbed_id, cross_factory_id,
134 cross_connector_type_name)
136 def defer_add_trace(self, guid, trace_id):
137 if not guid in self._create:
138 raise RuntimeError("Element guid %d doesn't exist" % guid)
139 factory_id = self._create[guid]
140 factory = self._factories[factory_id]
141 if not trace_id in factory.traces:
142 raise RuntimeError("Element type '%s' has no trace '%s'" %
143 (factory_id, trace_id))
144 if not guid in self._add_trace:
145 self._add_trace[guid] = list()
146 self._add_trace[guid].append(trace_id)
148 def defer_add_address(self, guid, address, netprefix, broadcast):
149 if not guid in self._create:
150 raise RuntimeError("Element guid %d doesn't exist" % guid)
151 factory_id = self._create[guid]
152 factory = self._factories[factory_id]
153 if not factory.allow_addresses:
154 raise RuntimeError("Element type '%s' doesn't support addresses" %
156 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
157 if guid in self._add_address:
158 count_addresses = len(self._add_address[guid])
159 if max_addresses == count_addresses:
160 raise RuntimeError("Element guid %d of type '%s' can't accept \
161 more addresses" % (guid, factory_id))
163 self._add_address[guid] = list()
164 self._add_address[guid].append((address, netprefix, broadcast))
166 def defer_add_route(self, guid, destination, netprefix, nexthop):
167 if not guid in self._create:
168 raise RuntimeError("Element guid %d doesn't exist" % guid)
169 factory_id = self._create[guid]
170 factory = self._factories[factory_id]
171 if not factory.allow_routes:
172 raise RuntimeError("Element type '%s' doesn't support routes" %
174 if not guid in self._add_route:
175 self._add_route[guid] = list()
176 self._add_route[guid].append((destination, netprefix, nexthop))
178 #do_setup(self): NotImplementedError
182 # order guids (elements) according to factory_id
183 for guid, factory_id in self._create.iteritems():
184 if not factory_id in guids:
185 guids[factory_id] = list()
186 guids[factory_id].append(guid)
187 # create elements following the factory_id order
188 for factory_id in self._metadata.create_order:
189 # omit the factories that have no element to create
190 if factory_id not in guids:
192 factory = self._factories[factory_id]
193 for guid in guids[factory_id]:
194 factory.create_function(self, guid)
195 parameters = self._get_parameters(guid)
196 for name, value in parameters.iteritems():
197 self.set(TIME_NOW, guid, name, value)
199 def _do_connect(self, init = True):
200 for guid1, connections in self._connect.iteritems():
201 element1 = self._elements[guid1]
202 factory_id1 = self._create[guid1]
203 factory1 = self._factories[factory_id1]
204 for connector_type_name1, connections2 in connections.iteritems():
205 connector_type1 = factory1.connector_type(connector_type_name1)
206 for guid2, connector_type_name2 in connections2.iteritems():
207 element2 = self._elements[guid2]
208 factory_id2 = self._create[guid2]
209 # Connections are executed in a "From -> To" direction only
210 # This explicitly ignores the "To -> From" (mirror)
211 # connections of every connection pair.
213 connect_code = connector_type1.connect_to_init_code(
214 self._testbed_id, factory_id2,
215 connector_type_name2)
217 connect_code = connector_type1.connect_to_compl_code(
218 self._testbed_id, factory_id2,
219 connector_type_name2)
221 connect_code(self, element1, element2)
223 def do_connect_init(self):
226 def do_connect_compl(self):
227 self._do_connect(init = False)
229 def do_preconfigure(self):
231 # order guids (elements) according to factory_id
232 for guid, factory_id in self._create.iteritems():
233 if not factory_id in guids:
234 guids[factory_id] = list()
235 guids[factory_id].append(guid)
236 # configure elements following the factory_id order
237 for factory_id in self._metadata.preconfigure_order:
238 # omit the factories that have no element to create
239 if factory_id not in guids:
241 factory = self._factories[factory_id]
242 if not factory.preconfigure_function:
244 for guid in guids[factory_id]:
245 factory.preconfigure_function(self, guid)
247 def do_configure(self):
249 # order guids (elements) according to factory_id
250 for guid, factory_id in self._create.iteritems():
251 if not factory_id in guids:
252 guids[factory_id] = list()
253 guids[factory_id].append(guid)
254 # configure elements following the factory_id order
255 for factory_id in self._metadata.configure_order:
256 # omit the factories that have no element to create
257 if factory_id not in guids:
259 factory = self._factories[factory_id]
260 if not factory.configure_function:
262 for guid in guids[factory_id]:
263 factory.configure_function(self, guid)
265 def _do_cross_connect(self, cross_data, init = True):
266 for guid, cross_connections in self._cross_connect.iteritems():
267 element = self._elements[guid]
268 factory_id = self._create[guid]
269 factory = self._factories[factory_id]
270 for connector_type_name, cross_connection in \
271 cross_connections.iteritems():
272 connector_type = factory.connector_type(connector_type_name)
273 (cross_testbed_id, cross_factory_id,
274 cross_connector_type_name) = cross_connection
276 connect_code = connector_type.connect_to_init_code(
277 cross_testbed_id, cross_factory_id,
278 cross_conector_type_name)
280 connect_code = connector_type.connect_to_compl_code(
281 cross_testbed_id, cross_factory_id,
282 cross_conector_type_name)
284 elem_data_guid = cross_data[cross_testbed_id][cross_guid]
285 connect_code(self, element, elem_cross_data)
287 def do_cross_connect_init(self, cross_data):
288 self._do_cross_connect(cross_data)
290 def do_cross_connect_compl(self, cross_data):
291 self._do_cross_connect(cross_data, init = False)
293 def set(self, time, guid, name, value):
294 if not guid in self._create:
295 raise RuntimeError("Element guid %d doesn't exist" % guid)
296 factory_id = self._create[guid]
297 factory = self._factories[factory_id]
298 if not factory.box_attributes.has_attribute(name):
299 raise AttributeError("Invalid attribute %s for element type %s" %
301 if self._started and factory.is_attribute_design_only(name):
302 raise AttributeError("Attribute %s can only be modified during experiment design" % name)
303 if not factory.box_attributes.is_attribute_value_valid(name, value):
304 raise AttributeError("Invalid value %s for attribute %s" % \
306 if guid not in self._set:
307 self._set[guid] = dict()
308 if time not in self._set[guid]:
309 self._set[guid][time] = dict()
310 self._set[guid][time][name] = value
312 def box_get(self, time, guid, name):
314 Helper for subclasses, gets an attribute from box definitions
315 if available. Throws KeyError if the GUID wasn't created
316 through the defer_create interface, and AttributeError if the
317 attribute isn't available (doesn't exist or is design-only)
319 if not guid in self._create:
320 raise KeyError, "Element guid %d doesn't exist" % guid
321 factory_id = self._create[guid]
322 factory = self._factories[factory_id]
323 if not factory.box_attributes.has_attribute(name):
324 raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
325 if self._started and factory.is_attribute_design_only(name):
326 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
327 return factory.box_attributes.get_attribute_value(name)
329 #get: NotImplementedError
331 def box_get_route(self, guid, index, attribute):
333 Helper implementation for get_route, returns information
334 given to defer_add_route.
336 Raises AttributeError if an invalid attribute is requested
337 or if the indexed routing rule does not exist.
339 Raises KeyError if the GUID has not been seen by
342 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
344 if attribute not in ATTRIBUTES:
345 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
347 attribute_index = ATTRIBUTES.index(attribute)
349 routes = self._add_route.get(guid)
351 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
353 if not (0 <= index < len(addresses)):
354 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
355 guid, self._testbed_id, index)
357 return routes[index][attribute_index]
359 def box_get_address(self, guid, index, attribute='Address'):
361 Helper implementation for get_address, returns information
362 given to defer_add_address
364 Raises AttributeError if an invalid attribute is requested
365 or if the indexed routing rule does not exist.
367 Raises KeyError if the GUID has not been seen by
370 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
372 if attribute not in ATTRIBUTES:
373 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
375 attribute_index = ATTRIBUTES.index(attribute)
377 addresses = self._add_address.get(guid)
379 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
381 if not (0 <= index < len(addresses)):
382 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
383 guid, self._testbed_id, index)
385 return addresses[index][attribute_index]
387 def get_attribute_list(self, guid):
388 factory_id = self._create[guid]
389 factory = self._factories[factory_id]
390 attribute_list = list()
391 return factory.box_attributes.attributes_list
393 def start(self, time = TIME_NOW):
394 for guid, factory_id in self._create.iteritems():
395 factory = self._factories[factory_id]
396 start_function = factory.start_function
398 start_function(self, guid)
401 #action: NotImplementedError
403 def stop(self, time = TIME_NOW):
404 for guid, factory_id in self._create.iteritems():
405 factory = self._factories[factory_id]
406 stop_function = factory.stop_function
408 stop_function(self, guid)
410 def status(self, guid):
411 if not guid in self._create:
412 raise RuntimeError("Element guid %d doesn't exist" % guid)
413 factory_id = self._create[guid]
414 factory = self._factories[factory_id]
415 status_function = factory.status_function
417 return status_function(self, guid)
418 return STATUS_UNDETERMINED
420 def trace(self, guid, trace_id, attribute='value'):
421 if attribute == 'value':
422 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
425 elif attribute == 'path':
426 content = self.trace_filename(guid, trace_id)
431 def trace_filename(self, guid, trace_id):
433 Return a trace's file path, for TestbedController's default
434 implementation of trace()
436 raise NotImplementedError
438 #shutdown: NotImplementedError
440 def get_connected(self, guid, connector_type_name,
441 other_connector_type_name):
442 """searchs the connected elements for the specific connector_type_name
444 if guid not in self._connect:
446 # all connections for all connectors for guid
447 all_connections = self._connect[guid]
448 if connector_type_name not in all_connections:
450 # all connections for the specific connector
451 connections = all_connections[connector_type_name]
452 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
453 in connections.iteritems() if \
454 otr_connector_type_name == other_connector_type_name]
455 return specific_connections
457 def _get_connection_count(self, guid, connection_type_name):
460 if guid in self._connect and connection_type_name in \
462 count = len(self._connect[guid][connection_type_name])
463 if guid in self._cross_connect and connection_type_name in \
464 self._cross_connect[guid]:
465 cross_count = len(self._cross_connect[guid][connection_type_name])
466 return count + cross_count
468 def _get_traces(self, guid):
469 return [] if guid not in self._add_trace else self._add_trace[guid]
471 def _get_parameters(self, guid):
472 return dict() if guid not in self._create_set else \
473 self._create_set[guid]