2 # -*- coding: utf-8 -*-
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, \
14 NODEIFACE = "NodeInterface"
15 TUNIFACE = "TunInterface"
16 APPLICATION = "Application"
19 PL_TESTBED_ID = "planetlab"
21 ### Connection functions ####
23 def connect_node_iface_node(testbed_instance, node, iface):
26 def connect_node_iface_inet(testbed_instance, iface, inet):
27 iface.has_internet = True
29 def connect_tun_iface_node(testbed_instance, node, iface):
32 def connect_app(testbed_instance, node, app):
36 node.required_packages.update(set(
37 app.depends.split() ))
40 ### Creation functions ###
42 def create_node(testbed_instance, guid):
43 parameters = testbed_instance._get_parameters(guid)
45 # create element with basic attributes
46 element = testbed_instance._make_node(parameters)
48 # add constraint on number of (real) interfaces
49 # by counting connected devices
50 dev_guids = testbed_instance.get_connected(guid, "node", "devs")
51 num_open_ifaces = sum( # count True values
52 TUNEIFACE == testbed_instance._get_factory_id(guid)
53 for guid in dev_guids )
54 element.min_num_external_ifaces = num_open_ifaces
56 testbed_instance.elements[guid] = element
58 def create_nodeiface(testbed_instance, guid):
59 parameters = testbed_instance._get_parameters(guid)
60 element = testbed_instance._make_node_iface(parameters)
61 testbed_instance.elements[guid] = element
63 def create_tuniface(testbed_instance, guid):
64 parameters = testbed_instance._get_parameters(guid)
65 element = testbed_instance._make_tun_iface(parameters)
66 testbed_instance.elements[guid] = element
68 def create_application(testbed_instance, guid):
69 parameters = testbed_instance._get_parameters(guid)
70 element = testbed_instance._make_application(parameters)
71 testbed_instance.elements[guid] = element
73 def create_internet(testbed_instance, guid):
74 parameters = testbed_instance._get_parameters(guid)
75 element = testbed_instance._make_internet(parameters)
76 testbed_instance.elements[guid] = element
78 ### Start/Stop functions ###
80 def start_application(testbed_instance, guid):
81 parameters = testbed_instance._get_parameters(guid)
82 traces = testbed_instance._get_traces(guid)
83 app = testbed_instance.elements[guid]
85 app.stdout = "stdout" in traces
86 app.stderr = "stderr" in traces
87 app.buildlog = "buildlog" in traces
91 def stop_application(testbed_instance, guid):
92 app = testbed_instance.elements[guid]
95 ### Status functions ###
97 def status_application(testbed_instance, guid):
98 if guid not in testbed_instance.elements.keys():
99 return STATUS_NOT_STARTED
101 app = testbed_instance.elements[guid]
104 ### Configure functions ###
106 def configure_nodeiface(testbed_instance, guid):
107 element = testbed_instance._elements[guid]
109 # Cannot explicitly configure addresses
110 if guid in testbed_instance._add_address:
111 del testbed_instance._add_address[guid]
114 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
115 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
116 siblings = [ self._element[dev_guid]
117 for dev_guid in dev_guids
118 if dev_guid != guid ]
120 # Fetch address from PLC api
121 element.pick_iface(siblings)
123 # Do some validations
126 def configure_tuniface(testbed_instance, guid):
127 element = testbed_instance._elements[guid]
128 if not guid in testbed_instance._add_address:
131 addresses = testbed_instance._add_address[guid]
132 for address in addresses:
133 (address, netprefix, broadcast) = address
134 raise NotImplementedError, "C'mon... TUNs are hard..."
136 # Do some validations
139 def configure_node(testbed_instance, guid):
140 node = testbed_instance._elements[guid]
142 # Just inject configuration stuff
143 node.home_path = "nepi-node-%s" % (guid,)
144 node.ident_path = testbed_instance.sliceSSHKey
145 node.slicename = testbed_instance.slicename
147 # Do some validations
150 # recently provisioned nodes may not be up yet
152 while not node.is_alive():
153 time.sleep(sleeptime)
154 sleeptime = min(30.0, sleeptime*1.5)
156 # this will be done in parallel in all nodes
157 # this call only spawns the process
158 node.install_dependencies()
160 def configure_application(testbed_instance, guid):
161 app = testbed_instance._elements[guid]
163 # Just inject configuration stuff
164 app.home_path = "nepi-app-%s" % (guid,)
165 app.ident_path = testbed_instance.sliceSSHKey
166 app.slicename = testbed_instance.slicename
168 # Do some validations
171 # Wait for dependencies
172 app.node.wait_dependencies()
177 ### Factory information ###
179 connector_types = dict({
181 "help": "Connector from node to applications",
187 "help": "Connector from node to network interfaces",
193 "help": "Connector from network interfaces to the internet",
199 "help": "Connector to a Node",
208 "from": (TESTBED_ID, NODE, "devs"),
209 "to": (TESTBED_ID, NODEIFACE, "node"),
210 "code": connect_node_iface_node,
214 "from": (TESTBED_ID, NODE, "devs"),
215 "to": (TESTBED_ID, TUNIFACE, "node"),
216 "code": connect_tun_iface_node,
220 "from": (TESTBED_ID, NODEIFACE, "inet"),
221 "to": (TESTBED_ID, INTERNET, "devs"),
222 "code": connect_node_iface_inet,
226 "from": (TESTBED_ID, NODE, "apps"),
227 "to": (TESTBED_ID, APPLICATION, "node"),
234 "forward_X11": dict({
235 "name": "forward_X11",
236 "help": "Forward x11 from main namespace to the node",
237 "type": Attribute.BOOL,
239 "flags": Attribute.DesignOnly,
240 "validation_function": validation.is_bool,
244 "help": "Constrain hostname during resource discovery. May use wildcards.",
245 "type": Attribute.STRING,
246 "flags": Attribute.DesignOnly,
247 "validation_function": validation.is_string,
249 "architecture": dict({
250 "name": "architecture",
251 "help": "Constrain architexture during resource discovery.",
252 "type": Attribute.ENUM,
253 "flags": Attribute.DesignOnly,
254 "allowed": ["x86_64",
256 "validation_function": validation.is_enum,
258 "operating_system": dict({
259 "name": "operatingSystem",
260 "help": "Constrain operating system during resource discovery.",
261 "type": Attribute.ENUM,
262 "flags": Attribute.DesignOnly,
268 "validation_function": validation.is_enum,
272 "help": "Constrain the PlanetLab site this node should reside on.",
273 "type": Attribute.ENUM,
274 "flags": Attribute.DesignOnly,
278 "validation_function": validation.is_enum,
282 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
283 "type": Attribute.BOOL,
285 "flags": Attribute.DesignOnly,
286 "validation_function": validation.is_bool,
288 "min_reliability": dict({
289 "name": "minReliability",
290 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
291 "type": Attribute.DOUBLE,
293 "flags": Attribute.DesignOnly,
294 "validation_function": validation.is_double,
296 "max_reliability": dict({
297 "name": "maxReliability",
298 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
299 "type": Attribute.DOUBLE,
301 "flags": Attribute.DesignOnly,
302 "validation_function": validation.is_double,
304 "min_bandwidth": dict({
305 "name": "minBandwidth",
306 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
307 "type": Attribute.DOUBLE,
309 "flags": Attribute.DesignOnly,
310 "validation_function": validation.is_double,
312 "max_bandwidth": dict({
313 "name": "maxBandwidth",
314 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
315 "type": Attribute.DOUBLE,
317 "flags": Attribute.DesignOnly,
318 "validation_function": validation.is_double,
324 "type": Attribute.BOOL,
326 "validation_function": validation.is_bool
330 "help": "This is the primary interface for the attached node",
331 "type": Attribute.BOOL,
333 "validation_function": validation.is_bool
335 "device_name": dict({
337 "help": "Device name",
338 "type": Attribute.STRING,
339 "flags": Attribute.DesignOnly,
340 "validation_function": validation.is_string
344 "help": "Maximum transmition unit for device",
345 "type": Attribute.INTEGER,
347 "validation_function": validation.is_integer_range(0,1500)
351 "help": "Network mask for the device (eg: 24 for /24 network)",
352 "type": Attribute.INTEGER,
353 "validation_function": validation.is_integer_range(8,24)
357 "help": "Enable SNAT (source NAT to the internet) no this device",
358 "type": Attribute.BOOL,
360 "validation_function": validation.is_bool
365 "help": "Command line string",
366 "type": Attribute.STRING,
367 "flags": Attribute.DesignOnly,
368 "validation_function": validation.is_string
372 "help": "System user",
373 "type": Attribute.BOOL,
374 "flags": Attribute.DesignOnly,
376 "validation_function": validation.is_bool
380 "help": "Standard input",
381 "type": Attribute.STRING,
382 "flags": Attribute.DesignOnly,
383 "validation_function": validation.is_string
388 "help": "Space-separated list of packages required to run the application",
389 "type": Attribute.STRING,
390 "flags": Attribute.DesignOnly,
391 "validation_function": validation.is_string
393 "build-depends": dict({
394 "name": "buildDepends",
395 "help": "Space-separated list of packages required to build the application",
396 "type": Attribute.STRING,
397 "flags": Attribute.DesignOnly,
398 "validation_function": validation.is_string
402 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
403 "Archives won't be expanded automatically.",
404 "type": Attribute.STRING,
405 "flags": Attribute.DesignOnly,
406 "validation_function": validation.is_string
410 "help": "Build commands to execute after deploying the sources. "
411 "Sources will be in the ${SOURCES} folder. "
412 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
413 "Try to make the commands return with a nonzero exit code on error.\n"
414 "Also, do not install any programs here, use the 'install' attribute. This will "
415 "help keep the built files constrained to the build folder (which may "
416 "not be the home folder), and will result in faster deployment. Also, "
417 "make sure to clean up temporary files, to reduce bandwidth usage between "
418 "nodes when transferring built packages.",
419 "type": Attribute.STRING,
420 "flags": Attribute.DesignOnly,
421 "validation_function": validation.is_string
425 "help": "Commands to transfer built files to their final destinations. "
426 "Sources will be in the initial working folder, and a special "
427 "tag ${SOURCES} can be used to reference the experiment's "
428 "home folder (where the application commands will run).\n"
429 "ALL sources and targets needed for execution must be copied there, "
430 "if building has been enabled.\n"
431 "That is, 'slave' nodes will not automatically get any source files. "
432 "'slave' nodes don't get build dependencies either, so if you need "
433 "make and other tools to install, be sure to provide them as "
434 "actual dependencies instead.",
435 "type": Attribute.STRING,
436 "flags": Attribute.DesignOnly,
437 "validation_function": validation.is_string
444 "help": "Standard output stream"
448 "help": "Application standard error",
452 "help": "Output of the build process",
456 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
458 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
460 factories_info = dict({
462 "allow_routes": False,
463 "help": "Virtualized Node (V-Server style)",
464 "category": "topology",
465 "create_function": create_node,
466 "preconfigure_function": configure_node,
479 "connector_types": ["devs", "apps"]
482 "allow_addresses": True,
483 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
484 "category": "devices",
485 "create_function": create_nodeiface,
486 "preconfigure_function": configure_nodeiface,
487 "box_attributes": [ ],
488 "connector_types": ["node", "inet"]
491 "allow_addresses": True,
492 "help": "Virtual TUN network interface",
493 "category": "devices",
494 "create_function": create_tuniface,
495 "preconfigure_function": configure_tuniface,
497 "up", "device_name", "mtu", "snat",
499 "connector_types": ["node"]
502 "help": "Generic executable command line application",
503 "category": "applications",
504 "create_function": create_application,
505 "start_function": start_application,
506 "status_function": status_application,
507 "stop_function": stop_application,
508 "configure_function": configure_application,
509 "box_attributes": ["command", "sudo", "stdin",
510 "depends", "build-depends", "build", "install",
512 "connector_types": ["node"],
513 "traces": ["stdout", "stderr"]
516 "help": "Internet routing",
517 "category": "topology",
518 "create_function": create_internet,
519 "connector_types": ["devs"],
523 testbed_attributes = dict({
526 "help": "The name of the PlanetLab slice to use",
527 "type": Attribute.STRING,
528 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
529 "validation_function": validation.is_string
533 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
534 "type": Attribute.STRING,
535 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
536 "validation_function": validation.is_string
540 "help": "The PlanetLab user's password.",
541 "type": Attribute.STRING,
542 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
543 "validation_function": validation.is_string
545 "slice_ssh_key": dict({
546 "name": "sliceSSHKey",
547 "help": "The controller-local path to the slice user's ssh private key. "
548 "It is the user's responsability to deploy this file where the controller "
549 "will run, it won't be done automatically because it's sensitive information. "
550 "It is recommended that a NEPI-specific user be created for this purpose and "
551 "this purpose alone.",
552 "type": Attribute.STRING,
553 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
554 "validation_function": validation.is_string
558 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
560 def connector_types(self):
561 return connector_types
564 def connections(self):
568 def attributes(self):
576 def create_order(self):
580 def configure_order(self):
581 return configure_order
584 def factories_info(self):
585 return factories_info
588 def testbed_attributes(self):
589 return testbed_attributes