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