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