Added attribute repository to PlanetLab CCNxDaemon to run the ccnr command after...
[nepi.git] / src / nepi / testbeds / planetlab / metadata.py
1 # -*- coding: utf-8 -*-
2
3 import time
4
5 from constants import TESTBED_ID, TESTBED_VERSION
6 from nepi.core import metadata
7 from nepi.core.metadata import Parallel
8 from nepi.core.attributes import Attribute
9 from nepi.util import tags, validation
10 from nepi.util.constants import ApplicationStatus as AS, \
11         FactoryCategories as FC, \
12         ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \
13         DeploymentConfiguration as DC
14
15 import functools
16 import os
17 import os.path
18 import weakref
19
20 NODE = "Node"
21 NODEIFACE = "NodeInterface"
22 TUNIFACE = "TunInterface"
23 TAPIFACE = "TapInterface"
24 APPLICATION = "Application"
25 CCNXDAEMON = "CCNxDaemon"
26 DEPENDENCY = "Dependency"
27 NEPIDEPENDENCY = "NepiDependency"
28 NS3DEPENDENCY = "NS3Dependency"
29 INTERNET = "Internet"
30 NETPIPE = "NetPipe"
31 TUNFILTER = "TunFilter"
32 CLASSQUEUEFILTER = "ClassQueueFilter"
33 LOGGINGCLASSQUEUEFILTER = "LoggingClassQueueFilter"
34 TOSQUEUEFILTER = "TosQueueFilter"
35 MULTICASTFORWARDER = "MulticastForwarder"
36 MULTICASTANNOUNCER = "MulticastAnnouncer"
37 MULTICASTROUTER = "MulticastRouter"
38
39 TUNFILTERS = (TUNFILTER, CLASSQUEUEFILTER, LOGGINGCLASSQUEUEFILTER, TOSQUEUEFILTER)
40 TAPFILTERS = (TUNFILTER, )
41 ALLFILTERS = (TUNFILTER, CLASSQUEUEFILTER, LOGGINGCLASSQUEUEFILTER, TOSQUEUEFILTER)
42
43 PL_TESTBED_ID = "planetlab"
44
45
46 ### Custom validation functions ###
47 def is_addrlist(attribute, value):
48     if not validation.is_string(attribute, value):
49         return False
50     
51     if not value:
52         # No empty strings
53         return False
54     
55     components = value.split(',')
56     
57     for component in components:
58         if '/' in component:
59             addr, mask = component.split('/',1)
60         else:
61             addr, mask = component, '32'
62         
63         if mask is not None and not (mask and mask.isdigit()):
64             # No empty or nonnumeric masks
65             return False
66         
67         if not validation.is_ip4_address(attribute, addr):
68             # Address part must be ipv4
69             return False
70         
71     return True
72
73 def is_portlist(attribute, value):
74     if not validation.is_string(attribute, value):
75         return False
76     
77     if not value:
78         # No empty strings
79         return False
80     
81     components = value.split(',')
82     
83     for component in components:
84         if '-' in component:
85             pfrom, pto = component.split('-',1)
86         else:
87             pfrom = pto = component
88         
89         if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit():
90             # No empty or nonnumeric ports
91             return False
92         
93     return True
94
95
96 ### Connection functions ####
97
98 def connect_node_iface_node(testbed_instance, node_guid, iface_guid):
99     node = testbed_instance._elements[node_guid]
100     iface = testbed_instance._elements[iface_guid]
101     iface.node = node
102
103 def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid):
104     iface = testbed_instance._elements[iface_guid]
105     iface.has_internet = True
106
107 def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
108     node = testbed_instance._elements[node_guid]
109     iface = testbed_instance._elements[iface_guid]
110     iface.node = node
111     node.required_vsys.update(('fd_tuntap', 'vif_up', 'vif_down'))
112     node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc'))
113
114 def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
115     iface = testbed_instance._elements[iface_guid]
116     peer_iface = testbed_instance._elements[peer_iface_guid]
117     iface.peer_iface = peer_iface
118     peer_iface.peer_iface = iface
119     iface.peer_proto = \
120     iface.tun_proto = \
121     peer_iface.peer_proto = \
122     peer_iface.tun_proto = proto
123     iface.tun_key = peer_iface.tun_key
124
125 def connect_tun_iface_filter(testbed_instance, iface_guid, filter_guid):
126     iface = testbed_instance._elements[iface_guid]
127     filt = testbed_instance._elements[filter_guid]
128     traces = testbed_instance._get_traces(filter_guid)
129     if 'dropped_stats' in traces: 
130         args = filt.args if filt.args else ""
131         filt.args = ','.join(filt.args.split(',') + ["logdropped=true",])
132     iface.filter_module = filt
133     filt.iface_guid = iface_guid
134     filt.iface = weakref.ref(iface)
135
136     if filt.peer_guid:
137         connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
138
139 def connect_filter_peer(proto, testbed_instance, filter_guid, peer_guid):
140     peer = testbed_instance._elements[peer_guid]
141     filt = testbed_instance._elements[filter_guid]
142     filt.peer_proto = proto
143     filt.peer_guid = peer_guid
144     if filt.iface_guid:
145         connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
146
147 def connect_filter_filter(proto, testbed_instance, filter_guid, peer_guid):
148     peer = testbed_instance._elements[peer_guid]
149     filt = testbed_instance._elements[filter_guid]
150     filt.peer_proto = proto
151     peer.peer_proto = proto
152     if filt.iface_guid:
153         peer.peer_guid = filt.iface_guid
154     if peer.iface_guid:
155         filt.peer_guid = peer.iface_guid
156     if filt.iface_guid and filt.peer_guid:
157         connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
158
159 def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data):
160     iface = testbed_instance._elements[iface_guid]
161     iface.peer_iface = None
162     iface.peer_addr = peer_iface_data.get("tun_addr")
163     iface.peer_proto = peer_iface_data.get("tun_proto") or proto
164     iface.peer_port = peer_iface_data.get("tun_port")
165     iface.peer_cipher = peer_iface_data.get("tun_cipher")
166     iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
167     iface.tun_proto = proto
168     
169     preconfigure_tuniface(testbed_instance, iface_guid)
170
171 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
172     # refresh (refreshable) attributes for second-phase
173     iface = testbed_instance._elements[iface_guid]
174     iface.peer_addr = peer_iface_data.get("tun_addr")
175     iface.peer_proto = peer_iface_data.get("tun_proto") or proto
176     iface.peer_port = peer_iface_data.get("tun_port")
177     iface.peer_cipher = peer_iface_data.get("tun_cipher")
178     
179     postconfigure_tuniface(testbed_instance, iface_guid)
180
181 def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
182     crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
183     crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
184
185 def crossconnect_filter_peer_init(proto, testbed_instance, filter_guid, peer_data):
186     filt = testbed_instance._elements[filter_guid]
187     filt.peer_proto = proto
188     crossconnect_tun_iface_peer_init(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data)
189
190 def crossconnect_filter_peer_compl(proto, testbed_instance, filter_guid, peer_data):
191     filt = testbed_instance._elements[filter_guid]
192     filt.peer_proto = proto
193     crossconnect_tun_iface_peer_compl(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data)
194
195 def crossconnect_filter_peer_both(proto, testbed_instance, filter_guid, peer_data):
196     crossconnect_filter_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
197     crossconnect_filter_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
198
199 def connect_dep(testbed_instance, node_guid, app_guid, node=None, app=None):
200     node = node or testbed_instance._elements[node_guid]
201     app = app or testbed_instance._elements[app_guid]
202     app.node = node
203     
204     if app.depends:
205         node.required_packages.update(set(
206             app.depends.split() ))
207    
208     if app.add_to_path:
209         if app.home_path and app.home_path not in node.pythonpath:
210             node.pythonpath.append(app.home_path)
211     
212     if app.env:
213         for envkey, envval in app.env.iteritems():
214             envval = app._replace_paths(envval)
215             node.env[envkey].append(envval)
216     
217     if app.rpmFusion:
218         node.rpmFusion = True
219
220 def connect_forwarder(testbed_instance, node_guid, fwd_guid):
221     node = testbed_instance._elements[node_guid]
222     fwd = testbed_instance._elements[fwd_guid]
223     node.multicast_forwarder = fwd
224     
225     if fwd.router:
226         connect_dep(testbed_instance, node_guid, None, app=fwd.router)
227
228     connect_dep(testbed_instance, node_guid, fwd_guid)
229
230 def connect_router(testbed_instance, fwd_guid, router_guid):
231     fwd = testbed_instance._elements[fwd_guid]
232     router = testbed_instance._elements[router_guid]
233     fwd.router = router
234     
235     if fwd.node:
236         connect_dep(testbed_instance, None, router_guid, node=fwd.node)
237
238 def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid):
239     node = testbed_instance._elements[node_guid]
240     netpipe = testbed_instance._elements[netpipe_guid]
241     netpipe.node = node
242     node.required_vsys.add('ipfw-be')
243     node.required_packages.add('ipfwslice')
244     
245
246 ### Creation functions ###
247
248 def create_node(testbed_instance, guid):
249     parameters = testbed_instance._get_parameters(guid)
250     
251     # create element with basic attributes
252     element = testbed_instance._make_node(parameters)
253     
254     # add constraint on number of (real) interfaces
255     # by counting connected devices
256     dev_guids = testbed_instance.get_connected(guid, "devs", "node")
257     num_open_ifaces = sum( # count True values
258         NODEIFACE == testbed_instance._get_factory_id(guid)
259         for guid in dev_guids )
260     element.min_num_external_ifaces = num_open_ifaces
261     
262     # require vroute vsys if we have routes to set up
263     routes = testbed_instance._add_route.get(guid)
264     if routes:
265         vsys = element.routing_method(routes,
266             testbed_instance.vsys_vnet)
267         element.required_vsys.add(vsys)
268     
269     testbed_instance.elements[guid] = element
270
271 def create_nodeiface(testbed_instance, guid):
272     parameters = testbed_instance._get_parameters(guid)
273     element = testbed_instance._make_node_iface(parameters)
274     testbed_instance.elements[guid] = element
275
276 def create_tuniface(testbed_instance, guid):
277     parameters = testbed_instance._get_parameters(guid)
278     element = testbed_instance._make_tun_iface(parameters)
279     
280     # Set custom addresses, if there are any already
281     # Setting this early helps set up P2P links
282     if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
283         addresses = testbed_instance._add_address[guid]
284         for address in addresses:
285             (address, netprefix, broadcast) = address
286             element.add_address(address, netprefix, broadcast)
287     
288     testbed_instance.elements[guid] = element
289
290 def create_tapiface(testbed_instance, guid):
291     parameters = testbed_instance._get_parameters(guid)
292     element = testbed_instance._make_tap_iface(parameters)
293     
294     # Set custom addresses, if there are any already
295     # Setting this early helps set up P2P links
296     if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
297         addresses = testbed_instance._add_address[guid]
298         for address in addresses:
299             (address, netprefix, broadcast) = address
300             element.add_address(address, netprefix, broadcast)
301     
302     testbed_instance.elements[guid] = element
303
304 def create_tunfilter(testbed_instance, guid):
305     parameters = testbed_instance._get_parameters(guid)
306     element = testbed_instance._make_tun_filter(parameters)
307     testbed_instance.elements[guid] = element
308
309 def create_classqueuefilter(testbed_instance, guid):
310     parameters = testbed_instance._get_parameters(guid)
311     element = testbed_instance._make_class_queue_filter(parameters)
312     testbed_instance.elements[guid] = element
313
314 def create_loggingclassqueuefilter(testbed_instance, guid):
315     parameters = testbed_instance._get_parameters(guid)
316     element = testbed_instance._make_logging_class_queue_filter(parameters)
317     testbed_instance.elements[guid] = element
318
319 def create_tosqueuefilter(testbed_instance, guid):
320     parameters = testbed_instance._get_parameters(guid)
321     element = testbed_instance._make_tos_queue_filter(parameters)
322     testbed_instance.elements[guid] = element
323
324 def create_application(testbed_instance, guid):
325     parameters = testbed_instance._get_parameters(guid)
326     element = testbed_instance._make_application(parameters)
327     
328     # Just inject configuration stuff
329     element.home_path = "nepi-app-%s" % (guid,)
330     
331     testbed_instance.elements[guid] = element
332
333 def create_ccnxdaemon(testbed_instance, guid):
334     parameters = testbed_instance._get_parameters(guid)
335     element = testbed_instance._make_application(parameters,
336             clazz  = testbed_instance._app.CCNxDaemon )
337     
338     # Just inject configuration stuff
339     element.home_path = "nepi-ccnd-%s" % (guid,)
340     
341     testbed_instance.elements[guid] = element
342
343 def create_dependency(testbed_instance, guid):
344     parameters = testbed_instance._get_parameters(guid)
345     element = testbed_instance._make_dependency(parameters)
346     
347     # Just inject configuration stuff
348     element.home_path = "nepi-dep-%s" % (guid,)
349     
350     testbed_instance.elements[guid] = element
351
352 def create_nepi_dependency(testbed_instance, guid):
353     parameters = testbed_instance._get_parameters(guid)
354     element = testbed_instance._make_nepi_dependency(parameters)
355     
356     # Just inject configuration stuff
357     element.home_path = "nepi-nepi-%s" % (guid,)
358     
359     testbed_instance.elements[guid] = element
360
361 def create_ns3_dependency(testbed_instance, guid):
362     parameters = testbed_instance._get_parameters(guid)
363     element = testbed_instance._make_ns3_dependency(parameters)
364     
365     # Just inject configuration stuff
366     element.home_path = "nepi-ns3-%s" % (guid,)
367     
368     testbed_instance.elements[guid] = element
369
370 def create_multicast_forwarder(testbed_instance, guid):
371     parameters = testbed_instance._get_parameters(guid)
372     element = testbed_instance._make_multicast_forwarder(parameters)
373     
374     # Just inject configuration stuff
375     element.home_path = "nepi-mcfwd-%s" % (guid,)
376     
377     testbed_instance.elements[guid] = element
378
379 def create_multicast_announcer(testbed_instance, guid):
380     parameters = testbed_instance._get_parameters(guid)
381     element = testbed_instance._make_multicast_announcer(parameters)
382     
383     # Just inject configuration stuff
384     element.home_path = "nepi-mcann-%s" % (guid,)
385     
386     testbed_instance.elements[guid] = element
387
388 def create_multicast_router(testbed_instance, guid):
389     parameters = testbed_instance._get_parameters(guid)
390     element = testbed_instance._make_multicast_router(parameters)
391     
392     # Just inject configuration stuff
393     element.home_path = "nepi-mcrt-%s" % (guid,)
394     
395     testbed_instance.elements[guid] = element
396
397 def create_internet(testbed_instance, guid):
398     parameters = testbed_instance._get_parameters(guid)
399     element = testbed_instance._make_internet(parameters)
400     testbed_instance.elements[guid] = element
401
402 def create_netpipe(testbed_instance, guid):
403     parameters = testbed_instance._get_parameters(guid)
404     element = testbed_instance._make_netpipe(parameters)
405     testbed_instance.elements[guid] = element
406
407 ### Start/Stop functions ###
408
409 def prestart_ccnxdaemon(testbed_instance, guid):
410     # ccnx daemon needs to start before the rest of the
411     # ccn applications
412     start_application(testbed_instance, guid)
413
414 def stop_ccndaemon(testbed_instance, guid):
415     app = testbed_instance.elements[guid]
416     app.kill()
417
418 def start_application(testbed_instance, guid):
419     parameters = testbed_instance._get_parameters(guid)
420     traces = testbed_instance._get_traces(guid)
421     app = testbed_instance.elements[guid]
422     
423     app.stdout = "stdout" in traces
424     app.stderr = "stderr" in traces
425     app.buildlog = "buildlog" in traces
426     app.outout = "output" in traces
427     
428     app.start()
429
430 def stop_application(testbed_instance, guid):
431     app = testbed_instance.elements[guid]
432     app.kill()
433
434 ### Status functions ###
435
436 def status_application(testbed_instance, guid):
437     if guid not in testbed_instance.elements.keys():
438         return AS.STATUS_NOT_STARTED
439     
440     app = testbed_instance.elements[guid]
441     return app.status()
442
443 def status_dependency(testbed_instance, guid):
444     if guid not in testbed_instance.elements.keys():
445         return AS.STATUS_NOT_STARTED
446     
447     dep = testbed_instance.elements[guid]
448     if dep.deployed():
449         return AS.STATUS_FINISHED
450     return AS.STATUS_RUNNING
451
452 ### Configure functions ###
453
454 def configure_nodeiface(testbed_instance, guid):
455     element = testbed_instance._elements[guid]
456     
457     # Cannot explicitly configure addresses
458     if guid in testbed_instance._add_address:
459         raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
460     
461     # Get siblings
462     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
463     dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
464     siblings = [ self._element[dev_guid] 
465                  for dev_guid in dev_guids
466                  if dev_guid != guid ]
467     
468     # Fetch address from PLC api
469     element.pick_iface(siblings)
470     
471     # Do some validations
472     element.validate()
473
474 def preconfigure_tuniface(testbed_instance, guid):
475     element = testbed_instance._elements[guid]
476     
477     # Set custom addresses if any, and if not set already
478     if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
479         addresses = testbed_instance._add_address[guid]
480         for address in addresses:
481             (address, netprefix, broadcast) = address
482             element.add_address(address, netprefix, broadcast)
483     
484     # Link to external interface, if any
485     for iface in testbed_instance._elements.itervalues():
486         if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
487             element.external_iface = iface
488             break
489
490     # Set standard TUN attributes
491     if (not element.tun_addr or not element.tun_port) and element.external_iface:
492         element.tun_addr = element.external_iface.address
493         element.tun_port = testbed_instance.tapPortBase + int(guid)
494
495     # Set enabled traces
496     traces = testbed_instance._get_traces(guid)
497     for capmode in ('pcap', 'packets'):
498         if capmode in traces:
499             element.capture = capmode
500             break
501     else:
502         element.capture = False
503     
504     # Do some validations
505     element.validate()
506     
507     # First-phase setup
508     element.prepare('tun-%s' % (guid,))
509
510 def postconfigure_tuniface(testbed_instance, guid):
511     element = testbed_instance._elements[guid]
512     
513     # Second-phase setup
514     element.launch()
515     
516 def prestart_tuniface(testbed_instance, guid):
517     element = testbed_instance._elements[guid]
518     
519     # Second-phase setup
520     element.wait()
521
522 def configure_node(testbed_instance, guid):
523     node = testbed_instance._elements[guid]
524     
525     # Just inject configuration stuff
526     node.home_path = "nepi-node-%s" % (guid,)
527     node.ident_path = testbed_instance.sliceSSHKey
528     node.slicename = testbed_instance.slicename
529     
530     # Do some validations
531     node.validate()
532     
533     # this will be done in parallel in all nodes
534     # this call only spawns the process
535     node.install_dependencies()
536
537 def configure_node_routes(testbed_instance, guid):
538     node = testbed_instance._elements[guid]
539     routes = testbed_instance._add_route.get(guid)
540     
541     if routes:
542         devs = [ dev
543             for dev_guid in testbed_instance.get_connected(guid, "devs", "node")
544             for dev in ( testbed_instance._elements.get(dev_guid) ,)
545             if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ]
546     
547         vsys = testbed_instance.vsys_vnet
548         
549         node.configure_routes(routes, devs, vsys)
550
551 def configure_application(testbed_instance, guid):
552     app = testbed_instance._elements[guid]
553     
554     # Do some validations
555     app.validate()
556     
557     # Wait for dependencies
558     app.node.wait_dependencies()
559     
560     # Install stuff
561     app.async_setup()
562
563 def configure_dependency(testbed_instance, guid):
564     dep = testbed_instance._elements[guid]
565     
566     # Do some validations
567     dep.validate()
568     
569     # Wait for dependencies
570     dep.node.wait_dependencies()
571     
572     # Install stuff
573     dep.async_setup()
574
575 def configure_announcer(testbed_instance, guid):
576     # Link ifaces
577     fwd = testbed_instance._elements[guid]
578     fwd.ifaces = [ dev
579         for node_guid in testbed_instance.get_connected(guid, "node", "apps")
580         for dev_guid in testbed_instance.get_connected(node_guid, "devs", "node")
581         for dev in ( testbed_instance._elements.get(dev_guid) ,)
582         if dev and isinstance(dev, testbed_instance._interfaces.TunIface)
583             and dev.multicast ]
584     
585     # Install stuff
586     configure_dependency(testbed_instance, guid)
587
588 def configure_forwarder(testbed_instance, guid):
589     configure_announcer(testbed_instance, guid)
590     
591     # Link ifaces to forwarder
592     fwd = testbed_instance._elements[guid]
593     for iface in fwd.ifaces:
594         iface.multicast_forwarder = '/var/run/mcastfwd'
595
596 def configure_router(testbed_instance, guid):
597     # Link ifaces
598     rt = testbed_instance._elements[guid]
599     rt.nonifaces = [ dev
600         for fwd_guid in testbed_instance.get_connected(guid, "fwd", "router")
601         for node_guid in testbed_instance.get_connected(fwd_guid, "node", "apps")
602         for dev_guid in testbed_instance.get_connected(node_guid, "devs", "node")
603         for dev in ( testbed_instance._elements.get(dev_guid) ,)
604         if dev and isinstance(dev, testbed_instance._interfaces.TunIface)
605             and not dev.multicast ]
606     
607     # Install stuff
608     configure_dependency(testbed_instance, guid)
609
610 def configure_netpipe(testbed_instance, guid):
611     netpipe = testbed_instance._elements[guid]
612     
613     # Do some validations
614     netpipe.validate()
615     
616     # Wait for dependencies
617     netpipe.node.wait_dependencies()
618     
619     # Install rules
620     netpipe.configure()
621
622 ### Factory information ###
623
624 connector_types = dict({
625     "apps": dict({
626                 "help": "Connector from node to applications", 
627                 "name": "apps",
628                 "max": -1, 
629                 "min": 0
630             }),
631     "devs": dict({
632                 "help": "Connector from node to network interfaces", 
633                 "name": "devs",
634                 "max": -1, 
635                 "min": 0
636             }),
637     "deps": dict({
638                 "help": "Connector from node to application dependencies "
639                         "(packages and applications that need to be installed)", 
640                 "name": "deps",
641                 "max": -1, 
642                 "min": 0
643             }),
644     "inet": dict({
645                 "help": "Connector from network interfaces to the internet", 
646                 "name": "inet",
647                 "max": 1, 
648                 "min": 1
649             }),
650     "node": dict({
651                 "help": "Connector to a Node", 
652                 "name": "node",
653                 "max": 1, 
654                 "min": 1
655             }),
656     "router": dict({
657                 "help": "Connector to a routing daemon", 
658                 "name": "router",
659                 "max": 1, 
660                 "min": 0
661             }),
662     "fwd": dict({
663                 "help": "Forwarder this routing daemon communicates with", 
664                 "name": "fwd",
665                 "max": 1, 
666                 "min": 1
667             }),
668     "pipes": dict({
669                 "help": "Connector to a NetPipe", 
670                 "name": "pipes",
671                 "max": 2, 
672                 "min": 0
673             }),
674     
675     "tcp": dict({
676                 "help": "ip-ip tunneling over TCP link", 
677                 "name": "tcp",
678                 "max": 1, 
679                 "min": 0
680             }),
681     "udp": dict({
682                 "help": "ip-ip tunneling over UDP datagrams", 
683                 "name": "udp",
684                 "max": 1, 
685                 "min": 0
686             }),
687     "gre": dict({
688                 "help": "IP or Ethernet tunneling using the GRE protocol", 
689                 "name": "gre",
690                 "max": 1, 
691                 "min": 0
692             }),
693     "fd->": dict({
694                 "help": "TUN device file descriptor provider", 
695                 "name": "fd->",
696                 "max": 1, 
697                 "min": 0
698             }),
699     "->fd": dict({
700                 "help": "TUN device file descriptor slot", 
701                 "name": "->fd",
702                 "max": 1, 
703                 "min": 0
704             }),
705    })
706
707 connections = [
708     dict({
709         "from": (TESTBED_ID, NODE, "devs"),
710         "to":   (TESTBED_ID, NODEIFACE, "node"),
711         "init_code": connect_node_iface_node,
712         "can_cross": False
713     }),
714     dict({
715         "from": (TESTBED_ID, NODE, "devs"),
716         "to":   (TESTBED_ID, TUNIFACE, "node"),
717         "init_code": connect_tun_iface_node,
718         "can_cross": False
719     }),
720     dict({
721         "from": (TESTBED_ID, NODE, "devs"),
722         "to":   (TESTBED_ID, TAPIFACE, "node"),
723         "init_code": connect_tun_iface_node,
724         "can_cross": False
725     }),
726     dict({
727         "from": (TESTBED_ID, NODEIFACE, "inet"),
728         "to":   (TESTBED_ID, INTERNET, "devs"),
729         "init_code": connect_node_iface_inet,
730         "can_cross": False
731     }),
732     dict({
733         "from": (TESTBED_ID, NODE, "apps"),
734         "to":   (TESTBED_ID, (APPLICATION, CCNXDAEMON, MULTICASTANNOUNCER), "node"),
735         "init_code": connect_dep,
736         "can_cross": False
737     }),
738     dict({
739         "from": (TESTBED_ID, NODE, "deps"),
740         "to":   (TESTBED_ID, (DEPENDENCY, NEPIDEPENDENCY, NS3DEPENDENCY), "node"),
741         "init_code": connect_dep,
742         "can_cross": False
743     }),
744     dict({
745         "from": (TESTBED_ID, NODE, "pipes"),
746         "to":   (TESTBED_ID, NETPIPE, "node"),
747         "init_code": connect_node_netpipe,
748         "can_cross": False
749     }),
750     dict({
751         "from": (TESTBED_ID, NODE, "apps"),
752         "to":   (TESTBED_ID, MULTICASTFORWARDER, "node"),
753         "init_code": connect_forwarder,
754         "can_cross": False
755     }),
756     dict({
757         "from": (TESTBED_ID, MULTICASTFORWARDER, "router"),
758         "to":   (TESTBED_ID, MULTICASTROUTER, "fwd"),
759         "init_code": connect_router,
760         "can_cross": False
761     }),
762     dict({
763         "from": (TESTBED_ID, TUNIFACE, "tcp"),
764         "to":   (TESTBED_ID, TUNIFACE, "tcp"),
765         "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
766         "can_cross": False
767     }),
768     dict({
769         "from": (TESTBED_ID, TUNIFACE, "udp"),
770         "to":   (TESTBED_ID, TUNIFACE, "udp"),
771         "init_code": functools.partial(connect_tun_iface_peer,"udp"),
772         "can_cross": False
773     }),
774     dict({
775         "from": (TESTBED_ID, TUNIFACE, "gre"),
776         "to":   (TESTBED_ID, TUNIFACE, "gre"),
777         "init_code": functools.partial(connect_tun_iface_peer,"gre"),
778         "can_cross": False
779     }),
780     dict({
781         "from": (TESTBED_ID, TUNIFACE, "fd->"),
782         "to":   (TESTBED_ID, TUNFILTERS, "->fd"),
783         "init_code": connect_tun_iface_filter,
784         "can_cross": False
785     }),
786     dict({
787         "from": (TESTBED_ID, TUNFILTERS, "tcp"),
788         "to":   (TESTBED_ID, TUNIFACE, "tcp"),
789         "init_code": functools.partial(connect_filter_peer,"tcp"),
790         "can_cross": False
791     }),
792     dict({
793         "from": (TESTBED_ID, TUNFILTERS, "udp"),
794         "to":   (TESTBED_ID, TUNIFACE, "udp"),
795         "init_code": functools.partial(connect_filter_peer,"udp"),
796         "can_cross": False
797     }),
798     dict({
799         "from": (TESTBED_ID, TAPIFACE, "tcp"),
800         "to":   (TESTBED_ID, TAPIFACE, "tcp"),
801         "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
802         "can_cross": False
803     }),
804     dict({
805         "from": (TESTBED_ID, TAPIFACE, "udp"),
806         "to":   (TESTBED_ID, TAPIFACE, "udp"),
807         "init_code": functools.partial(connect_tun_iface_peer,"udp"),
808         "can_cross": False
809     }),
810     dict({
811         "from": (TESTBED_ID, TAPIFACE, "gre"),
812         "to":   (TESTBED_ID, TAPIFACE, "gre"),
813         "init_code": functools.partial(connect_tun_iface_peer,"gre"),
814         "can_cross": False
815     }),
816     dict({
817         "from": (TESTBED_ID, TAPIFACE, "fd->"),
818         "to":   (TESTBED_ID, TAPFILTERS, "->fd"),
819         "init_code": connect_tun_iface_filter,
820         "can_cross": False
821     }),
822     dict({
823         "from": (TESTBED_ID, TAPFILTERS, "tcp"),
824         "to":   (TESTBED_ID, TAPIFACE, "tcp"),
825         "init_code": functools.partial(connect_filter_peer,"tcp"),
826         "can_cross": False
827     }),
828     dict({
829         "from": (TESTBED_ID, TAPFILTERS, "udp"),
830         "to":   (TESTBED_ID, TAPIFACE, "udp"),
831         "init_code": functools.partial(connect_filter_peer,"udp"),
832         "can_cross": False
833     }),
834     dict({
835         "from": (TESTBED_ID, TUNFILTERS, "tcp"),
836         "to":   (TESTBED_ID, TUNFILTERS, "tcp"),
837         "init_code": functools.partial(connect_filter_filter,"tcp"),
838         "can_cross": False
839     }),
840     dict({
841         "from": (TESTBED_ID, TUNFILTERS, "udp"),
842         "to":   (TESTBED_ID, TUNFILTERS, "udp"),
843         "init_code": functools.partial(connect_filter_filter,"udp"),
844         "can_cross": False
845     }),
846     dict({
847         "from": (TESTBED_ID, TAPFILTERS, "tcp"),
848         "to":   (TESTBED_ID, TAPFILTERS, "tcp"),
849         "init_code": functools.partial(connect_filter_filter,"tcp"),
850         "can_cross": False
851     }),
852     dict({
853         "from": (TESTBED_ID, TAPFILTERS, "udp"),
854         "to":   (TESTBED_ID, TAPFILTERS, "udp"),
855         "init_code": functools.partial(connect_filter_filter,"udp"),
856         "can_cross": False
857     }),
858     dict({
859         "from": (TESTBED_ID, TUNIFACE, "tcp"),
860         "to":   (None, None, "tcp"),
861         "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
862         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
863         "can_cross": True
864     }),
865     dict({
866         "from": (TESTBED_ID, TUNIFACE, "udp"),
867         "to":   (None, None, "udp"),
868         "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
869         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
870         "can_cross": True
871     }),
872     dict({
873         "from": (TESTBED_ID, TUNIFACE, "fd->"),
874         "to":   (None, None, "->fd"),
875         "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
876         "can_cross": True
877     }),
878     dict({
879         "from": (TESTBED_ID, TUNIFACE, "gre"),
880         "to":   (None, None, "gre"),
881         "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
882         "can_cross": True
883     }),
884     dict({
885         "from": (TESTBED_ID, TAPIFACE, "tcp"),
886         "to":   (None, None, "tcp"),
887         "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
888         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
889         "can_cross": True
890     }),
891     dict({
892         "from": (TESTBED_ID, TAPIFACE, "udp"),
893         "to":   (None, None, "udp"),
894         "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
895         "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
896         "can_cross": True
897     }),
898     dict({
899         "from": (TESTBED_ID, TAPIFACE, "fd->"),
900         "to":   (None, None, "->fd"),
901         "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
902         "can_cross": True
903     }),
904     # EGRE is an extension of PlanetLab, so we can't connect externally
905     # if the other testbed isn't another PlanetLab
906     dict({
907         "from": (TESTBED_ID, TAPIFACE, "gre"),
908         "to":   (TESTBED_ID, None, "gre"),
909         "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
910         "can_cross": True
911     }),
912     dict({
913         "from": (TESTBED_ID, ALLFILTERS, "tcp"),
914         "to":   (None, None, "tcp"),
915         "init_code": functools.partial(crossconnect_filter_peer_init,"tcp"),
916         "compl_code": functools.partial(crossconnect_filter_peer_compl,"tcp"),
917         "can_cross": True
918     }),
919     dict({
920         "from": (TESTBED_ID, ALLFILTERS, "udp"),
921         "to":   (None, None, "udp"),
922         "init_code": functools.partial(crossconnect_filter_peer_init,"udp"),
923         "compl_code": functools.partial(crossconnect_filter_peer_compl,"udp"),
924         "can_cross": True
925     }),
926 ]
927
928 attributes = dict({
929     "forward_X11": dict({      
930                 "name": "forward_X11",
931                 "help": "Forward x11 from main namespace to the node",
932                 "type": Attribute.BOOL, 
933                 "value": False,
934                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
935                 "validation_function": validation.is_bool,
936             }),
937     "hostname": dict({      
938                 "name": "hostname",
939                 "help": "Constrain hostname during resource discovery. May use wildcards.",
940                 "type": Attribute.STRING, 
941                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
942                 "validation_function": validation.is_string,
943             }),
944     "city": dict({      
945                 "name": "city",
946                 "help": "Constrain location (city) during resource discovery. May use wildcards.",
947                 "type": Attribute.STRING, 
948                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
949                 "validation_function": validation.is_string,
950             }),
951     "country": dict({      
952                 "name": "hostname",
953                 "help": "Constrain location (country) during resource discovery. May use wildcards.",
954                 "type": Attribute.STRING, 
955                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
956                 "validation_function": validation.is_string,
957             }),
958     "region": dict({      
959                 "name": "hostname",
960                 "help": "Constrain location (region) during resource discovery. May use wildcards.",
961                 "type": Attribute.STRING, 
962                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
963                 "validation_function": validation.is_string,
964             }),
965     "architecture": dict({      
966                 "name": "architecture",
967                 "help": "Constrain architexture during resource discovery.",
968                 "type": Attribute.ENUM, 
969                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
970                 "allowed": ["x86_64",
971                             "i386"],
972                 "validation_function": validation.is_enum,
973             }),
974     "operating_system": dict({      
975                 "name": "operatingSystem",
976                 "help": "Constrain operating system during resource discovery.",
977                 "type": Attribute.ENUM, 
978                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
979                 "allowed": ["f8",
980                             "f12",
981                             "f14",
982                             "centos",
983                             "other"],
984                 "validation_function": validation.is_enum,
985             }),
986     "site": dict({      
987                 "name": "site",
988                 "help": "Constrain the PlanetLab site this node should reside on.",
989                 "type": Attribute.ENUM, 
990                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
991                 "allowed": ["PLE",
992                             "PLC",
993                             "PLJ"],
994                 "validation_function": validation.is_enum,
995             }),
996     "min_reliability": dict({
997                 "name": "minReliability",
998                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
999                 "type": Attribute.DOUBLE,
1000                 "range": (0,100),
1001                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1002                 "validation_function": validation.is_number,
1003             }),
1004     "max_reliability": dict({
1005                 "name": "maxReliability",
1006                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
1007                 "type": Attribute.DOUBLE,
1008                 "range": (0,100),
1009                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1010                 "validation_function": validation.is_number,
1011             }),
1012     "min_bandwidth": dict({
1013                 "name": "minBandwidth",
1014                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
1015                 "type": Attribute.DOUBLE,
1016                 "range": (0,2**31),
1017                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1018                 "validation_function": validation.is_number,
1019             }),
1020     "max_bandwidth": dict({
1021                 "name": "maxBandwidth",
1022                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
1023                 "type": Attribute.DOUBLE,
1024                 "range": (0,2**31),
1025                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1026                 "validation_function": validation.is_number,
1027             }),
1028     "min_load": dict({
1029                 "name": "minLoad",
1030                 "help": "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
1031                 "type": Attribute.DOUBLE,
1032                 "range": (0,2**31),
1033                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1034                 "validation_function": validation.is_number,
1035             }),
1036     "max_load": dict({
1037                 "name": "maxLoad",
1038                 "help": "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
1039                 "type": Attribute.DOUBLE,
1040                 "range": (0,2**31),
1041                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1042                 "validation_function": validation.is_number,
1043             }),
1044     "min_cpu": dict({
1045                 "name": "minCpu",
1046                 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
1047                 "type": Attribute.DOUBLE,
1048                 "range": (0,100),
1049                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1050                 "validation_function": validation.is_number,
1051             }),
1052     "max_cpu": dict({
1053                 "name": "maxCpu",
1054                 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
1055                 "type": Attribute.DOUBLE,
1056                 "range": (0,100),
1057                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1058                 "validation_function": validation.is_number,
1059             }),
1060      "timeframe": dict({
1061                 "name": "timeframe",
1062                 "help": "Past time period in which to check information about the node. Values are year,month, week, latest", 
1063                 "type": Attribute.ENUM, 
1064                 "value": "week",
1065                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1066                 "allowed": ["latest",
1067                             "week",
1068                             "month",
1069                             "year"],
1070                 "validation_function": validation.is_enum,
1071             }),
1072            
1073     "up": dict({
1074                 "name": "up",
1075                 "help": "Link up",
1076                 "type": Attribute.BOOL,
1077                 "value": True,
1078                 "flags": Attribute.NoDefaultValue, 
1079                 "validation_function": validation.is_bool
1080             }),
1081     "primary": dict({
1082                 "name": "primary",
1083                 "help": "This is the primary interface for the attached node",
1084                 "type": Attribute.BOOL,
1085                 "value": True,
1086                 "validation_function": validation.is_bool
1087             }),
1088     "if_name": dict({
1089                 "name": "if_name",
1090                 "help": "Device name",
1091                 "type": Attribute.STRING,
1092                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1093                 "validation_function": validation.is_string
1094             }),
1095     "mtu":  dict({
1096                 "name": "mtu", 
1097                 "help": "Maximum transmition unit for device",
1098                 "type": Attribute.INTEGER,
1099                 "range": (0,1500),
1100                 "validation_function": validation.is_integer_range(0,1500)
1101             }),
1102     "mask":  dict({
1103                 "name": "mask", 
1104                 "help": "Network mask for the device (eg: 24 for /24 network)",
1105                 "type": Attribute.INTEGER,
1106                 "validation_function": validation.is_integer_range(8,24)
1107             }),
1108     "snat":  dict({
1109                 "name": "snat", 
1110                 "help": "Enable SNAT (source NAT to the internet) no this device",
1111                 "type": Attribute.BOOL,
1112                 "value": False,
1113                 "validation_function": validation.is_bool
1114             }),
1115     "multicast":  dict({
1116                 "name": "multicast", 
1117                 "help": "Enable multicast forwarding on this device. "
1118                         "Note that you still need a multicast routing daemon "
1119                         "in the node.",
1120                 "type": Attribute.BOOL,
1121                 "value": False,
1122                 "validation_function": validation.is_bool
1123             }),
1124     "pointopoint":  dict({
1125                 "name": "pointopoint", 
1126                 "help": "If the interface is a P2P link, the remote endpoint's IP "
1127                         "should be set on this attribute.",
1128                 "type": Attribute.STRING,
1129                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1130                 "validation_function": validation.is_string
1131             }),
1132     "bwlimit":  dict({
1133                 "name": "bwlimit", 
1134                 "help": "Emulated transmission speed (in kbytes per second)",
1135                 "type": Attribute.INTEGER,
1136                 "range" : (1,10*2**20),
1137                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1138                 "validation_function": validation.is_integer
1139             }),
1140     "txqueuelen":  dict({
1141                 "name": "txqueuelen", 
1142                 "help": "Transmission queue length (in packets)",
1143                 "type": Attribute.INTEGER,
1144                 "value": 1000,
1145                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1146                 "range" : (1,10000),
1147                 "validation_function": validation.is_integer
1148             }),
1149             
1150     "command": dict({
1151                 "name": "command",
1152                 "help": "Command line string",
1153                 "type": Attribute.STRING,
1154                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1155                 "validation_function": validation.is_string
1156             }),
1157     "ccnroutes": dict({
1158                 "name": "ccnRoutes",
1159                 "help": "Route can be static (e.g. udp ip) or multicast (e.g. udp 224.0.0.204 2869). To separate different route use '|' ",
1160                 "type": Attribute.STRING,
1161                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1162                 "validation_function": validation.is_string
1163             }),
1164      "sudo": dict({
1165                 "name": "sudo",
1166                 "help": "Run with root privileges",
1167                 "type": Attribute.BOOL,
1168                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1169                 "value": False,
1170                 "validation_function": validation.is_bool
1171             }),
1172     "stdin": dict({
1173                 "name": "stdin",
1174                 "help": "Standard input",
1175                 "type": Attribute.STRING,
1176                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1177                 "validation_function": validation.is_string
1178             }),
1179             
1180     "depends": dict({
1181                 "name": "depends",
1182                 "help": "Space-separated list of packages required to run the application",
1183                 "type": Attribute.STRING,
1184                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1185                 "validation_function": validation.is_string
1186             }),
1187     "build-depends": dict({
1188                 "name": "buildDepends",
1189                 "help": "Space-separated list of packages required to build the application",
1190                 "type": Attribute.STRING,
1191                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1192                 "validation_function": validation.is_string
1193             }),
1194     "rpm-fusion": dict({
1195                 "name": "rpmFusion",
1196                 "help": "True if required packages can be found in the RpmFusion repository",
1197                 "type": Attribute.BOOL,
1198                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1199                 "value": False,
1200                 "validation_function": validation.is_bool
1201             }),
1202     "sources": dict({
1203                 "name": "sources",
1204                 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
1205                         "Archives won't be expanded automatically.",
1206                 "type": Attribute.STRING,
1207                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1208                 "validation_function": validation.is_string
1209             }),
1210     "ccnxversion": dict({      
1211                 "name": "ccnxVersion",
1212                 "help": "Version of ccnx source code to install in the node.",
1213                 "type": Attribute.ENUM, 
1214                 "value": "0.6.0",
1215                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1216                 "allowed": ["0.6.0",
1217                             "0.7.1"],
1218                 "validation_function": validation.is_enum,
1219             }),
1220     "repository": dict({      
1221                 "name": "repository",
1222                 "help": "If True the ccnr command will be issued on start, and a repository will be created.",
1223                 "type": Attribute.BOOL, 
1224                 "value": False,
1225                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1226                 "validation_function": validation.is_bool,
1227             }),
1228      "ccnlocalport" : dict({
1229             "name" : "ccnLocalPort", 
1230             "help" : "Local port to bind the ccn daemon. (i.e. CCN_LOCAL_PORT=)",
1231             "type" : Attribute.INTEGER,
1232             "flags" : Attribute.DesignInvisible | \
1233                     Attribute.ExecInvisible | \
1234                     Attribute.ExecImmutable | \
1235                     Attribute.Metadata,
1236             "validation_function" : validation.is_integer,
1237             }),
1238     "build": dict({
1239                 "name": "build",
1240                 "help": "Build commands to execute after deploying the sources. "
1241                         "Sources will be in the ${SOURCES} folder. "
1242                         "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
1243                         "Try to make the commands return with a nonzero exit code on error.\n"
1244                         "Also, do not install any programs here, use the 'install' attribute. This will "
1245                         "help keep the built files constrained to the build folder (which may "
1246                         "not be the home folder), and will result in faster deployment. Also, "
1247                         "make sure to clean up temporary files, to reduce bandwidth usage between "
1248                         "nodes when transferring built packages.",
1249                 "type": Attribute.STRING,
1250                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1251                 "validation_function": validation.is_string
1252             }),
1253     "install": dict({
1254                 "name": "install",
1255                 "help": "Commands to transfer built files to their final destinations. "
1256                         "Sources will be in the initial working folder, and a special "
1257                         "tag ${SOURCES} can be used to reference the experiment's "
1258                         "home folder (where the application commands will run).\n"
1259                         "ALL sources and targets needed for execution must be copied there, "
1260                         "if building has been enabled.\n"
1261                         "That is, 'slave' nodes will not automatically get any source files. "
1262                         "'slave' nodes don't get build dependencies either, so if you need "
1263                         "make and other tools to install, be sure to provide them as "
1264                         "actual dependencies instead.",
1265                 "type": Attribute.STRING,
1266                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1267                 "validation_function": validation.is_string
1268             }),
1269     
1270     "netpipe_mode": dict({      
1271                 "name": "mode",
1272                 "help": "Link mode:\n"
1273                         " * SERVER: applies to incoming connections\n"
1274                         " * CLIENT: applies to outgoing connections\n"
1275                         " * SERVICE: applies to both",
1276                 "type": Attribute.ENUM, 
1277                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1278                 "allowed": ["SERVER",
1279                             "CLIENT",
1280                             "SERVICE"],
1281                 "validation_function": validation.is_enum,
1282             }),
1283     "port_list":  dict({
1284                 "name": "portList", 
1285                 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
1286                 "type": Attribute.STRING,
1287                 "validation_function": is_portlist,
1288             }),
1289     "addr_list":  dict({
1290                 "name": "addrList", 
1291                 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
1292                 "type": Attribute.STRING,
1293                 "validation_function": is_addrlist,
1294             }),
1295     "bw_in":  dict({
1296                 "name": "bwIn", 
1297                 "help": "Inbound bandwidth limit (in Mbit/s)",
1298                 "type": Attribute.DOUBLE,
1299                 "validation_function": validation.is_number,
1300             }),
1301     "bw_out":  dict({
1302                 "name": "bwOut", 
1303                 "help": "Outbound bandwidth limit (in Mbit/s)",
1304                 "type": Attribute.DOUBLE,
1305                 "validation_function": validation.is_number,
1306             }),
1307     "plr_in":  dict({
1308                 "name": "plrIn", 
1309                 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
1310                 "type": Attribute.DOUBLE,
1311                 "validation_function": validation.is_number,
1312             }),
1313     "plr_out":  dict({
1314                 "name": "plrOut", 
1315                 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
1316                 "type": Attribute.DOUBLE,
1317                 "validation_function": validation.is_number,
1318             }),
1319     "delay_in":  dict({
1320                 "name": "delayIn", 
1321                 "help": "Inbound packet delay (in milliseconds)",
1322                 "type": Attribute.INTEGER,
1323                 "range": (0,60000),
1324                 "validation_function": validation.is_integer,
1325             }),
1326     "delay_out":  dict({
1327                 "name": "delayOut", 
1328                 "help": "Outbound packet delay (in milliseconds)",
1329                 "type": Attribute.INTEGER,
1330                 "range": (0,60000),
1331                 "validation_function": validation.is_integer,
1332             }),
1333     "module": dict({
1334                 "name": "module",
1335                 "help": "Path to a .c or .py source for a filter module, or a binary .so",
1336                 "type": Attribute.STRING,
1337                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1338                 "validation_function": validation.is_string
1339             }),
1340     "args": dict({
1341                 "name": "args",
1342                 "help": "Module arguments - comma-separated list of name=value pairs",
1343                 "type": Attribute.STRING,
1344                 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1345                 "validation_function": validation.is_string
1346             }),
1347     "routing_algorithm": dict({      
1348             "name": "algorithm",
1349             "help": "Routing algorithm.",
1350             "value": "dvmrp",
1351             "type": Attribute.ENUM, 
1352             "allowed": ["dvmrp"],
1353             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1354             "validation_function": validation.is_enum,
1355         }),
1356     })
1357
1358 traces = dict({
1359     "stdout": dict({
1360                 "name": "stdout",
1361                 "help": "Standard output stream"
1362               }),
1363     "stderr": dict({
1364                 "name": "stderr",
1365                 "help": "Application standard error",
1366               }),
1367     "buildlog": dict({
1368                 "name": "buildlog",
1369                 "help": "Output of the build process",
1370               }), 
1371     
1372     "netpipe_stats": dict({
1373                 "name": "netpipeStats",
1374                 "help": "Information about rule match counters, packets dropped, etc.",
1375               }),
1376
1377     "packets": dict({
1378                 "name": "packets",
1379                 "help": "Detailled log of all packets going through the interface",
1380               }),
1381     "pcap": dict({
1382                 "name": "pcap",
1383                 "help": "PCAP trace of all packets going through the interface",
1384               }),
1385     "output": dict({
1386                 "name": "output",
1387                 "help": "Extra output trace for applications. When activated this trace can be referenced with wildcard a reference from an Application command line. Ex: command: 'tcpdump -w {#[elemet-label].trace[trace-id].[name|path]#}' ",
1388               }),
1389     "dropped_stats": dict({
1390                 "name": "dropped_stats",
1391                 "help": "Information on dropped packets on a filer or queue associated to a network interface",
1392             }),
1393     "queue_stats_f": dict({
1394                 "name": "queue_stats_f",
1395                 "help": "Detailled, fine-grained information on egress queue state, csv format.",
1396             }),
1397     "queue_stats_b": dict({
1398                 "name": "queue_stats_b",
1399                 "help": "Detailled, fine-grained information on ingress queue state, csv format.",
1400             }),
1401     })
1402
1403 create_order = [ 
1404     INTERNET, NODE, NODEIFACE, CLASSQUEUEFILTER, LOGGINGCLASSQUEUEFILTER, TOSQUEUEFILTER, 
1405     MULTICASTANNOUNCER, MULTICASTFORWARDER, MULTICASTROUTER, 
1406     TUNFILTER, TAPIFACE, TUNIFACE, NETPIPE, 
1407     NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, CCNXDAEMON, APPLICATION ]
1408
1409 configure_order = [ 
1410     INTERNET, Parallel(NODE), 
1411     NODEIFACE, 
1412     Parallel(MULTICASTANNOUNCER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTROUTER), 
1413     Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, 
1414     Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(CCNXDAEMON),
1415     Parallel(APPLICATION)]
1416
1417 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
1418 start_order = [ INTERNET, 
1419     NODEIFACE, 
1420     Parallel(TAPIFACE), Parallel(TUNIFACE), 
1421     Parallel(NODE), NETPIPE, 
1422     Parallel(MULTICASTANNOUNCER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTROUTER), 
1423     Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(CCNXDAEMON),
1424     Parallel(APPLICATION)]
1425
1426 # cleanup order
1427 shutdown_order = [ 
1428     Parallel(APPLICATION), 
1429     Parallel (CCNXDAEMON),
1430     Parallel(MULTICASTROUTER), Parallel(MULTICASTFORWARDER), Parallel(MULTICASTANNOUNCER), 
1431     Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), 
1432     Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), 
1433     NODEIFACE, Parallel(NODE) ]
1434
1435 factories_info = dict({
1436     NODE: dict({
1437             "help": "Virtualized Node (V-Server style)",
1438             "category": FC.CATEGORY_NODES,
1439             "create_function": create_node,
1440             "preconfigure_function": configure_node,
1441             "prestart_function": configure_node_routes,
1442             "box_attributes": [
1443                 "forward_X11",
1444                 "hostname",
1445                 "architecture",
1446                 "operating_system",
1447                 "site",
1448                 "min_reliability",
1449                 "max_reliability",
1450                 "min_bandwidth",
1451                 "max_bandwidth",
1452                 "min_load",
1453                 "max_load",
1454                 "min_cpu",
1455                 "max_cpu",
1456                 "timeframe",
1457                 
1458                 # NEPI-in-NEPI attributes
1459                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
1460             ],
1461             "connector_types": ["devs", "apps", "pipes", "deps"],
1462             "tags": [tags.NODE, tags.ALLOW_ROUTES],
1463        }),
1464     NODEIFACE: dict({
1465             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
1466             "category": FC.CATEGORY_DEVICES,
1467             "create_function": create_nodeiface,
1468             "preconfigure_function": configure_nodeiface,
1469             "box_attributes": [ ],
1470             "connector_types": ["node", "inet"],
1471             "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
1472         }),
1473     TUNIFACE: dict({
1474             "help": "Virtual TUN network interface (layer 3)",
1475             "category": FC.CATEGORY_DEVICES,
1476             "create_function": create_tuniface,
1477             "preconfigure_function": preconfigure_tuniface,
1478             "configure_function": postconfigure_tuniface,
1479             "prestart_function": prestart_tuniface,
1480             "box_attributes": [
1481                 "up", "if_name", "mtu", "snat", "pointopoint", "multicast", "bwlimit",
1482                 "txqueuelen",
1483                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1484             ],
1485             "traces": ["packets", "pcap"],
1486             "connector_types": ["node","udp","tcp","fd->","gre"],
1487             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1488         }),
1489     TAPIFACE: dict({
1490             "help": "Virtual TAP network interface (layer 2)",
1491             "category": FC.CATEGORY_DEVICES,
1492             "create_function": create_tapiface,
1493             "preconfigure_function": preconfigure_tuniface,
1494             "configure_function": postconfigure_tuniface,
1495             "prestart_function": prestart_tuniface,
1496             "box_attributes": [
1497                 "up", "if_name", "mtu", "snat", "pointopoint", "multicast", "bwlimit",
1498                 "txqueuelen",
1499                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1500             ],
1501             "traces": ["packets", "pcap"],
1502             "connector_types": ["node","udp","tcp","fd->","gre"],
1503             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1504         }),
1505     TUNFILTER: dict({
1506             "help": "TUN/TAP stream filter\n\n"
1507                     "If specified, it should be either a .py or .so module. "
1508                     "It will be loaded, and all incoming and outgoing packets "
1509                     "will be routed through it. The filter will not be responsible "
1510                     "for buffering, packet queueing is performed in tun_connect "
1511                     "already, so it should not concern itself with it. It should "
1512                     "not, however, block in one direction if the other is congested.\n"
1513                     "\n"
1514                     "Modules are expected to have the following methods:\n"
1515                     "\tinit(**args)\n"
1516                     "\t\tIf arguments are given, this method will be called with the\n"
1517                     "\t\tgiven arguments (as keyword args in python modules, or a single\n"
1518                     "\taccept_packet(packet, direction):\n"
1519                     "\t\tDecide whether to drop the packet. Direction is 0 for packets "
1520                         "coming from the local side to the remote, and 1 is for packets "
1521                         "coming from the remote side to the local. Return a boolean, "
1522                         "true if the packet is not to be dropped.\n"
1523                     "\tfilter_init():\n"
1524                     "\t\tInitializes a filtering pipe (filter_run). It should "
1525                         "return two file descriptors to use as a bidirectional "
1526                         "pipe: local and remote. 'local' is where packets from the "
1527                         "local side will be written to. After filtering, those packets "
1528                         "should be written to 'remote', where tun_connect will read "
1529                         "from, and it will forward them to the remote peer. "
1530                         "Packets from the remote peer will be written to 'remote', "
1531                         "where the filter is expected to read from, and eventually "
1532                         "forward them to the local side. If the file descriptors are "
1533                         "not nonblocking, they will be set to nonblocking. So it's "
1534                         "better to set them from the start like that.\n"
1535                     "\tfilter_run(local, remote):\n"
1536                     "\t\tIf filter_init is provided, it will be called repeatedly, "
1537                         "in a separate thread until the process is killed. It should "
1538                         "sleep at most for a second.\n"
1539                     "\tfilter_close(local, remote):\n"
1540                     "\t\tCalled then the process is killed, if filter_init was provided. "
1541                         "It should, among other things, close the file descriptors.\n"
1542                     "\n"
1543                     "Python modules are expected to return a tuple in filter_init, "
1544                     "either of file descriptors or file objects, while native ones "
1545                     "will receive two int*.\n"
1546                     "\n"
1547                     "Python modules can additionally contain a custom queue class "
1548                     "that will replace the FIFO used by default. The class should "
1549                     "be named 'queueclass' and contain an interface compatible with "
1550                     "collections.deque. That is, indexing (especiall for q[0]), "
1551                     "bool(q), popleft, appendleft, pop (right), append (right), "
1552                     "len(q) and clear.",
1553             "category": FC.CATEGORY_CHANNELS,
1554             "create_function": create_tunfilter,
1555             "box_attributes": [
1556                 "module", "args",
1557                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1558             ],
1559             "connector_types": ["->fd","udp","tcp"],
1560         }),
1561     CLASSQUEUEFILTER : dict({
1562             "help": "TUN classful queue, uses a separate queue for each user-definable class.\n\n"
1563                     "It takes two arguments, both of which have sensible defaults:\n"
1564                     "\tsize: the base size of each class' queue\n"
1565                     "\tclasses: the class definitions, which follow the following syntax:\n"
1566                     '\t   <CLASSLIST> ::= <CLASS> ":" CLASSLIST\n'
1567                     '\t                |  <CLASS>\n'
1568                     '\t   <CLASS>     ::= <PROTOLIST> "*" <PRIORITYSPEC>\n'
1569                     '\t                |  <DFLTCLASS>\n'
1570                     '\t   <DFLTCLASS> ::= "*" <PRIORITYSPEC>\n'
1571                     '\t   <PROTOLIST> ::= <PROTO> "." <PROTOLIST>\n'
1572                     '\t                |  <PROTO>\n'
1573                     '\t   <PROTO>     ::= <NAME> | <NUMBER>\n'
1574                     '\t   <NAME>      ::= --see http://en.wikipedia.org/wiki/List_of_IP_protocol_numbers --\n'
1575                     '\t                   --only in lowercase, with special characters removed--\n'
1576                     '\t                   --or see below--\n'
1577                     '\t   <NUMBER>    ::= [0-9]+\n'
1578                     '\t   <PRIORITYSPEC> ::= <THOUGHPUT> [ "#" <SIZE> ] [ "p" <PRIORITY> ]\n'
1579                     '\t   <THOUGHPUT> ::= NUMBER -- default 1\n'
1580                     '\t   <PRIORITY>  ::= NUMBER -- default 0\n'
1581                     '\t   <SIZE>      ::= NUMBER -- default 1\n'
1582                     "\n"
1583                     "Size, thoughput and priority are all relative terms. "
1584                     "Sizes are multipliers for the size argument, thoughput "
1585                     "is applied relative to other classes and the same with "
1586                     "priority.",
1587             "category": FC.CATEGORY_CHANNELS,
1588             "create_function": create_classqueuefilter,
1589             "box_attributes": [
1590                 "args",
1591                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1592             ],
1593             "connector_types": ["->fd","udp","tcp"],
1594             "traces": ["dropped_stats"],
1595         }),
1596     LOGGINGCLASSQUEUEFILTER : dict({
1597             "help": "TUN classful queue, uses a separate queue for each user-definable class.\n"
1598                     "See ClassQueueFilter. This version adds detailled queue state tracing.",
1599             "category": FC.CATEGORY_CHANNELS,
1600             "create_function": create_loggingclassqueuefilter,
1601             "box_attributes": [
1602                 "args",
1603                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1604             ],
1605             "connector_types": ["->fd","udp","tcp"],
1606             "traces": ["dropped_stats","queue_stats_f","queue_stats_b"],
1607         }),
1608     TOSQUEUEFILTER : dict({
1609             "help": "TUN classfull queue that classifies according to the TOS (RFC 791) IP field.\n\n"
1610                     "It takes a size argument that specifies the size of each class. As TOS is a "
1611                     "subset of DiffServ, this queue half-implements DiffServ.",
1612             "category": FC.CATEGORY_CHANNELS,
1613             "create_function": create_tosqueuefilter,
1614             "box_attributes": [
1615                 "args",
1616                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1617             ],
1618             "connector_types": ["->fd","udp","tcp"],
1619         }),
1620
1621     APPLICATION: dict({
1622             "help": "Generic executable command line application",
1623             "category": FC.CATEGORY_APPLICATIONS,
1624             "create_function": create_application,
1625             "start_function": start_application,
1626             "status_function": status_application,
1627             "stop_function": stop_application,
1628             "configure_function": configure_application,
1629             "box_attributes": ["command", "sudo", "stdin",
1630                                "depends", "build-depends", "build", "install",
1631                                "sources", "rpm-fusion" ],
1632             "connector_types": ["node"],
1633             "traces": ["stdout", "stderr", "buildlog", "output"],
1634             "tags": [tags.APPLICATION],
1635         }),
1636
1637     CCNXDAEMON: dict({
1638             "help": "CCNx daemon",
1639             "category": FC.CATEGORY_APPLICATIONS,
1640             "create_function": create_ccnxdaemon,
1641             "prestart_function": prestart_ccnxdaemon,
1642             "status_function": status_application,
1643             "stop_function": stop_application,
1644             "configure_function": configure_application,
1645             "box_attributes": ["ccnroutes", "build", "ccnlocalport",
1646                 "install", "ccnxversion", "sources", "repository"],
1647             "connector_types": ["node"],
1648             "traces": ["stdout", "stderr", "buildlog", "output"],
1649             "tags": [tags.APPLICATION],
1650         }),
1651     DEPENDENCY: dict({
1652             "help": "Requirement for package or application to be installed on some node",
1653             "category": FC.CATEGORY_APPLICATIONS,
1654             "create_function": create_dependency,
1655             "preconfigure_function": configure_dependency,
1656             "status_function": status_dependency,
1657             "box_attributes": ["depends", "build-depends", "build", "install",
1658                                "sources", "rpm-fusion" ],
1659             "connector_types": ["node"],
1660             "traces": ["buildlog"],
1661         }),
1662     NEPIDEPENDENCY: dict({
1663             "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1664             "category": FC.CATEGORY_APPLICATIONS,
1665             "create_function": create_nepi_dependency,
1666             "preconfigure_function": configure_dependency,
1667             "box_attributes": [],
1668             "connector_types": ["node"],
1669             "traces": ["buildlog"],
1670         }),
1671     NS3DEPENDENCY: dict({
1672             "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1673             "category": FC.CATEGORY_APPLICATIONS,
1674             "create_function": create_ns3_dependency,
1675             "preconfigure_function": configure_dependency,
1676             "box_attributes": [ ],
1677             "connector_types": ["node"],
1678             "traces": ["buildlog"],
1679         }),
1680     MULTICASTFORWARDER: dict({
1681             "help": "This application installs a userspace packet forwarder "
1682                     "that, when connected to a node, filters all packets "
1683                     "flowing through multicast-capable virtual interfaces "
1684                     "and applies custom-specified routing policies.",
1685             "category": FC.CATEGORY_APPLICATIONS,
1686             "create_function": create_multicast_forwarder,
1687             "preconfigure_function": configure_forwarder,
1688             "start_function": start_application,
1689             "status_function": status_application,
1690             "stop_function": stop_application,
1691             "box_attributes": [ ],
1692             "connector_types": ["node","router"],
1693             "traces": ["buildlog","stderr"],
1694         }),
1695     MULTICASTANNOUNCER: dict({
1696             "help": "This application installs a userspace daemon that "
1697                     "monitors multicast membership and announces it on all "
1698                     "multicast-capable interfaces.\n"
1699                     "This does not usually happen automatically on PlanetLab slivers.",
1700             "category": FC.CATEGORY_APPLICATIONS,
1701             "create_function": create_multicast_announcer,
1702             "preconfigure_function": configure_announcer,
1703             "start_function": start_application,
1704             "status_function": status_application,
1705             "stop_function": stop_application,
1706             "box_attributes": [ ],
1707             "connector_types": ["node"],
1708             "traces": ["buildlog","stderr"],
1709         }),
1710     MULTICASTROUTER: dict({
1711             "help": "This application installs a userspace daemon that "
1712                     "monitors multicast membership and announces it on all "
1713                     "multicast-capable interfaces.\n"
1714                     "This does not usually happen automatically on PlanetLab slivers.",
1715             "category": FC.CATEGORY_APPLICATIONS,
1716             "create_function": create_multicast_router,
1717             "preconfigure_function": configure_router,
1718             "start_function": start_application,
1719             "status_function": status_application,
1720             "stop_function": stop_application,
1721             "box_attributes": ["routing_algorithm"],
1722             "connector_types": ["fwd"],
1723             "traces": ["buildlog","stdout","stderr"],
1724         }),
1725     INTERNET: dict({
1726             "help": "Internet routing",
1727             "category": FC.CATEGORY_CHANNELS,
1728             "create_function": create_internet,
1729             "connector_types": ["devs"],
1730             "tags": [tags.INTERNET],
1731         }),
1732     NETPIPE: dict({
1733             "help": "Link emulation",
1734             "category": FC.CATEGORY_CHANNELS,
1735             "create_function": create_netpipe,
1736             "configure_function": configure_netpipe,
1737             "box_attributes": ["netpipe_mode",
1738                                "addr_list", "port_list",
1739                                "bw_in","plr_in","delay_in",
1740                                "bw_out","plr_out","delay_out"],
1741             "connector_types": ["node"],
1742             "traces": ["netpipe_stats"],
1743         }),
1744 })
1745
1746 testbed_attributes = dict({
1747         "slice_hrn": dict({
1748             "name": "sliceHrn",
1749             "help": "The hierarchical Resource Name (HRN) for the PlanetLab slice.",
1750             "type": Attribute.STRING,
1751             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1752             "validation_function": validation.is_string
1753         }),
1754         "sfa": dict({
1755             "name": "sfa",
1756             "help": "Activates the use of SFA for node reservation.",
1757             "type": Attribute.BOOL,
1758             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1759             "validation_function": validation.is_bool
1760         }),
1761         "slice": dict({
1762             "name": "slice",
1763             "help": "The name of the PlanetLab slice to use",
1764             "type": Attribute.STRING,
1765             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1766             "validation_function": validation.is_string
1767         }),
1768         "auth_user": dict({
1769             "name": "authUser",
1770             "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1771             "type": Attribute.STRING,
1772             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1773             "validation_function": validation.is_string
1774         }),
1775         "auth_pass": dict({
1776             "name": "authPass",
1777             "help": "The PlanetLab user's password.",
1778             "type": Attribute.STRING,
1779             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1780             "validation_function": validation.is_string
1781         }),
1782         "plc_host": dict({
1783             "name": "plcHost",
1784             "help": "The PlanetLab PLC API host",
1785             "type": Attribute.STRING,
1786             "value": "www.planet-lab.eu",
1787             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1788             "validation_function": validation.is_string
1789         }),
1790         "plc_url": dict({
1791             "name": "plcUrl",
1792             "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1793             "type": Attribute.STRING,
1794             "value": "https://%(hostname)s:443/PLCAPI/",
1795             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1796             "validation_function": validation.is_string
1797         }),
1798         "proxy": dict({
1799             "name": "proxy",
1800             "help": "Https proxy to connect to the outside world",
1801             "type": Attribute.STRING,
1802             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1803             "validation_function": validation.is_string
1804         }),
1805         "p2p_deployment": dict({
1806             "name": "p2pDeployment",
1807             "help": "Enable peer-to-peer deployment of applications and dependencies. "
1808                     "When enabled, dependency packages and applications are "
1809                     "deployed in a P2P fashion, picking a single node to do "
1810                     "the building or repo download, while all the others "
1811                     "cooperatively exchange resulting binaries or rpms. "
1812                     "When deploying to many nodes, this is a far more efficient "
1813                     "use of resources. It does require re-encrypting and distributing "
1814                     "the slice's private key. Though it is implemented in a secure "
1815                     "fashion, if they key's sole purpose is not PlanetLab, then this "
1816                     "feature should be disabled.",
1817             "type": Attribute.BOOL,
1818             "value": True,
1819             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1820             "validation_function": validation.is_bool
1821         }),
1822         "slice_ssh_key": dict({
1823             "name": "sliceSSHKey",
1824             "help": "The controller-local path to the slice user's ssh private key. "
1825                     "It is the user's responsability to deploy this file where the controller "
1826                     "will run, it won't be done automatically because it's sensitive information. "
1827                     "It is recommended that a NEPI-specific user be created for this purpose and "
1828                     "this purpose alone.",
1829             "type": Attribute.STRING,
1830             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1831             "validation_function": validation.is_string
1832         }),
1833         "pl_log_level": dict({      
1834             "name": "plLogLevel",
1835             "help": "Verbosity of logging of planetlab events.",
1836             "value": "INFO",
1837             "type": Attribute.ENUM, 
1838             "allowed": ["DEBUG",
1839                         "INFO",
1840                         "WARNING",
1841                         "ERROR",
1842                         "CRITICAL"],
1843             "validation_function": validation.is_enum,
1844         }),
1845         "tap_port_base":  dict({
1846             "name": "tapPortBase", 
1847             "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.",
1848             "type": Attribute.INTEGER,
1849             "value": 15000,
1850             "range": (2000,30000),
1851             "validation_function": validation.is_integer_range(2000,30000)
1852         }),
1853         "clean_proc": dict({
1854             "name": "cleanProc",
1855             "help": "Set to True if the slice will be dedicated to this experiment. "
1856                     "NEPI will perform node and slice process cleanup, making sure slices are "
1857                     "in a clean, repeatable state before running the experiment.",
1858             "type": Attribute.BOOL,
1859             "value": False,
1860             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1861             "validation_function": validation.is_bool
1862         }),
1863         "clean_home": dict({
1864             "name": "cleanHome",
1865             "help": "Set to True all preexistent directories in the home "
1866                     "directory of each sliver will be removed before the "
1867                     "start of the experiment.",
1868             "type": Attribute.BOOL,
1869             "value": False,
1870             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1871             "validation_function": validation.is_bool
1872         }),
1873     })
1874
1875 supported_recovery_policies = [
1876         DC.POLICY_FAIL,
1877         DC.POLICY_RESTART,
1878         DC.POLICY_RECOVER,
1879     ]
1880
1881 class MetadataInfo(metadata.MetadataInfo):
1882     @property
1883     def connector_types(self):
1884         return connector_types
1885
1886     @property
1887     def connections(self):
1888         return connections
1889
1890     @property
1891     def attributes(self):
1892         return attributes
1893
1894     @property
1895     def traces(self):
1896         return traces
1897
1898     @property
1899     def create_order(self):
1900         return create_order
1901
1902     @property
1903     def configure_order(self):
1904         return configure_order
1905
1906     @property
1907     def prestart_order(self):
1908         return start_order
1909
1910     @property
1911     def start_order(self):
1912         return start_order
1913
1914     @property
1915     def factories_info(self):
1916         return factories_info
1917
1918     @property
1919     def testbed_attributes(self):
1920         return testbed_attributes
1921
1922     @property
1923     def testbed_id(self):
1924         return TESTBED_ID
1925
1926     @property
1927     def testbed_version(self):
1928         return TESTBED_VERSION
1929
1930     @property
1931     def supported_recovery_policies(self):
1932         return supported_recovery_policies
1933
1934