Initially working version of PlanetLab testbed implementation.
[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_preconfigure(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.preconfigure_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.preconfigure_function:
228                 continue
229             for guid in guids[factory_id]:
230                 factory.preconfigure_function(self, guid)
231
232     def do_configure(self):
233         guids = dict()
234         # order guids (elements) according to factory_id
235         for guid, factory_id in self._create.iteritems():
236             if not factory_id in guids:
237                guids[factory_id] = list()
238             guids[factory_id].append(guid)
239         # configure elements following the factory_id order
240         for factory_id in self._metadata.configure_order:
241             # omit the factories that have no element to create
242             if factory_id not in guids:
243                 continue
244             factory = self._factories[factory_id]
245             if not factory.configure_function:
246                 continue
247             for guid in guids[factory_id]:
248                 factory.configure_function(self, guid)
249
250     def do_cross_connect(self):
251         for guid, cross_connections in self._cross_connect.iteritems():
252             element = self._elements[guid]
253             factory_id = self._create[guid]
254             factory = self._factories[factory_id]
255             for connector_type_name, cross_connection in \
256                     cross_connections.iteritems():
257                 connector_type = factory.connector_type(connector_type_name)
258                 (cross_testbed_id, cross_factory_id, 
259                         cross_connector_type_name) = cross_connection
260                 code_to_connect = connector_type.code_to_connect(
261                     cross_guid, cross_testbed_id, cross_factory_id, 
262                     cross_conector_type_name)
263                 if code_to_connect:
264                     code_to_connect(element, cross_guid)       
265
266     def set(self, time, guid, name, value):
267         if not guid in self._create:
268             raise RuntimeError("Element guid %d doesn't exist" % guid)
269         factory_id = self._create[guid]
270         factory = self._factories[factory_id]
271         if not factory.box_attributes.has_attribute(name):
272             raise RuntimeError("Invalid attribute %s for element type %s" %
273                     (name, factory_id))
274         if self._started and factory.is_attribute_design_only(name):
275             raise RuntimeError("Attribute %s can only be modified during experiment design" % name)
276         factory.box_attributes.set_attribute_value(name, value)
277         if guid not in self._set:
278             self._set[guid] = dict()
279         if time not in self._set[guid]:
280             self._set[guid][time] = dict()
281         self._set[guid][time][name] = value
282
283     def box_get(self, time, guid, name):
284         """
285         Helper for subclasses, gets an attribute from box definitions
286         if available. Throws KeyError if the GUID wasn't created
287         through the defer_create interface, and AttributeError if the
288         attribute isn't available (doesn't exist or is design-only)
289         """
290         if not guid in self._create:
291             raise KeyError, "Element guid %d doesn't exist" % guid
292         factory_id = self._create[guid]
293         factory = self._factories[factory_id]
294         if not factory.box_attributes.has_attribute(name):
295             raise AttributeError, "Invalid attribute %s for element type %s" % (name, factory_id)
296         if self._started and factory.is_attribute_design_only(name):
297             raise AttributeError, "Attribute %s can only be queried during experiment design" % name
298         return factory.box_attributes.get_attribute_value(name)
299
300     #get: NotImplementedError
301
302     def box_get_route(self, guid, index, attribute):
303         """
304         Helper implementation for get_route, returns information
305         given to defer_add_route.
306         
307         Raises AttributeError if an invalid attribute is requested
308             or if the indexed routing rule does not exist.
309         
310         Raises KeyError if the GUID has not been seen by
311             defer_add_route
312         """
313         ATTRIBUTES = ['Destination', 'NetPrefix', 'NextHop']
314         
315         if attribute not in ATTRIBUTES:
316             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
317         
318         attribute_index = ATTRIBUTES.index(attribute)
319         
320         routes = self._add_route.get(guid)
321         if not routes:
322             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
323         
324         if not (0 <= index < len(addresses)):
325             raise AttributeError, "GUID %r at %s does not have a routing entry #%s" % (
326                 guid, self._testbed_id, index)
327         
328         return routes[index][attribute_index]
329
330     def box_get_address(self, guid, index, attribute='Address'):
331         """
332         Helper implementation for get_address, returns information
333         given to defer_add_address
334         
335         Raises AttributeError if an invalid attribute is requested
336             or if the indexed routing rule does not exist.
337         
338         Raises KeyError if the GUID has not been seen by
339             defer_add_address
340         """
341         ATTRIBUTES = ['Address', 'NetPrefix', 'Broadcast']
342         
343         if attribute not in ATTRIBUTES:
344             raise AttributeError, "Attribute %r invalid for addresses of %r" % (attribute, guid)
345         
346         attribute_index = ATTRIBUTES.index(attribute)
347         
348         addresses = self._add_address.get(guid)
349         if not addresses:
350             raise KeyError, "GUID %r not found in %s" % (guid, self._testbed_id)
351         
352         if not (0 <= index < len(addresses)):
353             raise AttributeError, "GUID %r at %s does not have an address #%s" % (
354                 guid, self._testbed_id, index)
355         
356         return addresses[index][attribute_index]
357
358
359     def start(self, time = TIME_NOW):
360         for guid, factory_id in self._create.iteritems():
361             factory = self._factories[factory_id]
362             start_function = factory.start_function
363             if start_function:
364                 start_function(self, guid)
365         self._started = True
366
367     #action: NotImplementedError
368
369     def stop(self, time = TIME_NOW):
370         for guid, factory_id in self._create.iteritems():
371             factory = self._factories[factory_id]
372             stop_function = factory.stop_function
373             if stop_function:
374                 stop_function(self, guid)
375
376     def status(self, guid):
377         if not guid in self._create:
378             raise RuntimeError("Element guid %d doesn't exist" % guid)
379         factory_id = self._create[guid]
380         factory = self._factories[factory_id]
381         status_function = factory.status_function
382         if status_function:
383             return status_function(self, guid)
384         return STATUS_UNDETERMINED
385
386     def trace(self, guid, trace_id, attribute='value'):
387         if attribute == 'value':
388             fd = open("%s" % self.trace_filename(guid, trace_id), "r")
389             content = fd.read()
390             fd.close()
391         elif attribute == 'path':
392             content = self.trace_filename(guid, trace_id)
393         else:
394             content = None
395         return content
396
397     def trace_filename(self, guid, trace_id):
398         """
399         Return a trace's file path, for TestbedController's default 
400         implementation of trace()
401         """
402         raise NotImplementedError
403
404     #shutdown: NotImplementedError
405
406     def get_connected(self, guid, connector_type_name, 
407             other_connector_type_name):
408         """searchs the connected elements for the specific connector_type_name 
409         pair"""
410         if guid not in self._connect:
411             return []
412         # all connections for all connectors for guid
413         all_connections = self._connect[guid]
414         if connector_type_name not in all_connections:
415             return []
416         # all connections for the specific connector
417         connections = all_connections[connector_type_name]
418         specific_connections = [otr_guid for otr_guid, otr_connector_type_name \
419                 in connections.iteritems() if \
420                 otr_connector_type_name == other_connector_type_name]
421         return specific_connections
422
423     def _get_connection_count(self, guid, connection_type_name):
424         count = 0
425         cross_count = 0
426         if guid in self._connect and connection_type_name in \
427                 self._connect[guid]:
428             count = len(self._connect[guid][connection_type_name])
429         if guid in self._cross_connect and connection_type_name in \
430                 self._cross_connect[guid]:
431             cross_count = len(self._cross_connect[guid][connection_type_name])
432         return count + cross_count
433
434     def _get_traces(self, guid):
435         return [] if guid not in self._add_trace else self._add_trace[guid]
436
437     def _get_parameters(self, guid):
438         return dict() if guid not in self._create_set else \
439                 self._create_set[guid]
440