merge -r 411
[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     "if_name": dict({
932                 "name": "if_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     })
1132
1133 traces = dict({
1134     "stdout": dict({
1135                 "name": "stdout",
1136                 "help": "Standard output stream"
1137               }),
1138     "stderr": dict({
1139                 "name": "stderr",
1140                 "help": "Application standard error",
1141               }),
1142     "buildlog": dict({
1143                 "name": "buildlog",
1144                 "help": "Output of the build process",
1145               }), 
1146     
1147     "netpipe_stats": dict({
1148                 "name": "netpipeStats",
1149                 "help": "Information about rule match counters, packets dropped, etc.",
1150               }),
1151
1152     "packets": dict({
1153                 "name": "packets",
1154                 "help": "Detailled log of all packets going through the interface",
1155               }),
1156     "pcap": dict({
1157                 "name": "pcap",
1158                 "help": "PCAP trace of all packets going through the interface",
1159               }),
1160     })
1161
1162 create_order = [ INTERNET, NODE, NODEIFACE, TUNFILTER, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
1163
1164 configure_order = [ INTERNET, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1165
1166 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
1167 start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1168
1169 # cleanup order
1170 shutdown_order = [ Parallel(APPLICATION), Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), NODEIFACE, Parallel(NODE) ]
1171
1172 factories_info = dict({
1173     NODE: dict({
1174             "help": "Virtualized Node (V-Server style)",
1175             "category": FC.CATEGORY_NODES,
1176             "create_function": create_node,
1177             "preconfigure_function": configure_node,
1178             "prestart_function": configure_node_routes,
1179             "box_attributes": [
1180                 "forward_X11",
1181                 "hostname",
1182                 "architecture",
1183                 "operating_system",
1184                 "site",
1185                 "min_reliability",
1186                 "max_reliability",
1187                 "min_bandwidth",
1188                 "max_bandwidth",
1189                 
1190                 # NEPI-in-NEPI attributes
1191                 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
1192             ],
1193             "connector_types": ["devs", "apps", "pipes", "deps"],
1194             "tags": [tags.NODE, tags.ALLOW_ROUTES],
1195        }),
1196     NODEIFACE: dict({
1197             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
1198             "category": FC.CATEGORY_DEVICES,
1199             "create_function": create_nodeiface,
1200             "preconfigure_function": configure_nodeiface,
1201             "box_attributes": [ ],
1202             "connector_types": ["node", "inet"],
1203             "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
1204         }),
1205     TUNIFACE: dict({
1206             "help": "Virtual TUN network interface (layer 3)",
1207             "category": FC.CATEGORY_DEVICES,
1208             "create_function": create_tuniface,
1209             "preconfigure_function": preconfigure_tuniface,
1210             "configure_function": postconfigure_tuniface,
1211             "prestart_function": wait_tuniface,
1212             "box_attributes": [
1213                 "up", "if_name", "mtu", "snat", "pointopoint",
1214                 "txqueuelen",
1215                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1216             ],
1217             "traces": ["packets", "pcap"],
1218             "connector_types": ["node","udp","tcp","fd->","gre"],
1219             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1220         }),
1221     TAPIFACE: dict({
1222             "help": "Virtual TAP network interface (layer 2)",
1223             "category": FC.CATEGORY_DEVICES,
1224             "create_function": create_tapiface,
1225             "preconfigure_function": preconfigure_tuniface,
1226             "configure_function": postconfigure_tuniface,
1227             "prestart_function": wait_tuniface,
1228             "box_attributes": [
1229                 "up", "if_name", "mtu", "snat", "pointopoint",
1230                 "txqueuelen",
1231                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1232             ],
1233             "traces": ["packets", "pcap"],
1234             "connector_types": ["node","udp","tcp","fd->","gre"],
1235             "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1236         }),
1237     TUNFILTER: dict({
1238             "help": "TUN/TAP stream filter",
1239             "category": FC.CATEGORY_CHANNELS,
1240             "create_function": create_tunfilter,
1241             "box_attributes": [
1242                 "module",
1243                 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1244             ],
1245             "connector_types": ["->fd","udp","tcp"],
1246         }),
1247     APPLICATION: dict({
1248             "help": "Generic executable command line application",
1249             "category": FC.CATEGORY_APPLICATIONS,
1250             "create_function": create_application,
1251             "start_function": start_application,
1252             "status_function": status_application,
1253             "stop_function": stop_application,
1254             "configure_function": configure_application,
1255             "box_attributes": ["command", "sudo", "stdin",
1256                                "depends", "build-depends", "build", "install",
1257                                "sources", "rpm-fusion" ],
1258             "connector_types": ["node"],
1259             "traces": ["stdout", "stderr", "buildlog"],
1260             "tags": [tags.APPLICATION],
1261         }),
1262     DEPENDENCY: dict({
1263             "help": "Requirement for package or application to be installed on some node",
1264             "category": FC.CATEGORY_APPLICATIONS,
1265             "create_function": create_dependency,
1266             "preconfigure_function": configure_dependency,
1267             "box_attributes": ["depends", "build-depends", "build", "install",
1268                                "sources", "rpm-fusion" ],
1269             "connector_types": ["node"],
1270             "traces": ["buildlog"],
1271         }),
1272     NEPIDEPENDENCY: dict({
1273             "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1274             "category": FC.CATEGORY_APPLICATIONS,
1275             "create_function": create_nepi_dependency,
1276             "preconfigure_function": configure_dependency,
1277             "box_attributes": [],
1278             "connector_types": ["node"],
1279             "traces": ["buildlog"],
1280         }),
1281     NS3DEPENDENCY: dict({
1282             "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1283             "category": FC.CATEGORY_APPLICATIONS,
1284             "create_function": create_ns3_dependency,
1285             "preconfigure_function": configure_dependency,
1286             "box_attributes": [ ],
1287             "connector_types": ["node"],
1288             "traces": ["buildlog"],
1289         }),
1290     INTERNET: dict({
1291             "help": "Internet routing",
1292             "category": FC.CATEGORY_CHANNELS,
1293             "create_function": create_internet,
1294             "connector_types": ["devs"],
1295             "tags": [tags.INTERNET],
1296         }),
1297     NETPIPE: dict({
1298             "help": "Link emulation",
1299             "category": FC.CATEGORY_CHANNELS,
1300             "create_function": create_netpipe,
1301             "configure_function": configure_netpipe,
1302             "box_attributes": ["netpipe_mode",
1303                                "addr_list", "port_list",
1304                                "bw_in","plr_in","delay_in",
1305                                "bw_out","plr_out","delay_out"],
1306             "connector_types": ["node"],
1307             "traces": ["netpipe_stats"],
1308         }),
1309 })
1310
1311 testbed_attributes = dict({
1312         "slice": dict({
1313             "name": "slice",
1314             "help": "The name of the PlanetLab slice to use",
1315             "type": Attribute.STRING,
1316             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1317             "validation_function": validation.is_string
1318         }),
1319         "auth_user": dict({
1320             "name": "authUser",
1321             "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1322             "type": Attribute.STRING,
1323             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1324             "validation_function": validation.is_string
1325         }),
1326         "auth_pass": dict({
1327             "name": "authPass",
1328             "help": "The PlanetLab user's password.",
1329             "type": Attribute.STRING,
1330             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1331             "validation_function": validation.is_string
1332         }),
1333         "plc_host": dict({
1334             "name": "plcHost",
1335             "help": "The PlanetLab PLC API host",
1336             "type": Attribute.STRING,
1337             "value": "www.planet-lab.eu",
1338             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1339             "validation_function": validation.is_string
1340         }),
1341         "plc_url": dict({
1342             "name": "plcUrl",
1343             "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1344             "type": Attribute.STRING,
1345             "value": "https://%(hostname)s:443/PLCAPI/",
1346             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1347             "validation_function": validation.is_string
1348         }),
1349         "p2p_deployment": dict({
1350             "name": "p2pDeployment",
1351             "help": "Enable peer-to-peer deployment of applications and dependencies. "
1352                     "When enabled, dependency packages and applications are "
1353                     "deployed in a P2P fashion, picking a single node to do "
1354                     "the building or repo download, while all the others "
1355                     "cooperatively exchange resulting binaries or rpms. "
1356                     "When deploying to many nodes, this is a far more efficient "
1357                     "use of resources. It does require re-encrypting and distributing "
1358                     "the slice's private key. Though it is implemented in a secure "
1359                     "fashion, if they key's sole purpose is not PlanetLab, then this "
1360                     "feature should be disabled.",
1361             "type": Attribute.BOOL,
1362             "value": True,
1363             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1364             "validation_function": validation.is_bool
1365         }),
1366         "slice_ssh_key": dict({
1367             "name": "sliceSSHKey",
1368             "help": "The controller-local path to the slice user's ssh private key. "
1369                     "It is the user's responsability to deploy this file where the controller "
1370                     "will run, it won't be done automatically because it's sensitive information. "
1371                     "It is recommended that a NEPI-specific user be created for this purpose and "
1372                     "this purpose alone.",
1373             "type": Attribute.STRING,
1374             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1375             "validation_function": validation.is_string
1376         }),
1377         "pl_log_level": dict({      
1378             "name": "plLogLevel",
1379             "help": "Verbosity of logging of planetlab events.",
1380             "value": "ERROR",
1381             "type": Attribute.ENUM, 
1382             "allowed": ["DEBUG",
1383                         "INFO",
1384                         "WARNING",
1385                         "ERROR",
1386                         "CRITICAL"],
1387             "validation_function": validation.is_enum,
1388         }),
1389         "tap_port_base":  dict({
1390             "name": "tapPortBase", 
1391             "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.",
1392             "type": Attribute.INTEGER,
1393             "value": 15000,
1394             "range": (2000,30000),
1395             "validation_function": validation.is_integer_range(2000,30000)
1396         }),
1397         "dedicated_slice": dict({
1398             "name": "dedicatedSlice",
1399             "help": "Set to True if the slice will be dedicated to this experiment. "
1400                     "NEPI will perform node and slice cleanup, making sure slices are "
1401                     "in a clean, repeatable state before running the experiment.",
1402             "type": Attribute.BOOL,
1403             "value": False,
1404             "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1405             "validation_function": validation.is_bool
1406         }),
1407     })
1408
1409 supported_recovery_policies = [
1410         DC.POLICY_FAIL,
1411         DC.POLICY_RESTART,
1412         DC.POLICY_RECOVER,
1413     ]
1414
1415 class MetadataInfo(metadata.MetadataInfo):
1416     @property
1417     def connector_types(self):
1418         return connector_types
1419
1420     @property
1421     def connections(self):
1422         return connections
1423
1424     @property
1425     def attributes(self):
1426         return attributes
1427
1428     @property
1429     def traces(self):
1430         return traces
1431
1432     @property
1433     def create_order(self):
1434         return create_order
1435
1436     @property
1437     def configure_order(self):
1438         return configure_order
1439
1440     @property
1441     def prestart_order(self):
1442         return start_order
1443
1444     @property
1445     def start_order(self):
1446         return start_order
1447
1448     @property
1449     def factories_info(self):
1450         return factories_info
1451
1452     @property
1453     def testbed_attributes(self):
1454         return testbed_attributes
1455
1456     @property
1457     def testbed_id(self):
1458         return TESTBED_ID
1459
1460     @property
1461     def testbed_version(self):
1462         return TESTBED_VERSION
1463
1464     @property
1465     def supported_recovery_policies(self):
1466         return supported_recovery_policies
1467
1468