2 # -*- coding: utf-8 -*-
6 from constants import TESTBED_ID, TESTBED_VERSION
7 from nepi.core import metadata
8 from nepi.core.metadata import Parallel
9 from nepi.core.attributes import Attribute
10 from nepi.util import tags, validation
11 from nepi.util.constants import ApplicationStatus as AS, \
12 FactoryCategories as FC, \
13 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP, \
14 DeploymentConfiguration as DC
21 NODEIFACE = "NodeInterface"
22 TUNIFACE = "TunInterface"
23 TAPIFACE = "TapInterface"
24 APPLICATION = "Application"
25 DEPENDENCY = "Dependency"
26 NEPIDEPENDENCY = "NepiDependency"
27 NS3DEPENDENCY = "NS3Dependency"
31 PL_TESTBED_ID = "planetlab"
34 ### Custom validation functions ###
35 def is_addrlist(attribute, value):
36 if not validation.is_string(attribute, value):
43 components = value.split(',')
45 for component in components:
47 addr, mask = component.split('/',1)
49 addr, mask = component, '32'
51 if mask is not None and not (mask and mask.isdigit()):
52 # No empty or nonnumeric masks
55 if not validation.is_ip4_address(attribute, addr):
56 # Address part must be ipv4
61 def is_portlist(attribute, value):
62 if not validation.is_string(attribute, value):
69 components = value.split(',')
71 for component in components:
73 pfrom, pto = component.split('-',1)
75 pfrom = pto = component
77 if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit():
78 # No empty or nonnumeric ports
84 ### Connection functions ####
86 def connect_node_iface_node(testbed_instance, node_guid, iface_guid):
87 node = testbed_instance._elements[node_guid]
88 iface = testbed_instance._elements[iface_guid]
91 def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid):
92 iface = testbed_instance._elements[iface_guid]
93 iface.has_internet = True
95 def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
96 node = testbed_instance._elements[node_guid]
97 iface = testbed_instance._elements[iface_guid]
99 node.required_vsys.update(('fd_tuntap', 'vif_up', 'vif_down'))
100 node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc'))
102 def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
103 iface = testbed_instance._elements[iface_guid]
104 peer_iface = testbed_instance._elements[peer_iface_guid]
105 iface.peer_iface = peer_iface
107 iface.tun_proto = proto
108 iface.tun_key = peer_iface.tun_key
110 def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data):
111 iface = testbed_instance._elements[iface_guid]
112 iface.peer_iface = None
113 iface.peer_addr = peer_iface_data.get("tun_addr")
114 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
115 iface.peer_port = peer_iface_data.get("tun_port")
116 iface.peer_cipher = peer_iface_data.get("tun_cipher")
117 iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
118 iface.tun_proto = proto
120 preconfigure_tuniface(testbed_instance, iface_guid)
122 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
123 # refresh (refreshable) attributes for second-phase
124 iface = testbed_instance._elements[iface_guid]
125 iface.peer_addr = peer_iface_data.get("tun_addr")
126 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
127 iface.peer_port = peer_iface_data.get("tun_port")
128 iface.peer_cipher = peer_iface_data.get("tun_cipher")
130 postconfigure_tuniface(testbed_instance, iface_guid)
132 def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
133 crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
134 crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
136 def connect_dep(testbed_instance, node_guid, app_guid):
137 node = testbed_instance._elements[node_guid]
138 app = testbed_instance._elements[app_guid]
142 node.required_packages.update(set(
143 app.depends.split() ))
146 if app.home_path and app.home_path not in node.pythonpath:
147 node.pythonpath.append(app.home_path)
150 for envkey, envval in app.env.iteritems():
151 envval = app._replace_paths(envval)
152 node.env[envkey].append(envval)
155 node.rpmFusion = True
157 def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid):
158 node = testbed_instance._elements[node_guid]
159 netpipe = testbed_instance._elements[netpipe_guid]
161 node.required_vsys.add('ipfw-be')
162 node.required_packages.add('ipfwslice')
165 ### Creation functions ###
167 def create_node(testbed_instance, guid):
168 parameters = testbed_instance._get_parameters(guid)
170 # create element with basic attributes
171 element = testbed_instance._make_node(parameters)
173 # add constraint on number of (real) interfaces
174 # by counting connected devices
175 dev_guids = testbed_instance.get_connected(guid, "devs", "node")
176 num_open_ifaces = sum( # count True values
177 NODEIFACE == testbed_instance._get_factory_id(guid)
178 for guid in dev_guids )
179 element.min_num_external_ifaces = num_open_ifaces
181 # require vroute vsys if we have routes to set up
182 routes = testbed_instance._add_route.get(guid)
184 element.required_vsys.add("vroute")
186 testbed_instance.elements[guid] = element
188 def create_nodeiface(testbed_instance, guid):
189 parameters = testbed_instance._get_parameters(guid)
190 element = testbed_instance._make_node_iface(parameters)
191 testbed_instance.elements[guid] = element
193 def create_tuniface(testbed_instance, guid):
194 parameters = testbed_instance._get_parameters(guid)
195 element = testbed_instance._make_tun_iface(parameters)
197 # Set custom addresses, if there are any already
198 # Setting this early helps set up P2P links
199 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
200 addresses = testbed_instance._add_address[guid]
201 for address in addresses:
202 (address, netprefix, broadcast) = address
203 element.add_address(address, netprefix, broadcast)
205 testbed_instance.elements[guid] = element
207 def create_tapiface(testbed_instance, guid):
208 parameters = testbed_instance._get_parameters(guid)
209 element = testbed_instance._make_tap_iface(parameters)
211 # Set custom addresses, if there are any already
212 # Setting this early helps set up P2P links
213 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
214 addresses = testbed_instance._add_address[guid]
215 for address in addresses:
216 (address, netprefix, broadcast) = address
217 element.add_address(address, netprefix, broadcast)
219 testbed_instance.elements[guid] = element
221 def create_application(testbed_instance, guid):
222 parameters = testbed_instance._get_parameters(guid)
223 element = testbed_instance._make_application(parameters)
225 # Just inject configuration stuff
226 element.home_path = "nepi-app-%s" % (guid,)
228 testbed_instance.elements[guid] = element
230 def create_dependency(testbed_instance, guid):
231 parameters = testbed_instance._get_parameters(guid)
232 element = testbed_instance._make_dependency(parameters)
234 # Just inject configuration stuff
235 element.home_path = "nepi-dep-%s" % (guid,)
237 testbed_instance.elements[guid] = element
239 def create_nepi_dependency(testbed_instance, guid):
240 parameters = testbed_instance._get_parameters(guid)
241 element = testbed_instance._make_nepi_dependency(parameters)
243 # Just inject configuration stuff
244 element.home_path = "nepi-nepi-%s" % (guid,)
246 testbed_instance.elements[guid] = element
248 def create_ns3_dependency(testbed_instance, guid):
249 parameters = testbed_instance._get_parameters(guid)
250 element = testbed_instance._make_ns3_dependency(parameters)
252 # Just inject configuration stuff
253 element.home_path = "nepi-ns3-%s" % (guid,)
255 testbed_instance.elements[guid] = element
257 def create_internet(testbed_instance, guid):
258 parameters = testbed_instance._get_parameters(guid)
259 element = testbed_instance._make_internet(parameters)
260 testbed_instance.elements[guid] = element
262 def create_netpipe(testbed_instance, guid):
263 parameters = testbed_instance._get_parameters(guid)
264 element = testbed_instance._make_netpipe(parameters)
265 testbed_instance.elements[guid] = element
267 ### Start/Stop functions ###
269 def start_application(testbed_instance, guid):
270 parameters = testbed_instance._get_parameters(guid)
271 traces = testbed_instance._get_traces(guid)
272 app = testbed_instance.elements[guid]
274 app.stdout = "stdout" in traces
275 app.stderr = "stderr" in traces
276 app.buildlog = "buildlog" in traces
280 def stop_application(testbed_instance, guid):
281 app = testbed_instance.elements[guid]
284 ### Status functions ###
286 def status_application(testbed_instance, guid):
287 if guid not in testbed_instance.elements.keys():
288 return AS.STATUS_NOT_STARTED
290 app = testbed_instance.elements[guid]
293 ### Configure functions ###
295 def configure_nodeiface(testbed_instance, guid):
296 element = testbed_instance._elements[guid]
298 # Cannot explicitly configure addresses
299 if guid in testbed_instance._add_address:
300 raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
303 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
304 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
305 siblings = [ self._element[dev_guid]
306 for dev_guid in dev_guids
307 if dev_guid != guid ]
309 # Fetch address from PLC api
310 element.pick_iface(siblings)
312 # Do some validations
315 def preconfigure_tuniface(testbed_instance, guid):
316 element = testbed_instance._elements[guid]
318 # Set custom addresses if any, and if not set already
319 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
320 addresses = testbed_instance._add_address[guid]
321 for address in addresses:
322 (address, netprefix, broadcast) = address
323 element.add_address(address, netprefix, broadcast)
325 # Link to external interface, if any
326 for iface in testbed_instance._elements.itervalues():
327 if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
328 element.external_iface = iface
331 # Set standard TUN attributes
332 if (not element.tun_addr or not element.tun_port) and element.external_iface:
333 element.tun_addr = element.external_iface.address
334 element.tun_port = testbed_instance.tapPortBase + int(guid)
337 traces = testbed_instance._get_traces(guid)
338 for capmode in ('pcap', 'packets'):
339 if capmode in traces:
340 element.capture = capmode
343 element.capture = False
345 # Do some validations
349 if element.peer_proto:
350 if element.peer_iface and isinstance(element.peer_iface, testbed_instance._interfaces.TunIface):
352 listening = id(element) < id(element.peer_iface)
355 if not element.tun_addr or not element.tun_port:
357 elif not element.peer_addr or not element.peer_port:
360 # both have addresses...
361 # ...the one with the lesser address listens
362 listening = element.tun_addr < element.peer_addr
367 def postconfigure_tuniface(testbed_instance, guid):
368 element = testbed_instance._elements[guid]
373 def wait_tuniface(testbed_instance, guid):
374 element = testbed_instance._elements[guid]
377 element.async_launch_wait()
380 def configure_node(testbed_instance, guid):
381 node = testbed_instance._elements[guid]
383 # Just inject configuration stuff
384 node.home_path = "nepi-node-%s" % (guid,)
385 node.ident_path = testbed_instance.sliceSSHKey
386 node.slicename = testbed_instance.slicename
388 # Do some validations
391 # this will be done in parallel in all nodes
392 # this call only spawns the process
393 node.install_dependencies()
395 def configure_node_routes(testbed_instance, guid):
396 node = testbed_instance._elements[guid]
397 routes = testbed_instance._add_route.get(guid)
401 for dev_guid in testbed_instance.get_connected(guid, "devs", "node")
402 for dev in ( testbed_instance._elements.get(dev_guid) ,)
403 if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ]
405 node.configure_routes(routes, devs)
407 def configure_application(testbed_instance, guid):
408 app = testbed_instance._elements[guid]
410 # Do some validations
413 # Wait for dependencies
414 app.node.wait_dependencies()
419 def configure_dependency(testbed_instance, guid):
420 dep = testbed_instance._elements[guid]
422 # Do some validations
425 # Wait for dependencies
426 dep.node.wait_dependencies()
431 def configure_netpipe(testbed_instance, guid):
432 netpipe = testbed_instance._elements[guid]
434 # Do some validations
437 # Wait for dependencies
438 netpipe.node.wait_dependencies()
443 ### Factory information ###
445 connector_types = dict({
447 "help": "Connector from node to applications",
453 "help": "Connector from node to network interfaces",
459 "help": "Connector from node to application dependencies "
460 "(packages and applications that need to be installed)",
466 "help": "Connector from network interfaces to the internet",
472 "help": "Connector to a Node",
478 "help": "Connector to a NetPipe",
485 "help": "ip-ip tunneling over TCP link",
491 "help": "ip-ip tunneling over UDP datagrams",
497 "help": "IP or Ethernet tunneling using the GRE protocol",
503 "help": "TUN device file descriptor provider",
512 "from": (TESTBED_ID, NODE, "devs"),
513 "to": (TESTBED_ID, NODEIFACE, "node"),
514 "init_code": connect_node_iface_node,
518 "from": (TESTBED_ID, NODE, "devs"),
519 "to": (TESTBED_ID, TUNIFACE, "node"),
520 "init_code": connect_tun_iface_node,
524 "from": (TESTBED_ID, NODE, "devs"),
525 "to": (TESTBED_ID, TAPIFACE, "node"),
526 "init_code": connect_tun_iface_node,
530 "from": (TESTBED_ID, NODEIFACE, "inet"),
531 "to": (TESTBED_ID, INTERNET, "devs"),
532 "init_code": connect_node_iface_inet,
536 "from": (TESTBED_ID, NODE, "apps"),
537 "to": (TESTBED_ID, APPLICATION, "node"),
538 "init_code": connect_dep,
542 "from": (TESTBED_ID, NODE, "deps"),
543 "to": (TESTBED_ID, DEPENDENCY, "node"),
544 "init_code": connect_dep,
548 "from": (TESTBED_ID, NODE, "deps"),
549 "to": (TESTBED_ID, NEPIDEPENDENCY, "node"),
550 "init_code": connect_dep,
554 "from": (TESTBED_ID, NODE, "deps"),
555 "to": (TESTBED_ID, NS3DEPENDENCY, "node"),
556 "init_code": connect_dep,
560 "from": (TESTBED_ID, NODE, "pipes"),
561 "to": (TESTBED_ID, NETPIPE, "node"),
562 "init_code": connect_node_netpipe,
566 "from": (TESTBED_ID, TUNIFACE, "tcp"),
567 "to": (TESTBED_ID, TUNIFACE, "tcp"),
568 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
572 "from": (TESTBED_ID, TUNIFACE, "udp"),
573 "to": (TESTBED_ID, TUNIFACE, "udp"),
574 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
578 "from": (TESTBED_ID, TUNIFACE, "gre"),
579 "to": (TESTBED_ID, TUNIFACE, "gre"),
580 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
584 "from": (TESTBED_ID, TAPIFACE, "tcp"),
585 "to": (TESTBED_ID, TAPIFACE, "tcp"),
586 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
590 "from": (TESTBED_ID, TAPIFACE, "udp"),
591 "to": (TESTBED_ID, TAPIFACE, "udp"),
592 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
596 "from": (TESTBED_ID, TAPIFACE, "gre"),
597 "to": (TESTBED_ID, TAPIFACE, "gre"),
598 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
602 "from": (TESTBED_ID, TUNIFACE, "tcp"),
603 "to": (None, None, "tcp"),
604 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
605 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
609 "from": (TESTBED_ID, TUNIFACE, "udp"),
610 "to": (None, None, "udp"),
611 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
612 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
616 "from": (TESTBED_ID, TUNIFACE, "fd->"),
617 "to": (None, None, "->fd"),
618 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
622 "from": (TESTBED_ID, TUNIFACE, "gre"),
623 "to": (None, None, "gre"),
624 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
628 "from": (TESTBED_ID, TAPIFACE, "tcp"),
629 "to": (None, None, "tcp"),
630 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
631 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
635 "from": (TESTBED_ID, TAPIFACE, "udp"),
636 "to": (None, None, "udp"),
637 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
638 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
642 "from": (TESTBED_ID, TAPIFACE, "fd->"),
643 "to": (None, None, "->fd"),
644 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
647 # EGRE is an extension of PlanetLab, so we can't connect externally
648 # if the other testbed isn't another PlanetLab
650 "from": (TESTBED_ID, TAPIFACE, "gre"),
651 "to": (TESTBED_ID, None, "gre"),
652 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
658 "forward_X11": dict({
659 "name": "forward_X11",
660 "help": "Forward x11 from main namespace to the node",
661 "type": Attribute.BOOL,
663 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
664 "validation_function": validation.is_bool,
668 "help": "Constrain hostname during resource discovery. May use wildcards.",
669 "type": Attribute.STRING,
670 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
671 "validation_function": validation.is_string,
675 "help": "Constrain location (city) during resource discovery. May use wildcards.",
676 "type": Attribute.STRING,
677 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
678 "validation_function": validation.is_string,
682 "help": "Constrain location (country) during resource discovery. May use wildcards.",
683 "type": Attribute.STRING,
684 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
685 "validation_function": validation.is_string,
689 "help": "Constrain location (region) during resource discovery. May use wildcards.",
690 "type": Attribute.STRING,
691 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
692 "validation_function": validation.is_string,
694 "architecture": dict({
695 "name": "architecture",
696 "help": "Constrain architexture during resource discovery.",
697 "type": Attribute.ENUM,
698 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
699 "allowed": ["x86_64",
701 "validation_function": validation.is_enum,
703 "operating_system": dict({
704 "name": "operatingSystem",
705 "help": "Constrain operating system during resource discovery.",
706 "type": Attribute.ENUM,
707 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
713 "validation_function": validation.is_enum,
717 "help": "Constrain the PlanetLab site this node should reside on.",
718 "type": Attribute.ENUM,
719 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
723 "validation_function": validation.is_enum,
725 "min_reliability": dict({
726 "name": "minReliability",
727 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
728 "type": Attribute.DOUBLE,
730 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
731 "validation_function": validation.is_number,
733 "max_reliability": dict({
734 "name": "maxReliability",
735 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
736 "type": Attribute.DOUBLE,
738 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
739 "validation_function": validation.is_number,
741 "min_bandwidth": dict({
742 "name": "minBandwidth",
743 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
744 "type": Attribute.DOUBLE,
746 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
747 "validation_function": validation.is_number,
749 "max_bandwidth": dict({
750 "name": "maxBandwidth",
751 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
752 "type": Attribute.DOUBLE,
754 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
755 "validation_function": validation.is_number,
759 "help": "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
760 "type": Attribute.DOUBLE,
762 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
763 "validation_function": validation.is_number,
767 "help": "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
768 "type": Attribute.DOUBLE,
770 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
771 "validation_function": validation.is_number,
775 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
776 "type": Attribute.DOUBLE,
778 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
779 "validation_function": validation.is_number,
783 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
784 "type": Attribute.DOUBLE,
786 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
787 "validation_function": validation.is_number,
793 "type": Attribute.BOOL,
795 "validation_function": validation.is_bool
799 "help": "This is the primary interface for the attached node",
800 "type": Attribute.BOOL,
802 "validation_function": validation.is_bool
804 "device_name": dict({
806 "help": "Device name",
807 "type": Attribute.STRING,
808 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
809 "validation_function": validation.is_string
813 "help": "Maximum transmition unit for device",
814 "type": Attribute.INTEGER,
816 "validation_function": validation.is_integer_range(0,1500)
820 "help": "Network mask for the device (eg: 24 for /24 network)",
821 "type": Attribute.INTEGER,
822 "validation_function": validation.is_integer_range(8,24)
826 "help": "Enable SNAT (source NAT to the internet) no this device",
827 "type": Attribute.BOOL,
829 "validation_function": validation.is_bool
831 "pointopoint": dict({
832 "name": "pointopoint",
833 "help": "If the interface is a P2P link, the remote endpoint's IP "
834 "should be set on this attribute.",
835 "type": Attribute.STRING,
836 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
837 "validation_function": validation.is_string
840 "name": "txqueuelen",
841 "help": "Transmission queue length (in packets)",
842 "type": Attribute.INTEGER,
844 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
846 "validation_function": validation.is_integer
851 "help": "Command line string",
852 "type": Attribute.STRING,
853 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
854 "validation_function": validation.is_string
858 "help": "Run with root privileges",
859 "type": Attribute.BOOL,
860 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
862 "validation_function": validation.is_bool
866 "help": "Standard input",
867 "type": Attribute.STRING,
868 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
869 "validation_function": validation.is_string
874 "help": "Space-separated list of packages required to run the application",
875 "type": Attribute.STRING,
876 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
877 "validation_function": validation.is_string
879 "build-depends": dict({
880 "name": "buildDepends",
881 "help": "Space-separated list of packages required to build the application",
882 "type": Attribute.STRING,
883 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
884 "validation_function": validation.is_string
888 "help": "True if required packages can be found in the RpmFusion repository",
889 "type": Attribute.BOOL,
890 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
892 "validation_function": validation.is_bool
896 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
897 "Archives won't be expanded automatically.",
898 "type": Attribute.STRING,
899 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
900 "validation_function": validation.is_string
904 "help": "Build commands to execute after deploying the sources. "
905 "Sources will be in the ${SOURCES} folder. "
906 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
907 "Try to make the commands return with a nonzero exit code on error.\n"
908 "Also, do not install any programs here, use the 'install' attribute. This will "
909 "help keep the built files constrained to the build folder (which may "
910 "not be the home folder), and will result in faster deployment. Also, "
911 "make sure to clean up temporary files, to reduce bandwidth usage between "
912 "nodes when transferring built packages.",
913 "type": Attribute.STRING,
914 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
915 "validation_function": validation.is_string
919 "help": "Commands to transfer built files to their final destinations. "
920 "Sources will be in the initial working folder, and a special "
921 "tag ${SOURCES} can be used to reference the experiment's "
922 "home folder (where the application commands will run).\n"
923 "ALL sources and targets needed for execution must be copied there, "
924 "if building has been enabled.\n"
925 "That is, 'slave' nodes will not automatically get any source files. "
926 "'slave' nodes don't get build dependencies either, so if you need "
927 "make and other tools to install, be sure to provide them as "
928 "actual dependencies instead.",
929 "type": Attribute.STRING,
930 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
931 "validation_function": validation.is_string
934 "netpipe_mode": dict({
936 "help": "Link mode:\n"
937 " * SERVER: applies to incoming connections\n"
938 " * CLIENT: applies to outgoing connections\n"
939 " * SERVICE: applies to both",
940 "type": Attribute.ENUM,
941 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
942 "allowed": ["SERVER",
945 "validation_function": validation.is_enum,
949 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
950 "type": Attribute.STRING,
951 "validation_function": is_portlist,
955 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
956 "type": Attribute.STRING,
957 "validation_function": is_addrlist,
961 "help": "Inbound bandwidth limit (in Mbit/s)",
962 "type": Attribute.DOUBLE,
963 "validation_function": validation.is_number,
967 "help": "Outbound bandwidth limit (in Mbit/s)",
968 "type": Attribute.DOUBLE,
969 "validation_function": validation.is_number,
973 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
974 "type": Attribute.DOUBLE,
975 "validation_function": validation.is_number,
979 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
980 "type": Attribute.DOUBLE,
981 "validation_function": validation.is_number,
985 "help": "Inbound packet delay (in milliseconds)",
986 "type": Attribute.INTEGER,
988 "validation_function": validation.is_integer,
992 "help": "Outbound packet delay (in milliseconds)",
993 "type": Attribute.INTEGER,
995 "validation_function": validation.is_integer,
1002 "help": "Standard output stream"
1006 "help": "Application standard error",
1010 "help": "Output of the build process",
1013 "netpipe_stats": dict({
1014 "name": "netpipeStats",
1015 "help": "Information about rule match counters, packets dropped, etc.",
1020 "help": "Detailled log of all packets going through the interface",
1024 "help": "PCAP trace of all packets going through the interface",
1028 create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
1030 configure_order = [ INTERNET, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1032 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
1033 start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1036 shutdown_order = [ Parallel(APPLICATION), Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), NODEIFACE, Parallel(NODE) ]
1038 factories_info = dict({
1040 "help": "Virtualized Node (V-Server style)",
1041 "category": FC.CATEGORY_NODES,
1042 "create_function": create_node,
1043 "preconfigure_function": configure_node,
1044 "prestart_function": configure_node_routes,
1056 # NEPI-in-NEPI attributes
1057 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
1059 "connector_types": ["devs", "apps", "pipes", "deps"],
1060 "tags": [tags.NODE, tags.ALLOW_ROUTES],
1063 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
1064 "category": FC.CATEGORY_DEVICES,
1065 "create_function": create_nodeiface,
1066 "preconfigure_function": configure_nodeiface,
1067 "box_attributes": [ ],
1068 "connector_types": ["node", "inet"],
1069 "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
1072 "help": "Virtual TUN network interface (layer 3)",
1073 "category": FC.CATEGORY_DEVICES,
1074 "create_function": create_tuniface,
1075 "preconfigure_function": preconfigure_tuniface,
1076 "configure_function": postconfigure_tuniface,
1077 "prestart_function": wait_tuniface,
1079 "up", "device_name", "mtu", "snat", "pointopoint",
1081 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1083 "traces": ["packets", "pcap"],
1084 "connector_types": ["node","udp","tcp","fd->","gre"],
1085 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1088 "help": "Virtual TAP network interface (layer 2)",
1089 "category": FC.CATEGORY_DEVICES,
1090 "create_function": create_tapiface,
1091 "preconfigure_function": preconfigure_tuniface,
1092 "configure_function": postconfigure_tuniface,
1093 "prestart_function": wait_tuniface,
1095 "up", "device_name", "mtu", "snat", "pointopoint",
1097 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1099 "traces": ["packets", "pcap"],
1100 "connector_types": ["node","udp","tcp","fd->","gre"],
1101 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1104 "help": "Generic executable command line application",
1105 "category": FC.CATEGORY_APPLICATIONS,
1106 "create_function": create_application,
1107 "start_function": start_application,
1108 "status_function": status_application,
1109 "stop_function": stop_application,
1110 "configure_function": configure_application,
1111 "box_attributes": ["command", "sudo", "stdin",
1112 "depends", "build-depends", "build", "install",
1113 "sources", "rpm-fusion" ],
1114 "connector_types": ["node"],
1115 "traces": ["stdout", "stderr", "buildlog"],
1116 "tags": [tags.APPLICATION],
1119 "help": "Requirement for package or application to be installed on some node",
1120 "category": FC.CATEGORY_APPLICATIONS,
1121 "create_function": create_dependency,
1122 "preconfigure_function": configure_dependency,
1123 "box_attributes": ["depends", "build-depends", "build", "install",
1124 "sources", "rpm-fusion" ],
1125 "connector_types": ["node"],
1126 "traces": ["buildlog"],
1128 NEPIDEPENDENCY: dict({
1129 "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1130 "category": FC.CATEGORY_APPLICATIONS,
1131 "create_function": create_nepi_dependency,
1132 "preconfigure_function": configure_dependency,
1133 "box_attributes": [],
1134 "connector_types": ["node"],
1135 "traces": ["buildlog"],
1137 NS3DEPENDENCY: dict({
1138 "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1139 "category": FC.CATEGORY_APPLICATIONS,
1140 "create_function": create_ns3_dependency,
1141 "preconfigure_function": configure_dependency,
1142 "box_attributes": [ ],
1143 "connector_types": ["node"],
1144 "traces": ["buildlog"],
1147 "help": "Internet routing",
1148 "category": FC.CATEGORY_CHANNELS,
1149 "create_function": create_internet,
1150 "connector_types": ["devs"],
1151 "tags": [tags.INTERNET],
1154 "help": "Link emulation",
1155 "category": FC.CATEGORY_CHANNELS,
1156 "create_function": create_netpipe,
1157 "configure_function": configure_netpipe,
1158 "box_attributes": ["netpipe_mode",
1159 "addr_list", "port_list",
1160 "bw_in","plr_in","delay_in",
1161 "bw_out","plr_out","delay_out"],
1162 "connector_types": ["node"],
1163 "traces": ["netpipe_stats"],
1167 testbed_attributes = dict({
1170 "help": "The name of the PlanetLab slice to use",
1171 "type": Attribute.STRING,
1172 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1173 "validation_function": validation.is_string
1177 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1178 "type": Attribute.STRING,
1179 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1180 "validation_function": validation.is_string
1184 "help": "The PlanetLab user's password.",
1185 "type": Attribute.STRING,
1186 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1187 "validation_function": validation.is_string
1191 "help": "The PlanetLab PLC API host",
1192 "type": Attribute.STRING,
1193 "value": "www.planet-lab.eu",
1194 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1195 "validation_function": validation.is_string
1199 "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1200 "type": Attribute.STRING,
1201 "value": "https://%(hostname)s:443/PLCAPI/",
1202 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1203 "validation_function": validation.is_string
1205 "p2p_deployment": dict({
1206 "name": "p2pDeployment",
1207 "help": "Enable peer-to-peer deployment of applications and dependencies. "
1208 "When enabled, dependency packages and applications are "
1209 "deployed in a P2P fashion, picking a single node to do "
1210 "the building or repo download, while all the others "
1211 "cooperatively exchange resulting binaries or rpms. "
1212 "When deploying to many nodes, this is a far more efficient "
1213 "use of resources. It does require re-encrypting and distributing "
1214 "the slice's private key. Though it is implemented in a secure "
1215 "fashion, if they key's sole purpose is not PlanetLab, then this "
1216 "feature should be disabled.",
1217 "type": Attribute.BOOL,
1219 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1220 "validation_function": validation.is_bool
1222 "slice_ssh_key": dict({
1223 "name": "sliceSSHKey",
1224 "help": "The controller-local path to the slice user's ssh private key. "
1225 "It is the user's responsability to deploy this file where the controller "
1226 "will run, it won't be done automatically because it's sensitive information. "
1227 "It is recommended that a NEPI-specific user be created for this purpose and "
1228 "this purpose alone.",
1229 "type": Attribute.STRING,
1230 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1231 "validation_function": validation.is_string
1233 "pl_log_level": dict({
1234 "name": "plLogLevel",
1235 "help": "Verbosity of logging of planetlab events.",
1237 "type": Attribute.ENUM,
1238 "allowed": ["DEBUG",
1243 "validation_function": validation.is_enum,
1245 "tap_port_base": dict({
1246 "name": "tapPortBase",
1247 "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.",
1248 "type": Attribute.INTEGER,
1250 "range": (2000,30000),
1251 "validation_function": validation.is_integer_range(2000,30000)
1253 "dedicated_slice": dict({
1254 "name": "dedicatedSlice",
1255 "help": "Set to True if the slice will be dedicated to this experiment. "
1256 "NEPI will perform node and slice cleanup, making sure slices are "
1257 "in a clean, repeatable state before running the experiment.",
1258 "type": Attribute.BOOL,
1260 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1261 "validation_function": validation.is_bool
1265 supported_recovery_policies = [
1271 class MetadataInfo(metadata.MetadataInfo):
1273 def connector_types(self):
1274 return connector_types
1277 def connections(self):
1281 def attributes(self):
1289 def create_order(self):
1293 def configure_order(self):
1294 return configure_order
1297 def prestart_order(self):
1301 def start_order(self):
1305 def factories_info(self):
1306 return factories_info
1309 def testbed_attributes(self):
1310 return testbed_attributes
1313 def testbed_id(self):
1317 def testbed_version(self):
1318 return TESTBED_VERSION
1321 def supported_recovery_policies(self):
1322 return supported_recovery_policies