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