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