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() ))
37 ### Creation functions ###
39 def create_node(testbed_instance, guid):
40 parameters = testbed_instance._get_parameters(guid)
42 # create element with basic attributes
43 element = testbed_instance._make_node(parameters)
45 # add constraint on number of (real) interfaces
46 # by counting connected devices
47 dev_guids = testbed_instance.get_connected(guid, "node", "devs")
48 num_open_ifaces = sum( # count True values
49 TUNEIFACE == testbed_instance._get_factory_id(guid)
50 for guid in dev_guids )
51 element.min_num_external_ifaces = num_open_ifaces
53 testbed_instance.elements[guid] = element
55 def create_nodeiface(testbed_instance, guid):
56 parameters = testbed_instance._get_parameters(guid)
57 element = testbed_instance._make_node_iface(parameters)
58 testbed_instance.elements[guid] = element
60 def create_tuniface(testbed_instance, guid):
61 parameters = testbed_instance._get_parameters(guid)
62 element = testbed_instance._make_tun_iface(parameters)
63 testbed_instance.elements[guid] = element
65 def create_application(testbed_instance, guid):
66 parameters = testbed_instance._get_parameters(guid)
67 element = testbed_instance._make_application(parameters)
68 testbed_instance.elements[guid] = element
70 def create_internet(testbed_instance, guid):
71 parameters = testbed_instance._get_parameters(guid)
72 element = testbed_instance._make_internet(parameters)
73 testbed_instance.elements[guid] = element
75 ### Start/Stop functions ###
77 def start_application(testbed_instance, guid):
78 parameters = testbed_instance._get_parameters(guid)
79 traces = testbed_instance._get_traces(guid)
80 app = testbed_instance.elements[guid]
82 app.stdout = "stdout" in traces
83 app.stderr = "stderr" in traces
84 app.buildlog = "buildlog" in traces
88 def stop_application(testbed_instance, guid):
89 app = testbed_instance.elements[guid]
92 ### Status functions ###
94 def status_application(testbed_instance, guid):
95 if guid not in testbed_instance.elements.keys():
96 return STATUS_NOT_STARTED
98 app = testbed_instance.elements[guid]
101 ### Configure functions ###
103 def configure_nodeiface(testbed_instance, guid):
104 element = testbed_instance._elements[guid]
106 # Cannot explicitly configure addresses
107 if guid in testbed_instance._add_address:
108 del testbed_instance._add_address[guid]
111 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
112 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
113 siblings = [ self._element[dev_guid]
114 for dev_guid in dev_guids
115 if dev_guid != guid ]
117 # Fetch address from PLC api
118 element.pick_iface(siblings)
120 # Do some validations
123 def configure_tuniface(testbed_instance, guid):
124 element = testbed_instance._elements[guid]
125 if not guid in testbed_instance._add_address:
128 addresses = testbed_instance._add_address[guid]
129 for address in addresses:
130 (address, netprefix, broadcast) = address
131 raise NotImplementedError, "C'mon... TUNs are hard..."
133 # Do some validations
136 def configure_node(testbed_instance, guid):
137 node = testbed_instance._elements[guid]
139 # Just inject configuration stuff
140 node.home_path = "nepi-node-%s" % (guid,)
141 node.ident_path = testbed_instance.sliceSSHKey
142 node.slicename = testbed_instance.slicename
144 # If we have only one candidate, simply use it
145 candidates = node.find_candidates(
146 filter_slice_id = testbed_instance.slice_id)
147 if len(candidates) == 1:
148 node.assign_node_id(iter(candidates).next())
150 # Do some validations
153 # TODO: this should be done in parallel in all nodes
154 node.install_dependencies()
156 def configure_application(testbed_instance, guid):
157 app = testbed_instance._elements[guid]
159 # Just inject configuration stuff
160 app.home_path = "nepi-app-%s" % (guid,)
161 app.ident_path = testbed_instance.sliceSSHKey
162 app.slicename = testbed_instance.slicename
164 # Do some validations
167 # Wait for dependencies
168 app.node.wait_dependencies()
173 ### Factory information ###
175 connector_types = dict({
177 "help": "Connector from node to applications",
183 "help": "Connector from node to network interfaces",
189 "help": "Connector from network interfaces to the internet",
195 "help": "Connector to a Node",
204 "from": (TESTBED_ID, NODE, "devs"),
205 "to": (TESTBED_ID, NODEIFACE, "node"),
206 "code": connect_node_iface_node,
210 "from": (TESTBED_ID, NODE, "devs"),
211 "to": (TESTBED_ID, TUNIFACE, "node"),
212 "code": connect_tun_iface_node,
216 "from": (TESTBED_ID, NODEIFACE, "inet"),
217 "to": (TESTBED_ID, INTERNET, "devs"),
218 "code": connect_node_iface_inet,
222 "from": (TESTBED_ID, NODE, "apps"),
223 "to": (TESTBED_ID, APPLICATION, "node"),
230 "forward_X11": dict({
231 "name": "forward_X11",
232 "help": "Forward x11 from main namespace to the node",
233 "type": Attribute.BOOL,
235 "flags": Attribute.DesignOnly,
236 "validation_function": validation.is_bool,
240 "help": "Constrain hostname during resource discovery. May use wildcards.",
241 "type": Attribute.STRING,
242 "flags": Attribute.DesignOnly,
243 "validation_function": validation.is_string,
245 "architecture": dict({
246 "name": "architecture",
247 "help": "Constrain architexture during resource discovery.",
248 "type": Attribute.ENUM,
249 "flags": Attribute.DesignOnly,
250 "allowed": ["x86_64",
252 "validation_function": validation.is_enum,
254 "operating_system": dict({
255 "name": "operatingSystem",
256 "help": "Constrain operating system during resource discovery.",
257 "type": Attribute.ENUM,
258 "flags": Attribute.DesignOnly,
264 "validation_function": validation.is_enum,
268 "help": "Constrain the PlanetLab site this node should reside on.",
269 "type": Attribute.ENUM,
270 "flags": Attribute.DesignOnly,
274 "validation_function": validation.is_enum,
278 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
279 "type": Attribute.BOOL,
281 "flags": Attribute.DesignOnly,
282 "validation_function": validation.is_bool,
284 "min_reliability": dict({
285 "name": "minReliability",
286 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
287 "type": Attribute.DOUBLE,
289 "flags": Attribute.DesignOnly,
290 "validation_function": validation.is_double,
292 "max_reliability": dict({
293 "name": "maxReliability",
294 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
295 "type": Attribute.DOUBLE,
297 "flags": Attribute.DesignOnly,
298 "validation_function": validation.is_double,
300 "min_bandwidth": dict({
301 "name": "minBandwidth",
302 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
303 "type": Attribute.DOUBLE,
305 "flags": Attribute.DesignOnly,
306 "validation_function": validation.is_double,
308 "max_bandwidth": dict({
309 "name": "maxBandwidth",
310 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
311 "type": Attribute.DOUBLE,
313 "flags": Attribute.DesignOnly,
314 "validation_function": validation.is_double,
320 "type": Attribute.BOOL,
322 "validation_function": validation.is_bool
326 "help": "This is the primary interface for the attached node",
327 "type": Attribute.BOOL,
329 "validation_function": validation.is_bool
331 "device_name": dict({
333 "help": "Device name",
334 "type": Attribute.STRING,
335 "flags": Attribute.DesignOnly,
336 "validation_function": validation.is_string
340 "help": "Maximum transmition unit for device",
341 "type": Attribute.INTEGER,
343 "validation_function": validation.is_integer_range(0,1500)
347 "help": "Network mask for the device (eg: 24 for /24 network)",
348 "type": Attribute.INTEGER,
349 "validation_function": validation.is_integer_range(8,24)
353 "help": "Enable SNAT (source NAT to the internet) no this device",
354 "type": Attribute.BOOL,
356 "validation_function": validation.is_bool
361 "help": "Command line string",
362 "type": Attribute.STRING,
363 "flags": Attribute.DesignOnly,
364 "validation_function": validation.is_string
368 "help": "System user",
369 "type": Attribute.BOOL,
370 "flags": Attribute.DesignOnly,
372 "validation_function": validation.is_bool
376 "help": "Standard input",
377 "type": Attribute.STRING,
378 "flags": Attribute.DesignOnly,
379 "validation_function": validation.is_string
384 "help": "Space-separated list of packages required to run the application",
385 "type": Attribute.STRING,
386 "flags": Attribute.DesignOnly,
387 "validation_function": validation.is_string
389 "build-depends": dict({
390 "name": "buildDepends",
391 "help": "Space-separated list of packages required to build the application",
392 "type": Attribute.STRING,
393 "flags": Attribute.DesignOnly,
394 "validation_function": validation.is_string
398 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
399 "Archives won't be expanded automatically.",
400 "type": Attribute.STRING,
401 "flags": Attribute.DesignOnly,
402 "validation_function": validation.is_string
406 "help": "Build commands to execute after deploying the sources. "
407 "Sources will be in the initial working folder. "
408 "Example: cd my-app && ./configure && make && make install.\n"
409 "Try to make the commands return with a nonzero exit code on error.",
410 "type": Attribute.STRING,
411 "flags": Attribute.DesignOnly,
412 "validation_function": validation.is_string
419 "help": "Standard output stream"
423 "help": "Application standard error",
427 "help": "Output of the build process",
431 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
433 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
435 factories_info = dict({
437 "allow_routes": False,
438 "help": "Virtualized Node (V-Server style)",
439 "category": "topology",
440 "create_function": create_node,
441 "preconfigure_function": configure_node,
454 "connector_types": ["devs", "apps"]
457 "allow_addresses": True,
458 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
459 "category": "devices",
460 "create_function": create_nodeiface,
461 "preconfigure_function": configure_nodeiface,
462 "box_attributes": [ ],
463 "connector_types": ["node", "inet"]
466 "allow_addresses": True,
467 "help": "Virtual TUN network interface",
468 "category": "devices",
469 "create_function": create_tuniface,
470 "preconfigure_function": configure_tuniface,
472 "up", "device_name", "mtu", "snat",
474 "connector_types": ["node"]
477 "help": "Generic executable command line application",
478 "category": "applications",
479 "create_function": create_application,
480 "start_function": start_application,
481 "status_function": status_application,
482 "stop_function": stop_application,
483 "configure_function": configure_application,
484 "box_attributes": ["command", "sudo", "stdin"],
485 "connector_types": ["node"],
486 "traces": ["stdout", "stderr"]
489 "help": "Internet routing",
490 "category": "topology",
491 "create_function": create_internet,
492 "connector_types": ["devs"],
496 testbed_attributes = dict({
499 "help": "The name of the PlanetLab slice to use",
500 "type": Attribute.STRING,
501 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
502 "validation_function": validation.is_string
506 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
507 "type": Attribute.STRING,
508 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
509 "validation_function": validation.is_string
513 "help": "The PlanetLab user's password.",
514 "type": Attribute.STRING,
515 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
516 "validation_function": validation.is_string
518 "slice_ssh_key": dict({
519 "name": "sliceSSHKey",
520 "help": "The controller-local path to the slice user's ssh private key. "
521 "It is the user's responsability to deploy this file where the controller "
522 "will run, it won't be done automatically because it's sensitive information. "
523 "It is recommended that a NEPI-specific user be created for this purpose and "
524 "this purpose alone.",
525 "type": Attribute.STRING,
526 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
527 "validation_function": validation.is_string
531 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
533 def connector_types(self):
534 return connector_types
537 def connections(self):
541 def attributes(self):
549 def create_order(self):
553 def configure_order(self):
554 return configure_order
557 def factories_info(self):
558 return factories_info
561 def testbed_attributes(self):
562 return testbed_attributes