testbed_impl.get improved
[nepi.git] / src / nepi / core / testbed_impl.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
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, \
8     TESTBED_STATUS_ZERO, \
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
16
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()
25
26         # experiment construction instructions
27         self._create = dict()
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()
36
37         # log of set operations
38         self._setlog = dict()
39         # last set operations
40         self._set = dict()
41
42         # testbed element instances
43         self._elements = dict()
44
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()
49
50     @property
51     def guids(self):
52         return self._create.keys()
53
54     @property
55     def elements(self):
56         return self._elements
57     
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)
61
62     def defer_configure(self, name, value):
63         if not self._attributes.has_attribute(name):
64             raise AttributeError("Invalid attribute %s for testbed" % name)
65         # Validation
66         self._attributes.set_attribute_value(name, value)
67         self._configure[name] = value
68
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" %
75                     guid)
76         self._create[guid] = factory_id
77
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_id = self._create[guid]
82         factory = self._factories[factory_id]
83         if not factory.box_attributes.has_attribute(name):
84             raise AttributeError("Invalid attribute %s for element type %s" %
85                     (name, factory_id))
86         if not factory.box_attributes.is_attribute_value_valid(name, value):
87             raise AttributeError("Invalid value %s for attribute %s" % \
88                 (value, name))
89         if guid not in self._create_set:
90             self._create_set[guid] = dict()
91         self._create_set[guid][name] = value
92
93     def defer_factory_set(self, guid, name, value):
94         if not guid in self._create:
95             raise RuntimeError("Element guid %d doesn't exist" % guid)
96         factory_id = self._create[guid]
97         factory = self._factories[factory_id]
98         if not factory.has_attribute(name):
99             raise AttributeError("Invalid attribute %s for element type %s" %
100                     (name, factory_id))
101         if not factory.is_attribute_value_valid(name, value):
102             raise AttributeError("Invalid value %s for attribute %s" % \
103                 (value, name))
104         if guid not in self._factory_set:
105             self._factory_set[guid] = dict()
106         self._factory_set[guid][name] = value
107
108     def defer_connect(self, guid1, connector_type_name1, guid2, 
109             connector_type_name2):
110         factory_id1 = self._create[guid1]
111         factory_id2 = self._create[guid2]
112         count = self._get_connection_count(guid1, connector_type_name1)
113         factory1 = self._factories[factory_id1]
114         connector_type = factory1.connector_type(connector_type_name1)
115         connector_type.can_connect(self._testbed_id, factory_id2, 
116                 connector_type_name2, count)
117         if not guid1 in self._connect:
118             self._connect[guid1] = dict()
119         if not connector_type_name1 in self._connect[guid1]:
120              self._connect[guid1][connector_type_name1] = dict()
121         self._connect[guid1][connector_type_name1][guid2] = \
122                connector_type_name2
123         if not guid2 in self._connect:
124             self._connect[guid2] = dict()
125         if not connector_type_name2 in self._connect[guid2]:
126              self._connect[guid2][connector_type_name2] = dict()
127         self._connect[guid2][connector_type_name2][guid1] = \
128                 connector_type_name1
129
130     def defer_cross_connect(self, guid, connector_type_name, cross_guid, 
131             cross_testbed_id, cross_factory_id, cross_connector_type_name):
132         factory_id = self._create[guid]
133         count = self._get_connection_count(guid, connector_type_name)
134         factory = self._factories[factory_id]
135         connector_type = factory.connector_type(connector_type_name)
136         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
137                 cross_connector_type_name, count, must_cross = True)
138         if not guid in self._cross_connect:
139             self._cross_connect[guid] = dict()
140         if not connector_type_name in self._cross_connect[guid]:
141              self._cross_connect[guid][connector_type_name] = dict()
142         self._cross_connect[guid][connector_type_name] = \
143                 (cross_guid, cross_testbed_id, cross_factory_id, 
144                         cross_connector_type_name)
145
146     def defer_add_trace(self, guid, trace_id):
147         if not guid in self._create:
148             raise RuntimeError("Element guid %d doesn't exist" % guid)
149         factory_id = self._create[guid]
150         factory = self._factories[factory_id]
151         if not trace_id in factory.traces:
152             raise RuntimeError("Element type '%s' has no trace '%s'" %
153                     (factory_id, trace_id))
154         if not guid in self._add_trace:
155             self._add_trace[guid] = list()
156         self._add_trace[guid].append(trace_id)
157
158     def defer_add_address(self, guid, address, netprefix, broadcast):
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_addresses:
164             raise RuntimeError("Element type '%s' doesn't support addresses" %
165                     factory_id)
166             max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
167         if guid in self._add_address:
168             count_addresses = len(self._add_address[guid])
169             if max_addresses == count_addresses:
170                 raise RuntimeError("Element guid %d of type '%s' can't accept \
171                         more addresses" % (guid, factory_id))
172         else:
173             self._add_address[guid] = list()
174         self._add_address[guid].append((address, netprefix, broadcast))
175
176     def defer_add_route(self, guid, destination, netprefix, nexthop):
177         if not guid in self._create:
178             raise RuntimeError("Element guid %d doesn't exist" % guid)
179         factory_id = self._create[guid]
180         factory = self._factories[factory_id]
181         if not factory.allow_routes:
182             raise RuntimeError("Element type '%s' doesn't support routes" %
183                     factory_id)
184         if not guid in self._add_route:
185             self._add_route[guid] = list()
186         self._add_route[guid].append((destination, netprefix, nexthop)) 
187
188     def do_setup(self):
189         self._status = TESTBED_STATUS_SETUP
190
191     def do_create(self):
192         guids = dict()
193         # order guids (elements) according to factory_id
194         for guid, factory_id in self._create.iteritems():
195             if not factory_id in guids:
196                guids[factory_id] = list()
197             guids[factory_id].append(guid)
198         # create elements following the factory_id order
199         for factory_id in self._metadata.create_order:
200             # omit the factories that have no element to create
201             if factory_id not in guids:
202                 continue
203             factory = self._factories[factory_id]
204             for guid in guids[factory_id]:
205                 factory.create_function(self, guid)
206                 parameters = self._get_parameters(guid)
207                 for name, value in parameters.iteritems():
208                     self.set(TIME_NOW, guid, name, value)
209         self._status = TESTBED_STATUS_CREATED
210
211     def _do_connect(self, init = True):
212         for guid1, connections in self._connect.iteritems():
213             element1 = self._elements[guid1]
214             factory_id1 = self._create[guid1]
215             factory1 = self._factories[factory_id1]
216             for connector_type_name1, connections2 in connections.iteritems():
217                 connector_type1 = factory1.connector_type(connector_type_name1)
218                 for guid2, connector_type_name2 in connections2.iteritems():
219                     element2 = self._elements[guid2]
220                     factory_id2 = self._create[guid2]
221                     # Connections are executed in a "From -> To" direction only
222                     # This explicitly ignores the "To -> From" (mirror) 
223                     # connections of every connection pair.
224                     if init:
225                         connect_code = connector_type1.connect_to_init_code(
226                                 self._testbed_id, factory_id2, 
227                                 connector_type_name2)
228                     else:
229                         connect_code = connector_type1.connect_to_compl_code(
230                                 self._testbed_id, factory_id2, 
231                                 connector_type_name2)
232                     if connect_code:
233                         connect_code(self, element1, element2)
234
235     def do_connect_init(self):
236         self._do_connect()
237
238     def do_connect_compl(self):
239         self._do_connect(init = False)
240         self._status = TESTBED_STATUS_CONNECTED
241
242     def do_preconfigure(self):
243         guids = dict()
244         # order guids (elements) according to factory_id
245         for guid, factory_id in self._create.iteritems():
246             if not factory_id in guids:
247                guids[factory_id] = list()
248             guids[factory_id].append(guid)
249         # configure elements following the factory_id order
250         for factory_id in self._metadata.preconfigure_order:
251             # omit the factories that have no element to create
252             if factory_id not in guids:
253                 continue
254             factory = self._factories[factory_id]
255             if not factory.preconfigure_function:
256                 continue
257             for guid in guids[factory_id]:
258                 factory.preconfigure_function(self, guid)
259
260     def do_configure(self):
261         guids = dict()
262         # order guids (elements) according to factory_id
263         for guid, factory_id in self._create.iteritems():
264             if not factory_id in guids:
265                guids[factory_id] = list()
266             guids[factory_id].append(guid)
267         # configure elements following the factory_id order
268         for factory_id in self._metadata.configure_order:
269             # omit the factories that have no element to create
270             if factory_id not in guids:
271                 continue
272             factory = self._factories[factory_id]
273             if not factory.configure_function:
274                 continue
275             for guid in guids[factory_id]:
276                 factory.configure_function(self, guid)
277         self._status = TESTBED_STATUS_CONFIGURED
278
279     def _do_cross_connect(self, cross_data, init = True):
280         for guid, cross_connections in self._cross_connect.iteritems():
281             element = self._elements[guid]
282             factory_id = self._create[guid]
283             factory = self._factories[factory_id]
284             for connector_type_name, cross_connection in \
285                     cross_connections.iteritems():
286                 connector_type = factory.connector_type(connector_type_name)
287                 (cross_testbed_id, cross_factory_id, 
288                         cross_connector_type_name) = cross_connection
289                 if init:
290                     connect_code = connector_type.connect_to_init_code(
291                         cross_testbed_id, cross_factory_id, 
292                         cross_conector_type_name)
293                 else:
294                     connect_code = connector_type.connect_to_compl_code(
295                         cross_testbed_id, cross_factory_id, 
296                         cross_conector_type_name)
297                 if connect_code:
298                     elem_data_guid = cross_data[cross_testbed_id][cross_guid]
299                     connect_code(self, element, elem_cross_data)       
300
301     def do_cross_connect_init(self, cross_data):
302         self._do_cross_connect(cross_data)
303
304     def do_cross_connect_compl(self, cross_data):
305         self._do_cross_connect(cross_data, init = False)
306         self._status = TESTBED_STATUS_CROSS_CONNECTED
307
308     def set(self, time, guid, name, value):
309         if not guid in self._create:
310             raise RuntimeError("Element guid %d doesn't exist" % guid)
311         factory_id = self._create[guid]
312         factory = self._factories[factory_id]
313         if not factory.box_attributes.has_attribute(name):
314             raise AttributeError("Invalid attribute %s for element type %s" %
315                     (name, factory_id))
316         if self._status > TESTBED_STATUS_CREATED and \
317                 factory.box_attributes.is_attribute_design_only(name):
318             raise AttributeError("Attribute %s can only be modified during experiment design" % name)
319         if not factory.box_attributes.is_attribute_value_valid(name, value):
320             raise AttributeError("Invalid value %s for attribute %s" % \
321                     (value, name))
322         if guid not in self._set:
323             self._set[guid] = dict()
324             self._setlog[guid] = dict()
325         if time not in self._setlog[guid]:
326             self._setlog[guid][time] = dict()
327         self._setlog[guid][time][name] = value
328         self._set[guid][name] = value
329
330     def get(self, time, guid, name):
331         """
332         gets an attribute from box definitions if available. 
333         Throws KeyError if the GUID wasn't created
334         through the defer_create interface, and AttributeError if the
335         attribute isn't available (doesn't exist or is design-only)
336         """
337         if not guid in self._create:
338             raise KeyError, "Element guid %d doesn't exist" % guid
339         factory_id = self._create[guid]
340         factory = self._factories[factory_id]
341         if not factory.box_attributes.has_attribute(name):
342             raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
343         if self._status > TESTBED_STATUS_CREATED and \
344                 factory.box_attributes.is_attribute_design_only(name):
345             raise AttributeError, "Attribute %s can only be queried during experiment design" % name
346         if guid in self._set and name in self._set[guid]:
347             return self._set[guid][name]
348         if guid in self._create_set and name in self._create_set[guid]:
349             return self._create_set[guid][name]
350         return factory.box_attributes.get_attribute_value(name)
351
352     def get_route(self, guid, index, attribute):
353         """
354         returns information given to defer_add_route.
355         
356         Raises AttributeError if an invalid attribute is requested
357             or if the indexed routing rule does not exist.
358         
359         Raises KeyError if the GUID has not been seen by
360             defer_add_route
361         """
362         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
363         
364         if attribute not in ATTRIBUTES:
365             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
366         
367         attribute_index = ATTRIBUTES.index(attribute)
368         
369         routes = self._add_route.get(guid)
370         if not routes:
371             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
372        
373         index = int(index)
374         if not (0 <= index < len(addresses)):
375             raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
376                 guid, self._testbed_id, index)
377         
378         return routes[index][attribute_index]
379
380     def get_address(self, guid, index, attribute='Address'):
381         """
382         returns information given to defer_add_address
383         
384         Raises AttributeError if an invalid attribute is requested
385             or if the indexed routing rule does not exist.
386         
387         Raises KeyError if the GUID has not been seen by
388             defer_add_address
389         """
390         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
391         
392         if attribute not in ATTRIBUTES:
393             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
394         
395         attribute_index = ATTRIBUTES.index(attribute)
396         
397         addresses = self._add_address.get(guid)
398         if not addresses:
399             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
400         
401         index = int(index)
402         if not (0 <= index < len(addresses)):
403             raise AttributeError, "GUID %r at %s does not have an address #%s" % (
404                 guid, self._testbed_id, index)
405         
406         return addresses[index][attribute_index]
407
408     def get_attribute_list(self, guid):
409         factory_id = self._create[guid]
410         factory = self._factories[factory_id]
411         attribute_list = list()
412         return factory.box_attributes.attributes_list
413
414     def start(self, time = TIME_NOW):
415         for guid, factory_id in self._create.iteritems():
416             factory = self._factories[factory_id]
417             start_function = factory.start_function
418             if start_function:
419                 start_function(self, guid)
420         self._status = TESTBED_STATUS_STARTED
421
422     #action: NotImplementedError
423
424     def stop(self, time = TIME_NOW):
425         for guid, factory_id in self._create.iteritems():
426             factory = self._factories[factory_id]
427             stop_function = factory.stop_function
428             if stop_function:
429                 stop_function(self, guid)
430         self._status = TESTBED_STATUS_STOPPED
431
432     def status(self, guid):
433         if not guid in self._create:
434             raise RuntimeError("Element guid %d doesn't exist" % guid)
435         factory_id = self._create[guid]
436         factory = self._factories[factory_id]
437         status_function = factory.status_function
438         if status_function:
439             return status_function(self, guid)
440         return STATUS_UNDETERMINED
441
442     def trace(self, guid, trace_id, attribute='value'):
443         if attribute == 'value':
444             fd = open("%s" % self.trace_filename(guid, trace_id), "r")
445             content = fd.read()
446             fd.close()
447         elif attribute == 'path':
448             content = self.trace_filename(guid, trace_id)
449         else:
450             content = None
451         return content
452
453     def trace_filename(self, guid, trace_id):
454         """
455         Return a trace's file path, for TestbedController's default 
456         implementation of trace()
457         """
458         raise NotImplementedError
459
460     #shutdown: NotImplementedError
461
462     def get_connected(self, guid, connector_type_name, 
463             other_connector_type_name):
464         """searchs the connected elements for the specific connector_type_name 
465         pair"""
466         if guid not in self._connect:
467             return []
468         # all connections for all connectors for guid
469         all_connections = self._connect[guid]
470         if connector_type_name not in all_connections:
471             return []
472         # all connections for the specific connector
473         connections = all_connections[connector_type_name]
474         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
475                 in connections.iteritems() if \
476                 otr_connector_type_name == other_connector_type_name]
477         return specific_connections
478
479     def _get_connection_count(self, guid, connection_type_name):
480         count = 0
481         cross_count = 0
482         if guid in self._connect and connection_type_name in \
483                 self._connect[guid]:
484             count = len(self._connect[guid][connection_type_name])
485         if guid in self._cross_connect and connection_type_name in \
486                 self._cross_connect[guid]:
487             cross_count = len(self._cross_connect[guid][connection_type_name])
488         return count + cross_count
489
490     def _get_traces(self, guid):
491         return [] if guid not in self._add_trace else self._add_trace[guid]
492
493     def _get_parameters(self, guid):
494         return dict() if guid not in self._create_set else \
495                 self._create_set[guid]
496