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