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.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
117 iface.tun_proto = proto
119 preconfigure_tuniface(testbed_instance, iface_guid)
121 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
122 # refresh (refreshable) attributes for second-phase
123 iface = testbed_instance._elements[iface_guid]
124 iface.peer_addr = peer_iface_data.get("tun_addr")
125 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
126 iface.peer_port = peer_iface_data.get("tun_port")
128 postconfigure_tuniface(testbed_instance, iface_guid)
130 def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
131 crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
132 crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
134 def connect_dep(testbed_instance, node_guid, app_guid):
135 node = testbed_instance._elements[node_guid]
136 app = testbed_instance._elements[app_guid]
140 node.required_packages.update(set(
141 app.depends.split() ))
144 if app.home_path and app.home_path not in node.pythonpath:
145 node.pythonpath.append(app.home_path)
148 for envkey, envval in app.env.iteritems():
149 envval = app._replace_paths(envval)
150 node.env[envkey].append(envval)
153 node.rpmFusion = True
155 def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid):
156 node = testbed_instance._elements[node_guid]
157 netpipe = testbed_instance._elements[netpipe_guid]
159 node.required_vsys.add('ipfw-be')
160 node.required_packages.add('ipfwslice')
163 ### Creation functions ###
165 def create_node(testbed_instance, guid):
166 parameters = testbed_instance._get_parameters(guid)
168 # create element with basic attributes
169 element = testbed_instance._make_node(parameters)
171 # add constraint on number of (real) interfaces
172 # by counting connected devices
173 dev_guids = testbed_instance.get_connected(guid, "devs", "node")
174 num_open_ifaces = sum( # count True values
175 NODEIFACE == testbed_instance._get_factory_id(guid)
176 for guid in dev_guids )
177 element.min_num_external_ifaces = num_open_ifaces
179 # require vroute vsys if we have routes to set up
180 routes = testbed_instance._add_route.get(guid)
182 element.required_vsys.add("vroute")
184 testbed_instance.elements[guid] = element
186 def create_nodeiface(testbed_instance, guid):
187 parameters = testbed_instance._get_parameters(guid)
188 element = testbed_instance._make_node_iface(parameters)
189 testbed_instance.elements[guid] = element
191 def create_tuniface(testbed_instance, guid):
192 parameters = testbed_instance._get_parameters(guid)
193 element = testbed_instance._make_tun_iface(parameters)
195 # Set custom addresses, if there are any already
196 # Setting this early helps set up P2P links
197 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
198 addresses = testbed_instance._add_address[guid]
199 for address in addresses:
200 (address, netprefix, broadcast) = address
201 element.add_address(address, netprefix, broadcast)
203 testbed_instance.elements[guid] = element
205 def create_tapiface(testbed_instance, guid):
206 parameters = testbed_instance._get_parameters(guid)
207 element = testbed_instance._make_tap_iface(parameters)
209 # Set custom addresses, if there are any already
210 # Setting this early helps set up P2P links
211 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
212 addresses = testbed_instance._add_address[guid]
213 for address in addresses:
214 (address, netprefix, broadcast) = address
215 element.add_address(address, netprefix, broadcast)
217 testbed_instance.elements[guid] = element
219 def create_application(testbed_instance, guid):
220 parameters = testbed_instance._get_parameters(guid)
221 element = testbed_instance._make_application(parameters)
223 # Just inject configuration stuff
224 element.home_path = "nepi-app-%s" % (guid,)
226 testbed_instance.elements[guid] = element
228 def create_dependency(testbed_instance, guid):
229 parameters = testbed_instance._get_parameters(guid)
230 element = testbed_instance._make_dependency(parameters)
232 # Just inject configuration stuff
233 element.home_path = "nepi-dep-%s" % (guid,)
235 testbed_instance.elements[guid] = element
237 def create_nepi_dependency(testbed_instance, guid):
238 parameters = testbed_instance._get_parameters(guid)
239 element = testbed_instance._make_nepi_dependency(parameters)
241 # Just inject configuration stuff
242 element.home_path = "nepi-nepi-%s" % (guid,)
244 testbed_instance.elements[guid] = element
246 def create_ns3_dependency(testbed_instance, guid):
247 parameters = testbed_instance._get_parameters(guid)
248 element = testbed_instance._make_ns3_dependency(parameters)
250 # Just inject configuration stuff
251 element.home_path = "nepi-ns3-%s" % (guid,)
253 testbed_instance.elements[guid] = element
255 def create_internet(testbed_instance, guid):
256 parameters = testbed_instance._get_parameters(guid)
257 element = testbed_instance._make_internet(parameters)
258 testbed_instance.elements[guid] = element
260 def create_netpipe(testbed_instance, guid):
261 parameters = testbed_instance._get_parameters(guid)
262 element = testbed_instance._make_netpipe(parameters)
263 testbed_instance.elements[guid] = element
265 ### Start/Stop functions ###
267 def start_application(testbed_instance, guid):
268 parameters = testbed_instance._get_parameters(guid)
269 traces = testbed_instance._get_traces(guid)
270 app = testbed_instance.elements[guid]
272 app.stdout = "stdout" in traces
273 app.stderr = "stderr" in traces
274 app.buildlog = "buildlog" in traces
278 def stop_application(testbed_instance, guid):
279 app = testbed_instance.elements[guid]
282 ### Status functions ###
284 def status_application(testbed_instance, guid):
285 if guid not in testbed_instance.elements.keys():
286 return AS.STATUS_NOT_STARTED
288 app = testbed_instance.elements[guid]
291 ### Configure functions ###
293 def configure_nodeiface(testbed_instance, guid):
294 element = testbed_instance._elements[guid]
296 # Cannot explicitly configure addresses
297 if guid in testbed_instance._add_address:
298 raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
301 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
302 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
303 siblings = [ self._element[dev_guid]
304 for dev_guid in dev_guids
305 if dev_guid != guid ]
307 # Fetch address from PLC api
308 element.pick_iface(siblings)
310 # Do some validations
313 def preconfigure_tuniface(testbed_instance, guid):
314 element = testbed_instance._elements[guid]
316 # Set custom addresses if any, and if not set already
317 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
318 addresses = testbed_instance._add_address[guid]
319 for address in addresses:
320 (address, netprefix, broadcast) = address
321 element.add_address(address, netprefix, broadcast)
323 # Link to external interface, if any
324 for iface in testbed_instance._elements.itervalues():
325 if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
326 element.external_iface = iface
329 # Set standard TUN attributes
330 if (not element.tun_addr or not element.tun_port) and element.external_iface:
331 element.tun_addr = element.external_iface.address
332 element.tun_port = testbed_instance.tapPortBase + int(guid)
335 traces = testbed_instance._get_traces(guid)
336 for capmode in ('pcap', 'packets'):
337 if capmode in traces:
338 element.capture = capmode
341 element.capture = False
343 # Do some validations
347 if element.peer_proto:
348 if element.peer_iface and isinstance(element.peer_iface, testbed_instance._interfaces.TunIface):
350 listening = id(element) < id(element.peer_iface)
353 if not element.tun_addr or not element.tun_port:
355 elif not element.peer_addr or not element.peer_port:
358 # both have addresses...
359 # ...the one with the lesser address listens
360 listening = element.tun_addr < element.peer_addr
365 def postconfigure_tuniface(testbed_instance, guid):
366 element = testbed_instance._elements[guid]
371 def wait_tuniface(testbed_instance, guid):
372 element = testbed_instance._elements[guid]
375 element.async_launch_wait()
378 def configure_node(testbed_instance, guid):
379 node = testbed_instance._elements[guid]
381 # Just inject configuration stuff
382 node.home_path = "nepi-node-%s" % (guid,)
383 node.ident_path = testbed_instance.sliceSSHKey
384 node.slicename = testbed_instance.slicename
386 # Do some validations
389 # this will be done in parallel in all nodes
390 # this call only spawns the process
391 node.install_dependencies()
393 def configure_node_routes(testbed_instance, guid):
394 node = testbed_instance._elements[guid]
395 routes = testbed_instance._add_route.get(guid)
399 for dev_guid in testbed_instance.get_connected(guid, "devs", "node")
400 for dev in ( testbed_instance._elements.get(dev_guid) ,)
401 if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ]
403 node.configure_routes(routes, devs)
405 def configure_application(testbed_instance, guid):
406 app = testbed_instance._elements[guid]
408 # Do some validations
411 # Wait for dependencies
412 app.node.wait_dependencies()
417 def configure_dependency(testbed_instance, guid):
418 dep = testbed_instance._elements[guid]
420 # Do some validations
423 # Wait for dependencies
424 dep.node.wait_dependencies()
429 def configure_netpipe(testbed_instance, guid):
430 netpipe = testbed_instance._elements[guid]
432 # Do some validations
435 # Wait for dependencies
436 netpipe.node.wait_dependencies()
441 ### Factory information ###
443 connector_types = dict({
445 "help": "Connector from node to applications",
451 "help": "Connector from node to network interfaces",
457 "help": "Connector from node to application dependencies "
458 "(packages and applications that need to be installed)",
464 "help": "Connector from network interfaces to the internet",
470 "help": "Connector to a Node",
476 "help": "Connector to a NetPipe",
483 "help": "ip-ip tunneling over TCP link",
489 "help": "ip-ip tunneling over UDP datagrams",
495 "help": "IP or Ethernet tunneling using the GRE protocol",
501 "help": "TUN device file descriptor provider",
510 "from": (TESTBED_ID, NODE, "devs"),
511 "to": (TESTBED_ID, NODEIFACE, "node"),
512 "init_code": connect_node_iface_node,
516 "from": (TESTBED_ID, NODE, "devs"),
517 "to": (TESTBED_ID, TUNIFACE, "node"),
518 "init_code": connect_tun_iface_node,
522 "from": (TESTBED_ID, NODE, "devs"),
523 "to": (TESTBED_ID, TAPIFACE, "node"),
524 "init_code": connect_tun_iface_node,
528 "from": (TESTBED_ID, NODEIFACE, "inet"),
529 "to": (TESTBED_ID, INTERNET, "devs"),
530 "init_code": connect_node_iface_inet,
534 "from": (TESTBED_ID, NODE, "apps"),
535 "to": (TESTBED_ID, APPLICATION, "node"),
536 "init_code": connect_dep,
540 "from": (TESTBED_ID, NODE, "deps"),
541 "to": (TESTBED_ID, DEPENDENCY, "node"),
542 "init_code": connect_dep,
546 "from": (TESTBED_ID, NODE, "deps"),
547 "to": (TESTBED_ID, NEPIDEPENDENCY, "node"),
548 "init_code": connect_dep,
552 "from": (TESTBED_ID, NODE, "deps"),
553 "to": (TESTBED_ID, NS3DEPENDENCY, "node"),
554 "init_code": connect_dep,
558 "from": (TESTBED_ID, NODE, "pipes"),
559 "to": (TESTBED_ID, NETPIPE, "node"),
560 "init_code": connect_node_netpipe,
564 "from": (TESTBED_ID, TUNIFACE, "tcp"),
565 "to": (TESTBED_ID, TUNIFACE, "tcp"),
566 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
570 "from": (TESTBED_ID, TUNIFACE, "udp"),
571 "to": (TESTBED_ID, TUNIFACE, "udp"),
572 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
576 "from": (TESTBED_ID, TUNIFACE, "gre"),
577 "to": (TESTBED_ID, TUNIFACE, "gre"),
578 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
582 "from": (TESTBED_ID, TAPIFACE, "tcp"),
583 "to": (TESTBED_ID, TAPIFACE, "tcp"),
584 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
588 "from": (TESTBED_ID, TAPIFACE, "udp"),
589 "to": (TESTBED_ID, TAPIFACE, "udp"),
590 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
594 "from": (TESTBED_ID, TAPIFACE, "gre"),
595 "to": (TESTBED_ID, TAPIFACE, "gre"),
596 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
600 "from": (TESTBED_ID, TUNIFACE, "tcp"),
601 "to": (None, None, "tcp"),
602 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
603 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
607 "from": (TESTBED_ID, TUNIFACE, "udp"),
608 "to": (None, None, "udp"),
609 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
610 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
614 "from": (TESTBED_ID, TUNIFACE, "fd->"),
615 "to": (None, None, "->fd"),
616 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
620 "from": (TESTBED_ID, TUNIFACE, "gre"),
621 "to": (None, None, "gre"),
622 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
626 "from": (TESTBED_ID, TAPIFACE, "tcp"),
627 "to": (None, None, "tcp"),
628 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
629 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
633 "from": (TESTBED_ID, TAPIFACE, "udp"),
634 "to": (None, None, "udp"),
635 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
636 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
640 "from": (TESTBED_ID, TAPIFACE, "fd->"),
641 "to": (None, None, "->fd"),
642 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
645 # EGRE is an extension of PlanetLab, so we can't connect externally
646 # if the other testbed isn't another PlanetLab
648 "from": (TESTBED_ID, TAPIFACE, "gre"),
649 "to": (TESTBED_ID, None, "gre"),
650 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
656 "forward_X11": dict({
657 "name": "forward_X11",
658 "help": "Forward x11 from main namespace to the node",
659 "type": Attribute.BOOL,
661 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
662 "validation_function": validation.is_bool,
666 "help": "Constrain hostname during resource discovery. May use wildcards.",
667 "type": Attribute.STRING,
668 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
669 "validation_function": validation.is_string,
673 "help": "Constrain location (city) during resource discovery. May use wildcards.",
674 "type": Attribute.STRING,
675 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
676 "validation_function": validation.is_string,
680 "help": "Constrain location (country) during resource discovery. May use wildcards.",
681 "type": Attribute.STRING,
682 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
683 "validation_function": validation.is_string,
687 "help": "Constrain location (region) during resource discovery. May use wildcards.",
688 "type": Attribute.STRING,
689 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
690 "validation_function": validation.is_string,
692 "architecture": dict({
693 "name": "architecture",
694 "help": "Constrain architexture during resource discovery.",
695 "type": Attribute.ENUM,
696 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
697 "allowed": ["x86_64",
699 "validation_function": validation.is_enum,
701 "operating_system": dict({
702 "name": "operatingSystem",
703 "help": "Constrain operating system during resource discovery.",
704 "type": Attribute.ENUM,
705 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
711 "validation_function": validation.is_enum,
715 "help": "Constrain the PlanetLab site this node should reside on.",
716 "type": Attribute.ENUM,
717 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
721 "validation_function": validation.is_enum,
723 "min_reliability": dict({
724 "name": "minReliability",
725 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
726 "type": Attribute.DOUBLE,
728 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
729 "validation_function": validation.is_number,
731 "max_reliability": dict({
732 "name": "maxReliability",
733 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
734 "type": Attribute.DOUBLE,
736 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
737 "validation_function": validation.is_number,
739 "min_bandwidth": dict({
740 "name": "minBandwidth",
741 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
742 "type": Attribute.DOUBLE,
744 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
745 "validation_function": validation.is_number,
747 "max_bandwidth": dict({
748 "name": "maxBandwidth",
749 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
750 "type": Attribute.DOUBLE,
752 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
753 "validation_function": validation.is_number,
757 "help": "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
758 "type": Attribute.DOUBLE,
760 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
761 "validation_function": validation.is_number,
765 "help": "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
766 "type": Attribute.DOUBLE,
768 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
769 "validation_function": validation.is_number,
773 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
774 "type": Attribute.DOUBLE,
776 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
777 "validation_function": validation.is_number,
781 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
782 "type": Attribute.DOUBLE,
784 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
785 "validation_function": validation.is_number,
791 "type": Attribute.BOOL,
793 "validation_function": validation.is_bool
797 "help": "This is the primary interface for the attached node",
798 "type": Attribute.BOOL,
800 "validation_function": validation.is_bool
802 "device_name": dict({
804 "help": "Device name",
805 "type": Attribute.STRING,
806 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
807 "validation_function": validation.is_string
811 "help": "Maximum transmition unit for device",
812 "type": Attribute.INTEGER,
814 "validation_function": validation.is_integer_range(0,1500)
818 "help": "Network mask for the device (eg: 24 for /24 network)",
819 "type": Attribute.INTEGER,
820 "validation_function": validation.is_integer_range(8,24)
824 "help": "Enable SNAT (source NAT to the internet) no this device",
825 "type": Attribute.BOOL,
827 "validation_function": validation.is_bool
829 "pointopoint": dict({
830 "name": "pointopoint",
831 "help": "If the interface is a P2P link, the remote endpoint's IP "
832 "should be set on this attribute.",
833 "type": Attribute.STRING,
834 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
835 "validation_function": validation.is_string
838 "name": "txqueuelen",
839 "help": "Transmission queue length (in packets)",
840 "type": Attribute.INTEGER,
842 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
844 "validation_function": validation.is_integer
849 "help": "Command line string",
850 "type": Attribute.STRING,
851 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
852 "validation_function": validation.is_string
856 "help": "Run with root privileges",
857 "type": Attribute.BOOL,
858 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
860 "validation_function": validation.is_bool
864 "help": "Standard input",
865 "type": Attribute.STRING,
866 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
867 "validation_function": validation.is_string
872 "help": "Space-separated list of packages required to run the application",
873 "type": Attribute.STRING,
874 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
875 "validation_function": validation.is_string
877 "build-depends": dict({
878 "name": "buildDepends",
879 "help": "Space-separated list of packages required to build the application",
880 "type": Attribute.STRING,
881 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
882 "validation_function": validation.is_string
886 "help": "True if required packages can be found in the RpmFusion repository",
887 "type": Attribute.BOOL,
888 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
890 "validation_function": validation.is_bool
894 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
895 "Archives won't be expanded automatically.",
896 "type": Attribute.STRING,
897 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
898 "validation_function": validation.is_string
902 "help": "Build commands to execute after deploying the sources. "
903 "Sources will be in the ${SOURCES} folder. "
904 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
905 "Try to make the commands return with a nonzero exit code on error.\n"
906 "Also, do not install any programs here, use the 'install' attribute. This will "
907 "help keep the built files constrained to the build folder (which may "
908 "not be the home folder), and will result in faster deployment. Also, "
909 "make sure to clean up temporary files, to reduce bandwidth usage between "
910 "nodes when transferring built packages.",
911 "type": Attribute.STRING,
912 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
913 "validation_function": validation.is_string
917 "help": "Commands to transfer built files to their final destinations. "
918 "Sources will be in the initial working folder, and a special "
919 "tag ${SOURCES} can be used to reference the experiment's "
920 "home folder (where the application commands will run).\n"
921 "ALL sources and targets needed for execution must be copied there, "
922 "if building has been enabled.\n"
923 "That is, 'slave' nodes will not automatically get any source files. "
924 "'slave' nodes don't get build dependencies either, so if you need "
925 "make and other tools to install, be sure to provide them as "
926 "actual dependencies instead.",
927 "type": Attribute.STRING,
928 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
929 "validation_function": validation.is_string
932 "netpipe_mode": dict({
934 "help": "Link mode:\n"
935 " * SERVER: applies to incoming connections\n"
936 " * CLIENT: applies to outgoing connections\n"
937 " * SERVICE: applies to both",
938 "type": Attribute.ENUM,
939 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
940 "allowed": ["SERVER",
943 "validation_function": validation.is_enum,
947 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
948 "type": Attribute.STRING,
949 "validation_function": is_portlist,
953 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
954 "type": Attribute.STRING,
955 "validation_function": is_addrlist,
959 "help": "Inbound bandwidth limit (in Mbit/s)",
960 "type": Attribute.DOUBLE,
961 "validation_function": validation.is_number,
965 "help": "Outbound bandwidth limit (in Mbit/s)",
966 "type": Attribute.DOUBLE,
967 "validation_function": validation.is_number,
971 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
972 "type": Attribute.DOUBLE,
973 "validation_function": validation.is_number,
977 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
978 "type": Attribute.DOUBLE,
979 "validation_function": validation.is_number,
983 "help": "Inbound packet delay (in milliseconds)",
984 "type": Attribute.INTEGER,
986 "validation_function": validation.is_integer,
990 "help": "Outbound packet delay (in milliseconds)",
991 "type": Attribute.INTEGER,
993 "validation_function": validation.is_integer,
1000 "help": "Standard output stream"
1004 "help": "Application standard error",
1008 "help": "Output of the build process",
1011 "netpipe_stats": dict({
1012 "name": "netpipeStats",
1013 "help": "Information about rule match counters, packets dropped, etc.",
1018 "help": "Detailled log of all packets going through the interface",
1022 "help": "PCAP trace of all packets going through the interface",
1026 create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
1028 configure_order = [ INTERNET, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1030 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
1031 start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1034 shutdown_order = [ Parallel(APPLICATION), Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), NODEIFACE, Parallel(NODE) ]
1036 factories_info = dict({
1038 "help": "Virtualized Node (V-Server style)",
1039 "category": FC.CATEGORY_NODES,
1040 "create_function": create_node,
1041 "preconfigure_function": configure_node,
1042 "prestart_function": configure_node_routes,
1054 # NEPI-in-NEPI attributes
1055 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
1057 "connector_types": ["devs", "apps", "pipes", "deps"],
1058 "tags": [tags.NODE, tags.ALLOW_ROUTES],
1061 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
1062 "category": FC.CATEGORY_DEVICES,
1063 "create_function": create_nodeiface,
1064 "preconfigure_function": configure_nodeiface,
1065 "box_attributes": [ ],
1066 "connector_types": ["node", "inet"],
1067 "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
1070 "help": "Virtual TUN network interface (layer 3)",
1071 "category": FC.CATEGORY_DEVICES,
1072 "create_function": create_tuniface,
1073 "preconfigure_function": preconfigure_tuniface,
1074 "configure_function": postconfigure_tuniface,
1075 "prestart_function": wait_tuniface,
1077 "up", "device_name", "mtu", "snat", "pointopoint",
1079 "tun_proto", "tun_addr", "tun_port", "tun_key"
1081 "traces": ["packets", "pcap"],
1082 "connector_types": ["node","udp","tcp","fd->","gre"],
1083 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1086 "help": "Virtual TAP network interface (layer 2)",
1087 "category": FC.CATEGORY_DEVICES,
1088 "create_function": create_tapiface,
1089 "preconfigure_function": preconfigure_tuniface,
1090 "configure_function": postconfigure_tuniface,
1091 "prestart_function": wait_tuniface,
1093 "up", "device_name", "mtu", "snat", "pointopoint",
1095 "tun_proto", "tun_addr", "tun_port", "tun_key"
1097 "traces": ["packets", "pcap"],
1098 "connector_types": ["node","udp","tcp","fd->","gre"],
1099 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1102 "help": "Generic executable command line application",
1103 "category": FC.CATEGORY_APPLICATIONS,
1104 "create_function": create_application,
1105 "start_function": start_application,
1106 "status_function": status_application,
1107 "stop_function": stop_application,
1108 "configure_function": configure_application,
1109 "box_attributes": ["command", "sudo", "stdin",
1110 "depends", "build-depends", "build", "install",
1111 "sources", "rpm-fusion" ],
1112 "connector_types": ["node"],
1113 "traces": ["stdout", "stderr", "buildlog"],
1114 "tags": [tags.APPLICATION],
1117 "help": "Requirement for package or application to be installed on some node",
1118 "category": FC.CATEGORY_APPLICATIONS,
1119 "create_function": create_dependency,
1120 "preconfigure_function": configure_dependency,
1121 "box_attributes": ["depends", "build-depends", "build", "install",
1122 "sources", "rpm-fusion" ],
1123 "connector_types": ["node"],
1124 "traces": ["buildlog"],
1126 NEPIDEPENDENCY: dict({
1127 "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1128 "category": FC.CATEGORY_APPLICATIONS,
1129 "create_function": create_nepi_dependency,
1130 "preconfigure_function": configure_dependency,
1131 "box_attributes": [],
1132 "connector_types": ["node"],
1133 "traces": ["buildlog"],
1135 NS3DEPENDENCY: dict({
1136 "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1137 "category": FC.CATEGORY_APPLICATIONS,
1138 "create_function": create_ns3_dependency,
1139 "preconfigure_function": configure_dependency,
1140 "box_attributes": [ ],
1141 "connector_types": ["node"],
1142 "traces": ["buildlog"],
1145 "help": "Internet routing",
1146 "category": FC.CATEGORY_CHANNELS,
1147 "create_function": create_internet,
1148 "connector_types": ["devs"],
1149 "tags": [tags.INTERNET],
1152 "help": "Link emulation",
1153 "category": FC.CATEGORY_CHANNELS,
1154 "create_function": create_netpipe,
1155 "configure_function": configure_netpipe,
1156 "box_attributes": ["netpipe_mode",
1157 "addr_list", "port_list",
1158 "bw_in","plr_in","delay_in",
1159 "bw_out","plr_out","delay_out"],
1160 "connector_types": ["node"],
1161 "traces": ["netpipe_stats"],
1165 testbed_attributes = dict({
1168 "help": "The name of the PlanetLab slice to use",
1169 "type": Attribute.STRING,
1170 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1171 "validation_function": validation.is_string
1175 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1176 "type": Attribute.STRING,
1177 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1178 "validation_function": validation.is_string
1182 "help": "The PlanetLab user's password.",
1183 "type": Attribute.STRING,
1184 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1185 "validation_function": validation.is_string
1189 "help": "The PlanetLab PLC API host",
1190 "type": Attribute.STRING,
1191 "value": "www.planet-lab.eu",
1192 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1193 "validation_function": validation.is_string
1197 "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1198 "type": Attribute.STRING,
1199 "value": "https://%(hostname)s:443/PLCAPI/",
1200 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1201 "validation_function": validation.is_string
1203 "p2p_deployment": dict({
1204 "name": "p2pDeployment",
1205 "help": "Enable peer-to-peer deployment of applications and dependencies. "
1206 "When enabled, dependency packages and applications are "
1207 "deployed in a P2P fashion, picking a single node to do "
1208 "the building or repo download, while all the others "
1209 "cooperatively exchange resulting binaries or rpms. "
1210 "When deploying to many nodes, this is a far more efficient "
1211 "use of resources. It does require re-encrypting and distributing "
1212 "the slice's private key. Though it is implemented in a secure "
1213 "fashion, if they key's sole purpose is not PlanetLab, then this "
1214 "feature should be disabled.",
1215 "type": Attribute.BOOL,
1217 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1218 "validation_function": validation.is_bool
1220 "slice_ssh_key": dict({
1221 "name": "sliceSSHKey",
1222 "help": "The controller-local path to the slice user's ssh private key. "
1223 "It is the user's responsability to deploy this file where the controller "
1224 "will run, it won't be done automatically because it's sensitive information. "
1225 "It is recommended that a NEPI-specific user be created for this purpose and "
1226 "this purpose alone.",
1227 "type": Attribute.STRING,
1228 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1229 "validation_function": validation.is_string
1231 "pl_log_level": dict({
1232 "name": "plLogLevel",
1233 "help": "Verbosity of logging of planetlab events.",
1235 "type": Attribute.ENUM,
1236 "allowed": ["DEBUG",
1241 "validation_function": validation.is_enum,
1243 "tap_port_base": dict({
1244 "name": "tapPortBase",
1245 "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.",
1246 "type": Attribute.INTEGER,
1248 "range": (2000,30000),
1249 "validation_function": validation.is_integer_range(2000,30000)
1251 "dedicated_slice": dict({
1252 "name": "dedicatedSlice",
1253 "help": "Set to True if the slice will be dedicated to this experiment. "
1254 "NEPI will perform node and slice cleanup, making sure slices are "
1255 "in a clean, repeatable state before running the experiment.",
1256 "type": Attribute.BOOL,
1258 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1259 "validation_function": validation.is_bool
1263 supported_recovery_policies = [
1269 class MetadataInfo(metadata.MetadataInfo):
1271 def connector_types(self):
1272 return connector_types
1275 def connections(self):
1279 def attributes(self):
1287 def create_order(self):
1291 def configure_order(self):
1292 return configure_order
1295 def prestart_order(self):
1299 def start_order(self):
1303 def factories_info(self):
1304 return factories_info
1307 def testbed_attributes(self):
1308 return testbed_attributes
1311 def testbed_id(self):
1315 def testbed_version(self):
1316 return TESTBED_VERSION
1319 def supported_recovery_policies(self):
1320 return supported_recovery_policies