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