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 defer_configure(self, name, value):
49 if not self._attributes.has_attribute(name):
50 raise RuntimeError("Invalid attribute %s for testbed" % name)
52 self._attributes.set_attribute_value(name, value)
53 self._configure[name] = value
55 def defer_create(self, guid, factory_id):
56 if factory_id not in self._factories:
57 raise RuntimeError("Invalid element type %s for testbed version %s" %
58 (factory_id, self._testbed_version))
59 if guid in self._create:
60 raise RuntimeError("Cannot add elements with the same guid: %d" %
62 self._create[guid] = factory_id
64 def defer_create_set(self, guid, name, value):
65 if not guid in self._create:
66 raise RuntimeError("Element guid %d doesn't exist" % guid)
67 factory_id = self._create[guid]
68 factory = self._factories[factory_id]
69 if not factory.box_attributes.has_attribute(name):
70 raise RuntimeError("Invalid attribute %s for element type %s" %
72 factory.box_attributes.set_attribute_value(name, value)
73 if guid not in self._create_set:
74 self._create_set[guid] = dict()
75 self._create_set[guid][name] = value
77 def defer_factory_set(self, guid, name, value):
78 if not guid in self._create:
79 raise RuntimeError("Element guid %d doesn't exist" % guid)
80 factory_id = self._create[guid]
81 factory = self._factories[factory_id]
82 if not factory.has_attribute(name):
83 raise RuntimeError("Invalid attribute %s for element type %s" %
85 factory.set_attribute_value(name, value)
86 if guid not in self._factory_set:
87 self._factory_set[guid] = dict()
88 self._factory_set[guid][name] = value
90 def defer_connect(self, guid1, connector_type_name1, guid2,
91 connector_type_name2):
92 factory_id1 = self._create[guid1]
93 factory_id2 = self._create[guid2]
94 count = self._get_connection_count(guid1, connector_type_name1)
95 factory1 = self._factories[factory_id1]
96 connector_type = factory1.connector_type(connector_type_name1)
97 connector_type.can_connect(self._testbed_id, factory_id2,
98 connector_type_name2, count)
99 if not guid1 in self._connect:
100 self._connect[guid1] = dict()
101 if not connector_type_name1 in self._connect[guid1]:
102 self._connect[guid1][connector_type_name1] = dict()
103 self._connect[guid1][connector_type_name1][guid2] = \
105 if not guid2 in self._connect:
106 self._connect[guid2] = dict()
107 if not connector_type_name2 in self._connect[guid2]:
108 self._connect[guid2][connector_type_name2] = dict()
109 self._connect[guid2][connector_type_name2][guid1] = \
112 def defer_cross_connect(self, guid, connector_type_name, cross_guid,
113 cross_testbed_id, cross_factory_id, cross_connector_type_name):
114 factory_id = self._create[guid]
115 count = self._get_connection_count(guid, connector_type_name)
116 factory = self._factories[factory_id]
117 connector_type = factory.connector_type(connector_type_name)
118 connector_type.can_connect(cross_testbed_id, cross_factory_id,
119 cross_connector_type_name, count, must_cross = True)
120 if not guid in self._connect:
121 self._cross_connect[guid] = dict()
122 if not connector_type_name in self._cross_connect[guid]:
123 self._cross_connect[guid][connector_type_name] = dict()
124 self._cross_connect[guid][connector_type_name] = \
125 (cross_guid, cross_testbed_id, cross_factory_id,
126 cross_connector_type_name)
128 def defer_add_trace(self, guid, trace_id):
129 if not guid in self._create:
130 raise RuntimeError("Element guid %d doesn't exist" % guid)
131 factory_id = self._create[guid]
132 factory = self._factories[factory_id]
133 if not trace_id in factory.traces:
134 raise RuntimeError("Element type '%s' has no trace '%s'" %
135 (factory_id, trace_id))
136 if not guid in self._add_trace:
137 self._add_trace[guid] = list()
138 self._add_trace[guid].append(trace_id)
140 def defer_add_address(self, guid, address, netprefix, broadcast):
141 if not guid in self._create:
142 raise RuntimeError("Element guid %d doesn't exist" % guid)
143 factory_id = self._create[guid]
144 factory = self._factories[factory_id]
145 if not factory.allow_addresses:
146 raise RuntimeError("Element type '%s' doesn't support addresses" %
148 max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
149 if guid in self._add_address:
150 count_addresses = len(self._add_address[guid])
151 if max_addresses == count_addresses:
152 raise RuntimeError("Element guid %d of type '%s' can't accept \
153 more addresses" % (guid, factory_id))
155 self._add_address[guid] = list()
156 self._add_address[guid].append((address, netprefix, broadcast))
158 def defer_add_route(self, guid, destination, netprefix, nexthop):
159 if not guid in self._create:
160 raise RuntimeError("Element guid %d doesn't exist" % guid)
161 factory_id = self._create[guid]
162 factory = self._factories[factory_id]
163 if not factory.allow_routes:
164 raise RuntimeError("Element type '%s' doesn't support routes" %
166 if not guid in self._add_route:
167 self._add_route[guid] = list()
168 self._add_route[guid].append((destination, netprefix, nexthop))
170 #do_setup(self): NotImplementedError
174 # order guids (elements) according to factory_id
175 for guid, factory_id in self._create.iteritems():
176 if not factory_id in guids:
177 guids[factory_id] = list()
178 guids[factory_id].append(guid)
179 # create elements following the factory_id order
180 for factory_id in self._metadata.create_order:
181 # omit the factories that have no element to create
182 if factory_id not in guids:
184 factory = self._factories[factory_id]
185 for guid in guids[factory_id]:
186 factory.create_function(self, guid)
187 parameters = self._get_parameters(guid)
188 for name, value in parameters.iteritems():
189 self.set(TIME_NOW, guid, name, value)
191 def do_connect(self):
192 for guid1, connections in self._connect.iteritems():
193 element1 = self._elements[guid1]
194 factory_id1 = self._create[guid1]
195 factory1 = self._factories[factory_id1]
196 for connector_type_name1, connections2 in connections.iteritems():
197 connector_type1 = factory1.connector_type(connector_type_name1)
198 for guid2, connector_type_name2 in connections2.iteritems():
199 element2 = self._elements[guid2]
200 factory_id2 = self._create[guid2]
201 # Connections are executed in a "From -> To" direction only
202 # This explicitly ignores the "To -> From" (mirror)
203 # connections of every connection pair.
204 code_to_connect = connector_type1.code_to_connect(
205 self._testbed_id, factory_id2,
206 connector_type_name2)
208 code_to_connect(self, element1, element2)
210 def do_configure(self):
212 # order guids (elements) according to factory_id
213 for guid, factory_id in self._create.iteritems():
214 if not factory_id in guids:
215 guids[factory_id] = list()
216 guids[factory_id].append(guid)
217 # configure elements following the factory_id order
218 for factory_id in self._metadata.configure_order:
219 # omit the factories that have no element to create
220 if factory_id not in guids:
222 factory = self._factories[factory_id]
223 if not factory.configure_function:
225 for guid in guids[factory_id]:
226 factory.configure_function(self, guid)
228 def do_cross_connect(self):
229 for guid, cross_connections in self._cross_connect.iteritems():
230 element = self._elements[guid]
231 factory_id = self._create[guid]
232 factory = self._factories[factory_id]
233 for connector_type_name, cross_connection in \
234 cross_connections.iteritems():
235 connector_type = factory.connector_type(connector_type_name)
236 (cross_testbed_id, cross_factory_id,
237 cross_connector_type_name) = cross_connection
238 code_to_connect = connector_type.code_to_connect(
239 cross_guid, cross_testbed_id, cross_factory_id,
240 cross_conector_type_name)
242 code_to_connect(element, cross_guid)
244 def set(self, time, guid, name, value):
245 if not guid in self._create:
246 raise RuntimeError("Element guid %d doesn't exist" % guid)
247 factory_id = self._create[guid]
248 factory = self._factories[factory_id]
249 if not factory.box_attributes.has_attribute(name):
250 raise RuntimeError("Invalid attribute %s for element type %s" %
252 if self._started and factory.is_attribute_design_only(name):
253 raise RuntimeError("Attribute %s can only be modified during experiment design" % name)
254 factory.box_attributes.set_attribute_value(name, value)
255 if guid not in self._set:
256 self._set[guid] = dict()
257 if time not in self._set[guid]:
258 self._set[guid][time] = dict()
259 self._set[guid][time][name] = value
261 def box_get(self, time, guid, name):
263 Helper for subclasses, gets an attribute from box definitions
264 if available. Throws KeyError if the GUID wasn't created
265 through the defer_create interface, and AttributeError if the
266 attribute isn't available (doesn't exist or is design-only)
268 if not guid in self._create:
269 raise KeyError, "Element guid %d doesn't exist" % guid
270 factory_id = self._create[guid]
271 factory = self._factories[factory_id]
272 if not factory.box_attributes.has_attribute(name):
273 raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
274 if self._started and factory.is_attribute_design_only(name):
275 raise AttributeError, "Attribute %s can only be queried during experiment design" % name
276 return factory.box_attributes.get_attribute_value(name)
278 #get: NotImplementedError
280 def box_get_route(self, guid, index, attribute):
282 Helper implementation for get_route, returns information
283 given to defer_add_route.
285 Raises AttributeError if an invalid attribute is requested
286 or if the indexed routing rule does not exist.
288 Raises KeyError if the GUID has not been seen by
291 ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
293 if attribute not in ATTRIBUTES:
294 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
296 attribute_index = ATTRIBUTES.index(attribute)
298 routes = self._add_route.get(guid)
300 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
302 if not (0 <= index < len(addresses)):
303 raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
304 guid, self._testbed_id, index)
306 return routes[index][attribute_index]
308 def box_get_address(self, guid, index, attribute='Address'):
310 Helper implementation for get_address, returns information
311 given to defer_add_address
313 Raises AttributeError if an invalid attribute is requested
314 or if the indexed routing rule does not exist.
316 Raises KeyError if the GUID has not been seen by
319 ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
321 if attribute not in ATTRIBUTES:
322 raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
324 attribute_index = ATTRIBUTES.index(attribute)
326 addresses = self._add_address.get(guid)
328 raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
330 if not (0 <= index < len(addresses)):
331 raise AttributeError, "GUID %r at %s does not have an address #%s" % (
332 guid, self._testbed_id, index)
334 return addresses[index][attribute_index]
337 def start(self, time = TIME_NOW):
338 for guid, factory_id in self._create.iteritems():
339 factory = self._factories[factory_id]
340 start_function = factory.start_function
342 start_function(self, guid)
345 #action: NotImplementedError
347 def stop(self, time = TIME_NOW):
348 for guid, factory_id in self._create.iteritems():
349 factory = self._factories[factory_id]
350 stop_function = factory.stop_function
352 stop_function(self, guid)
354 def status(self, guid):
355 if not guid in self._create:
356 raise RuntimeError("Element guid %d doesn't exist" % guid)
357 factory_id = self._create[guid]
358 factory = self._factories[factory_id]
359 status_function = factory.status_function
361 return status_function(self, guid)
362 return STATUS_UNDETERMINED
364 def trace(self, guid, trace_id, attribute='value'):
365 if attribute == 'value':
366 fd = open("%s" % self.trace_filename(guid, trace_id), "r")
369 elif attribute == 'path':
370 content = self.trace_filename(guid, trace_id)
375 def trace_filename(self, guid, trace_id):
377 Return a trace's file path, for TestbedController's default
378 implementation of trace()
380 raise NotImplementedError
382 #shutdown: NotImplementedError
384 def get_connected(self, guid, connector_type_name,
385 other_connector_type_name):
386 """searchs the connected elements for the specific connector_type_name
388 if guid not in self._connect:
390 # all connections for all connectors for guid
391 all_connections = self._connect[guid]
392 if connector_type_name not in all_connections:
394 # all connections for the specific connector
395 connections = all_connections[connector_type_name]
396 specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
397 in connections.iteritems() if \
398 otr_connector_type_name == other_connector_type_name]
399 return specific_connections
401 def _get_connection_count(self, guid, connection_type_name):
404 if guid in self._connect and connection_type_name in \
406 count = len(self._connect[guid][connection_type_name])
407 if guid in self._cross_connect and connection_type_name in \
408 self._cross_connect[guid]:
409 cross_count = len(self._cross_connect[guid][connection_type_name])
410 return count + cross_count
412 def _get_traces(self, guid):
413 return [] if guid not in self._add_trace else self._add_trace[guid]
415 def _get_parameters(self, guid):
416 return dict() if guid not in self._create_set else \
417 self._create_set[guid]