added tags to boxes. For now only one tag: MOBILE
[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 import collections
18
19 class TestbedController(execute.TestbedController):
20     def __init__(self, testbed_id, testbed_version):
21         super(TestbedController, self).__init__(testbed_id, testbed_version)
22         self._status = TESTBED_STATUS_ZERO
23         # testbed attributes for validation
24         self._attributes = None
25         # element factories for validation
26         self._factories = dict()
27
28         # experiment construction instructions
29         self._create = dict()
30         self._create_set = dict()
31         self._factory_set = dict()
32         self._connect = dict()
33         self._cross_connect = dict()
34         self._add_trace = dict()
35         self._add_address = dict()
36         self._add_route = dict()
37         self._configure = dict()
38
39         # log of set operations
40         self._setlog = dict()
41         # last set operations
42         self._set = dict()
43
44         # testbed element instances
45         self._elements = dict()
46
47         self._metadata = Metadata(self._testbed_id, self._testbed_version)
48         for factory in self._metadata.build_execute_factories():
49             self._factories[factory.factory_id] = factory
50         self._attributes = self._metadata.testbed_attributes()
51         self._root_directory = None
52     
53     @property
54     def root_directory(self):
55         return self._root_directory
56
57     @property
58     def guids(self):
59         return self._create.keys()
60
61     @property
62     def elements(self):
63         return self._elements
64     
65     def _get_factory_id(self, guid):
66         """ Returns the factory ID of the (perhaps not yet) created object """
67         return self._create.get(guid, None)
68
69     def defer_configure(self, name, value):
70         if not self._attributes.has_attribute(name):
71             raise AttributeError("Invalid attribute %s for testbed" % name)
72         # Validation
73         self._attributes.set_attribute_value(name, value)
74         self._configure[name] = value
75
76     def defer_create(self, guid, factory_id):
77         if factory_id not in self._factories:
78             raise AttributeError("Invalid element type %s for testbed version %s" %
79                     (factory_id, self._testbed_version))
80         if guid in self._create:
81             raise AttributeError("Cannot add elements with the same guid: %d" %
82                     guid)
83         self._create[guid] = factory_id
84
85     def defer_create_set(self, guid, name, value):
86         if not guid in self._create:
87             raise RuntimeError("Element guid %d doesn't exist" % guid)
88         factory = self._get_factory(guid)
89         if not factory.box_attributes.has_attribute(name):
90             raise AttributeError("Invalid attribute %s for element type %s" %
91                     (name, factory.factory_id))
92         if not factory.box_attributes.is_attribute_value_valid(name, value):
93             raise AttributeError("Invalid value %s for attribute %s" % \
94                 (value, name))
95         if guid not in self._create_set:
96             self._create_set[guid] = dict()
97         self._create_set[guid][name] = value
98
99     def defer_factory_set(self, guid, name, value):
100         if not guid in self._create:
101             raise RuntimeError("Element guid %d doesn't exist" % guid)
102         factory = self._get_factory(guid)
103         if not factory.has_attribute(name):
104             raise AttributeError("Invalid attribute %s for element type %s" %
105                     (name, factory.factory_id))
106         if not factory.is_attribute_value_valid(name, value):
107             raise AttributeError("Invalid value %s for attribute %s" % \
108                 (value, name))
109         if guid not in self._factory_set:
110             self._factory_set[guid] = dict()
111         self._factory_set[guid][name] = value
112
113     def defer_connect(self, guid1, connector_type_name1, guid2, 
114             connector_type_name2):
115         factory1 = self._get_factory(guid1)
116         factory_id2 = self._create[guid2]
117         count = self._get_connection_count(guid1, connector_type_name1)
118         connector_type = factory1.connector_type(connector_type_name1)
119         connector_type.can_connect(self._testbed_id, factory_id2, 
120                 connector_type_name2, count, False)
121         if not guid1 in self._connect:
122             self._connect[guid1] = dict()
123         if not connector_type_name1 in self._connect[guid1]:
124              self._connect[guid1][connector_type_name1] = dict()
125         self._connect[guid1][connector_type_name1][guid2] = \
126                connector_type_name2
127         if not guid2 in self._connect:
128             self._connect[guid2] = dict()
129         if not connector_type_name2 in self._connect[guid2]:
130              self._connect[guid2][connector_type_name2] = dict()
131         self._connect[guid2][connector_type_name2][guid1] = \
132                 connector_type_name1
133
134     def defer_cross_connect(self, guid, connector_type_name, cross_guid, 
135             cross_testbed_guid, cross_testbed_id, cross_factory_id, 
136             cross_connector_type_name):
137         factory = self._get_factory(guid)
138         count = self._get_connection_count(guid, connector_type_name)
139         connector_type = factory.connector_type(connector_type_name)
140         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
141                 cross_connector_type_name, count, True)
142         if not guid in self._cross_connect:
143             self._cross_connect[guid] = dict()
144         if not connector_type_name in self._cross_connect[guid]:
145              self._cross_connect[guid][connector_type_name] = dict()
146         self._cross_connect[guid][connector_type_name] = \
147                 (cross_guid, cross_testbed_guid, cross_testbed_id, 
148                 cross_factory_id, cross_connector_type_name)
149
150     def defer_add_trace(self, guid, trace_id):
151         if not guid in self._create:
152             raise RuntimeError("Element guid %d doesn't exist" % guid)
153         factory = self._get_factory(guid)
154         if not trace_id in factory.traces:
155             raise RuntimeError("Element type '%s' has no trace '%s'" %
156                     (factory.factory_id, trace_id))
157         if not guid in self._add_trace:
158             self._add_trace[guid] = list()
159         self._add_trace[guid].append(trace_id)
160
161     def defer_add_address(self, guid, address, netprefix, broadcast):
162         if not guid in self._create:
163             raise RuntimeError("Element guid %d doesn't exist" % guid)
164         factory = self._get_factory(guid)
165         if not factory.allow_addresses:
166             raise RuntimeError("Element type '%s' doesn't support addresses" %
167                     factory.factory_id)
168             max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
169         if guid in self._add_address:
170             count_addresses = len(self._add_address[guid])
171             if max_addresses == count_addresses:
172                 raise RuntimeError("Element guid %d of type '%s' can't accept \
173                         more addresses" % (guid, factory.factory_id))
174         else:
175             self._add_address[guid] = list()
176         self._add_address[guid].append((address, netprefix, broadcast))
177
178     def defer_add_route(self, guid, destination, netprefix, nexthop):
179         if not guid in self._create:
180             raise RuntimeError("Element guid %d doesn't exist" % guid)
181         factory = self._get_factory(guid)
182         if not factory.allow_routes:
183             raise RuntimeError("Element type '%s' doesn't support routes" %
184                     factory.factory_id)
185         if not guid in self._add_route:
186             self._add_route[guid] = list()
187         self._add_route[guid].append((destination, netprefix, nexthop)) 
188
189     def do_setup(self):
190         self._root_directory = self._attributes.\
191             get_attribute_value("rootDirectory")
192         self._status = TESTBED_STATUS_SETUP
193
194     def do_create(self):
195         def set_params(self, guid):
196             parameters = self._get_parameters(guid)
197             for name, value in parameters.iteritems():
198                 self.set(guid, name, value)
199             
200         self._do_in_factory_order(
201             'create_function',
202             self._metadata.create_order,
203             postaction = set_params )
204         self._status = TESTBED_STATUS_CREATED
205
206     def _do_connect(self, init = True):
207         for guid1, connections in self._connect.iteritems():
208             factory1 = self._get_factory(guid1)
209             for connector_type_name1, connections2 in connections.iteritems():
210                 connector_type1 = factory1.connector_type(connector_type_name1)
211                 for guid2, connector_type_name2 in connections2.iteritems():
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                                 False)
221                     else:
222                         connect_code = connector_type1.connect_to_compl_code(
223                                 self._testbed_id, factory_id2, 
224                                 connector_type_name2,
225                                 False)
226                     if connect_code:
227                         connect_code(self, guid1, guid2)
228
229     def do_connect_init(self):
230         self._do_connect()
231
232     def do_connect_compl(self):
233         self._do_connect(init = False)
234         self._status = TESTBED_STATUS_CONNECTED
235
236     def _do_in_factory_order(self, action, order, postaction = None):
237         guids = collections.defaultdict(list)
238         # order guids (elements) according to factory_id
239         for guid, factory_id in self._create.iteritems():
240             guids[factory_id].append(guid)
241         # configure elements following the factory_id order
242         for factory_id in 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 getattr(factory, action):
248                 continue
249             for guid in guids[factory_id]:
250                 getattr(factory, action)(self, guid)
251                 if postaction:
252                     postaction(self, guid)
253
254     def do_preconfigure(self):
255         self._do_in_factory_order(
256             'preconfigure_function',
257             self._metadata.preconfigure_order )
258
259     def do_configure(self):
260         self._do_in_factory_order(
261             'configure_function',
262             self._metadata.configure_order )
263         self._status = TESTBED_STATUS_CONFIGURED
264
265     def do_prestart(self):
266         self._do_in_factory_order(
267             'prestart_function',
268             self._metadata.prestart_order )
269
270     def _do_cross_connect(self, cross_data, init = True):
271         for guid, cross_connections in self._cross_connect.iteritems():
272             factory = self._get_factory(guid)
273             for connector_type_name, cross_connection in \
274                     cross_connections.iteritems():
275                 connector_type = factory.connector_type(connector_type_name)
276                 (cross_guid, cross_testbed_guid, cross_testbed_id,
277                     cross_factory_id, cross_connector_type_name) = cross_connection
278                 if init:
279                     connect_code = connector_type.connect_to_init_code(
280                         cross_testbed_id, cross_factory_id, 
281                         cross_connector_type_name,
282                         True)
283                 else:
284                     connect_code = connector_type.connect_to_compl_code(
285                         cross_testbed_id, cross_factory_id, 
286                         cross_connector_type_name,
287                         True)
288                 if connect_code:
289                     elem_cross_data = cross_data[cross_testbed_guid][cross_guid]
290                     connect_code(self, guid, 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, guid, name, value, time = TIME_NOW):
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_STARTED 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, guid, name, time = TIME_NOW):
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 guid in self._set and name in self._set[guid]:
334             return self._set[guid][name]
335         if guid in self._create_set and name in self._create_set[guid]:
336             return self._create_set[guid][name]
337         return factory.box_attributes.get_attribute_value(name)
338
339     def get_route(self, guid, index, attribute):
340         """
341         returns information given to defer_add_route.
342         
343         Raises AttributeError if an invalid attribute is requested
344             or if the indexed routing rule does not exist.
345         
346         Raises KeyError if the GUID has not been seen by
347             defer_add_route
348         """
349         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
350         
351         if attribute not in ATTRIBUTES:
352             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
353         
354         attribute_index = ATTRIBUTES.index(attribute)
355         
356         routes = self._add_route.get(guid)
357         if not routes:
358             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
359        
360         index = int(index)
361         if not (0 <= index < len(addresses)):
362             raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
363                 guid, self._testbed_id, index)
364         
365         return routes[index][attribute_index]
366
367     def get_address(self, guid, index, attribute='Address'):
368         """
369         returns information given to defer_add_address
370         
371         Raises AttributeError if an invalid attribute is requested
372             or if the indexed routing rule does not exist.
373         
374         Raises KeyError if the GUID has not been seen by
375             defer_add_address
376         """
377         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
378         
379         if attribute not in ATTRIBUTES:
380             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
381         
382         attribute_index = ATTRIBUTES.index(attribute)
383         
384         addresses = self._add_address.get(guid)
385         if not addresses:
386             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
387         
388         index = int(index)
389         if not (0 <= index < len(addresses)):
390             raise AttributeError, "GUID %r at %s does not have an address #%s" % (
391                 guid, self._testbed_id, index)
392         
393         return addresses[index][attribute_index]
394
395     def get_tags(self, guid):
396         factory = self._get_factory(guid)
397         return factory.tags
398
399     def get_attribute_list(self, guid):
400         factory = self._get_factory(guid)
401         attribute_list = list()
402         return factory.box_attributes.attributes_list
403
404     def start(self, time = TIME_NOW):
405         self._do_in_factory_order(
406             'start_function',
407             self._metadata.start_order )
408         self._status = TESTBED_STATUS_STARTED
409
410     #action: NotImplementedError
411
412     def stop(self, time = TIME_NOW):
413         self._do_in_factory_order(
414             'stop_function',
415             reversed(self._metadata.start_order) )
416         self._status = TESTBED_STATUS_STOPPED
417
418     def status(self, guid = None):
419         if not guid:
420             return self._status
421         if not guid in self._create:
422             raise RuntimeError("Element guid %d doesn't exist" % guid)
423         factory = self._get_factory(guid)
424         status_function = factory.status_function
425         if status_function:
426             return status_function(self, guid)
427         return STATUS_UNDETERMINED
428
429     def trace(self, guid, trace_id, attribute='value'):
430         if attribute == 'value':
431             fd = open("%s" % self.trace_filename(guid, trace_id), "r")
432             content = fd.read()
433             fd.close()
434         elif attribute == 'path':
435             content = self.trace_filename(guid, trace_id)
436         else:
437             content = None
438         return content
439
440     def trace_filename(self, guid, trace_id):
441         """
442         Return a trace's file path, for TestbedController's default 
443         implementation of trace()
444         """
445         raise NotImplementedError
446
447     #shutdown: NotImplementedError
448
449     def get_connected(self, guid, connector_type_name, 
450             other_connector_type_name):
451         """searchs the connected elements for the specific connector_type_name 
452         pair"""
453         if guid not in self._connect:
454             return []
455         # all connections for all connectors for guid
456         all_connections = self._connect[guid]
457         if connector_type_name not in all_connections:
458             return []
459         # all connections for the specific connector
460         connections = all_connections[connector_type_name]
461         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
462                 in connections.iteritems() if \
463                 otr_connector_type_name == other_connector_type_name]
464         return specific_connections
465
466     def _get_connection_count(self, guid, connection_type_name):
467         count = 0
468         cross_count = 0
469         if guid in self._connect and connection_type_name in \
470                 self._connect[guid]:
471             count = len(self._connect[guid][connection_type_name])
472         if guid in self._cross_connect and connection_type_name in \
473                 self._cross_connect[guid]:
474             cross_count = len(self._cross_connect[guid][connection_type_name])
475         return count + cross_count
476
477     def _get_traces(self, guid):
478         return [] if guid not in self._add_trace else self._add_trace[guid]
479
480     def _get_parameters(self, guid):
481         return dict() if guid not in self._create_set else \
482                 self._create_set[guid]
483
484     def _get_factory(self, guid):
485         factory_id = self._create[guid]
486         return self._factories[factory_id]
487