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