Implementing PlanetLab testbed
[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 AF_INET, AF_INET6, STATUS_UNDETERMINED, TIME_NOW
8
9 class TestbedController(execute.TestbedController):
10     def __init__(self, testbed_id, testbed_version):
11         super(TestbedController, self).__init__(testbed_id, testbed_version)
12         self._started = False
13         # testbed attributes for validation
14         self._attributes = None
15         # element factories for validation
16         self._factories = dict()
17
18         # experiment construction instructions
19         self._create = dict()
20         self._create_set = dict()
21         self._factory_set = dict()
22         self._connect = dict()
23         self._cross_connect = dict()
24         self._add_trace = dict()
25         self._add_address = dict()
26         self._add_route = dict()
27         self._configure = dict()
28
29         # log of set operations
30         self._set = dict()
31
32         # testbed element instances
33         self._elements = dict()
34
35         self._metadata = Metadata(self._testbed_id, self._testbed_version)
36         for factory in self._metadata.build_execute_factories():
37             self._factories[factory.factory_id] = factory
38         self._attributes = self._metadata.testbed_attributes()
39
40     @property
41     def guids(self):
42         return self._create.keys()
43
44     @property
45     def elements(self):
46         return self._elements
47     
48     def _get_factory_id(self, guid):
49         """ Returns the factory ID of the (perhaps not yet) created object """
50         return self._create.get(guid, None)
51
52     def defer_configure(self, name, value):
53         if not self._attributes.has_attribute(name):
54             raise RuntimeError("Invalid attribute %s for testbed" % name)
55         # Validation
56         self._attributes.set_attribute_value(name, value)
57         self._configure[name] = value
58
59     def defer_create(self, guid, factory_id):
60         if factory_id not in self._factories:
61             raise RuntimeError("Invalid element type %s for testbed version %s" %
62                     (factory_id, self._testbed_version))
63         if guid in self._create:
64             raise RuntimeError("Cannot add elements with the same guid: %d" %
65                     guid)
66         self._create[guid] = factory_id
67
68     def defer_create_set(self, guid, name, value):
69         if not guid in self._create:
70             raise RuntimeError("Element guid %d doesn't exist" % guid)
71         factory_id = self._create[guid]
72         factory = self._factories[factory_id]
73         if not factory.box_attributes.has_attribute(name):
74             raise RuntimeError("Invalid attribute %s for element type %s" %
75                     (name, factory_id))
76         factory.box_attributes.set_attribute_value(name, value)
77         if guid not in self._create_set:
78             self._create_set[guid] = dict()
79         self._create_set[guid][name] = value
80
81     def defer_factory_set(self, guid, name, value):
82         if not guid in self._create:
83             raise RuntimeError("Element guid %d doesn't exist" % guid)
84         factory_id = self._create[guid]
85         factory = self._factories[factory_id]
86         if not factory.has_attribute(name):
87             raise RuntimeError("Invalid attribute %s for element type %s" %
88                     (name, factory_id))
89         factory.set_attribute_value(name, value)
90         if guid not in self._factory_set:
91             self._factory_set[guid] = dict()
92         self._factory_set[guid][name] = value
93
94     def defer_connect(self, guid1, connector_type_name1, guid2, 
95             connector_type_name2):
96         factory_id1 = self._create[guid1]
97         factory_id2 = self._create[guid2]
98         count = self._get_connection_count(guid1, connector_type_name1)
99         factory1 = self._factories[factory_id1]
100         connector_type = factory1.connector_type(connector_type_name1)
101         connector_type.can_connect(self._testbed_id, factory_id2, 
102                 connector_type_name2, count)
103         if not guid1 in self._connect:
104             self._connect[guid1] = dict()
105         if not connector_type_name1 in self._connect[guid1]:
106              self._connect[guid1][connector_type_name1] = dict()
107         self._connect[guid1][connector_type_name1][guid2] = \
108                connector_type_name2
109         if not guid2 in self._connect:
110             self._connect[guid2] = dict()
111         if not connector_type_name2 in self._connect[guid2]:
112              self._connect[guid2][connector_type_name2] = dict()
113         self._connect[guid2][connector_type_name2][guid1] = \
114                 connector_type_name1
115
116     def defer_cross_connect(self, guid, connector_type_name, cross_guid, 
117             cross_testbed_id, cross_factory_id, cross_connector_type_name):
118         factory_id = self._create[guid]
119         count = self._get_connection_count(guid, connector_type_name)
120         factory = self._factories[factory_id]
121         connector_type = factory.connector_type(connector_type_name)
122         connector_type.can_connect(cross_testbed_id, cross_factory_id, 
123                 cross_connector_type_name, count, must_cross = True)
124         if not guid in self._connect:
125             self._cross_connect[guid] = dict()
126         if not connector_type_name in self._cross_connect[guid]:
127              self._cross_connect[guid][connector_type_name] = dict()
128         self._cross_connect[guid][connector_type_name] = \
129                 (cross_guid, cross_testbed_id, cross_factory_id, 
130                         cross_connector_type_name)
131
132     def defer_add_trace(self, guid, trace_id):
133         if not guid in self._create:
134             raise RuntimeError("Element guid %d doesn't exist" % guid)
135         factory_id = self._create[guid]
136         factory = self._factories[factory_id]
137         if not trace_id in factory.traces:
138             raise RuntimeError("Element type '%s' has no trace '%s'" %
139                     (factory_id, trace_id))
140         if not guid in self._add_trace:
141             self._add_trace[guid] = list()
142         self._add_trace[guid].append(trace_id)
143
144     def defer_add_address(self, guid, address, netprefix, broadcast):
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 factory.allow_addresses:
150             raise RuntimeError("Element type '%s' doesn't support addresses" %
151                     factory_id)
152             max_addresses = 1 # TODO: MAKE THIS PARAMETRIZABLE
153         if guid in self._add_address:
154             count_addresses = len(self._add_address[guid])
155             if max_addresses == count_addresses:
156                 raise RuntimeError("Element guid %d of type '%s' can't accept \
157                         more addresses" % (guid, factory_id))
158         else:
159             self._add_address[guid] = list()
160         self._add_address[guid].append((address, netprefix, broadcast))
161
162     def defer_add_route(self, guid, destination, netprefix, nexthop):
163         if not guid in self._create:
164             raise RuntimeError("Element guid %d doesn't exist" % guid)
165         factory_id = self._create[guid]
166         factory = self._factories[factory_id]
167         if not factory.allow_routes:
168             raise RuntimeError("Element type '%s' doesn't support routes" %
169                     factory_id)
170         if not guid in self._add_route:
171             self._add_route[guid] = list()
172         self._add_route[guid].append((destination, netprefix, nexthop)) 
173
174     #do_setup(self): NotImplementedError
175
176     def do_create(self):
177         guids = dict()
178         # order guids (elements) according to factory_id
179         for guid, factory_id in self._create.iteritems():
180             if not factory_id in guids:
181                guids[factory_id] = list()
182             guids[factory_id].append(guid)
183         # create elements following the factory_id order
184         for factory_id in self._metadata.create_order:
185             # omit the factories that have no element to create
186             if factory_id not in guids:
187                 continue
188             factory = self._factories[factory_id]
189             for guid in guids[factory_id]:
190                 factory.create_function(self, guid)
191                 parameters = self._get_parameters(guid)
192                 for name, value in parameters.iteritems():
193                     self.set(TIME_NOW, guid, name, value)
194
195     def do_connect(self):
196         for guid1, connections in self._connect.iteritems():
197             element1 = self._elements[guid1]
198             factory_id1 = self._create[guid1]
199             factory1 = self._factories[factory_id1]
200             for connector_type_name1, connections2 in connections.iteritems():
201                 connector_type1 = factory1.connector_type(connector_type_name1)
202                 for guid2, connector_type_name2 in connections2.iteritems():
203                     element2 = self._elements[guid2]
204                     factory_id2 = self._create[guid2]
205                     # Connections are executed in a "From -> To" direction only
206                     # This explicitly ignores the "To -> From" (mirror) 
207                     # connections of every connection pair. 
208                     code_to_connect = connector_type1.code_to_connect(
209                             self._testbed_id, factory_id2, 
210                             connector_type_name2)
211                     if code_to_connect:
212                         code_to_connect(self, element1, element2)
213
214     def do_configure(self):
215         guids = dict()
216         # order guids (elements) according to factory_id
217         for guid, factory_id in self._create.iteritems():
218             if not factory_id in guids:
219                guids[factory_id] = list()
220             guids[factory_id].append(guid)
221         # configure elements following the factory_id order
222         for factory_id in self._metadata.configure_order:
223             # omit the factories that have no element to create
224             if factory_id not in guids:
225                 continue
226             factory = self._factories[factory_id]
227             if not factory.configure_function:
228                 continue
229             for guid in guids[factory_id]:
230                 factory.configure_function(self, guid)
231
232     def do_cross_connect(self):
233         for guid, cross_connections in self._cross_connect.iteritems():
234             element = self._elements[guid]
235             factory_id = self._create[guid]
236             factory = self._factories[factory_id]
237             for connector_type_name, cross_connection in \
238                     cross_connections.iteritems():
239                 connector_type = factory.connector_type(connector_type_name)
240                 (cross_testbed_id, cross_factory_id, 
241                         cross_connector_type_name) = cross_connection
242                 code_to_connect = connector_type.code_to_connect(
243                     cross_guid, cross_testbed_id, cross_factory_id, 
244                     cross_conector_type_name)
245                 if code_to_connect:
246                     code_to_connect(element, cross_guid)       
247
248     def set(self, time, guid, name, value):
249         if not guid in self._create:
250             raise RuntimeError("Element guid %d doesn't exist" % guid)
251         factory_id = self._create[guid]
252         factory = self._factories[factory_id]
253         if not factory.box_attributes.has_attribute(name):
254             raise RuntimeError("Invalid attribute %s for element type %s" %
255                     (name, factory_id))
256         if self._started and factory.is_attribute_design_only(name):
257             raise RuntimeError("Attribute %s can only be modified during experiment design" % name)
258         factory.box_attributes.set_attribute_value(name, value)
259         if guid not in self._set:
260             self._set[guid] = dict()
261         if time not in self._set[guid]:
262             self._set[guid][time] = dict()
263         self._set[guid][time][name] = value
264
265     def box_get(self, time, guid, name):
266         """
267         Helper for subclasses, gets an attribute from box definitions
268         if available. Throws KeyError if the GUID wasn't created
269         through the defer_create interface, and AttributeError if the
270         attribute isn't available (doesn't exist or is design-only)
271         """
272         if not guid in self._create:
273             raise KeyError, "Element guid %d doesn't exist" % guid
274         factory_id = self._create[guid]
275         factory = self._factories[factory_id]
276         if not factory.box_attributes.has_attribute(name):
277             raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
278         if self._started and factory.is_attribute_design_only(name):
279             raise AttributeError, "Attribute %s can only be queried during experiment design" % name
280         return factory.box_attributes.get_attribute_value(name)
281
282     #get: NotImplementedError
283
284     def box_get_route(self, guid, index, attribute):
285         """
286         Helper implementation for get_route, returns information
287         given to defer_add_route.
288         
289         Raises AttributeError if an invalid attribute is requested
290             or if the indexed routing rule does not exist.
291         
292         Raises KeyError if the GUID has not been seen by
293             defer_add_route
294         """
295         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
296         
297         if attribute not in ATTRIBUTES:
298             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
299         
300         attribute_index = ATTRIBUTES.index(attribute)
301         
302         routes = self._add_route.get(guid)
303         if not routes:
304             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
305         
306         if not (0 <= index < len(addresses)):
307             raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
308                 guid, self._testbed_id, index)
309         
310         return routes[index][attribute_index]
311
312     def box_get_address(self, guid, index, attribute='Address'):
313         """
314         Helper implementation for get_address, returns information
315         given to defer_add_address
316         
317         Raises AttributeError if an invalid attribute is requested
318             or if the indexed routing rule does not exist.
319         
320         Raises KeyError if the GUID has not been seen by
321             defer_add_address
322         """
323         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
324         
325         if attribute not in ATTRIBUTES:
326             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
327         
328         attribute_index = ATTRIBUTES.index(attribute)
329         
330         addresses = self._add_address.get(guid)
331         if not addresses:
332             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
333         
334         if not (0 <= index < len(addresses)):
335             raise AttributeError, "GUID %r at %s does not have an address #%s" % (
336                 guid, self._testbed_id, index)
337         
338         return addresses[index][attribute_index]
339
340
341     def start(self, time = TIME_NOW):
342         for guid, factory_id in self._create.iteritems():
343             factory = self._factories[factory_id]
344             start_function = factory.start_function
345             if start_function:
346                 start_function(self, guid)
347         self._started = True
348
349     #action: NotImplementedError
350
351     def stop(self, time = TIME_NOW):
352         for guid, factory_id in self._create.iteritems():
353             factory = self._factories[factory_id]
354             stop_function = factory.stop_function
355             if stop_function:
356                 stop_function(self, guid)
357
358     def status(self, guid):
359         if not guid in self._create:
360             raise RuntimeError("Element guid %d doesn't exist" % guid)
361         factory_id = self._create[guid]
362         factory = self._factories[factory_id]
363         status_function = factory.status_function
364         if status_function:
365             return status_function(self, guid)
366         return STATUS_UNDETERMINED
367
368     def trace(self, guid, trace_id, attribute='value'):
369         if attribute == 'value':
370             fd = open("%s" % self.trace_filename(guid, trace_id), "r")
371             content = fd.read()
372             fd.close()
373         elif attribute == 'path':
374             content = self.trace_filename(guid, trace_id)
375         else:
376             content = None
377         return content
378
379     def trace_filename(self, guid, trace_id):
380         """
381         Return a trace's file path, for TestbedController's default 
382         implementation of trace()
383         """
384         raise NotImplementedError
385
386     #shutdown: NotImplementedError
387
388     def get_connected(self, guid, connector_type_name, 
389             other_connector_type_name):
390         """searchs the connected elements for the specific connector_type_name 
391         pair"""
392         if guid not in self._connect:
393             return []
394         # all connections for all connectors for guid
395         all_connections = self._connect[guid]
396         if connector_type_name not in all_connections:
397             return []
398         # all connections for the specific connector
399         connections = all_connections[connector_type_name]
400         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
401                 in connections.iteritems() if \
402                 otr_connector_type_name == other_connector_type_name]
403         return specific_connections
404
405     def _get_connection_count(self, guid, connection_type_name):
406         count = 0
407         cross_count = 0
408         if guid in self._connect and connection_type_name in \
409                 self._connect[guid]:
410             count = len(self._connect[guid][connection_type_name])
411         if guid in self._cross_connect and connection_type_name in \
412                 self._cross_connect[guid]:
413             cross_count = len(self._cross_connect[guid][connection_type_name])
414         return count + cross_count
415
416     def _get_traces(self, guid):
417         return [] if guid not in self._add_trace else self._add_trace[guid]
418
419     def _get_parameters(self, guid):
420         return dict() if guid not in self._create_set else \
421                 self._create_set[guid]
422