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 RuntimeError("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 RuntimeError("Invalid element type %s for testbed version %s" %
62 (factory_id, self._testbed_version))
63 if guid in self._create:
64 raise RuntimeError("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 RuntimeError("Invalid attribute %s for element type %s" %
76 factory.box_attributes.set_attribute_value(name, value)
77 if guid not in self._create_set:
78 self._create_set[guid] = dict()
79 self._create_set[guid][name] = value
81 def defer_factory_set(self, guid, name, value):
82 if not guid in self._create:
83 raise RuntimeError("Element guid %d doesn't exist" % guid)
84 factory_id = self._create[guid]
85 factory = self._factories[factory_id]
86 if not factory.has_attribute(name):
87 raise RuntimeError("Invalid attribute %s for element type %s" %
89 factory.set_attribute_value(name, value)
90 if guid not in self._factory_set:
91 self._factory_set[guid] = dict()
92 self._factory_set[guid][name] = value
94 def defer_connect(self, guid1, connector_type_name1, guid2,
95 connector_type_name2):
96 factory_id1 = self._create[guid1]
97 factory_id2 = self._create[guid2]
98 count = self._get_connection_count(guid1, connector_type_name1)
99 factory1 = self._factories[factory_id1]
100 connector_type = factory1.connector_type(connector_type_name1)
101 connector_type.can_connect(self._testbed_id, factory_id2,
102 connector_type_name2, count)
103 if not guid1 in self._connect:
104 self._connect[guid1] = dict()
105 if not connector_type_name1 in self._connect[guid1]:
106 self._connect[guid1][connector_type_name1] = dict()
107 self._connect[guid1][connector_type_name1][guid2] = \
109 if not guid2 in self._connect:
110 self._connect[guid2] = dict()
111 if not connector_type_name2 in self._connect[guid2]:
112 self._connect[guid2][connector_type_name2] = dict()
113 self._connect[guid2][connector_type_name2][guid1] = \
116 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
117 cross_testbed_id, cross_factory_id, cross_connector_type_name):
118 factory_id = self._create[guid]
119 count = self._get_connection_count(guid, connector_type_name)
120 factory = self._factories[factory_id]
121 connector_type = factory.connector_type(connector_type_name)
122 connector_type.can_connect(cross_testbed_id, cross_factory_id,
123 cross_connector_type_name, count, must_cross = True)
124 if not guid in self._connect:
125 self._cross_connect[guid] = dict()
126 if not connector_type_name in self._cross_connect[guid]:
127 self._cross_connect[guid][connector_type_name] = dict()
128 self._cross_connect[guid][connector_type_name] = \
129 (cross_guid, cross_testbed_id, cross_factory_id,
130 cross_connector_type_name)
132 def defer_add_trace(self, guid, trace_id):
133 if not guid in self._create:
134 raise RuntimeError("Element guid %d doesn't exist" % guid)
135 factory_id = self._create[guid]
136 factory = self._factories[factory_id]
137 if not trace_id in factory.traces:
138 raise RuntimeError("Element type '%s' has no trace '%s'" %
139 (factory_id, trace_id))
140 if not guid in self._add_trace:
141 self._add_trace[guid] = list()
142 self._add_trace[guid].append(trace_id)
144 def defer_add_address(self, guid, address, netprefix, broadcast):
145 if not guid in self._create:
146 raise RuntimeError("Element guid %d doesn't exist" % guid)
147 factory_id = self._create[guid]
148 factory = self._factories[factory_id]
149 if not factory.allow_addresses:
150 raise RuntimeError("Element type '%s' doesn't support addresses" %
152 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
153 if guid in self._add_address:
154 count_addresses = len(self._add_address[guid])
155 if max_addresses == count_addresses:
156 raise RuntimeError("Element guid %d of type '%s' can't accept \
157 more addresses" % (guid, factory_id))
159 self._add_address[guid] = list()
160 self._add_address[guid].append((address, netprefix, broadcast))
162 def defer_add_route(self, guid, destination, netprefix, nexthop):
163 if not guid in self._create:
164 raise RuntimeError("Element guid %d doesn't exist" % guid)
165 factory_id = self._create[guid]
166 factory = self._factories[factory_id]
167 if not factory.allow_routes:
168 raise RuntimeError("Element type '%s' doesn't support routes" %
170 if not guid in self._add_route:
171 self._add_route[guid] = list()
172 self._add_route[guid].append((destination, netprefix, nexthop))
174 #do_setup(self): NotImplementedError
178 # order guids (elements) according to factory_id
179 for guid, factory_id in self._create.iteritems():
180 if not factory_id in guids:
181 guids[factory_id] = list()
182 guids[factory_id].append(guid)
183 # create elements following the factory_id order
184 for factory_id in self._metadata.create_order:
185 # omit the factories that have no element to create
186 if factory_id not in guids:
188 factory = self._factories[factory_id]
189 for guid in guids[factory_id]:
190 factory.create_function(self, guid)
191 parameters = self._get_parameters(guid)
192 for name, value in parameters.iteritems():
193 self.set(TIME_NOW, guid, name, value)
195 def do_connect(self):
196 for guid1, connections in self._connect.iteritems():
197 element1 = self._elements[guid1]
198 factory_id1 = self._create[guid1]
199 factory1 = self._factories[factory_id1]
200 for connector_type_name1, connections2 in connections.iteritems():
201 connector_type1 = factory1.connector_type(connector_type_name1)
202 for guid2, connector_type_name2 in connections2.iteritems():
203 element2 = self._elements[guid2]
204 factory_id2 = self._create[guid2]
205 # Connections are executed in a "From -> To" direction only
206 # This explicitly ignores the "To -> From" (mirror)
207 # connections of every connection pair.
208 code_to_connect = connector_type1.code_to_connect(
209 self._testbed_id, factory_id2,
210 connector_type_name2)
212 code_to_connect(self, element1, element2)
214 def do_configure(self):
216 # order guids (elements) according to factory_id
217 for guid, factory_id in self._create.iteritems():
218 if not factory_id in guids:
219 guids[factory_id] = list()
220 guids[factory_id].append(guid)
221 # configure elements following the factory_id order
222 for factory_id in self._metadata.configure_order:
223 # omit the factories that have no element to create
224 if factory_id not in guids:
226 factory = self._factories[factory_id]
227 if not factory.configure_function:
229 for guid in guids[factory_id]:
230 factory.configure_function(self, guid)
232 def do_cross_connect(self):
233 for guid, cross_connections in self._cross_connect.iteritems():
234 element = self._elements[guid]
235 factory_id = self._create[guid]
236 factory = self._factories[factory_id]
237 for connector_type_name, cross_connection in \
238 cross_connections.iteritems():
239 connector_type = factory.connector_type(connector_type_name)
240 (cross_testbed_id, cross_factory_id,
241 cross_connector_type_name) = cross_connection
242 code_to_connect = connector_type.code_to_connect(
243 cross_guid, cross_testbed_id, cross_factory_id,
244 cross_conector_type_name)
246 code_to_connect(element, cross_guid)
248 def set(self, time, guid, name, value):
249 if not guid in self._create:
250 raise RuntimeError("Element guid %d doesn't exist" % guid)
251 factory_id = self._create[guid]
252 factory = self._factories[factory_id]
253 if not factory.box_attributes.has_attribute(name):
254 raise RuntimeError("Invalid attribute %s for element type %s" %
256 if self._started and factory.is_attribute_design_only(name):
257 raise RuntimeError("Attribute %s can only be modified during experiment design" % name)
258 factory.box_attributes.set_attribute_value(name, value)
259 if guid not in self._set:
260 self._set[guid] = dict()
261 if time not in self._set[guid]:
262 self._set[guid][time] = dict()
263 self._set[guid][time][name] = value
265 def box_get(self, time, guid, name):
267 Helper for subclasses, gets an attribute from box definitions
268 if available. Throws KeyError if the GUID wasn't created
269 through the defer_create interface, and AttributeError if the
270 attribute isn't available (doesn't exist or is design-only)
272 if not guid in self._create:
273 raise KeyError, "Element guid %d doesn't exist" % guid
274 factory_id = self._create[guid]
275 factory = self._factories[factory_id]
276 if not factory.box_attributes.has_attribute(name):
277 raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
278 if self._started and factory.is_attribute_design_only(name):
279 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
280 return factory.box_attributes.get_attribute_value(name)
282 #get: NotImplementedError
284 def box_get_route(self, guid, index, attribute):
286 Helper implementation for get_route, returns information
287 given to defer_add_route.
289 Raises AttributeError if an invalid attribute is requested
290 or if the indexed routing rule does not exist.
292 Raises KeyError if the GUID has not been seen by
295 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
297 if attribute not in ATTRIBUTES:
298 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
300 attribute_index = ATTRIBUTES.index(attribute)
302 routes = self._add_route.get(guid)
304 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
306 if not (0 <= index < len(addresses)):
307 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
308 guid, self._testbed_id, index)
310 return routes[index][attribute_index]
312 def box_get_address(self, guid, index, attribute='Address'):
314 Helper implementation for get_address, returns information
315 given to defer_add_address
317 Raises AttributeError if an invalid attribute is requested
318 or if the indexed routing rule does not exist.
320 Raises KeyError if the GUID has not been seen by
323 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
325 if attribute not in ATTRIBUTES:
326 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
328 attribute_index = ATTRIBUTES.index(attribute)
330 addresses = self._add_address.get(guid)
332 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
334 if not (0 <= index < len(addresses)):
335 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
336 guid, self._testbed_id, index)
338 return addresses[index][attribute_index]
341 def start(self, time = TIME_NOW):
342 for guid, factory_id in self._create.iteritems():
343 factory = self._factories[factory_id]
344 start_function = factory.start_function
346 start_function(self, guid)
349 #action: NotImplementedError
351 def stop(self, time = TIME_NOW):
352 for guid, factory_id in self._create.iteritems():
353 factory = self._factories[factory_id]
354 stop_function = factory.stop_function
356 stop_function(self, guid)
358 def status(self, guid):
359 if not guid in self._create:
360 raise RuntimeError("Element guid %d doesn't exist" % guid)
361 factory_id = self._create[guid]
362 factory = self._factories[factory_id]
363 status_function = factory.status_function
365 return status_function(self, guid)
366 return STATUS_UNDETERMINED
368 def trace(self, guid, trace_id, attribute='value'):
369 if attribute == 'value':
370 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
373 elif attribute == 'path':
374 content = self.trace_filename(guid, trace_id)
379 def trace_filename(self, guid, trace_id):
381 Return a trace's file path, for TestbedController's default
382 implementation of trace()
384 raise NotImplementedError
386 #shutdown: NotImplementedError
388 def get_connected(self, guid, connector_type_name,
389 other_connector_type_name):
390 """searchs the connected elements for the specific connector_type_name
392 if guid not in self._connect:
394 # all connections for all connectors for guid
395 all_connections = self._connect[guid]
396 if connector_type_name not in all_connections:
398 # all connections for the specific connector
399 connections = all_connections[connector_type_name]
400 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
401 in connections.iteritems() if \
402 otr_connector_type_name == other_connector_type_name]
403 return specific_connections
405 def _get_connection_count(self, guid, connection_type_name):
408 if guid in self._connect and connection_type_name in \
410 count = len(self._connect[guid][connection_type_name])
411 if guid in self._cross_connect and connection_type_name in \
412 self._cross_connect[guid]:
413 cross_count = len(self._cross_connect[guid][connection_type_name])
414 return count + cross_count
416 def _get_traces(self, guid):
417 return [] if guid not in self._add_trace else self._add_trace[guid]
419 def _get_parameters(self, guid):
420 return dict() if guid not in self._create_set else \
421 self._create_set[guid]