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