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