box_get removed and replaced for get 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._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 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     def box_get_route(self, guid, index, attribute):
345         """
346         Helper implementation for get_route, returns information
347         given to defer_add_route.
348         
349         Raises AttributeError if an invalid attribute is requested
350             or if the indexed routing rule does not exist.
351         
352         Raises KeyError if the GUID has not been seen by
353             defer_add_route
354         """
355         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
356         
357         if attribute not in ATTRIBUTES:
358             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
359         
360         attribute_index = ATTRIBUTES.index(attribute)
361         
362         routes = self._add_route.get(guid)
363         if not routes:
364             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
365         
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 box_get_address(self, guid, index, attribute='Address'):
373         """
374         Helper implementation for get_address, returns information
375         given to defer_add_address
376         
377         Raises AttributeError if an invalid attribute is requested
378             or if the indexed routing rule does not exist.
379         
380         Raises KeyError if the GUID has not been seen by
381             defer_add_address
382         """
383         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
384         
385         if attribute not in ATTRIBUTES:
386             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
387         
388         attribute_index = ATTRIBUTES.index(attribute)
389         
390         addresses = self._add_address.get(guid)
391         if not addresses:
392             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
393         
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_id = self._create[guid]
402         factory = self._factories[factory_id]
403         attribute_list = list()
404         return factory.box_attributes.attributes_list
405
406     def start(self, time = TIME_NOW):
407         for guid, factory_id in self._create.iteritems():
408             factory = self._factories[factory_id]
409             start_function = factory.start_function
410             if start_function:
411                 start_function(self, guid)
412         self._status = TESTBED_STATUS_STARTED
413
414     #action: NotImplementedError
415
416     def stop(self, time = TIME_NOW):
417         for guid, factory_id in self._create.iteritems():
418             factory = self._factories[factory_id]
419             stop_function = factory.stop_function
420             if stop_function:
421                 stop_function(self, guid)
422         self._status = TESTBED_STATUS_STOPPED
423
424     def status(self, guid):
425         if not guid in self._create:
426             raise RuntimeError("Element guid %d doesn't exist" % guid)
427         factory_id = self._create[guid]
428         factory = self._factories[factory_id]
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