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
20 NODEIFACE = "NodeInterface"
21 TUNIFACE = "TunInterface"
22 TAPIFACE = "TapInterface"
23 APPLICATION = "Application"
24 DEPENDENCY = "Dependency"
25 NEPIDEPENDENCY = "NepiDependency"
26 NS3DEPENDENCY = "NS3Dependency"
30 PL_TESTBED_ID = "planetlab"
33 ### Custom validation functions ###
34 def is_addrlist(attribute, value):
35 if not validation.is_string(attribute, value):
42 components = value.split(',')
44 for component in components:
46 addr, mask = component.split('/',1)
48 addr, mask = component, '32'
50 if mask is not None and not (mask and mask.isdigit()):
51 # No empty or nonnumeric masks
54 if not validation.is_ip4_address(attribute, addr):
55 # Address part must be ipv4
60 def is_portlist(attribute, value):
61 if not validation.is_string(attribute, value):
68 components = value.split(',')
70 for component in components:
72 pfrom, pto = component.split('-',1)
74 pfrom = pto = component
76 if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit():
77 # No empty or nonnumeric ports
83 ### Connection functions ####
85 def connect_node_iface_node(testbed_instance, node_guid, iface_guid):
86 node = testbed_instance._elements[node_guid]
87 iface = testbed_instance._elements[iface_guid]
90 def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid):
91 iface = testbed_instance._elements[iface_guid]
92 iface.has_internet = True
94 def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
95 node = testbed_instance._elements[node_guid]
96 iface = testbed_instance._elements[iface_guid]
98 node.required_vsys.update(('fd_tuntap', 'vif_up', 'vif_down'))
99 node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc'))
101 def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
102 iface = testbed_instance._elements[iface_guid]
103 peer_iface = testbed_instance._elements[peer_iface_guid]
104 iface.peer_iface = peer_iface
106 iface.tun_proto = proto
107 iface.tun_key = peer_iface.tun_key
109 def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data):
110 iface = testbed_instance._elements[iface_guid]
111 iface.peer_iface = None
112 iface.peer_addr = peer_iface_data.get("tun_addr")
113 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
114 iface.peer_port = peer_iface_data.get("tun_port")
115 iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
116 iface.tun_proto = proto
118 preconfigure_tuniface(testbed_instance, iface_guid)
120 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
121 # refresh (refreshable) attributes for second-phase
122 iface = testbed_instance._elements[iface_guid]
123 iface.peer_addr = peer_iface_data.get("tun_addr")
124 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
125 iface.peer_port = peer_iface_data.get("tun_port")
127 postconfigure_tuniface(testbed_instance, iface_guid)
129 def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
130 crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
131 crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
133 def connect_dep(testbed_instance, node_guid, app_guid):
134 node = testbed_instance._elements[node_guid]
135 app = testbed_instance._elements[app_guid]
139 node.required_packages.update(set(
140 app.depends.split() ))
143 if app.home_path and app.home_path not in node.pythonpath:
144 node.pythonpath.append(app.home_path)
147 for envkey, envval in app.env.iteritems():
148 envval = app._replace_paths(envval)
149 node.env[envkey].append(envval)
152 node.rpmFusion = True
154 def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid):
155 node = testbed_instance._elements[node_guid]
156 netpipe = testbed_instance._elements[netpipe_guid]
158 node.required_vsys.add('ipfw-be')
159 node.required_packages.add('ipfwslice')
162 ### Creation functions ###
164 def create_node(testbed_instance, guid):
165 parameters = testbed_instance._get_parameters(guid)
167 # create element with basic attributes
168 element = testbed_instance._make_node(parameters)
170 # add constraint on number of (real) interfaces
171 # by counting connected devices
172 dev_guids = testbed_instance.get_connected(guid, "devs", "node")
173 num_open_ifaces = sum( # count True values
174 NODEIFACE == testbed_instance._get_factory_id(guid)
175 for guid in dev_guids )
176 element.min_num_external_ifaces = num_open_ifaces
178 # require vroute vsys if we have routes to set up
179 routes = testbed_instance._add_route.get(guid)
181 element.required_vsys.add("vroute")
183 testbed_instance.elements[guid] = element
185 def create_nodeiface(testbed_instance, guid):
186 parameters = testbed_instance._get_parameters(guid)
187 element = testbed_instance._make_node_iface(parameters)
188 testbed_instance.elements[guid] = element
190 def create_tuniface(testbed_instance, guid):
191 parameters = testbed_instance._get_parameters(guid)
192 element = testbed_instance._make_tun_iface(parameters)
194 # Set custom addresses, if there are any already
195 # Setting this early helps set up P2P links
196 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
197 addresses = testbed_instance._add_address[guid]
198 for address in addresses:
199 (address, netprefix, broadcast) = address
200 element.add_address(address, netprefix, broadcast)
202 testbed_instance.elements[guid] = element
204 def create_tapiface(testbed_instance, guid):
205 parameters = testbed_instance._get_parameters(guid)
206 element = testbed_instance._make_tap_iface(parameters)
208 # Set custom addresses, if there are any already
209 # Setting this early helps set up P2P links
210 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
211 addresses = testbed_instance._add_address[guid]
212 for address in addresses:
213 (address, netprefix, broadcast) = address
214 element.add_address(address, netprefix, broadcast)
216 testbed_instance.elements[guid] = element
218 def create_application(testbed_instance, guid):
219 parameters = testbed_instance._get_parameters(guid)
220 element = testbed_instance._make_application(parameters)
222 # Just inject configuration stuff
223 element.home_path = "nepi-app-%s" % (guid,)
225 testbed_instance.elements[guid] = element
227 def create_dependency(testbed_instance, guid):
228 parameters = testbed_instance._get_parameters(guid)
229 element = testbed_instance._make_dependency(parameters)
231 # Just inject configuration stuff
232 element.home_path = "nepi-dep-%s" % (guid,)
234 testbed_instance.elements[guid] = element
236 def create_nepi_dependency(testbed_instance, guid):
237 parameters = testbed_instance._get_parameters(guid)
238 element = testbed_instance._make_nepi_dependency(parameters)
240 # Just inject configuration stuff
241 element.home_path = "nepi-nepi-%s" % (guid,)
243 testbed_instance.elements[guid] = element
245 def create_ns3_dependency(testbed_instance, guid):
246 parameters = testbed_instance._get_parameters(guid)
247 element = testbed_instance._make_ns3_dependency(parameters)
249 # Just inject configuration stuff
250 element.home_path = "nepi-ns3-%s" % (guid,)
252 testbed_instance.elements[guid] = element
254 def create_internet(testbed_instance, guid):
255 parameters = testbed_instance._get_parameters(guid)
256 element = testbed_instance._make_internet(parameters)
257 testbed_instance.elements[guid] = element
259 def create_netpipe(testbed_instance, guid):
260 parameters = testbed_instance._get_parameters(guid)
261 element = testbed_instance._make_netpipe(parameters)
262 testbed_instance.elements[guid] = element
264 ### Start/Stop functions ###
266 def start_application(testbed_instance, guid):
267 parameters = testbed_instance._get_parameters(guid)
268 traces = testbed_instance._get_traces(guid)
269 app = testbed_instance.elements[guid]
271 app.stdout = "stdout" in traces
272 app.stderr = "stderr" in traces
273 app.buildlog = "buildlog" in traces
277 def stop_application(testbed_instance, guid):
278 app = testbed_instance.elements[guid]
281 ### Status functions ###
283 def status_application(testbed_instance, guid):
284 if guid not in testbed_instance.elements.keys():
285 return AS.STATUS_NOT_STARTED
287 app = testbed_instance.elements[guid]
290 ### Configure functions ###
292 def configure_nodeiface(testbed_instance, guid):
293 element = testbed_instance._elements[guid]
295 # Cannot explicitly configure addresses
296 if guid in testbed_instance._add_address:
297 raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
300 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
301 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
302 siblings = [ self._element[dev_guid]
303 for dev_guid in dev_guids
304 if dev_guid != guid ]
306 # Fetch address from PLC api
307 element.pick_iface(siblings)
309 # Do some validations
312 def preconfigure_tuniface(testbed_instance, guid):
313 element = testbed_instance._elements[guid]
315 # Set custom addresses if any, and if not set already
316 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
317 addresses = testbed_instance._add_address[guid]
318 for address in addresses:
319 (address, netprefix, broadcast) = address
320 element.add_address(address, netprefix, broadcast)
322 # Link to external interface, if any
323 for iface in testbed_instance._elements.itervalues():
324 if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
325 element.external_iface = iface
328 # Set standard TUN attributes
329 if (not element.tun_addr or not element.tun_port) and element.external_iface:
330 element.tun_addr = element.external_iface.address
331 element.tun_port = 15000 + int(guid)
334 traces = testbed_instance._get_traces(guid)
335 for capmode in ('pcap', 'packets'):
336 if capmode in traces:
337 element.capture = capmode
340 element.capture = False
342 # Do some validations
346 if element.peer_proto:
347 if element.peer_iface and isinstance(element.peer_iface, testbed_instance._interfaces.TunIface):
349 listening = id(element) < id(element.peer_iface)
352 if not element.tun_addr or not element.tun_port:
354 elif not element.peer_addr or not element.peer_port:
357 # both have addresses...
358 # ...the one with the lesser address listens
359 listening = element.tun_addr < element.peer_addr
364 def postconfigure_tuniface(testbed_instance, guid):
365 element = testbed_instance._elements[guid]
370 def wait_tuniface(testbed_instance, guid):
371 element = testbed_instance._elements[guid]
374 element.async_launch_wait()
377 def configure_node(testbed_instance, guid):
378 node = testbed_instance._elements[guid]
380 # Just inject configuration stuff
381 node.home_path = "nepi-node-%s" % (guid,)
382 node.ident_path = testbed_instance.sliceSSHKey
383 node.slicename = testbed_instance.slicename
385 # Do some validations
388 # this will be done in parallel in all nodes
389 # this call only spawns the process
390 node.install_dependencies()
392 def configure_node_routes(testbed_instance, guid):
393 node = testbed_instance._elements[guid]
394 routes = testbed_instance._add_route.get(guid)
398 for dev_guid in testbed_instance.get_connected(guid, "devs", "node")
399 for dev in ( testbed_instance._elements.get(dev_guid) ,)
400 if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ]
402 node.configure_routes(routes, devs)
404 def configure_application(testbed_instance, guid):
405 app = testbed_instance._elements[guid]
407 # Do some validations
410 # Wait for dependencies
411 app.node.wait_dependencies()
416 def configure_dependency(testbed_instance, guid):
417 dep = testbed_instance._elements[guid]
419 # Do some validations
422 # Wait for dependencies
423 dep.node.wait_dependencies()
428 def configure_netpipe(testbed_instance, guid):
429 netpipe = testbed_instance._elements[guid]
431 # Do some validations
434 # Wait for dependencies
435 netpipe.node.wait_dependencies()
440 ### Factory information ###
442 connector_types = dict({
444 "help": "Connector from node to applications",
450 "help": "Connector from node to network interfaces",
456 "help": "Connector from node to application dependencies "
457 "(packages and applications that need to be installed)",
463 "help": "Connector from network interfaces to the internet",
469 "help": "Connector to a Node",
475 "help": "Connector to a NetPipe",
482 "help": "ip-ip tunneling over TCP link",
488 "help": "ip-ip tunneling over UDP datagrams",
494 "help": "TUN device file descriptor provider",
503 "from": (TESTBED_ID, NODE, "devs"),
504 "to": (TESTBED_ID, NODEIFACE, "node"),
505 "init_code": connect_node_iface_node,
509 "from": (TESTBED_ID, NODE, "devs"),
510 "to": (TESTBED_ID, TUNIFACE, "node"),
511 "init_code": connect_tun_iface_node,
515 "from": (TESTBED_ID, NODE, "devs"),
516 "to": (TESTBED_ID, TAPIFACE, "node"),
517 "init_code": connect_tun_iface_node,
521 "from": (TESTBED_ID, NODEIFACE, "inet"),
522 "to": (TESTBED_ID, INTERNET, "devs"),
523 "init_code": connect_node_iface_inet,
527 "from": (TESTBED_ID, NODE, "apps"),
528 "to": (TESTBED_ID, APPLICATION, "node"),
529 "init_code": connect_dep,
533 "from": (TESTBED_ID, NODE, "deps"),
534 "to": (TESTBED_ID, DEPENDENCY, "node"),
535 "init_code": connect_dep,
539 "from": (TESTBED_ID, NODE, "deps"),
540 "to": (TESTBED_ID, NEPIDEPENDENCY, "node"),
541 "init_code": connect_dep,
545 "from": (TESTBED_ID, NODE, "deps"),
546 "to": (TESTBED_ID, NS3DEPENDENCY, "node"),
547 "init_code": connect_dep,
551 "from": (TESTBED_ID, NODE, "pipes"),
552 "to": (TESTBED_ID, NETPIPE, "node"),
553 "init_code": connect_node_netpipe,
557 "from": (TESTBED_ID, TUNIFACE, "tcp"),
558 "to": (TESTBED_ID, TUNIFACE, "tcp"),
559 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
563 "from": (TESTBED_ID, TUNIFACE, "udp"),
564 "to": (TESTBED_ID, TUNIFACE, "udp"),
565 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
569 "from": (TESTBED_ID, TAPIFACE, "tcp"),
570 "to": (TESTBED_ID, TAPIFACE, "tcp"),
571 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
575 "from": (TESTBED_ID, TAPIFACE, "udp"),
576 "to": (TESTBED_ID, TAPIFACE, "udp"),
577 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
581 "from": (TESTBED_ID, TUNIFACE, "tcp"),
582 "to": (None, None, "tcp"),
583 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
584 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
588 "from": (TESTBED_ID, TUNIFACE, "udp"),
589 "to": (None, None, "udp"),
590 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
591 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
595 "from": (TESTBED_ID, TUNIFACE, "fd->"),
596 "to": (None, None, "->fd"),
597 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
601 "from": (TESTBED_ID, TAPIFACE, "tcp"),
602 "to": (None, None, "tcp"),
603 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
604 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
608 "from": (TESTBED_ID, TAPIFACE, "udp"),
609 "to": (None, None, "udp"),
610 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
611 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
615 "from": (TESTBED_ID, TAPIFACE, "fd->"),
616 "to": (None, None, "->fd"),
617 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
623 "forward_X11": dict({
624 "name": "forward_X11",
625 "help": "Forward x11 from main namespace to the node",
626 "type": Attribute.BOOL,
628 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
629 "validation_function": validation.is_bool,
633 "help": "Constrain hostname during resource discovery. May use wildcards.",
634 "type": Attribute.STRING,
635 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
636 "validation_function": validation.is_string,
638 "architecture": dict({
639 "name": "architecture",
640 "help": "Constrain architexture during resource discovery.",
641 "type": Attribute.ENUM,
642 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
643 "allowed": ["x86_64",
645 "validation_function": validation.is_enum,
647 "operating_system": dict({
648 "name": "operatingSystem",
649 "help": "Constrain operating system during resource discovery.",
650 "type": Attribute.ENUM,
651 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
657 "validation_function": validation.is_enum,
661 "help": "Constrain the PlanetLab site this node should reside on.",
662 "type": Attribute.ENUM,
663 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
667 "validation_function": validation.is_enum,
669 "min_reliability": dict({
670 "name": "minReliability",
671 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
672 "type": Attribute.DOUBLE,
674 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
675 "validation_function": validation.is_double,
677 "max_reliability": dict({
678 "name": "maxReliability",
679 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
680 "type": Attribute.DOUBLE,
682 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
683 "validation_function": validation.is_double,
685 "min_bandwidth": dict({
686 "name": "minBandwidth",
687 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
688 "type": Attribute.DOUBLE,
690 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
691 "validation_function": validation.is_double,
693 "max_bandwidth": dict({
694 "name": "maxBandwidth",
695 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
696 "type": Attribute.DOUBLE,
698 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
699 "validation_function": validation.is_double,
705 "type": Attribute.BOOL,
707 "validation_function": validation.is_bool
711 "help": "This is the primary interface for the attached node",
712 "type": Attribute.BOOL,
714 "validation_function": validation.is_bool
716 "device_name": dict({
718 "help": "Device name",
719 "type": Attribute.STRING,
720 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
721 "validation_function": validation.is_string
725 "help": "Maximum transmition unit for device",
726 "type": Attribute.INTEGER,
728 "validation_function": validation.is_integer_range(0,1500)
732 "help": "Network mask for the device (eg: 24 for /24 network)",
733 "type": Attribute.INTEGER,
734 "validation_function": validation.is_integer_range(8,24)
738 "help": "Enable SNAT (source NAT to the internet) no this device",
739 "type": Attribute.BOOL,
741 "validation_function": validation.is_bool
743 "pointopoint": dict({
744 "name": "pointopoint",
745 "help": "If the interface is a P2P link, the remote endpoint's IP "
746 "should be set on this attribute.",
747 "type": Attribute.STRING,
748 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
749 "validation_function": validation.is_string
753 "help": "Transmission queue length (in packets)",
754 "type": Attribute.INTEGER,
755 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
757 "validation_function": validation.is_integer
762 "help": "Command line string",
763 "type": Attribute.STRING,
764 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
765 "validation_function": validation.is_string
769 "help": "Run with root privileges",
770 "type": Attribute.BOOL,
771 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
773 "validation_function": validation.is_bool
777 "help": "Standard input",
778 "type": Attribute.STRING,
779 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
780 "validation_function": validation.is_string
785 "help": "Space-separated list of packages required to run the application",
786 "type": Attribute.STRING,
787 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
788 "validation_function": validation.is_string
790 "build-depends": dict({
791 "name": "buildDepends",
792 "help": "Space-separated list of packages required to build the application",
793 "type": Attribute.STRING,
794 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
795 "validation_function": validation.is_string
799 "help": "True if required packages can be found in the RpmFusion repository",
800 "type": Attribute.BOOL,
801 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
803 "validation_function": validation.is_bool
807 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
808 "Archives won't be expanded automatically.",
809 "type": Attribute.STRING,
810 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
811 "validation_function": validation.is_string
815 "help": "Build commands to execute after deploying the sources. "
816 "Sources will be in the ${SOURCES} folder. "
817 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
818 "Try to make the commands return with a nonzero exit code on error.\n"
819 "Also, do not install any programs here, use the 'install' attribute. This will "
820 "help keep the built files constrained to the build folder (which may "
821 "not be the home folder), and will result in faster deployment. Also, "
822 "make sure to clean up temporary files, to reduce bandwidth usage between "
823 "nodes when transferring built packages.",
824 "type": Attribute.STRING,
825 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
826 "validation_function": validation.is_string
830 "help": "Commands to transfer built files to their final destinations. "
831 "Sources will be in the initial working folder, and a special "
832 "tag ${SOURCES} can be used to reference the experiment's "
833 "home folder (where the application commands will run).\n"
834 "ALL sources and targets needed for execution must be copied there, "
835 "if building has been enabled.\n"
836 "That is, 'slave' nodes will not automatically get any source files. "
837 "'slave' nodes don't get build dependencies either, so if you need "
838 "make and other tools to install, be sure to provide them as "
839 "actual dependencies instead.",
840 "type": Attribute.STRING,
841 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
842 "validation_function": validation.is_string
845 "netpipe_mode": dict({
847 "help": "Link mode:\n"
848 " * SERVER: applies to incoming connections\n"
849 " * CLIENT: applies to outgoing connections\n"
850 " * SERVICE: applies to both",
851 "type": Attribute.ENUM,
852 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
853 "allowed": ["SERVER",
856 "validation_function": validation.is_enum,
860 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
861 "type": Attribute.STRING,
862 "validation_function": is_portlist,
866 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
867 "type": Attribute.STRING,
868 "validation_function": is_addrlist,
872 "help": "Inbound bandwidth limit (in Mbit/s)",
873 "type": Attribute.DOUBLE,
874 "validation_function": validation.is_double,
878 "help": "Outbound bandwidth limit (in Mbit/s)",
879 "type": Attribute.DOUBLE,
880 "validation_function": validation.is_double,
884 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
885 "type": Attribute.DOUBLE,
886 "validation_function": validation.is_double,
890 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
891 "type": Attribute.DOUBLE,
892 "validation_function": validation.is_double,
896 "help": "Inbound packet delay (in milliseconds)",
897 "type": Attribute.INTEGER,
899 "validation_function": validation.is_integer,
903 "help": "Outbound packet delay (in milliseconds)",
904 "type": Attribute.INTEGER,
906 "validation_function": validation.is_integer,
913 "help": "Standard output stream"
917 "help": "Application standard error",
921 "help": "Output of the build process",
924 "netpipe_stats": dict({
925 "name": "netpipeStats",
926 "help": "Information about rule match counters, packets dropped, etc.",
931 "help": "Detailled log of all packets going through the interface",
935 "help": "PCAP trace of all packets going through the interface",
939 create_order = [ INTERNET, NODE, NODEIFACE, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
941 configure_order = [ INTERNET, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
943 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
944 start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
946 factories_info = dict({
948 "help": "Virtualized Node (V-Server style)",
949 "category": FC.CATEGORY_NODES,
950 "create_function": create_node,
951 "preconfigure_function": configure_node,
952 "prestart_function": configure_node_routes,
964 # NEPI-in-NEPI attributes
965 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
967 "connector_types": ["devs", "apps", "pipes", "deps"],
968 "tags": [tags.NODE, tags.ALLOW_ROUTES],
971 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
972 "category": FC.CATEGORY_DEVICES,
973 "create_function": create_nodeiface,
974 "preconfigure_function": configure_nodeiface,
975 "box_attributes": [ ],
976 "connector_types": ["node", "inet"],
977 "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
980 "help": "Virtual TUN network interface (layer 3)",
981 "category": FC.CATEGORY_DEVICES,
982 "create_function": create_tuniface,
983 "preconfigure_function": preconfigure_tuniface,
984 "configure_function": postconfigure_tuniface,
985 "prestart_function": wait_tuniface,
987 "up", "device_name", "mtu", "snat", "pointopoint",
989 "tun_proto", "tun_addr", "tun_port", "tun_key"
991 "traces": ["packets", "pcap"],
992 "connector_types": ["node","udp","tcp","fd->"],
993 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
996 "help": "Virtual TAP network interface (layer 2)",
997 "category": FC.CATEGORY_DEVICES,
998 "create_function": create_tapiface,
999 "preconfigure_function": preconfigure_tuniface,
1000 "configure_function": postconfigure_tuniface,
1001 "prestart_function": wait_tuniface,
1003 "up", "device_name", "mtu", "snat", "pointopoint",
1005 "tun_proto", "tun_addr", "tun_port", "tun_key"
1007 "traces": ["packets", "pcap"],
1008 "connector_types": ["node","udp","tcp","fd->"],
1009 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1012 "help": "Generic executable command line application",
1013 "category": FC.CATEGORY_APPLICATIONS,
1014 "create_function": create_application,
1015 "start_function": start_application,
1016 "status_function": status_application,
1017 "stop_function": stop_application,
1018 "configure_function": configure_application,
1019 "box_attributes": ["command", "sudo", "stdin",
1020 "depends", "build-depends", "build", "install",
1021 "sources", "rpm-fusion" ],
1022 "connector_types": ["node"],
1023 "traces": ["stdout", "stderr", "buildlog"],
1024 "tags": [tags.APPLICATION],
1027 "help": "Requirement for package or application to be installed on some node",
1028 "category": FC.CATEGORY_APPLICATIONS,
1029 "create_function": create_dependency,
1030 "preconfigure_function": configure_dependency,
1031 "box_attributes": ["depends", "build-depends", "build", "install",
1032 "sources", "rpm-fusion" ],
1033 "connector_types": ["node"],
1034 "traces": ["buildlog"],
1036 NEPIDEPENDENCY: dict({
1037 "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1038 "category": FC.CATEGORY_APPLICATIONS,
1039 "create_function": create_nepi_dependency,
1040 "preconfigure_function": configure_dependency,
1041 "box_attributes": [],
1042 "connector_types": ["node"],
1043 "traces": ["buildlog"],
1045 NS3DEPENDENCY: dict({
1046 "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1047 "category": FC.CATEGORY_APPLICATIONS,
1048 "create_function": create_ns3_dependency,
1049 "preconfigure_function": configure_dependency,
1050 "box_attributes": [ ],
1051 "connector_types": ["node"],
1052 "traces": ["buildlog"],
1055 "help": "Internet routing",
1056 "category": FC.CATEGORY_CHANNELS,
1057 "create_function": create_internet,
1058 "connector_types": ["devs"],
1059 "tags": [tags.INTERNET],
1062 "help": "Link emulation",
1063 "category": FC.CATEGORY_CHANNELS,
1064 "create_function": create_netpipe,
1065 "configure_function": configure_netpipe,
1066 "box_attributes": ["netpipe_mode",
1067 "addr_list", "port_list",
1068 "bw_in","plr_in","delay_in",
1069 "bw_out","plr_out","delay_out"],
1070 "connector_types": ["node"],
1071 "traces": ["netpipe_stats"],
1075 testbed_attributes = dict({
1078 "help": "The name of the PlanetLab slice to use",
1079 "type": Attribute.STRING,
1080 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1081 "validation_function": validation.is_string
1085 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1086 "type": Attribute.STRING,
1087 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1088 "validation_function": validation.is_string
1092 "help": "The PlanetLab user's password.",
1093 "type": Attribute.STRING,
1094 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1095 "validation_function": validation.is_string
1099 "help": "The PlanetLab PLC API host",
1100 "type": Attribute.STRING,
1101 "value": "www.planet-lab.eu",
1102 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1103 "validation_function": validation.is_string
1107 "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1108 "type": Attribute.STRING,
1109 "value": "https://%(hostname)s:443/PLCAPI/",
1110 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1111 "validation_function": validation.is_string
1113 "slice_ssh_key": dict({
1114 "name": "sliceSSHKey",
1115 "help": "The controller-local path to the slice user's ssh private key. "
1116 "It is the user's responsability to deploy this file where the controller "
1117 "will run, it won't be done automatically because it's sensitive information. "
1118 "It is recommended that a NEPI-specific user be created for this purpose and "
1119 "this purpose alone.",
1120 "type": Attribute.STRING,
1121 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1122 "validation_function": validation.is_string
1124 "pl_log_level": dict({
1125 "name": "plLogLevel",
1126 "help": "Verbosity of logging of planetlab events.",
1128 "type": Attribute.ENUM,
1129 "allowed": ["DEBUG",
1134 "validation_function": validation.is_enum,
1138 class MetadataInfo(metadata.MetadataInfo):
1140 def connector_types(self):
1141 return connector_types
1144 def connections(self):
1148 def attributes(self):
1156 def create_order(self):
1160 def configure_order(self):
1161 return configure_order
1164 def prestart_order(self):
1168 def start_order(self):
1172 def factories_info(self):
1173 return factories_info
1176 def testbed_attributes(self):
1177 return testbed_attributes
1180 def testbed_id(self):
1184 def testbed_version(self):
1185 return TESTBED_VERSION