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