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