2 # -*- coding: utf-8 -*-
4 from constants import TESTBED_ID
5 from nepi.core import metadata
6 from nepi.core.attributes import Attribute
7 from nepi.util import validation
8 from nepi.util.constants import STATUS_NOT_STARTED, STATUS_RUNNING, \
12 NODEIFACE = "NodeInterface"
13 TUNIFACE = "TunInterface"
14 APPLICATION = "Application"
17 PL_TESTBED_ID = "planetlab"
19 ### Connection functions ####
21 def connect_node_iface_node(testbed_instance, node, iface):
24 def connect_node_iface_inet(testbed_instance, iface, inet):
25 iface.has_internet = True
27 def connect_tun_iface_node(testbed_instance, node, iface):
30 def connect_app(testbed_instance, node, app):
34 node.required_packages.update(set(
35 app.depends.split() ))
38 ### Creation functions ###
40 def create_node(testbed_instance, guid):
41 parameters = testbed_instance._get_parameters(guid)
43 # create element with basic attributes
44 element = testbed_instance._make_node(parameters)
46 # add constraint on number of (real) interfaces
47 # by counting connected devices
48 dev_guids = testbed_instance.get_connected(guid, "node", "devs")
49 num_open_ifaces = sum( # count True values
50 TUNEIFACE == testbed_instance._get_factory_id(guid)
51 for guid in dev_guids )
52 element.min_num_external_ifaces = num_open_ifaces
54 testbed_instance.elements[guid] = element
56 def create_nodeiface(testbed_instance, guid):
57 parameters = testbed_instance._get_parameters(guid)
58 element = testbed_instance._make_node_iface(parameters)
59 testbed_instance.elements[guid] = element
61 def create_tuniface(testbed_instance, guid):
62 parameters = testbed_instance._get_parameters(guid)
63 element = testbed_instance._make_tun_iface(parameters)
64 testbed_instance.elements[guid] = element
66 def create_application(testbed_instance, guid):
67 parameters = testbed_instance._get_parameters(guid)
68 element = testbed_instance._make_application(parameters)
69 testbed_instance.elements[guid] = element
71 def create_internet(testbed_instance, guid):
72 parameters = testbed_instance._get_parameters(guid)
73 element = testbed_instance._make_internet(parameters)
74 testbed_instance.elements[guid] = element
76 ### Start/Stop functions ###
78 def start_application(testbed_instance, guid):
79 parameters = testbed_instance._get_parameters(guid)
80 traces = testbed_instance._get_traces(guid)
81 app = testbed_instance.elements[guid]
83 app.stdout = "stdout" in traces
84 app.stderr = "stderr" in traces
85 app.buildlog = "buildlog" in traces
89 def stop_application(testbed_instance, guid):
90 app = testbed_instance.elements[guid]
93 ### Status functions ###
95 def status_application(testbed_instance, guid):
96 if guid not in testbed_instance.elements.keys():
97 return STATUS_NOT_STARTED
99 app = testbed_instance.elements[guid]
102 ### Configure functions ###
104 def configure_nodeiface(testbed_instance, guid):
105 element = testbed_instance._elements[guid]
107 # Cannot explicitly configure addresses
108 if guid in testbed_instance._add_address:
109 del testbed_instance._add_address[guid]
112 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
113 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
114 siblings = [ self._element[dev_guid]
115 for dev_guid in dev_guids
116 if dev_guid != guid ]
118 # Fetch address from PLC api
119 element.pick_iface(siblings)
121 # Do some validations
124 def configure_tuniface(testbed_instance, guid):
125 element = testbed_instance._elements[guid]
126 if not guid in testbed_instance._add_address:
129 addresses = testbed_instance._add_address[guid]
130 for address in addresses:
131 (address, netprefix, broadcast) = address
132 raise NotImplementedError, "C'mon... TUNs are hard..."
134 # Do some validations
137 def configure_node(testbed_instance, guid):
138 node = testbed_instance._elements[guid]
140 # Just inject configuration stuff
141 node.home_path = "nepi-node-%s" % (guid,)
142 node.ident_path = testbed_instance.sliceSSHKey
143 node.slicename = testbed_instance.slicename
145 # If we have only one candidate, simply use it
146 candidates = node.find_candidates(
147 filter_slice_id = testbed_instance.slice_id)
148 if len(candidates) == 1:
149 node.assign_node_id(iter(candidates).next())
151 # Do some validations
154 # TODO: this should be done in parallel in all nodes
155 node.install_dependencies()
157 def configure_application(testbed_instance, guid):
158 app = testbed_instance._elements[guid]
160 # Just inject configuration stuff
161 app.home_path = "nepi-app-%s" % (guid,)
162 app.ident_path = testbed_instance.sliceSSHKey
163 app.slicename = testbed_instance.slicename
165 # Do some validations
168 # Wait for dependencies
169 app.node.wait_dependencies()
174 ### Factory information ###
176 connector_types = dict({
178 "help": "Connector from node to applications",
184 "help": "Connector from node to network interfaces",
190 "help": "Connector from network interfaces to the internet",
196 "help": "Connector to a Node",
205 "from": (TESTBED_ID, NODE, "devs"),
206 "to": (TESTBED_ID, NODEIFACE, "node"),
207 "code": connect_node_iface_node,
211 "from": (TESTBED_ID, NODE, "devs"),
212 "to": (TESTBED_ID, TUNIFACE, "node"),
213 "code": connect_tun_iface_node,
217 "from": (TESTBED_ID, NODEIFACE, "inet"),
218 "to": (TESTBED_ID, INTERNET, "devs"),
219 "code": connect_node_iface_inet,
223 "from": (TESTBED_ID, NODE, "apps"),
224 "to": (TESTBED_ID, APPLICATION, "node"),
231 "forward_X11": dict({
232 "name": "forward_X11",
233 "help": "Forward x11 from main namespace to the node",
234 "type": Attribute.BOOL,
236 "flags": Attribute.DesignOnly,
237 "validation_function": validation.is_bool,
241 "help": "Constrain hostname during resource discovery. May use wildcards.",
242 "type": Attribute.STRING,
243 "flags": Attribute.DesignOnly,
244 "validation_function": validation.is_string,
246 "architecture": dict({
247 "name": "architecture",
248 "help": "Constrain architexture during resource discovery.",
249 "type": Attribute.ENUM,
250 "flags": Attribute.DesignOnly,
251 "allowed": ["x86_64",
253 "validation_function": validation.is_enum,
255 "operating_system": dict({
256 "name": "operatingSystem",
257 "help": "Constrain operating system during resource discovery.",
258 "type": Attribute.ENUM,
259 "flags": Attribute.DesignOnly,
265 "validation_function": validation.is_enum,
269 "help": "Constrain the PlanetLab site this node should reside on.",
270 "type": Attribute.ENUM,
271 "flags": Attribute.DesignOnly,
275 "validation_function": validation.is_enum,
279 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
280 "type": Attribute.BOOL,
282 "flags": Attribute.DesignOnly,
283 "validation_function": validation.is_bool,
285 "min_reliability": dict({
286 "name": "minReliability",
287 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
288 "type": Attribute.DOUBLE,
290 "flags": Attribute.DesignOnly,
291 "validation_function": validation.is_double,
293 "max_reliability": dict({
294 "name": "maxReliability",
295 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
296 "type": Attribute.DOUBLE,
298 "flags": Attribute.DesignOnly,
299 "validation_function": validation.is_double,
301 "min_bandwidth": dict({
302 "name": "minBandwidth",
303 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
304 "type": Attribute.DOUBLE,
306 "flags": Attribute.DesignOnly,
307 "validation_function": validation.is_double,
309 "max_bandwidth": dict({
310 "name": "maxBandwidth",
311 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
312 "type": Attribute.DOUBLE,
314 "flags": Attribute.DesignOnly,
315 "validation_function": validation.is_double,
321 "type": Attribute.BOOL,
323 "validation_function": validation.is_bool
327 "help": "This is the primary interface for the attached node",
328 "type": Attribute.BOOL,
330 "validation_function": validation.is_bool
332 "device_name": dict({
334 "help": "Device name",
335 "type": Attribute.STRING,
336 "flags": Attribute.DesignOnly,
337 "validation_function": validation.is_string
341 "help": "Maximum transmition unit for device",
342 "type": Attribute.INTEGER,
344 "validation_function": validation.is_integer_range(0,1500)
348 "help": "Network mask for the device (eg: 24 for /24 network)",
349 "type": Attribute.INTEGER,
350 "validation_function": validation.is_integer_range(8,24)
354 "help": "Enable SNAT (source NAT to the internet) no this device",
355 "type": Attribute.BOOL,
357 "validation_function": validation.is_bool
362 "help": "Command line string",
363 "type": Attribute.STRING,
364 "flags": Attribute.DesignOnly,
365 "validation_function": validation.is_string
369 "help": "System user",
370 "type": Attribute.BOOL,
371 "flags": Attribute.DesignOnly,
373 "validation_function": validation.is_bool
377 "help": "Standard input",
378 "type": Attribute.STRING,
379 "flags": Attribute.DesignOnly,
380 "validation_function": validation.is_string
385 "help": "Space-separated list of packages required to run the application",
386 "type": Attribute.STRING,
387 "flags": Attribute.DesignOnly,
388 "validation_function": validation.is_string
390 "build-depends": dict({
391 "name": "buildDepends",
392 "help": "Space-separated list of packages required to build the application",
393 "type": Attribute.STRING,
394 "flags": Attribute.DesignOnly,
395 "validation_function": validation.is_string
399 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
400 "Archives won't be expanded automatically.",
401 "type": Attribute.STRING,
402 "flags": Attribute.DesignOnly,
403 "validation_function": validation.is_string
407 "help": "Build commands to execute after deploying the sources. "
408 "Sources will be in the initial working folder. "
409 "Example: cd my-app && ./configure && make && make install.\n"
410 "Try to make the commands return with a nonzero exit code on error.",
411 "type": Attribute.STRING,
412 "flags": Attribute.DesignOnly,
413 "validation_function": validation.is_string
420 "help": "Standard output stream"
424 "help": "Application standard error",
428 "help": "Output of the build process",
432 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
434 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
436 factories_info = dict({
438 "allow_routes": False,
439 "help": "Virtualized Node (V-Server style)",
440 "category": "topology",
441 "create_function": create_node,
442 "preconfigure_function": configure_node,
455 "connector_types": ["devs", "apps"]
458 "allow_addresses": True,
459 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
460 "category": "devices",
461 "create_function": create_nodeiface,
462 "preconfigure_function": configure_nodeiface,
463 "box_attributes": [ ],
464 "connector_types": ["node", "inet"]
467 "allow_addresses": True,
468 "help": "Virtual TUN network interface",
469 "category": "devices",
470 "create_function": create_tuniface,
471 "preconfigure_function": configure_tuniface,
473 "up", "device_name", "mtu", "snat",
475 "connector_types": ["node"]
478 "help": "Generic executable command line application",
479 "category": "applications",
480 "create_function": create_application,
481 "start_function": start_application,
482 "status_function": status_application,
483 "stop_function": stop_application,
484 "configure_function": configure_application,
485 "box_attributes": ["command", "sudo", "stdin",
486 "depends", "build-depends", "build",
488 "connector_types": ["node"],
489 "traces": ["stdout", "stderr"]
492 "help": "Internet routing",
493 "category": "topology",
494 "create_function": create_internet,
495 "connector_types": ["devs"],
499 testbed_attributes = dict({
502 "help": "The name of the PlanetLab slice to use",
503 "type": Attribute.STRING,
504 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
505 "validation_function": validation.is_string
509 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
510 "type": Attribute.STRING,
511 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
512 "validation_function": validation.is_string
516 "help": "The PlanetLab user's password.",
517 "type": Attribute.STRING,
518 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
519 "validation_function": validation.is_string
521 "slice_ssh_key": dict({
522 "name": "sliceSSHKey",
523 "help": "The controller-local path to the slice user's ssh private key. "
524 "It is the user's responsability to deploy this file where the controller "
525 "will run, it won't be done automatically because it's sensitive information. "
526 "It is recommended that a NEPI-specific user be created for this purpose and "
527 "this purpose alone.",
528 "type": Attribute.STRING,
529 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
530 "validation_function": validation.is_string
534 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
536 def connector_types(self):
537 return connector_types
540 def connections(self):
544 def attributes(self):
552 def create_order(self):
556 def configure_order(self):
557 return configure_order
560 def factories_info(self):
561 return factories_info
564 def testbed_attributes(self):
565 return testbed_attributes