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