Ticket #14: WIP, only intra-PL TUN connections, required groundwork for cross-backend...
[nepi.git] / src / nepi / testbeds / planetlab / metadata_v01.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import time
5
6 from constants import TESTBED_ID
7 from nepi.core import metadata
8 from nepi.core.attributes import Attribute
9 from nepi.util import validation
10 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
11         STATUS_FINISHED
12
13 import functools
14 import os
15 import os.path
16
17 NODE = "Node"
18 NODEIFACE = "NodeInterface"
19 TUNIFACE = "TunInterface"
20 APPLICATION = "Application"
21 INTERNET = "Internet"
22 NETPIPE = "NetPipe"
23
24 PL_TESTBED_ID = "planetlab"
25
26
27 ### Custom validation functions ###
28 def is_addrlist(attribute, value):
29     if not validation.is_string(attribute, value):
30         return False
31     
32     if not value:
33         # No empty strings
34         return False
35     
36     components = value.split(',')
37     
38     for component in components:
39         if '/' in component:
40             addr, mask = component.split('/',1)
41         else:
42             addr, mask = component, 32
43         
44         if mask is not None and not (mask and mask.isdigit()):
45             # No empty or nonnumeric masks
46             return False
47         
48         if not validation.is_ip4_address(attribute, value):
49             # Address part must be ipv4
50             return False
51         
52     return True
53
54 def is_portlist(attribute, value):
55     if not validation.is_string(attribute, value):
56         return False
57     
58     if not value:
59         # No empty strings
60         return False
61     
62     components = value.split(',')
63     
64     for component in components:
65         if '-' in component:
66             pfrom, pto = component.split('-',1)
67         else:
68             pfrom = pto = component
69         
70         if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit():
71             # No empty or nonnumeric ports
72             return False
73         
74     return True
75
76
77 ### Connection functions ####
78
79 def connect_node_iface_node(testbed_instance, node, iface):
80     iface.node = node
81
82 def connect_node_iface_inet(testbed_instance, iface, inet):
83     iface.has_internet = True
84
85 def connect_tun_iface_node(testbed_instance, node, iface):
86     if not node.emulation:
87         raise RuntimeError, "Use of TUN interfaces requires emulation"
88     iface.node = node
89     node.required_vsys.update(('fd_tuntap', 'vif_up'))
90
91 def connect_tun_iface_peer(proto, testbed_instance, iface, peer_iface):
92     iface.peer_iface = peer_iface
93     iface.peer_proto = \
94     iface.tun_proto = proto
95
96 def connect_app(testbed_instance, node, app):
97     app.node = node
98     
99     if app.depends:
100         node.required_packages.update(set(
101             app.depends.split() ))
102
103 def connect_node_netpipe(testbed_instance, node, netpipe):
104     if not node.emulation:
105         raise RuntimeError, "Use of NetPipes requires emulation"
106     netpipe.node = node
107     
108
109 ### Creation functions ###
110
111 def create_node(testbed_instance, guid):
112     parameters = testbed_instance._get_parameters(guid)
113     
114     # create element with basic attributes
115     element = testbed_instance._make_node(parameters)
116     
117     # add constraint on number of (real) interfaces
118     # by counting connected devices
119     dev_guids = testbed_instance.get_connected(guid, "node", "devs")
120     num_open_ifaces = sum( # count True values
121         NODEIFACE == testbed_instance._get_factory_id(guid)
122         for guid in dev_guids )
123     element.min_num_external_ifaces = num_open_ifaces
124     
125     testbed_instance.elements[guid] = element
126
127 def create_nodeiface(testbed_instance, guid):
128     parameters = testbed_instance._get_parameters(guid)
129     element = testbed_instance._make_node_iface(parameters)
130     testbed_instance.elements[guid] = element
131
132 def create_tuniface(testbed_instance, guid):
133     parameters = testbed_instance._get_parameters(guid)
134     element = testbed_instance._make_tun_iface(parameters)
135     testbed_instance.elements[guid] = element
136
137 def create_application(testbed_instance, guid):
138     parameters = testbed_instance._get_parameters(guid)
139     element = testbed_instance._make_application(parameters)
140     testbed_instance.elements[guid] = element
141
142 def create_internet(testbed_instance, guid):
143     parameters = testbed_instance._get_parameters(guid)
144     element = testbed_instance._make_internet(parameters)
145     testbed_instance.elements[guid] = element
146
147 def create_netpipe(testbed_instance, guid):
148     parameters = testbed_instance._get_parameters(guid)
149     element = testbed_instance._make_netpipe(parameters)
150     testbed_instance.elements[guid] = element
151
152 ### Start/Stop functions ###
153
154 def start_application(testbed_instance, guid):
155     parameters = testbed_instance._get_parameters(guid)
156     traces = testbed_instance._get_traces(guid)
157     app = testbed_instance.elements[guid]
158     
159     app.stdout = "stdout" in traces
160     app.stderr = "stderr" in traces
161     app.buildlog = "buildlog" in traces
162     
163     app.start()
164
165 def stop_application(testbed_instance, guid):
166     app = testbed_instance.elements[guid]
167     app.kill()
168
169 ### Status functions ###
170
171 def status_application(testbed_instance, guid):
172     if guid not in testbed_instance.elements.keys():
173         return STATUS_NOT_STARTED
174     
175     app = testbed_instance.elements[guid]
176     return app.status()
177
178 ### Configure functions ###
179
180 def configure_nodeiface(testbed_instance, guid):
181     element = testbed_instance._elements[guid]
182     
183     # Cannot explicitly configure addresses
184     if guid in testbed_instance._add_address:
185         raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
186     
187     # Get siblings
188     node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
189     dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
190     siblings = [ self._element[dev_guid] 
191                  for dev_guid in dev_guids
192                  if dev_guid != guid ]
193     
194     # Fetch address from PLC api
195     element.pick_iface(siblings)
196     
197     # Do some validations
198     element.validate()
199
200 def preconfigure_tuniface(testbed_instance, guid):
201     element = testbed_instance._elements[guid]
202     
203     # Set custom addresses if any
204     if guid in testbed_instance._add_address:
205         addresses = testbed_instance._add_address[guid]
206         for address in addresses:
207             (address, netprefix, broadcast) = address
208             element.add_address(address, netprefix, broadcast)
209     
210     # Link to external interface, if any
211     for iface in testbed_instance._elements.itervalues():
212         if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
213             element.external_iface = iface
214             break
215
216     # Set standard TUN attributes
217     element.tun_addr = element.external_iface.address
218     element.tun_port = 15000 + int(guid)
219
220     # Set enabled traces
221     traces = testbed_instance._get_traces(guid)
222     element.capture = 'packets' in traces
223     
224     # Do some validations
225     element.validate()
226     
227     # First-phase setup
228     element.prepare( 
229         'tun-%s' % (guid,),
230         id(element) < id(element.peer_iface) )
231
232 def postconfigure_tuniface(testbed_instance, guid):
233     element = testbed_instance._elements[guid]
234     
235     # Second-phase setup
236     element.setup()
237     
238
239 def configure_node(testbed_instance, guid):
240     node = testbed_instance._elements[guid]
241     
242     # Just inject configuration stuff
243     node.home_path = "nepi-node-%s" % (guid,)
244     node.ident_path = testbed_instance.sliceSSHKey
245     node.slicename = testbed_instance.slicename
246     
247     # Do some validations
248     node.validate()
249     
250     # recently provisioned nodes may not be up yet
251     sleeptime = 1.0
252     while not node.is_alive():
253         time.sleep(sleeptime)
254         sleeptime = min(30.0, sleeptime*1.5)
255     
256     # this will be done in parallel in all nodes
257     # this call only spawns the process
258     node.install_dependencies()
259
260 def configure_application(testbed_instance, guid):
261     app = testbed_instance._elements[guid]
262     
263     # Just inject configuration stuff
264     app.home_path = "nepi-app-%s" % (guid,)
265     app.ident_path = testbed_instance.sliceSSHKey
266     app.slicename = testbed_instance.slicename
267     
268     # Do some validations
269     app.validate()
270     
271     # Wait for dependencies
272     app.node.wait_dependencies()
273     
274     # Install stuff
275     app.setup()
276
277 def configure_netpipe(testbed_instance, guid):
278     netpipe = testbed_instance._elements[guid]
279     
280     # Do some validations
281     netpipe.validate()
282     
283     # Wait for dependencies
284     netpipe.node.wait_dependencies()
285     
286     # Install rules
287     netpipe.configure()
288
289 ### Factory information ###
290
291 connector_types = dict({
292     "apps": dict({
293                 "help": "Connector from node to applications", 
294                 "name": "apps",
295                 "max": -1, 
296                 "min": 0
297             }),
298     "devs": dict({
299                 "help": "Connector from node to network interfaces", 
300                 "name": "devs",
301                 "max": -1, 
302                 "min": 0
303             }),
304     "inet": dict({
305                 "help": "Connector from network interfaces to the internet", 
306                 "name": "inet",
307                 "max": 1, 
308                 "min": 1
309             }),
310     "node": dict({
311                 "help": "Connector to a Node", 
312                 "name": "node",
313                 "max": 1, 
314                 "min": 1
315             }),
316     "pipes": dict({
317                 "help": "Connector to a NetPipe", 
318                 "name": "pipes",
319                 "max": 2, 
320                 "min": 0
321             }),
322     
323     "tcp": dict({
324                 "help": "ip-ip tunneling over TCP link", 
325                 "name": "tcp",
326                 "max": 1, 
327                 "min": 0
328             }),
329     "udp": dict({
330                 "help": "ip-ip tunneling over UDP datagrams", 
331                 "name": "udp",
332                 "max": 1, 
333                 "min": 0
334             }),
335    })
336
337 connections = [
338     dict({
339         "from": (TESTBED_ID, NODE, "devs"),
340         "to":   (TESTBED_ID, NODEIFACE, "node"),
341         "code": connect_node_iface_node,
342         "can_cross": False
343     }),
344     dict({
345         "from": (TESTBED_ID, NODE, "devs"),
346         "to":   (TESTBED_ID, TUNIFACE, "node"),
347         "code": connect_tun_iface_node,
348         "can_cross": False
349     }),
350     dict({
351         "from": (TESTBED_ID, NODEIFACE, "inet"),
352         "to":   (TESTBED_ID, INTERNET, "devs"),
353         "code": connect_node_iface_inet,
354         "can_cross": False
355     }),
356     dict({
357         "from": (TESTBED_ID, NODE, "apps"),
358         "to":   (TESTBED_ID, APPLICATION, "node"),
359         "code": connect_app,
360         "can_cross": False
361     }),
362     dict({
363         "from": (TESTBED_ID, NODE, "pipes"),
364         "to":   (TESTBED_ID, NETPIPE, "node"),
365         "code": connect_node_netpipe,
366         "can_cross": False
367     }),
368     dict({
369         "from": (TESTBED_ID, TUNIFACE, "tcp"),
370         "to":   (TESTBED_ID, TUNIFACE, "tcp"),
371         "code": functools.partial(connect_tun_iface_peer,"tcp"),
372         "can_cross": False
373     }),
374     dict({
375         "from": (TESTBED_ID, TUNIFACE, "udp"),
376         "to":   (TESTBED_ID, TUNIFACE, "udp"),
377         "code": functools.partial(connect_tun_iface_peer,"udp"),
378         "can_cross": False
379     }),
380 ]
381
382 attributes = dict({
383     "forward_X11": dict({      
384                 "name": "forward_X11",
385                 "help": "Forward x11 from main namespace to the node",
386                 "type": Attribute.BOOL, 
387                 "value": False,
388                 "flags": Attribute.DesignOnly,
389                 "validation_function": validation.is_bool,
390             }),
391     "hostname": dict({      
392                 "name": "hostname",
393                 "help": "Constrain hostname during resource discovery. May use wildcards.",
394                 "type": Attribute.STRING, 
395                 "flags": Attribute.DesignOnly,
396                 "validation_function": validation.is_string,
397             }),
398     "architecture": dict({      
399                 "name": "architecture",
400                 "help": "Constrain architexture during resource discovery.",
401                 "type": Attribute.ENUM, 
402                 "flags": Attribute.DesignOnly,
403                 "allowed": ["x86_64",
404                             "i386"],
405                 "validation_function": validation.is_enum,
406             }),
407     "operating_system": dict({      
408                 "name": "operatingSystem",
409                 "help": "Constrain operating system during resource discovery.",
410                 "type": Attribute.ENUM, 
411                 "flags": Attribute.DesignOnly,
412                 "allowed": ["f8",
413                             "f12",
414                             "f14",
415                             "centos",
416                             "other"],
417                 "validation_function": validation.is_enum,
418             }),
419     "site": dict({      
420                 "name": "site",
421                 "help": "Constrain the PlanetLab site this node should reside on.",
422                 "type": Attribute.ENUM, 
423                 "flags": Attribute.DesignOnly,
424                 "allowed": ["PLE",
425                             "PLC",
426                             "PLJ"],
427                 "validation_function": validation.is_enum,
428             }),
429     "emulation": dict({      
430                 "name": "emulation",
431                 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
432                 "type": Attribute.BOOL,
433                 "value": False, 
434                 "flags": Attribute.DesignOnly,
435                 "validation_function": validation.is_bool,
436             }),
437     "min_reliability": dict({
438                 "name": "minReliability",
439                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
440                 "type": Attribute.DOUBLE,
441                 "range": (0,100),
442                 "flags": Attribute.DesignOnly,
443                 "validation_function": validation.is_double,
444             }),
445     "max_reliability": dict({
446                 "name": "maxReliability",
447                 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
448                 "type": Attribute.DOUBLE,
449                 "range": (0,100),
450                 "flags": Attribute.DesignOnly,
451                 "validation_function": validation.is_double,
452             }),
453     "min_bandwidth": dict({
454                 "name": "minBandwidth",
455                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
456                 "type": Attribute.DOUBLE,
457                 "range": (0,2**31),
458                 "flags": Attribute.DesignOnly,
459                 "validation_function": validation.is_double,
460             }),
461     "max_bandwidth": dict({
462                 "name": "maxBandwidth",
463                 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
464                 "type": Attribute.DOUBLE,
465                 "range": (0,2**31),
466                 "flags": Attribute.DesignOnly,
467                 "validation_function": validation.is_double,
468             }),
469             
470     "up": dict({
471                 "name": "up",
472                 "help": "Link up",
473                 "type": Attribute.BOOL,
474                 "value": False,
475                 "validation_function": validation.is_bool
476             }),
477     "primary": dict({
478                 "name": "primary",
479                 "help": "This is the primary interface for the attached node",
480                 "type": Attribute.BOOL,
481                 "value": True,
482                 "validation_function": validation.is_bool
483             }),
484     "device_name": dict({
485                 "name": "name",
486                 "help": "Device name",
487                 "type": Attribute.STRING,
488                 "flags": Attribute.DesignOnly,
489                 "validation_function": validation.is_string
490             }),
491     "mtu":  dict({
492                 "name": "mtu", 
493                 "help": "Maximum transmition unit for device",
494                 "type": Attribute.INTEGER,
495                 "range": (0,1500),
496                 "validation_function": validation.is_integer_range(0,1500)
497             }),
498     "mask":  dict({
499                 "name": "mask", 
500                 "help": "Network mask for the device (eg: 24 for /24 network)",
501                 "type": Attribute.INTEGER,
502                 "validation_function": validation.is_integer_range(8,24)
503             }),
504     "snat":  dict({
505                 "name": "snat", 
506                 "help": "Enable SNAT (source NAT to the internet) no this device",
507                 "type": Attribute.BOOL,
508                 "value": False,
509                 "validation_function": validation.is_bool
510             }),
511     "txqueuelen":  dict({
512                 "name": "mask", 
513                 "help": "Transmission queue length (in packets)",
514                 "type": Attribute.INTEGER,
515                 "flags": Attribute.DesignOnly,
516                 "range" : (1,10000),
517                 "validation_function": validation.is_integer
518             }),
519             
520     "command": dict({
521                 "name": "command",
522                 "help": "Command line string",
523                 "type": Attribute.STRING,
524                 "flags": Attribute.DesignOnly,
525                 "validation_function": validation.is_string
526             }),
527     "sudo": dict({
528                 "name": "sudo",
529                 "help": "Run with root privileges",
530                 "type": Attribute.BOOL,
531                 "flags": Attribute.DesignOnly,
532                 "value": False,
533                 "validation_function": validation.is_bool
534             }),
535     "stdin": dict({
536                 "name": "stdin",
537                 "help": "Standard input",
538                 "type": Attribute.STRING,
539                 "flags": Attribute.DesignOnly,
540                 "validation_function": validation.is_string
541             }),
542             
543     "depends": dict({
544                 "name": "depends",
545                 "help": "Space-separated list of packages required to run the application",
546                 "type": Attribute.STRING,
547                 "flags": Attribute.DesignOnly,
548                 "validation_function": validation.is_string
549             }),
550     "build-depends": dict({
551                 "name": "buildDepends",
552                 "help": "Space-separated list of packages required to build the application",
553                 "type": Attribute.STRING,
554                 "flags": Attribute.DesignOnly,
555                 "validation_function": validation.is_string
556             }),
557     "sources": dict({
558                 "name": "sources",
559                 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
560                         "Archives won't be expanded automatically.",
561                 "type": Attribute.STRING,
562                 "flags": Attribute.DesignOnly,
563                 "validation_function": validation.is_string
564             }),
565     "build": dict({
566                 "name": "build",
567                 "help": "Build commands to execute after deploying the sources. "
568                         "Sources will be in the ${SOURCES} folder. "
569                         "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
570                         "Try to make the commands return with a nonzero exit code on error.\n"
571                         "Also, do not install any programs here, use the 'install' attribute. This will "
572                         "help keep the built files constrained to the build folder (which may "
573                         "not be the home folder), and will result in faster deployment. Also, "
574                         "make sure to clean up temporary files, to reduce bandwidth usage between "
575                         "nodes when transferring built packages.",
576                 "type": Attribute.STRING,
577                 "flags": Attribute.DesignOnly,
578                 "validation_function": validation.is_string
579             }),
580     "install": dict({
581                 "name": "install",
582                 "help": "Commands to transfer built files to their final destinations. "
583                         "Sources will be in the initial working folder, and a special "
584                         "tag ${SOURCES} can be used to reference the experiment's "
585                         "home folder (where the application commands will run).\n"
586                         "ALL sources and targets needed for execution must be copied there, "
587                         "if building has been enabled.\n"
588                         "That is, 'slave' nodes will not automatically get any source files. "
589                         "'slave' nodes don't get build dependencies either, so if you need "
590                         "make and other tools to install, be sure to provide them as "
591                         "actual dependencies instead.",
592                 "type": Attribute.STRING,
593                 "flags": Attribute.DesignOnly,
594                 "validation_function": validation.is_string
595             }),
596     
597     "netpipe_mode": dict({      
598                 "name": "mode",
599                 "help": "Link mode:\n"
600                         " * SERVER: applies to incoming connections\n"
601                         " * CLIENT: applies to outgoing connections\n"
602                         " * SERVICE: applies to both",
603                 "type": Attribute.ENUM, 
604                 "flags": Attribute.DesignOnly,
605                 "allowed": ["SERVER",
606                             "CLIENT",
607                             "SERVICE"],
608                 "validation_function": validation.is_enum,
609             }),
610     "port_list":  dict({
611                 "name": "portList", 
612                 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
613                 "type": Attribute.STRING,
614                 "validation_function": is_portlist,
615             }),
616     "addr_list":  dict({
617                 "name": "addrList", 
618                 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
619                 "type": Attribute.STRING,
620                 "validation_function": is_addrlist,
621             }),
622     "bw_in":  dict({
623                 "name": "bwIn", 
624                 "help": "Inbound bandwidth limit (in Mbit/s)",
625                 "type": Attribute.DOUBLE,
626                 "validation_function": validation.is_double,
627             }),
628     "bw_out":  dict({
629                 "name": "bwOut", 
630                 "help": "Outbound bandwidth limit (in Mbit/s)",
631                 "type": Attribute.DOUBLE,
632                 "validation_function": validation.is_double,
633             }),
634     "plr_in":  dict({
635                 "name": "plrIn", 
636                 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
637                 "type": Attribute.DOUBLE,
638                 "validation_function": validation.is_double,
639             }),
640     "plr_out":  dict({
641                 "name": "plrOut", 
642                 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
643                 "type": Attribute.DOUBLE,
644                 "validation_function": validation.is_double,
645             }),
646     "delay_in":  dict({
647                 "name": "delayIn", 
648                 "help": "Inbound packet delay (in milliseconds)",
649                 "type": Attribute.INTEGER,
650                 "range": (0,60000),
651                 "validation_function": validation.is_integer,
652             }),
653     "delay_out":  dict({
654                 "name": "delayOut", 
655                 "help": "Outbound packet delay (in milliseconds)",
656                 "type": Attribute.INTEGER,
657                 "range": (0,60000),
658                 "validation_function": validation.is_integer,
659             }),
660     })
661
662 traces = dict({
663     "stdout": dict({
664                 "name": "stdout",
665                 "help": "Standard output stream"
666               }),
667     "stderr": dict({
668                 "name": "stderr",
669                 "help": "Application standard error",
670               }),
671     "buildlog": dict({
672                 "name": "buildlog",
673                 "help": "Output of the build process",
674               }), 
675     
676     "netpipe_stats": dict({
677                 "name": "netpipeStats",
678                 "help": "Information about rule match counters, packets dropped, etc.",
679               }),
680
681     "packets": dict({
682                 "name": "packets",
683                 "help": "Detailled log of all packets going through the interface",
684               }),
685     })
686
687 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, APPLICATION ]
688
689 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, NETPIPE, APPLICATION ]
690
691 factories_info = dict({
692     NODE: dict({
693             "allow_routes": False,
694             "help": "Virtualized Node (V-Server style)",
695             "category": "topology",
696             "create_function": create_node,
697             "preconfigure_function": configure_node,
698             "box_attributes": [
699                 "forward_X11",
700                 "hostname",
701                 "architecture",
702                 "operating_system",
703                 "site",
704                 "emulation",
705                 "min_reliability",
706                 "max_reliability",
707                 "min_bandwidth",
708                 "max_bandwidth",
709             ],
710             "connector_types": ["devs", "apps", "pipes"]
711        }),
712     NODEIFACE: dict({
713             "has_addresses": True,
714             "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
715             "category": "devices",
716             "create_function": create_nodeiface,
717             "preconfigure_function": configure_nodeiface,
718             "box_attributes": [ ],
719             "connector_types": ["node", "inet"]
720         }),
721     TUNIFACE: dict({
722             "allow_addresses": True,
723             "help": "Virtual TUN network interface",
724             "category": "devices",
725             "create_function": create_tuniface,
726             "preconfigure_function": preconfigure_tuniface,
727             "configure_function": postconfigure_tuniface,
728             "box_attributes": [
729                 "up", "device_name", "mtu", "snat",
730                 "txqueuelen"
731             ],
732             "traces": ["packets"],
733             "connector_types": ["node","udp","tcp"]
734         }),
735     APPLICATION: dict({
736             "help": "Generic executable command line application",
737             "category": "applications",
738             "create_function": create_application,
739             "start_function": start_application,
740             "status_function": status_application,
741             "stop_function": stop_application,
742             "configure_function": configure_application,
743             "box_attributes": ["command", "sudo", "stdin",
744                                "depends", "build-depends", "build", "install",
745                                "sources" ],
746             "connector_types": ["node"],
747             "traces": ["stdout", "stderr"]
748         }),
749     INTERNET: dict({
750             "help": "Internet routing",
751             "category": "topology",
752             "create_function": create_internet,
753             "connector_types": ["devs"],
754         }),
755     NETPIPE: dict({
756             "help": "Link emulation",
757             "category": "topology",
758             "create_function": create_netpipe,
759             "configure_function": configure_netpipe,
760             "box_attributes": ["netpipe_mode",
761                                "addr_list", "port_list",
762                                "bw_in","plr_in","delay_in",
763                                "bw_out","plr_out","delay_out"],
764             "connector_types": ["node"],
765             "traces": ["netpipe_stats"]
766         }),
767 })
768
769 testbed_attributes = dict({
770         "slice": dict({
771             "name": "slice",
772             "help": "The name of the PlanetLab slice to use",
773             "type": Attribute.STRING,
774             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
775             "validation_function": validation.is_string
776         }),
777         "auth_user": dict({
778             "name": "authUser",
779             "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
780             "type": Attribute.STRING,
781             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
782             "validation_function": validation.is_string
783         }),
784         "auth_pass": dict({
785             "name": "authPass",
786             "help": "The PlanetLab user's password.",
787             "type": Attribute.STRING,
788             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
789             "validation_function": validation.is_string
790         }),
791         "slice_ssh_key": dict({
792             "name": "sliceSSHKey",
793             "help": "The controller-local path to the slice user's ssh private key. "
794                     "It is the user's responsability to deploy this file where the controller "
795                     "will run, it won't be done automatically because it's sensitive information. "
796                     "It is recommended that a NEPI-specific user be created for this purpose and "
797                     "this purpose alone.",
798             "type": Attribute.STRING,
799             "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
800             "validation_function": validation.is_string
801         }),
802     })
803
804 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
805     @property
806     def connector_types(self):
807         return connector_types
808
809     @property
810     def connections(self):
811         return connections
812
813     @property
814     def attributes(self):
815         return attributes
816
817     @property
818     def traces(self):
819         return traces
820
821     @property
822     def create_order(self):
823         return create_order
824
825     @property
826     def configure_order(self):
827         return configure_order
828
829     @property
830     def factories_info(self):
831         return factories_info
832
833     @property
834     def testbed_attributes(self):
835         return testbed_attributes
836