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