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