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):
33 ### Creation functions ###
35 def create_node(testbed_instance, guid):
36 parameters = testbed_instance._get_parameters(guid)
38 # create element with basic attributes
39 element = testbed_instance._make_node(parameters)
41 # add constraint on number of (real) interfaces
42 # by counting connected devices
43 dev_guids = testbed_instance.get_connected(guid, "node", "devs")
44 num_open_ifaces = sum( # count True values
45 TUNEIFACE == testbed_instance._get_factory_id(guid)
46 for guid in dev_guids )
47 element.min_num_external_ifaces = num_open_ifaces
49 testbed_instance.elements[guid] = element
51 def create_nodeiface(testbed_instance, guid):
52 parameters = testbed_instance._get_parameters(guid)
53 element = testbed_instance._make_node_iface(parameters)
54 testbed_instance.elements[guid] = element
56 def create_tuniface(testbed_instance, guid):
57 parameters = testbed_instance._get_parameters(guid)
58 element = testbed_instance._make_tun_iface(parameters)
59 testbed_instance.elements[guid] = element
61 def create_application(testbed_instance, guid):
62 parameters = testbed_instance._get_parameters(guid)
63 element = testbed_instance._make_application(parameters)
64 testbed_instance.elements[guid] = element
66 def create_internet(testbed_instance, guid):
67 parameters = testbed_instance._get_parameters(guid)
68 element = testbed_instance._make_internet(parameters)
69 testbed_instance.elements[guid] = element
71 ### Start/Stop functions ###
73 def start_application(testbed_instance, guid):
74 parameters = testbed_instance._get_parameters(guid)
75 traces = testbed_instance._get_traces(guid)
76 app = testbed_instance.elements[guid]
78 app.stdout = "stdout" in traces
79 app.stderr = "stderr" in traces
83 def stop_application(testbed_instance, guid):
84 app = testbed_instance.elements[guid]
87 ### Status functions ###
89 def status_application(testbed_instance, guid):
90 if guid not in testbed_instance.elements.keys():
91 return STATUS_NOT_STARTED
93 app = testbed_instance.elements[guid]
96 ### Configure functions ###
98 def configure_nodeiface(testbed_instance, guid):
99 element = testbed_instance._elements[guid]
101 # Cannot explicitly configure addresses
102 if guid in testbed_instance._add_address:
103 del testbed_instance._add_address[guid]
106 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
107 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
108 siblings = [ self._element[dev_guid]
109 for dev_guid in dev_guids
110 if dev_guid != guid ]
112 # Fetch address from PLC api
113 element.pick_iface(siblings)
115 # Do some validations
118 def configure_tuniface(testbed_instance, guid):
119 element = testbed_instance._elements[guid]
120 if not guid in testbed_instance._add_address:
123 addresses = testbed_instance._add_address[guid]
124 for address in addresses:
125 (address, netprefix, broadcast) = address
126 raise NotImplementedError, "C'mon... TUNs are hard..."
128 # Do some validations
131 def configure_node(testbed_instance, guid):
132 node = testbed_instance._elements[guid]
134 # If we have only one candidate, simply use it
135 candidates = node.find_candidates(
136 filter_slice_id = testbed_instance.slice_id)
137 if len(candidates) == 1:
138 node.assign_node_id(iter(candidates).next())
140 # Do some validations
143 def configure_application(testbed_instance, guid):
144 app = testbed_instance._elements[guid]
146 # Just inject configuration stuff
147 app.home_path = "nepi-app-%s" % (guid,)
148 app.ident_path = testbed_instance.sliceSSHKey
149 app.slicename = testbed_instance.slicename
151 # Do some validations
157 ### Factory information ###
159 connector_types = dict({
161 "help": "Connector from node to applications",
167 "help": "Connector from node to network interfaces",
173 "help": "Connector from network interfaces to the internet",
179 "help": "Connector to a Node",
188 "from": (TESTBED_ID, NODE, "devs"),
189 "to": (TESTBED_ID, NODEIFACE, "node"),
190 "code": connect_node_iface_node,
194 "from": (TESTBED_ID, NODE, "devs"),
195 "to": (TESTBED_ID, TUNIFACE, "node"),
196 "code": connect_tun_iface_node,
200 "from": (TESTBED_ID, NODEIFACE, "inet"),
201 "to": (TESTBED_ID, INTERNET, "devs"),
202 "code": connect_node_iface_inet,
206 "from": (TESTBED_ID, NODE, "apps"),
207 "to": (TESTBED_ID, APPLICATION, "node"),
214 "forward_X11": dict({
215 "name": "forward_X11",
216 "help": "Forward x11 from main namespace to the node",
217 "type": Attribute.BOOL,
219 "flags": Attribute.DesignOnly,
220 "validation_function": validation.is_bool,
224 "help": "Constrain hostname during resource discovery. May use wildcards.",
225 "type": Attribute.STRING,
226 "flags": Attribute.DesignOnly,
227 "validation_function": validation.is_string,
229 "architecture": dict({
230 "name": "architecture",
231 "help": "Constrain architexture during resource discovery.",
232 "type": Attribute.ENUM,
233 "flags": Attribute.DesignOnly,
234 "allowed": ["x86_64",
236 "validation_function": validation.is_enum,
238 "operating_system": dict({
239 "name": "operatingSystem",
240 "help": "Constrain operating system during resource discovery.",
241 "type": Attribute.ENUM,
242 "flags": Attribute.DesignOnly,
248 "validation_function": validation.is_enum,
252 "help": "Constrain the PlanetLab site this node should reside on.",
253 "type": Attribute.ENUM,
254 "flags": Attribute.DesignOnly,
258 "validation_function": validation.is_enum,
262 "help": "Enable emulation on this node. Enables NetfilterRoutes, bridges, and a host of other functionality.",
263 "type": Attribute.BOOL,
265 "flags": Attribute.DesignOnly,
266 "validation_function": validation.is_bool,
268 "min_reliability": dict({
269 "name": "minReliability",
270 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
271 "type": Attribute.DOUBLE,
273 "flags": Attribute.DesignOnly,
274 "validation_function": validation.is_double,
276 "max_reliability": dict({
277 "name": "maxReliability",
278 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
279 "type": Attribute.DOUBLE,
281 "flags": Attribute.DesignOnly,
282 "validation_function": validation.is_double,
284 "min_bandwidth": dict({
285 "name": "minBandwidth",
286 "help": "Constrain available bandwidth 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_bandwidth": dict({
293 "name": "maxBandwidth",
294 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
295 "type": Attribute.DOUBLE,
297 "flags": Attribute.DesignOnly,
298 "validation_function": validation.is_double,
304 "type": Attribute.BOOL,
306 "validation_function": validation.is_bool
310 "help": "This is the primary interface for the attached node",
311 "type": Attribute.BOOL,
313 "validation_function": validation.is_bool
315 "device_name": dict({
317 "help": "Device name",
318 "type": Attribute.STRING,
319 "flags": Attribute.DesignOnly,
320 "validation_function": validation.is_string
324 "help": "Maximum transmition unit for device",
325 "type": Attribute.INTEGER,
327 "validation_function": validation.is_integer_range(0,1500)
331 "help": "Network mask for the device (eg: 24 for /24 network)",
332 "type": Attribute.INTEGER,
333 "validation_function": validation.is_integer_range(8,24)
337 "help": "Enable SNAT (source NAT to the internet) no this device",
338 "type": Attribute.BOOL,
340 "validation_function": validation.is_bool
345 "help": "Command line string",
346 "type": Attribute.STRING,
347 "flags": Attribute.DesignOnly,
348 "validation_function": validation.is_string
352 "help": "System user",
353 "type": Attribute.BOOL,
354 "flags": Attribute.DesignOnly,
356 "validation_function": validation.is_bool
360 "help": "Standard input",
361 "type": Attribute.STRING,
362 "flags": Attribute.DesignOnly,
363 "validation_function": validation.is_string
370 "help": "Standard output stream"
374 "help": "Application standard error",
378 create_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
380 configure_order = [ INTERNET, NODE, NODEIFACE, TUNIFACE, APPLICATION ]
382 factories_info = dict({
384 "allow_routes": False,
385 "help": "Virtualized Node (V-Server style)",
386 "category": "topology",
387 "create_function": create_node,
388 "preconfigure_function": configure_node,
401 "connector_types": ["devs", "apps"]
404 "allow_addresses": True,
405 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
406 "category": "devices",
407 "create_function": create_nodeiface,
408 "preconfigure_function": configure_nodeiface,
409 "box_attributes": [ ],
410 "connector_types": ["node", "inet"]
413 "allow_addresses": True,
414 "help": "Virtual TUN network interface",
415 "category": "devices",
416 "create_function": create_tuniface,
417 "preconfigure_function": configure_tuniface,
419 "up", "device_name", "mtu", "snat",
421 "connector_types": ["node"]
424 "help": "Generic executable command line application",
425 "category": "applications",
426 "create_function": create_application,
427 "start_function": start_application,
428 "status_function": status_application,
429 "stop_function": stop_application,
430 "configure_function": configure_application,
431 "box_attributes": ["command", "sudo", "stdin"],
432 "connector_types": ["node"],
433 "traces": ["stdout", "stderr"]
436 "help": "Internet routing",
437 "category": "topology",
438 "create_function": create_internet,
439 "connector_types": ["devs"],
443 testbed_attributes = dict({
446 "help": "The name of the PlanetLab slice to use",
447 "type": Attribute.STRING,
448 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
449 "validation_function": validation.is_string
453 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
454 "type": Attribute.STRING,
455 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
456 "validation_function": validation.is_string
460 "help": "The PlanetLab user's password.",
461 "type": Attribute.STRING,
462 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
463 "validation_function": validation.is_string
465 "slice_ssh_key": dict({
466 "name": "sliceSSHKey",
467 "help": "The controller-local path to the slice user's ssh private key. "
468 "It is the user's responsability to deploy this file where the controller "
469 "will run, it won't be done automatically because it's sensitive information. "
470 "It is recommended that a NEPI-specific user be created for this purpose and "
471 "this purpose alone.",
472 "type": Attribute.STRING,
473 "flags": Attribute.DesignOnly | Attribute.HasNoDefaultValue,
474 "validation_function": validation.is_string
478 class VersionedMetadataInfo(metadata.VersionedMetadataInfo):
480 def connector_types(self):
481 return connector_types
484 def connections(self):
488 def attributes(self):
496 def create_order(self):
500 def configure_order(self):
501 return configure_order
504 def factories_info(self):
505 return factories_info
508 def testbed_attributes(self):
509 return testbed_attributes