Ticket #9: rename create/create_set/add_x/configure/connect -> defer_x
[nepi.git] / src / nepi / core / execute.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 from nepi.core.attributes import Attribute, AttributesMap
5 from nepi.util import proxy, validation
6 from nepi.util.constants import STATUS_FINISHED
7 from nepi.util.parser._xml import XmlExperimentParser
8 import sys
9
10 class ConnectorType(object):
11     def __init__(self, testbed_id, factory_id, name, max = -1, min = 0):
12         super(ConnectorType, self).__init__()
13         if max == -1:
14             max = sys.maxint
15         elif max <= 0:
16                 raise RuntimeError(
17              "The maximum number of connections allowed need to be more than 0")
18         if min < 0:
19             raise RuntimeError(
20              "The minimum number of connections allowed needs to be at least 0")
21         # connector_type_id -- univoquely identifies a connector type 
22         # across testbeds
23         self._connector_type_id = (testbed_id.lower(), factory_id.lower(), 
24                 name.lower())
25         # name -- display name for the connector type
26         self._name = name
27         # max -- maximum amount of connections that this type support, 
28         # -1 for no limit
29         self._max = max
30         # min -- minimum amount of connections required by this type of connector
31         self._min = min
32         # from_connections -- connections where the other connector is the "From"
33         # to_connections -- connections where the other connector is the "To"
34         # keys in the dictionary correspond to the 
35         # connector_type_id for possible connections. The value is a tuple:
36         # (can_cross, connect)
37         # can_cross: indicates if the connection is allowed accros different
38         #    testbed instances
39         # code: is the connection function to be invoked when the elements
40         #    are connected
41         self._from_connections = dict()
42         self._to_connections = dict()
43
44     @property
45     def connector_type_id(self):
46         return self._connector_type_id
47
48     @property
49     def name(self):
50         return self._name
51
52     @property
53     def max(self):
54         return self._max
55
56     @property
57     def min(self):
58         return self._min
59
60     def add_from_connection(self, testbed_id, factory_id, name, can_cross, code):
61         self._from_connections[(testbed_id.lower(), factory_id.lower(),
62             name.lower())] = (can_cross, code)
63
64     def add_to_connection(self, testbed_id, factory_id, name, can_cross, code):
65         self._to_connections[(testbed_id.lower(), factory_id.lower(), 
66             name.lower())] = (can_cross, code)
67
68     def can_connect(self, testbed_id, factory_id, name, count, 
69             must_cross = False):
70         connector_type_id = (testbed_id.lower(), factory_id.lower(),
71             name.lower())
72         if connector_type_id in self._from_connections:
73             (can_cross, code) = self._from_connections[connector_type_id]
74         elif connector_type_id in self._to_connections:
75             (can_cross, code) = self._to_connections[connector_type_id]
76         else:
77             return False
78         return not must_cross or can_cross
79
80     def code_to_connect(self, testbed_id, factory_id, name):
81         connector_type_id = (testbed_id.lower(), factory_id.lower(), 
82             name.lower())        
83         if not connector_type_id in self._to_connections.keys():
84             return False
85         (can_cross, code) = self._to_connections[connector_type_id]
86         return code
87
88 # TODO: create_function, start_function, stop_function, status_function 
89 # need a definition!
90 class Factory(AttributesMap):
91     def __init__(self, factory_id, create_function, start_function, 
92             stop_function, status_function, configure_function,
93             allow_addresses = False, allow_routes = False):
94         super(Factory, self).__init__()
95         self._factory_id = factory_id
96         self._allow_addresses = (allow_addresses == True)
97         self._allow_routes = (allow_routes == True)
98         self._create_function = create_function
99         self._start_function = start_function
100         self._stop_function = stop_function
101         self._status_function = status_function
102         self._configure_function = configure_function
103         self._connector_types = dict()
104         self._traces = list()
105         self._box_attributes = AttributesMap()
106
107     @property
108     def factory_id(self):
109         return self._factory_id
110
111     @property
112     def allow_addresses(self):
113         return self._allow_addresses
114
115     @property
116     def allow_routes(self):
117         return self._allow_routes
118
119     @property
120     def box_attributes(self):
121         return self._box_attributes
122
123     @property
124     def create_function(self):
125         return self._create_function
126
127     @property
128     def start_function(self):
129         return self._start_function
130
131     @property
132     def stop_function(self):
133         return self._stop_function
134
135     @property
136     def status_function(self):
137         return self._status_function
138
139     @property
140     def configure_function(self):
141         return self._configure_function
142
143     @property
144     def traces(self):
145         return self._traces
146
147     def connector_type(self, name):
148         return self._connector_types[name]
149
150     def add_connector_type(self, connector_type):
151         self._connector_types[connector_type.name] = connector_type
152
153     def add_trace(self, trace_id):
154         self._traces.append(trace_id)
155
156     def add_box_attribute(self, name, help, type, value = None, range = None,
157         allowed = None, flags = Attribute.NoFlags, validation_function = None):
158         self._box_attributes.add_attribute(name, help, type, value, range, 
159                 allowed, flags, validation_function)
160
161 class TestbedInstance(object):
162     def __init__(self, testbed_id, testbed_version):
163         self._testbed_id = testbed_id
164         self._testbed_version = testbed_version
165
166     @property
167     def guids(self):
168         raise NotImplementedError
169
170     def defer_configure(self, name, value):
171         """Instructs setting a configuartion attribute for the testbed instance"""
172         raise NotImplementedError
173
174     def defer_create(self, guid, factory_id):
175         """Instructs creation of element """
176         raise NotImplementedError
177
178     def defer_create_set(self, guid, name, value):
179         """Instructs setting an initial attribute on an element"""
180         raise NotImplementedError
181
182     def defer_factory_set(self, guid, name, value):
183         """Instructs setting an attribute on a factory"""
184         raise NotImplementedError
185
186     def defer_connect(self, guid1, connector_type_name1, guid2, 
187             connector_type_name2): 
188         """Instructs creation of a connection between the given connectors"""
189         raise NotImplementedError
190
191     def defer_cross_connect(self, guid, connector_type_name, cross_guid, 
192             cross_testbed_id, cross_factory_id, cross_connector_type_name):
193         """
194         Instructs creation of a connection between the given connectors 
195         of different testbed instances
196         """
197         raise NotImplementedError
198
199     def defer_add_trace(self, guid, trace_id):
200         """Instructs the addition of a trace"""
201         raise NotImplementedError
202
203     def defer_add_address(self, guid, address, netprefix, broadcast): 
204         """Instructs the addition of an address"""
205         raise NotImplementedError
206
207     def defer_add_route(self, guid, destination, netprefix, nexthop):
208         """Instructs the addition of a route"""
209         raise NotImplementedError
210
211     def do_setup(self):
212         """After do_setup the testbed initial configuration is done"""
213         raise NotImplementedError
214
215     def do_create(self):
216         """
217         After do_create all instructed elements are created and 
218         attributes setted
219         """
220         raise NotImplementedError
221
222     def do_connect(self):
223         """
224         After do_connect all internal connections between testbed elements
225         are done
226         """
227         raise NotImplementedError
228
229     def do_configure(self):
230         """After do_configure elements are configured"""
231         raise NotImplementedError
232
233     def do_cross_connect(self):
234         """
235         After do_cross_connect all external connections between different testbed 
236         elements are done
237         """
238         raise NotImplementedError
239
240     def start(self):
241         raise NotImplementedError
242
243     def stop(self):
244         raise NotImplementedError
245
246     def set(self, time, guid, name, value):
247         raise NotImplementedError
248
249     def get(self, time, guid, name):
250         raise NotImplementedError
251
252     def action(self, time, guid, action):
253         raise NotImplementedError
254
255     def status(self, guid):
256         raise NotImplementedError
257
258     def trace(self, guid, trace_id):
259         raise NotImplementedError
260
261     def shutdown(self):
262         raise NotImplementedError
263
264 class ExperimentController(object):
265     def __init__(self, experiment_xml):
266         self._experiment_xml = experiment_xml
267         self._testbeds = dict()
268         self._access_config = dict()
269
270     @property
271     def experiment_xml(self):
272         return self._experiment_xml
273
274     def set_access_configuration(self, testbed_guid, access_config):
275         self._access_config[testbed_guid] = access_config
276
277     def trace(self, testbed_guid, guid, trace_id):
278         return self._testbeds[testbed_guid].trace(guid, trace_id)
279
280     def start(self):
281         self._create_testbed_instances()
282         for testbed in self._testbeds.values():
283             testbed.do_setup()
284         for testbed in self._testbeds.values():
285             testbed.do_create()
286             testbed.do_connect()
287             testbed.do_configure()
288         for testbed in self._testbeds.values():
289             testbed.do_cross_connect()
290         for testbed in self._testbeds.values():
291             testbed.start()
292
293     def stop(self):
294        for testbed in self._testbeds.values():
295            testbed.stop()
296
297     def is_finished(self, guid):
298         for testbed in self._testbeds.values():
299             for guid_ in testbed.guids:
300                 if guid_ == guid:
301                     return testbed.status(guid) == STATUS_FINISHED
302         raise RuntimeError("No element exists with guid %d" % guid)    
303
304     def shutdown(self):
305        for testbed in self._testbeds.values():
306            testbed.shutdown()
307
308     def _create_testbed_instances(self):
309         parser = XmlExperimentParser()
310         data = parser.from_xml_to_data(self._experiment_xml)
311         element_guids = list()
312         for guid in data.guids:
313             if data.is_testbed_data(guid):
314                 (testbed_id, testbed_version) = data.get_testbed_data(guid)
315                 access_config = None if guid not in self._access_config else\
316                         self._access_config[guid]
317                 testbed = proxy.create_testbed_instance(testbed_id, 
318                         testbed_version, access_config)
319                 for (name, value) in data.get_attribute_data(guid):
320                     testbed.defer_configure(name, value)
321                 self._testbeds[guid] = testbed
322             else:
323                 element_guids.append(guid)
324         self._program_testbed_instances(element_guids, data)
325
326     def _program_testbed_instances(self, element_guids, data):
327         for guid in element_guids:
328             (testbed_guid, factory_id) = data.get_box_data(guid)
329             testbed = self._testbeds[testbed_guid]
330             testbed.defer_create(guid, factory_id)
331             for (name, value) in data.get_attribute_data(guid):
332                 testbed.defer_create_set(guid, name, value)
333
334         for guid in element_guids: 
335             (testbed_guid, factory_id) = data.get_box_data(guid)
336             testbed = self._testbeds[testbed_guid]
337             for (connector_type_name, other_guid, other_connector_type_name) \
338                     in data.get_connection_data(guid):
339                 (testbed_guid, factory_id) = data.get_box_data(guid)
340                 (other_testbed_guid, other_factory_id) = data.get_box_data(
341                         other_guid)
342                 if testbed_guid == other_testbed_guid:
343                     testbed.defer_connect(guid, connector_type_name, other_guid, 
344                         other_connector_type_name)
345                 else:
346                     testbed.defer_cross_connect(guid, connector_type_name, other_guid, 
347                         other_testbed_id, other_factory_id, other_connector_type_name)
348             for trace_id in data.get_trace_data(guid):
349                 testbed.defer_add_trace(guid, trace_id)
350             for (autoconf, address, netprefix, broadcast) in \
351                     data.get_address_data(guid):
352                 if address != None:
353                     testbed.defer_add_address(guid, address, netprefix, broadcast)
354             for (destination, netprefix, nexthop) in data.get_route_data(guid):
355                 testbed.defer_add_route(guid, destination, netprefix, nexthop)
356