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