Preserve the traceback when relaying exceptions from within threads
[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.core.connector import ConnectorTypeBase
6 from nepi.util import validation
7 from nepi.util.constants import STATUS_FINISHED, TIME_NOW
8 from nepi.util.parser._xml import XmlExperimentParser
9 import sys
10 import re
11 import threading
12 import ConfigParser
13 import os
14 import collections
15 import functools
16
17 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._]*)\])#}")
18 ATTRIBUTE_PATTERN_GUID_SUB = r"{#[%(guid)s]%(expr)s#}"
19 COMPONENT_PATTERN = re.compile(r"(?P<kind>[a-z]*)\[(?P<index>.*)\]")
20
21 class ConnectorType(ConnectorTypeBase):
22     def __init__(self, testbed_id, factory_id, name, max = -1, min = 0):
23         super(ConnectorType, self).__init__(testbed_id, factory_id, name, max, min)
24         # from_connections -- connections where the other connector is the "From"
25         # to_connections -- connections where the other connector is the "To"
26         # keys in the dictionary correspond to the 
27         # connector_type_id for possible connections. The value is a tuple:
28         # (can_cross, connect)
29         # can_cross: indicates if the connection is allowed accros different
30         #    testbed instances
31         # code: is the connection function to be invoked when the elements
32         #    are connected
33         self._from_connections = dict()
34         self._to_connections = dict()
35
36     def add_from_connection(self, testbed_id, factory_id, name, can_cross, 
37             init_code, compl_code):
38         type_id = self.make_connector_type_id(testbed_id, factory_id, name)
39         self._from_connections[type_id] = (can_cross, init_code, compl_code)
40
41     def add_to_connection(self, testbed_id, factory_id, name, can_cross, 
42             init_code, compl_code):
43         type_id = self.make_connector_type_id(testbed_id, factory_id, name)
44         self._to_connections[type_id] = (can_cross, init_code, compl_code)
45
46     def can_connect(self, testbed_id, factory_id, name, count, 
47             must_cross):
48         connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
49         for lookup_type_id in self._type_resolution_order(connector_type_id):
50             if lookup_type_id in self._from_connections:
51                 (can_cross, init_code, compl_code) = self._from_connections[lookup_type_id]
52             elif lookup_type_id in self._to_connections:
53                 (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
54             else:
55                 # keep trying
56                 continue
57             return not must_cross or can_cross
58         else:
59             return False
60
61     def _connect_to_code(self, testbed_id, factory_id, name,
62             must_cross):
63         connector_type_id = self.make_connector_type_id(testbed_id, factory_id, name)
64         for lookup_type_id in self._type_resolution_order(connector_type_id):
65             if lookup_type_id in self._to_connections:
66                 (can_cross, init_code, compl_code) = self._to_connections[lookup_type_id]
67                 if not must_cross or can_cross:
68                     return (init_code, compl_code)
69         else:
70             return (False, False)
71     
72     def connect_to_init_code(self, testbed_id, factory_id, name, must_cross):
73         return self._connect_to_code(testbed_id, factory_id, name, must_cross)[0]
74
75     def connect_to_compl_code(self, testbed_id, factory_id, name, must_cross):
76         return self._connect_to_code(testbed_id, factory_id, name, must_cross)[1]
77
78 class Factory(AttributesMap):
79     def __init__(self, factory_id, create_function, start_function, 
80             stop_function, status_function, 
81             configure_function, preconfigure_function,
82             allow_addresses = False, has_addresses = False,
83             allow_routes = False, has_routes = False):
84         super(Factory, self).__init__()
85         self._factory_id = factory_id
86         self._allow_addresses = bool(allow_addresses)
87         self._allow_routes = bool(allow_routes)
88         self._has_addresses = bool(has_addresses) or self._allow_addresses
89         self._has_routes = bool(has_routes) or self._allow_routes
90         self._create_function = create_function
91         self._start_function = start_function
92         self._stop_function = stop_function
93         self._status_function = status_function
94         self._configure_function = configure_function
95         self._preconfigure_function = preconfigure_function
96         self._connector_types = dict()
97         self._traces = list()
98         self._box_attributes = AttributesMap()
99
100     @property
101     def factory_id(self):
102         return self._factory_id
103
104     @property
105     def allow_addresses(self):
106         return self._allow_addresses
107
108     @property
109     def allow_routes(self):
110         return self._allow_routes
111
112     @property
113     def has_addresses(self):
114         return self._has_addresses
115
116     @property
117     def has_routes(self):
118         return self._has_routes
119
120     @property
121     def box_attributes(self):
122         return self._box_attributes
123
124     @property
125     def create_function(self):
126         return self._create_function
127
128     @property
129     def start_function(self):
130         return self._start_function
131
132     @property
133     def stop_function(self):
134         return self._stop_function
135
136     @property
137     def status_function(self):
138         return self._status_function
139
140     @property
141     def configure_function(self):
142         return self._configure_function
143
144     @property
145     def preconfigure_function(self):
146         return self._preconfigure_function
147
148     @property
149     def traces(self):
150         return self._traces
151
152     def connector_type(self, name):
153         return self._connector_types[name]
154
155     def add_connector_type(self, connector_type):
156         self._connector_types[connector_type.name] = connector_type
157
158     def add_trace(self, trace_id):
159         self._traces.append(trace_id)
160
161     def add_box_attribute(self, name, help, type, value = None, range = None,
162         allowed = None, flags = Attribute.NoFlags, validation_function = None):
163         self._box_attributes.add_attribute(name, help, type, value, range, 
164                 allowed, flags, validation_function)
165
166 class TestbedController(object):
167     def __init__(self, testbed_id, testbed_version):
168         self._testbed_id = testbed_id
169         self._testbed_version = testbed_version
170
171     @property
172     def testbed_id(self):
173         return self._testbed_id
174
175     @property
176     def testbed_version(self):
177         return self._testbed_version
178
179     @property
180     def guids(self):
181         raise NotImplementedError
182
183     def defer_configure(self, name, value):
184         """Instructs setting a configuartion attribute for the testbed instance"""
185         raise NotImplementedError
186
187     def defer_create(self, guid, factory_id):
188         """Instructs creation of element """
189         raise NotImplementedError
190
191     def defer_create_set(self, guid, name, value):
192         """Instructs setting an initial attribute on an element"""
193         raise NotImplementedError
194
195     def defer_factory_set(self, guid, name, value):
196         """Instructs setting an attribute on a factory"""
197         raise NotImplementedError
198
199     def defer_connect(self, guid1, connector_type_name1, guid2, 
200             connector_type_name2): 
201         """Instructs creation of a connection between the given connectors"""
202         raise NotImplementedError
203
204     def defer_cross_connect(self, 
205             guid, connector_type_name,
206             cross_guid, cross_testbed_guid,
207             cross_testbed_id, cross_factory_id,
208             cross_connector_type_name):
209         """
210         Instructs creation of a connection between the given connectors 
211         of different testbed instances
212         """
213         raise NotImplementedError
214
215     def defer_add_trace(self, guid, trace_id):
216         """Instructs the addition of a trace"""
217         raise NotImplementedError
218
219     def defer_add_address(self, guid, address, netprefix, broadcast): 
220         """Instructs the addition of an address"""
221         raise NotImplementedError
222
223     def defer_add_route(self, guid, destination, netprefix, nexthop):
224         """Instructs the addition of a route"""
225         raise NotImplementedError
226
227     def do_setup(self):
228         """After do_setup the testbed initial configuration is done"""
229         raise NotImplementedError
230
231     def do_create(self):
232         """
233         After do_create all instructed elements are created and 
234         attributes setted
235         """
236         raise NotImplementedError
237
238     def do_connect_init(self):
239         """
240         After do_connect_init all internal connections between testbed elements
241         are initiated
242         """
243         raise NotImplementedError
244
245     def do_connect_compl(self):
246         """
247         After do_connect all internal connections between testbed elements
248         are completed
249         """
250         raise NotImplementedError
251
252     def do_preconfigure(self):
253         """
254         Done just before resolving netrefs, after connection, before cross connections,
255         useful for early stages of configuration, for setting up stuff that might be
256         required for netref resolution.
257         """
258         raise NotImplementedError
259
260     def do_configure(self):
261         """After do_configure elements are configured"""
262         raise NotImplementedError
263
264     def do_cross_connect_init(self, cross_data):
265         """
266         After do_cross_connect_init initiation of all external connections 
267         between different testbed elements is performed
268         """
269         raise NotImplementedError
270
271     def do_cross_connect_compl(self, cross_data):
272         """
273         After do_cross_connect_compl completion of all external connections 
274         between different testbed elements is performed
275         """
276         raise NotImplementedError
277
278     def start(self):
279         raise NotImplementedError
280
281     def stop(self):
282         raise NotImplementedError
283
284     def set(self, guid, name, value, time = TIME_NOW):
285         raise NotImplementedError
286
287     def get(self, guid, name, time = TIME_NOW):
288         raise NotImplementedError
289     
290     def get_route(self, guid, index, attribute):
291         """
292         Params:
293             
294             guid: guid of box to query
295             index: number of routing entry to fetch
296             attribute: one of Destination, NextHop, NetPrefix
297         """
298         raise NotImplementedError
299
300     def get_address(self, guid, index, attribute='Address'):
301         """
302         Params:
303             
304             guid: guid of box to query
305             index: number of inteface to select
306             attribute: one of Address, NetPrefix, Broadcast
307         """
308         raise NotImplementedError
309
310     def get_attribute_list(self, guid):
311         raise NotImplementedError
312
313     def action(self, time, guid, action):
314         raise NotImplementedError
315
316     def status(self, guid):
317         raise NotImplementedError
318
319     def trace(self, guid, trace_id, attribute='value'):
320         raise NotImplementedError
321
322     def shutdown(self):
323         raise NotImplementedError
324
325 class ExperimentController(object):
326     def __init__(self, experiment_xml, root_dir):
327         self._experiment_xml = experiment_xml
328         self._testbeds = dict()
329         self._deployment_config = dict()
330         self._netrefs = collections.defaultdict(set)
331         self._testbed_netrefs = collections.defaultdict(set)
332         self._cross_data = dict()
333         self._root_dir = root_dir
334         self._netreffed_testbeds = set()
335         self._guids_in_testbed_cache = dict()
336
337         self.persist_experiment_xml()
338
339     @property
340     def experiment_xml(self):
341         return self._experiment_xml
342
343     def persist_experiment_xml(self):
344         xml_path = os.path.join(self._root_dir, "experiment.xml")
345         f = open(xml_path, "w")
346         f.write(self._experiment_xml)
347         f.close()
348
349     def trace(self, testbed_guid, guid, trace_id, attribute='value'):
350         return self._testbeds[testbed_guid].trace(guid, trace_id, attribute)
351
352     @staticmethod
353     def _parallel(callables):
354         excs = []
355         def wrap(callable):
356             @functools.wraps(callable)
357             def wrapped(*p, **kw):
358                 try:
359                     callable(*p, **kw)
360                 except:
361                     import traceback
362                     traceback.print_exc(file=sys.stderr)
363                     excs.append(sys.exc_info())
364             return wrapped
365         threads = [ threading.Thread(target=wrap(callable)) for callable in callables ]
366         for thread in threads:
367             thread.start()
368         for thread in threads:
369             thread.join()
370         for exc in excs:
371             eTyp, eVal, eLoc = exc
372             raise eTyp, eVal, eLoc
373
374     def start(self):
375         parser = XmlExperimentParser()
376         data = parser.from_xml_to_data(self._experiment_xml)
377         
378         self._init_testbed_controllers(data)
379         
380         # persist testbed connection data, for potential recovery
381         self._persist_testbed_proxies()
382         
383         def steps_to_configure(self, allowed_guids):
384             # perform setup in parallel for all test beds,
385             # wait for all threads to finish
386             self._parallel([testbed.do_setup 
387                             for guid,testbed in self._testbeds.iteritems()
388                             if guid in allowed_guids])
389             
390             # perform create-connect in parallel, wait
391             # (internal connections only)
392             self._parallel([testbed.do_create
393                             for guid,testbed in self._testbeds.iteritems()
394                             if guid in allowed_guids])
395
396             self._parallel([testbed.do_connect_init
397                             for guid,testbed in self._testbeds.iteritems()
398                             if guid in allowed_guids])
399
400             self._parallel([testbed.do_connect_compl
401                             for guid,testbed in self._testbeds.iteritems()
402                             if guid in allowed_guids])
403
404             self._parallel([testbed.do_preconfigure
405                             for guid,testbed in self._testbeds.iteritems()
406                             if guid in allowed_guids])
407             
408         steps_to_configure(self, self._testbeds)
409         
410         if self._netreffed_testbeds:
411             # initally resolve netrefs
412             self.do_netrefs(data, fail_if_undefined=False)
413             
414             # rinse and repeat, for netreffed testbeds
415             netreffed_testbeds = set(self._netreffed_testbeds)
416
417             self._init_testbed_controllers(data)
418             
419             # persist testbed connection data, for potential recovery
420             self._persist_testbed_proxies()
421
422             # configure dependant testbeds
423             steps_to_configure(self, netreffed_testbeds)
424             
425         # final netref step, fail if anything's left unresolved
426         self.do_netrefs(data, fail_if_undefined=True)
427         
428         self._program_testbed_cross_connections(data)
429         
430         # perform do_configure in parallel for al testbeds
431         # (it's internal configuration for each)
432         self._parallel([testbed.do_configure
433                         for testbed in self._testbeds.itervalues()])
434
435         
436         #print >>sys.stderr, "DO IT"
437         #import time
438         #time.sleep(60)
439         
440         # cross-connect (cannot be done in parallel)
441         for guid, testbed in self._testbeds.iteritems():
442             cross_data = self._get_cross_data(guid)
443             testbed.do_cross_connect_init(cross_data)
444         for guid, testbed in self._testbeds.iteritems():
445             cross_data = self._get_cross_data(guid)
446             testbed.do_cross_connect_compl(cross_data)
447        
448         # start experiment (parallel start on all testbeds)
449         self._parallel([testbed.start
450                         for testbed in self._testbeds.itervalues()])
451
452     def _persist_testbed_proxies(self):
453         TRANSIENT = ('Recover',)
454         
455         # persist access configuration for all testbeds, so that
456         # recovery mode can reconnect to them if it becomes necessary
457         conf = ConfigParser.RawConfigParser()
458         for testbed_guid, testbed_config in self._deployment_config.iteritems():
459             testbed_guid = str(testbed_guid)
460             conf.add_section(testbed_guid)
461             for attr in testbed_config.attributes_list:
462                 if attr not in TRANSIENT:
463                     conf.set(testbed_guid, attr, 
464                         testbed_config.get_attribute_value(attr))
465         
466         f = open(os.path.join(self._root_dir, 'deployment_config.ini'), 'w')
467         conf.write(f)
468         f.close()
469     
470     def _load_testbed_proxies(self):
471         TYPEMAP = {
472             STRING : 'get',
473             INTEGER : 'getint',
474             FLOAT : 'getfloat',
475             BOOLEAN : 'getboolean',
476         }
477         
478         # deferred import because proxy needs
479         # our class definitions to define proxies
480         import nepi.util.proxy as proxy
481         
482         conf = ConfigParser.RawConfigParser()
483         conf.read(os.path.join(self._root_dir, 'deployment_config.ini'))
484         for testbed_guid in conf.sections():
485             testbed_config = proxy.AccessConfiguration()
486             for attr in conf.options(testbed_guid):
487                 testbed_config.set_attribute_value(attr, 
488                     conf.get(testbed_guid, attr) )
489                 
490             testbed_guid = str(testbed_guid)
491             conf.add_section(testbed_guid)
492             for attr in testbed_config.attributes_list:
493                 if attr not in TRANSIENT:
494                     getter = getattr(conf, TYPEMAP.get(
495                         testbed_config.get_attribute_type(attr),
496                         'get') )
497                     testbed_config.set_attribute_value(
498                         testbed_guid, attr, getter(attr))
499     
500     def _unpersist_testbed_proxies(self):
501         try:
502             os.remove(os.path.join(self._root_dir, 'deployment_config.ini'))
503         except:
504             # Just print exceptions, this is just cleanup
505             import traceback
506             traceback.print_exc(file=sys.stderr)
507
508     def stop(self):
509        for testbed in self._testbeds.values():
510            testbed.stop()
511        self._unpersist_testbed_proxies()
512    
513     def recover(self):
514         # reload perviously persisted testbed access configurations
515         self._load_testbed_proxies()
516         
517         # recreate testbed proxies by reconnecting only
518         self._init_testbed_controllers(recover = True)
519         
520         # another time, for netrefs
521         self._init_testbed_controllers(recover = True)
522
523     def is_finished(self, guid):
524         for testbed in self._testbeds.values():
525             if guid in testbed.guids:
526                 return testbed.status(guid) == STATUS_FINISHED
527         raise RuntimeError("No element exists with guid %d" % guid)    
528
529     def set(self, testbed_guid, guid, name, value, time = TIME_NOW):
530         testbed = self._testbeds[testbed_guid]
531         testbed.set(guid, name, value, time)
532
533     def get(self, testbed_guid, guid, name, time = TIME_NOW):
534         testbed = self._testbeds[testbed_guid]
535         return testbed.get(guid, name, time)
536
537     def shutdown(self):
538         for testbed in self._testbeds.values():
539             testbed.shutdown()
540     
541     def _guids_in_testbed(self, testbed_guid):
542         if testbed_guid not in self._testbeds:
543             return set()
544         if testbed_guid not in self._guids_in_testbed_cache:
545             self._guids_in_testbed_cache[testbed_guid] = \
546                 set(self._testbeds[testbed_guid].guids)
547         return self._guids_in_testbed_cache[testbed_guid]
548
549     @staticmethod
550     def _netref_component_split(component):
551         match = COMPONENT_PATTERN.match(component)
552         if match:
553             return match.group("kind"), match.group("index")
554         else:
555             return component, None
556
557     _NETREF_COMPONENT_GETTERS = {
558         'addr':
559             lambda testbed, guid, index, name: 
560                 testbed.get_address(guid, int(index), name),
561         'route' :
562             lambda testbed, guid, index, name: 
563                 testbed.get_route(guid, int(index), name),
564         'trace' :
565             lambda testbed, guid, index, name: 
566                 testbed.trace(guid, index, name),
567         '' : 
568             lambda testbed, guid, index, name: 
569                 testbed.get(guid, name),
570     }
571     
572     def resolve_netref_value(self, value, failval = None):
573         match = ATTRIBUTE_PATTERN_BASE.search(value)
574         if match:
575             label = match.group("label")
576             if label.startswith('GUID-'):
577                 ref_guid = int(label[5:])
578                 if ref_guid:
579                     expr = match.group("expr")
580                     component = (match.group("component") or "")[1:] # skip the dot
581                     attribute = match.group("attribute")
582                     
583                     # split compound components into component kind and index
584                     # eg: 'addr[0]' -> ('addr', '0')
585                     component, component_index = self._netref_component_split(component)
586
587                     # find object and resolve expression
588                     for ref_testbed_guid, ref_testbed in self._testbeds.iteritems():
589                         if component not in self._NETREF_COMPONENT_GETTERS:
590                             raise ValueError, "Malformed netref: %r - unknown component" % (expr,)
591                         elif ref_guid not in self._guids_in_testbed(ref_testbed_guid):
592                             pass
593                         else:
594                             ref_value = self._NETREF_COMPONENT_GETTERS[component](
595                                 ref_testbed, ref_guid, component_index, attribute)
596                             if ref_value:
597                                 return value.replace(match.group(), ref_value)
598         # couldn't find value
599         return failval
600     
601     def do_netrefs(self, data, fail_if_undefined = False):
602         # element netrefs
603         for (testbed_guid, guid), attrs in self._netrefs.items():
604             testbed = self._testbeds.get(testbed_guid)
605             if testbed is not None:
606                 for name in set(attrs):
607                     value = testbed.get(guid, name)
608                     if isinstance(value, basestring):
609                         ref_value = self.resolve_netref_value(value)
610                         if ref_value is not None:
611                             testbed.set(guid, name, ref_value)
612                             attrs.remove(name)
613                         elif fail_if_undefined:
614                             raise ValueError, "Unresolvable netref in: %r=%r" % (name,value,)
615                 if not attrs:
616                     del self._netrefs[(testbed_guid, guid)]
617         
618         # testbed netrefs
619         for testbed_guid, attrs in self._testbed_netrefs.items():
620             tb_data = dict(data.get_attribute_data(testbed_guid))
621             if data:
622                 for name in set(attrs):
623                     value = tb_data.get(name)
624                     if isinstance(value, basestring):
625                         ref_value = self.resolve_netref_value(value)
626                         if ref_value is not None:
627                             data.set_attribute_data(testbed_guid, name, ref_value)
628                             attrs.remove(name)
629                         elif fail_if_undefined:
630                             raise ValueError, "Unresolvable netref in: %r" % (value,)
631                 if not attrs:
632                     del self._testbed_netrefs[testbed_guid]
633         
634
635     def _init_testbed_controllers(self, data, recover = False):
636         blacklist_testbeds = set(self._testbeds)
637         element_guids = list()
638         label_guids = dict()
639         data_guids = data.guids
640
641         # create testbed controllers
642         for guid in data_guids:
643             if data.is_testbed_data(guid):
644                 if guid not in self._testbeds:
645                     self._create_testbed_controller(guid, data, element_guids,
646                             recover)
647             else:
648                 (testbed_guid, factory_id) = data.get_box_data(guid)
649                 if testbed_guid not in blacklist_testbeds:
650                     element_guids.append(guid)
651                     label = data.get_attribute_data(guid, "label")
652                     if label is not None:
653                         if label in label_guids:
654                             raise RuntimeError, "Label %r is not unique" % (label,)
655                         label_guids[label] = guid
656
657         # replace references to elements labels for its guid
658         self._resolve_labels(data, data_guids, label_guids)
659     
660         # program testbed controllers
661         if not recover:
662             self._program_testbed_controllers(element_guids, data)
663
664     def _resolve_labels(self, data, data_guids, label_guids):
665         netrefs = self._netrefs
666         testbed_netrefs = self._testbed_netrefs
667         for guid in data_guids:
668             for name, value in data.get_attribute_data(guid):
669                 if isinstance(value, basestring):
670                     match = ATTRIBUTE_PATTERN_BASE.search(value)
671                     if match:
672                         label = match.group("label")
673                         if not label.startswith('GUID-'):
674                             ref_guid = label_guids.get(label)
675                             if ref_guid is not None:
676                                 value = ATTRIBUTE_PATTERN_BASE.sub(
677                                     ATTRIBUTE_PATTERN_GUID_SUB % dict(
678                                         guid = 'GUID-%d' % (ref_guid,),
679                                         expr = match.group("expr"),
680                                         label = label), 
681                                     value)
682                                 data.set_attribute_data(guid, name, value)
683                                 
684                                 # memorize which guid-attribute pairs require
685                                 # postprocessing, to avoid excessive controller-testbed
686                                 # communication at configuration time
687                                 # (which could require high-latency network I/O)
688                                 if not data.is_testbed_data(guid):
689                                     (testbed_guid, factory_id) = data.get_box_data(guid)
690                                     netrefs[(testbed_guid, guid)].add(name)
691                                 else:
692                                     testbed_netrefs[guid].add(name)
693
694     def _create_testbed_controller(self, guid, data, element_guids, recover):
695         (testbed_id, testbed_version) = data.get_testbed_data(guid)
696         deployment_config = self._deployment_config.get(guid)
697         
698         # deferred import because proxy needs
699         # our class definitions to define proxies
700         import nepi.util.proxy as proxy
701         
702         if deployment_config is None:
703             # need to create one
704             deployment_config = proxy.AccessConfiguration()
705             
706             for (name, value) in data.get_attribute_data(guid):
707                 if value is not None and deployment_config.has_attribute(name):
708                     # if any deployment config attribute has a netref, we can't
709                     # create this controller yet
710                     if isinstance(value, basestring) and ATTRIBUTE_PATTERN_BASE.search(value):
711                         # remember to re-issue this one
712                         self._netreffed_testbeds.add(guid)
713                         return
714                     
715                     # copy deployment config attribute
716                     deployment_config.set_attribute_value(name, value)
717             
718             # commit config
719             self._deployment_config[guid] = deployment_config
720         
721         if deployment_config is not None:
722             # force recovery mode 
723             deployment_config.set_attribute_value("recover",recover)
724         
725         testbed = proxy.create_testbed_controller(testbed_id, 
726                 testbed_version, deployment_config)
727         for (name, value) in data.get_attribute_data(guid):
728             testbed.defer_configure(name, value)
729         self._testbeds[guid] = testbed
730         if guid in self._netreffed_testbeds:
731             self._netreffed_testbeds.remove(guid)
732
733     def _program_testbed_controllers(self, element_guids, data):
734         for guid in element_guids:
735             (testbed_guid, factory_id) = data.get_box_data(guid)
736             testbed = self._testbeds.get(testbed_guid)
737             if testbed:
738                 testbed.defer_create(guid, factory_id)
739                 for (name, value) in data.get_attribute_data(guid):
740                     # Try to resolve create-time netrefs, if possible
741                     if isinstance(value, basestring) and ATTRIBUTE_PATTERN_BASE.search(value):
742                         try:
743                             nuvalue = self.resolve_netref_value(value)
744                         except:
745                             # Any trouble means we're not in shape to resolve the netref yet
746                             nuvalue = None
747                         if nuvalue is not None:
748                             # Only if we succeed we remove the netref deferral entry
749                             value = nuvalue
750                             data.set_attribute_data(guid, name, value)
751                             if (testbed_guid, guid) in self._netrefs:
752                                 self._netrefs[(testbed_guid, guid)].discard(name)
753                     testbed.defer_create_set(guid, name, value)
754
755         for guid in element_guids: 
756             (testbed_guid, factory_id) = data.get_box_data(guid)
757             testbed = self._testbeds.get(testbed_guid)
758             if testbed:
759                 for (connector_type_name, cross_guid, cross_connector_type_name) \
760                         in data.get_connection_data(guid):
761                     (testbed_guid, factory_id) = data.get_box_data(guid)
762                     (cross_testbed_guid, cross_factory_id) = data.get_box_data(
763                             cross_guid)
764                     if testbed_guid == cross_testbed_guid:
765                         testbed.defer_connect(guid, connector_type_name, 
766                                 cross_guid, cross_connector_type_name)
767                 for trace_id in data.get_trace_data(guid):
768                     testbed.defer_add_trace(guid, trace_id)
769                 for (autoconf, address, netprefix, broadcast) in \
770                         data.get_address_data(guid):
771                     if address != None:
772                         testbed.defer_add_address(guid, address, netprefix, 
773                                 broadcast)
774                 for (destination, netprefix, nexthop) in data.get_route_data(guid):
775                     testbed.defer_add_route(guid, destination, netprefix, nexthop)
776     
777     def _program_testbed_cross_connections(self, data):
778         data_guids = data.guids
779
780         for guid in data_guids: 
781             if not data.is_testbed_data(guid):
782                 (testbed_guid, factory_id) = data.get_box_data(guid)
783                 testbed = self._testbeds.get(testbed_guid)
784                 if testbed:
785                     for (connector_type_name, cross_guid, cross_connector_type_name) \
786                             in data.get_connection_data(guid):
787                         (testbed_guid, factory_id) = data.get_box_data(guid)
788                         (cross_testbed_guid, cross_factory_id) = data.get_box_data(
789                                 cross_guid)
790                         if testbed_guid != cross_testbed_guid:
791                             cross_testbed = self._testbeds[cross_testbed_guid]
792                             cross_testbed_id = cross_testbed.testbed_id
793                             testbed.defer_cross_connect(guid, connector_type_name, cross_guid, 
794                                     cross_testbed_guid, cross_testbed_id, cross_factory_id, 
795                                     cross_connector_type_name)
796                             # save cross data for later
797                             self._add_crossdata(testbed_guid, guid, cross_testbed_guid,
798                                     cross_guid)
799                 
800     def _add_crossdata(self, testbed_guid, guid, cross_testbed_guid, cross_guid):
801         if testbed_guid not in self._cross_data:
802             self._cross_data[testbed_guid] = dict()
803         if cross_testbed_guid not in self._cross_data[testbed_guid]:
804             self._cross_data[testbed_guid][cross_testbed_guid] = set()
805         self._cross_data[testbed_guid][cross_testbed_guid].add(cross_guid)
806
807     def _get_cross_data(self, testbed_guid):
808         cross_data = dict()
809         if not testbed_guid in self._cross_data:
810             return cross_data
811         for cross_testbed_guid, guid_list in \
812                 self._cross_data[testbed_guid].iteritems():
813             cross_data[cross_testbed_guid] = dict()
814             cross_testbed = self._testbeds[cross_testbed_guid]
815             for cross_guid in guid_list:
816                 elem_cross_data = dict(
817                     _guid = cross_guid,
818                     _testbed_guid = cross_testbed_guid,
819                     _testbed_id = cross_testbed.testbed_id,
820                     _testbed_version = cross_testbed.testbed_version)
821                 cross_data[cross_testbed_guid][cross_guid] = elem_cross_data
822                 attributes_list = cross_testbed.get_attribute_list(cross_guid)
823                 for attr_name in attributes_list:
824                     attr_value = cross_testbed.get(cross_guid, attr_name)
825                     elem_cross_data[attr_name] = attr_value
826         return cross_data
827