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