added TestbedController status flag
[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._set = dict()
39
40         # testbed element instances
41         self._elements = dict()
42
43         self._metadata = Metadata(self._testbed_id, self._testbed_version)
44         for factory in self._metadata.build_execute_factories():
45             self._factories[factory.factory_id] = factory
46         self._attributes = self._metadata.testbed_attributes()
47
48     @property
49     def guids(self):
50         return self._create.keys()
51
52     @property
53     def elements(self):
54         return self._elements
55     
56     def _get_factory_id(self, guid):
57         """ Returns the factory ID of the (perhaps not yet) created object """
58         return self._create.get(guid, None)
59
60     def defer_configure(self, name, value):
61         if not self._attributes.has_attribute(name):
62             raise AttributeError("Invalid attribute %s for testbed" % name)
63         # Validation
64         self._attributes.set_attribute_value(name, value)
65         self._configure[name] = value
66
67     def defer_create(self, guid, factory_id):
68         if factory_id not in self._factories:
69             raise AttributeError("Invalid element type %s for testbed version %s" %
70                     (factory_id, self._testbed_version))
71         if guid in self._create:
72             raise AttributeError("Cannot add elements with the same guid: %d" %
73                     guid)
74         self._create[guid] = factory_id
75
76     def defer_create_set(self, guid, name, value):
77         if not guid in self._create:
78             raise RuntimeError("Element guid %d doesn't exist" % guid)
79         factory_id = self._create[guid]
80         factory = self._factories[factory_id]
81         if not factory.box_attributes.has_attribute(name):
82             raise AttributeError("Invalid attribute %s for element type %s" %
83                     (name, factory_id))
84         if not factory.box_attributes.is_attribute_value_valid(name, value):
85             raise AttributeError("Invalid value %s for attribute %s" % \
86                 (value, name))
87         if guid not in self._create_set:
88             self._create_set[guid] = dict()
89         self._create_set[guid][name] = value
90
91     def defer_factory_set(self, guid, name, value):
92         if not guid in self._create:
93             raise RuntimeError("Element guid %d doesn't exist" % guid)
94         factory_id = self._create[guid]
95         factory = self._factories[factory_id]
96         if not factory.has_attribute(name):
97             raise AttributeError("Invalid attribute %s for element type %s" %
98                     (name, 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         factory_id1 = self._create[guid1]
109         factory_id2 = self._create[guid2]
110         count = self._get_connection_count(guid1, connector_type_name1)
111         factory1 = self._factories[factory_id1]
112         connector_type = factory1.connector_type(connector_type_name1)
113         connector_type.can_connect(self._testbed_id, factory_id2, 
114                 connector_type_name2, count)
115         if not guid1 in self._connect:
116             self._connect[guid1] = dict()
117         if not connector_type_name1 in self._connect[guid1]:
118              self._connect[guid1][connector_type_name1] = dict()
119         self._connect[guid1][connector_type_name1][guid2] = \
120                connector_type_name2
121         if not guid2 in self._connect:
122             self._connect[guid2] = dict()
123         if not connector_type_name2 in self._connect[guid2]:
124              self._connect[guid2][connector_type_name2] = dict()
125         self._connect[guid2][connector_type_name2][guid1] = \
126                 connector_type_name1
127
128     def defer_cross_connect(self, guid, connector_type_name, cross_guid, 
129             cross_testbed_id, cross_factory_id, cross_connector_type_name):
130         factory_id = self._create[guid]
131         count = self._get_connection_count(guid, connector_type_name)
132         factory = self._factories[factory_id]
133         connector_type = factory.connector_type(connector_type_name)
134         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
135                 cross_connector_type_name, count, must_cross = True)
136         if not guid in self._cross_connect:
137             self._cross_connect[guid] = dict()
138         if not connector_type_name in self._cross_connect[guid]:
139              self._cross_connect[guid][connector_type_name] = dict()
140         self._cross_connect[guid][connector_type_name] = \
141                 (cross_guid, cross_testbed_id, cross_factory_id, 
142                         cross_connector_type_name)
143
144     def defer_add_trace(self, guid, trace_id):
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 trace_id in factory.traces:
150             raise RuntimeError("Element type '%s' has no trace '%s'" %
151                     (factory_id, trace_id))
152         if not guid in self._add_trace:
153             self._add_trace[guid] = list()
154         self._add_trace[guid].append(trace_id)
155
156     def defer_add_address(self, guid, address, netprefix, broadcast):
157         if not guid in self._create:
158             raise RuntimeError("Element guid %d doesn't exist" % guid)
159         factory_id = self._create[guid]
160         factory = self._factories[factory_id]
161         if not factory.allow_addresses:
162             raise RuntimeError("Element type '%s' doesn't support addresses" %
163                     factory_id)
164             max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
165         if guid in self._add_address:
166             count_addresses = len(self._add_address[guid])
167             if max_addresses == count_addresses:
168                 raise RuntimeError("Element guid %d of type '%s' can't accept \
169                         more addresses" % (guid, factory_id))
170         else:
171             self._add_address[guid] = list()
172         self._add_address[guid].append((address, netprefix, broadcast))
173
174     def defer_add_route(self, guid, destination, netprefix, nexthop):
175         if not guid in self._create:
176             raise RuntimeError("Element guid %d doesn't exist" % guid)
177         factory_id = self._create[guid]
178         factory = self._factories[factory_id]
179         if not factory.allow_routes:
180             raise RuntimeError("Element type '%s' doesn't support routes" %
181                     factory_id)
182         if not guid in self._add_route:
183             self._add_route[guid] = list()
184         self._add_route[guid].append((destination, netprefix, nexthop)) 
185
186     def do_setup(self):
187         self._status = TESTBED_STATUS_SETUP
188
189     def do_create(self):
190         guids = dict()
191         # order guids (elements) according to factory_id
192         for guid, factory_id in self._create.iteritems():
193             if not factory_id in guids:
194                guids[factory_id] = list()
195             guids[factory_id].append(guid)
196         # create elements following the factory_id order
197         for factory_id in self._metadata.create_order:
198             # omit the factories that have no element to create
199             if factory_id not in guids:
200                 continue
201             factory = self._factories[factory_id]
202             for guid in guids[factory_id]:
203                 factory.create_function(self, guid)
204                 parameters = self._get_parameters(guid)
205                 for name, value in parameters.iteritems():
206                     self.set(TIME_NOW, guid, name, value)
207         self._status = TESTBED_STATUS_CREATED
208
209     def _do_connect(self, init = True):
210         for guid1, connections in self._connect.iteritems():
211             element1 = self._elements[guid1]
212             factory_id1 = self._create[guid1]
213             factory1 = self._factories[factory_id1]
214             for connector_type_name1, connections2 in connections.iteritems():
215                 connector_type1 = factory1.connector_type(connector_type_name1)
216                 for guid2, connector_type_name2 in connections2.iteritems():
217                     element2 = self._elements[guid2]
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, element1, element2)
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             element = self._elements[guid]
280             factory_id = self._create[guid]
281             factory = self._factories[factory_id]
282             for connector_type_name, cross_connection in \
283                     cross_connections.iteritems():
284                 connector_type = factory.connector_type(connector_type_name)
285                 (cross_testbed_id, cross_factory_id, 
286                         cross_connector_type_name) = cross_connection
287                 if init:
288                     connect_code = connector_type.connect_to_init_code(
289                         cross_testbed_id, cross_factory_id, 
290                         cross_conector_type_name)
291                 else:
292                     connect_code = connector_type.connect_to_compl_code(
293                         cross_testbed_id, cross_factory_id, 
294                         cross_conector_type_name)
295                 if connect_code:
296                     elem_data_guid = cross_data[cross_testbed_id][cross_guid]
297                     connect_code(self, element, elem_cross_data)       
298
299     def do_cross_connect_init(self, cross_data):
300         self._do_cross_connect(cross_data)
301
302     def do_cross_connect_compl(self, cross_data):
303         self._do_cross_connect(cross_data, init = False)
304         self._status = TESTBED_STATUS_CROSS_CONNECTED
305
306     def set(self, time, guid, name, value):
307         if not guid in self._create:
308             raise RuntimeError("Element guid %d doesn't exist" % guid)
309         factory_id = self._create[guid]
310         factory = self._factories[factory_id]
311         if not factory.box_attributes.has_attribute(name):
312             raise AttributeError("Invalid attribute %s for element type %s" %
313                     (name, factory_id))
314         if self._status > TESTBED_STATUS_CREATED and \
315                 factory.box_attributes.is_attribute_design_only(name):
316             raise AttributeError("Attribute %s can only be modified during experiment design" % name)
317         if not factory.box_attributes.is_attribute_value_valid(name, value):
318             raise AttributeError("Invalid value %s for attribute %s" % \
319                     (value, name))
320         if guid not in self._set:
321             self._set[guid] = dict()
322         if time not in self._set[guid]:
323             self._set[guid][time] = dict()
324         self._set[guid][time][name] = value
325
326     def box_get(self, time, guid, name):
327         """
328         Helper for subclasses, gets an attribute from box definitions
329         if available. Throws KeyError if the GUID wasn't created
330         through the defer_create interface, and AttributeError if the
331         attribute isn't available (doesn't exist or is design-only)
332         """
333         if not guid in self._create:
334             raise KeyError, "Element guid %d doesn't exist" % guid
335         factory_id = self._create[guid]
336         factory = self._factories[factory_id]
337         if not factory.box_attributes.has_attribute(name):
338             raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
339         if self._status > TESTBED_STATUS_CREATED and \
340                 factory.box_attributes.is_attribute_design_only(name):
341             raise AttributeError, "Attribute %s can only be queried during experiment design" % name
342         return factory.box_attributes.get_attribute_value(name)
343
344     #get: NotImplementedError
345
346     def box_get_route(self, guid, index, attribute):
347         """
348         Helper implementation for get_route, returns information
349         given to defer_add_route.
350         
351         Raises AttributeError if an invalid attribute is requested
352             or if the indexed routing rule does not exist.
353         
354         Raises KeyError if the GUID has not been seen by
355             defer_add_route
356         """
357         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
358         
359         if attribute not in ATTRIBUTES:
360             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
361         
362         attribute_index = ATTRIBUTES.index(attribute)
363         
364         routes = self._add_route.get(guid)
365         if not routes:
366             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
367         
368         if not (0 <= index < len(addresses)):
369             raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
370                 guid, self._testbed_id, index)
371         
372         return routes[index][attribute_index]
373
374     def box_get_address(self, guid, index, attribute='Address'):
375         """
376         Helper implementation for get_address, returns information
377         given to defer_add_address
378         
379         Raises AttributeError if an invalid attribute is requested
380             or if the indexed routing rule does not exist.
381         
382         Raises KeyError if the GUID has not been seen by
383             defer_add_address
384         """
385         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
386         
387         if attribute not in ATTRIBUTES:
388             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
389         
390         attribute_index = ATTRIBUTES.index(attribute)
391         
392         addresses = self._add_address.get(guid)
393         if not addresses:
394             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
395         
396         if not (0 <= index < len(addresses)):
397             raise AttributeError, "GUID %r at %s does not have an address #%s" % (
398                 guid, self._testbed_id, index)
399         
400         return addresses[index][attribute_index]
401
402     def get_attribute_list(self, guid):
403         factory_id = self._create[guid]
404         factory = self._factories[factory_id]
405         attribute_list = list()
406         return factory.box_attributes.attributes_list
407
408     def start(self, time = TIME_NOW):
409         for guid, factory_id in self._create.iteritems():
410             factory = self._factories[factory_id]
411             start_function = factory.start_function
412             if start_function:
413                 start_function(self, guid)
414         self._status = TESTBED_STATUS_STARTED
415
416     #action: NotImplementedError
417
418     def stop(self, time = TIME_NOW):
419         for guid, factory_id in self._create.iteritems():
420             factory = self._factories[factory_id]
421             stop_function = factory.stop_function
422             if stop_function:
423                 stop_function(self, guid)
424         self._status = TESTBED_STATUS_STOPPED
425
426     def status(self, guid):
427         if not guid in self._create:
428             raise RuntimeError("Element guid %d doesn't exist" % guid)
429         factory_id = self._create[guid]
430         factory = self._factories[factory_id]
431         status_function = factory.status_function
432         if status_function:
433             return status_function(self, guid)
434         return STATUS_UNDETERMINED
435
436     def trace(self, guid, trace_id, attribute='value'):
437         if attribute == 'value':
438             fd = open("%s" % self.trace_filename(guid, trace_id), "r")
439             content = fd.read()
440             fd.close()
441         elif attribute == 'path':
442             content = self.trace_filename(guid, trace_id)
443         else:
444             content = None
445         return content
446
447     def trace_filename(self, guid, trace_id):
448         """
449         Return a trace's file path, for TestbedController's default 
450         implementation of trace()
451         """
452         raise NotImplementedError
453
454     #shutdown: NotImplementedError
455
456     def get_connected(self, guid, connector_type_name, 
457             other_connector_type_name):
458         """searchs the connected elements for the specific connector_type_name 
459         pair"""
460         if guid not in self._connect:
461             return []
462         # all connections for all connectors for guid
463         all_connections = self._connect[guid]
464         if connector_type_name not in all_connections:
465             return []
466         # all connections for the specific connector
467         connections = all_connections[connector_type_name]
468         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
469                 in connections.iteritems() if \
470                 otr_connector_type_name == other_connector_type_name]
471         return specific_connections
472
473     def _get_connection_count(self, guid, connection_type_name):
474         count = 0
475         cross_count = 0
476         if guid in self._connect and connection_type_name in \
477                 self._connect[guid]:
478             count = len(self._connect[guid][connection_type_name])
479         if guid in self._cross_connect and connection_type_name in \
480                 self._cross_connect[guid]:
481             cross_count = len(self._cross_connect[guid][connection_type_name])
482         return count + cross_count
483
484     def _get_traces(self, guid):
485         return [] if guid not in self._add_trace else self._add_trace[guid]
486
487     def _get_parameters(self, guid):
488         return dict() if guid not in self._create_set else \
489                 self._create_set[guid]
490