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
22 NODEIFACE = "NodeInterface"
23 TUNIFACE = "TunInterface"
24 TAPIFACE = "TapInterface"
25 APPLICATION = "Application"
26 DEPENDENCY = "Dependency"
27 NEPIDEPENDENCY = "NepiDependency"
28 NS3DEPENDENCY = "NS3Dependency"
31 TUNFILTER = "TunFilter"
33 PL_TESTBED_ID = "planetlab"
36 ### Custom validation functions ###
37 def is_addrlist(attribute, value):
38 if not validation.is_string(attribute, value):
45 components = value.split(',')
47 for component in components:
49 addr, mask = component.split('/',1)
51 addr, mask = component, '32'
53 if mask is not None and not (mask and mask.isdigit()):
54 # No empty or nonnumeric masks
57 if not validation.is_ip4_address(attribute, addr):
58 # Address part must be ipv4
63 def is_portlist(attribute, value):
64 if not validation.is_string(attribute, value):
71 components = value.split(',')
73 for component in components:
75 pfrom, pto = component.split('-',1)
77 pfrom = pto = component
79 if not pfrom or not pto or not pfrom.isdigit() or not pto.isdigit():
80 # No empty or nonnumeric ports
86 ### Connection functions ####
88 def connect_node_iface_node(testbed_instance, node_guid, iface_guid):
89 node = testbed_instance._elements[node_guid]
90 iface = testbed_instance._elements[iface_guid]
93 def connect_node_iface_inet(testbed_instance, iface_guid, inet_guid):
94 iface = testbed_instance._elements[iface_guid]
95 iface.has_internet = True
97 def connect_tun_iface_node(testbed_instance, node_guid, iface_guid):
98 node = testbed_instance._elements[node_guid]
99 iface = testbed_instance._elements[iface_guid]
101 node.required_vsys.update(('fd_tuntap', 'vif_up', 'vif_down'))
102 node.required_packages.update(('python', 'python-crypto', 'python-setuptools', 'gcc'))
104 def connect_tun_iface_peer(proto, testbed_instance, iface_guid, peer_iface_guid):
105 iface = testbed_instance._elements[iface_guid]
106 peer_iface = testbed_instance._elements[peer_iface_guid]
107 iface.peer_iface = peer_iface
108 peer_iface.peer_iface = iface
111 peer_iface.peer_proto = \
112 peer_iface.tun_proto = proto
113 iface.tun_key = peer_iface.tun_key
115 def connect_tun_iface_filter(testbed_instance, iface_guid, filter_guid):
116 iface = testbed_instance._elements[iface_guid]
117 filt = testbed_instance._elements[filter_guid]
118 iface.filter_module = filt
119 filt.iface_guid = iface_guid
120 filt.iface = weakref.ref(iface)
122 connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
124 def connect_filter_peer(proto, testbed_instance, filter_guid, peer_guid):
125 peer = testbed_instance._elements[peer_guid]
126 filt = testbed_instance._elements[filter_guid]
127 filt.peer_proto = proto
128 filt.peer_guid = peer_guid
130 connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
132 def connect_filter_filter(proto, testbed_instance, filter_guid, peer_guid):
133 peer = testbed_instance._elements[peer_guid]
134 filt = testbed_instance._elements[filter_guid]
135 filt.peer_proto = proto
136 peer.peer_proto = proto
138 peer.peer_guid = filt.iface_guid
140 filt.peer_guid = peer.iface_guid
141 if filt.iface_guid and filt.peer_guid:
142 connect_tun_iface_peer(filt.peer_proto, testbed_instance, filt.iface_guid, filt.peer_guid)
144 def crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data):
145 iface = testbed_instance._elements[iface_guid]
146 iface.peer_iface = None
147 iface.peer_addr = peer_iface_data.get("tun_addr")
148 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
149 iface.peer_port = peer_iface_data.get("tun_port")
150 iface.peer_cipher = peer_iface_data.get("tun_cipher")
151 iface.tun_key = min(iface.tun_key, peer_iface_data.get("tun_key"))
152 iface.tun_proto = proto
154 preconfigure_tuniface(testbed_instance, iface_guid)
156 def crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data):
157 # refresh (refreshable) attributes for second-phase
158 iface = testbed_instance._elements[iface_guid]
159 iface.peer_addr = peer_iface_data.get("tun_addr")
160 iface.peer_proto = peer_iface_data.get("tun_proto") or proto
161 iface.peer_port = peer_iface_data.get("tun_port")
162 iface.peer_cipher = peer_iface_data.get("tun_cipher")
164 postconfigure_tuniface(testbed_instance, iface_guid)
166 def crossconnect_tun_iface_peer_both(proto, testbed_instance, iface_guid, peer_iface_data):
167 crossconnect_tun_iface_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
168 crossconnect_tun_iface_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
170 def crossconnect_filter_peer_init(proto, testbed_instance, filter_guid, peer_data):
171 filt = testbed_instance._elements[filter_guid]
172 filt.peer_proto = proto
173 crossconnect_tun_iface_peer_init(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data)
175 def crossconnect_filter_peer_compl(proto, testbed_instance, filter_guid, peer_data):
176 filt = testbed_instance._elements[filter_guid]
177 filt.peer_proto = proto
178 crossconnect_tun_iface_peer_compl(filt.peer_proto, testbed_instance, filt.iface_guid, peer_data)
180 def crossconnect_filter_peer_both(proto, testbed_instance, filter_guid, peer_data):
181 crossconnect_filter_peer_init(proto, testbed_instance, iface_guid, peer_iface_data)
182 crossconnect_filter_peer_compl(proto, testbed_instance, iface_guid, peer_iface_data)
185 def connect_dep(testbed_instance, node_guid, app_guid):
186 node = testbed_instance._elements[node_guid]
187 app = testbed_instance._elements[app_guid]
191 node.required_packages.update(set(
192 app.depends.split() ))
195 if app.home_path and app.home_path not in node.pythonpath:
196 node.pythonpath.append(app.home_path)
199 for envkey, envval in app.env.iteritems():
200 envval = app._replace_paths(envval)
201 node.env[envkey].append(envval)
204 node.rpmFusion = True
206 def connect_node_netpipe(testbed_instance, node_guid, netpipe_guid):
207 node = testbed_instance._elements[node_guid]
208 netpipe = testbed_instance._elements[netpipe_guid]
210 node.required_vsys.add('ipfw-be')
211 node.required_packages.add('ipfwslice')
214 ### Creation functions ###
216 def create_node(testbed_instance, guid):
217 parameters = testbed_instance._get_parameters(guid)
219 # create element with basic attributes
220 element = testbed_instance._make_node(parameters)
222 # add constraint on number of (real) interfaces
223 # by counting connected devices
224 dev_guids = testbed_instance.get_connected(guid, "devs", "node")
225 num_open_ifaces = sum( # count True values
226 NODEIFACE == testbed_instance._get_factory_id(guid)
227 for guid in dev_guids )
228 element.min_num_external_ifaces = num_open_ifaces
230 # require vroute vsys if we have routes to set up
231 routes = testbed_instance._add_route.get(guid)
233 vsys = element.routing_method(routes,
234 testbed_instance.vsys_vnet)
235 element.required_vsys.add(vsys)
237 testbed_instance.elements[guid] = element
239 def create_nodeiface(testbed_instance, guid):
240 parameters = testbed_instance._get_parameters(guid)
241 element = testbed_instance._make_node_iface(parameters)
242 testbed_instance.elements[guid] = element
244 def create_tuniface(testbed_instance, guid):
245 parameters = testbed_instance._get_parameters(guid)
246 element = testbed_instance._make_tun_iface(parameters)
248 # Set custom addresses, if there are any already
249 # Setting this early helps set up P2P links
250 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
251 addresses = testbed_instance._add_address[guid]
252 for address in addresses:
253 (address, netprefix, broadcast) = address
254 element.add_address(address, netprefix, broadcast)
256 testbed_instance.elements[guid] = element
258 def create_tapiface(testbed_instance, guid):
259 parameters = testbed_instance._get_parameters(guid)
260 element = testbed_instance._make_tap_iface(parameters)
262 # Set custom addresses, if there are any already
263 # Setting this early helps set up P2P links
264 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
265 addresses = testbed_instance._add_address[guid]
266 for address in addresses:
267 (address, netprefix, broadcast) = address
268 element.add_address(address, netprefix, broadcast)
270 testbed_instance.elements[guid] = element
272 def create_tunfilter(testbed_instance, guid):
273 parameters = testbed_instance._get_parameters(guid)
274 element = testbed_instance._make_tun_filter(parameters)
275 testbed_instance.elements[guid] = element
278 def create_application(testbed_instance, guid):
279 parameters = testbed_instance._get_parameters(guid)
280 element = testbed_instance._make_application(parameters)
282 # Just inject configuration stuff
283 element.home_path = "nepi-app-%s" % (guid,)
285 testbed_instance.elements[guid] = element
287 def create_dependency(testbed_instance, guid):
288 parameters = testbed_instance._get_parameters(guid)
289 element = testbed_instance._make_dependency(parameters)
291 # Just inject configuration stuff
292 element.home_path = "nepi-dep-%s" % (guid,)
294 testbed_instance.elements[guid] = element
296 def create_nepi_dependency(testbed_instance, guid):
297 parameters = testbed_instance._get_parameters(guid)
298 element = testbed_instance._make_nepi_dependency(parameters)
300 # Just inject configuration stuff
301 element.home_path = "nepi-nepi-%s" % (guid,)
303 testbed_instance.elements[guid] = element
305 def create_ns3_dependency(testbed_instance, guid):
306 parameters = testbed_instance._get_parameters(guid)
307 element = testbed_instance._make_ns3_dependency(parameters)
309 # Just inject configuration stuff
310 element.home_path = "nepi-ns3-%s" % (guid,)
312 testbed_instance.elements[guid] = element
314 def create_internet(testbed_instance, guid):
315 parameters = testbed_instance._get_parameters(guid)
316 element = testbed_instance._make_internet(parameters)
317 testbed_instance.elements[guid] = element
319 def create_netpipe(testbed_instance, guid):
320 parameters = testbed_instance._get_parameters(guid)
321 element = testbed_instance._make_netpipe(parameters)
322 testbed_instance.elements[guid] = element
324 ### Start/Stop functions ###
326 def start_application(testbed_instance, guid):
327 parameters = testbed_instance._get_parameters(guid)
328 traces = testbed_instance._get_traces(guid)
329 app = testbed_instance.elements[guid]
331 app.stdout = "stdout" in traces
332 app.stderr = "stderr" in traces
333 app.buildlog = "buildlog" in traces
334 app.outout = "output" in traces
338 def stop_application(testbed_instance, guid):
339 app = testbed_instance.elements[guid]
342 ### Status functions ###
344 def status_application(testbed_instance, guid):
345 if guid not in testbed_instance.elements.keys():
346 return AS.STATUS_NOT_STARTED
348 app = testbed_instance.elements[guid]
351 ### Configure functions ###
353 def configure_nodeiface(testbed_instance, guid):
354 element = testbed_instance._elements[guid]
356 # Cannot explicitly configure addresses
357 if guid in testbed_instance._add_address:
358 raise ValueError, "Cannot explicitly set address of public PlanetLab interface"
361 node_guid = testbed_instance.get_connected(guid, "node", "devs")[0]
362 dev_guids = testbed_instance.get_connected(node_guid, "node", "devs")
363 siblings = [ self._element[dev_guid]
364 for dev_guid in dev_guids
365 if dev_guid != guid ]
367 # Fetch address from PLC api
368 element.pick_iface(siblings)
370 # Do some validations
373 def preconfigure_tuniface(testbed_instance, guid):
374 element = testbed_instance._elements[guid]
376 # Set custom addresses if any, and if not set already
377 if guid in testbed_instance._add_address and not (element.address or element.netmask or element.netprefix):
378 addresses = testbed_instance._add_address[guid]
379 for address in addresses:
380 (address, netprefix, broadcast) = address
381 element.add_address(address, netprefix, broadcast)
383 # Link to external interface, if any
384 for iface in testbed_instance._elements.itervalues():
385 if isinstance(iface, testbed_instance._interfaces.NodeIface) and iface.node is element.node and iface.has_internet:
386 element.external_iface = iface
389 # Set standard TUN attributes
390 if (not element.tun_addr or not element.tun_port) and element.external_iface:
391 element.tun_addr = element.external_iface.address
392 element.tun_port = testbed_instance.tapPortBase + int(guid)
395 traces = testbed_instance._get_traces(guid)
396 for capmode in ('pcap', 'packets'):
397 if capmode in traces:
398 element.capture = capmode
401 element.capture = False
403 # Do some validations
407 if element.peer_proto:
408 if element.peer_iface and isinstance(element.peer_iface, testbed_instance._interfaces.TunIface):
410 listening = id(element) < id(element.peer_iface)
413 if not element.tun_addr or not element.tun_port:
415 elif not element.peer_addr or not element.peer_port:
418 # both have addresses...
419 # ...the one with the lesser address listens
420 listening = element.tun_addr < element.peer_addr
425 def postconfigure_tuniface(testbed_instance, guid):
426 element = testbed_instance._elements[guid]
431 def wait_tuniface(testbed_instance, guid):
432 element = testbed_instance._elements[guid]
435 element.async_launch_wait()
438 def configure_node(testbed_instance, guid):
439 node = testbed_instance._elements[guid]
441 # Just inject configuration stuff
442 node.home_path = "nepi-node-%s" % (guid,)
443 node.ident_path = testbed_instance.sliceSSHKey
444 node.slicename = testbed_instance.slicename
446 # Do some validations
449 # this will be done in parallel in all nodes
450 # this call only spawns the process
451 node.install_dependencies()
453 def configure_node_routes(testbed_instance, guid):
454 node = testbed_instance._elements[guid]
455 routes = testbed_instance._add_route.get(guid)
459 for dev_guid in testbed_instance.get_connected(guid, "devs", "node")
460 for dev in ( testbed_instance._elements.get(dev_guid) ,)
461 if dev and isinstance(dev, testbed_instance._interfaces.TunIface) ]
463 vsys = testbed_instance.vsys_vnet
465 node.configure_routes(routes, devs, vsys)
467 def configure_application(testbed_instance, guid):
468 app = testbed_instance._elements[guid]
470 # Do some validations
473 # Wait for dependencies
474 app.node.wait_dependencies()
479 def configure_dependency(testbed_instance, guid):
480 dep = testbed_instance._elements[guid]
482 # Do some validations
485 # Wait for dependencies
486 dep.node.wait_dependencies()
491 def configure_netpipe(testbed_instance, guid):
492 netpipe = testbed_instance._elements[guid]
494 # Do some validations
497 # Wait for dependencies
498 netpipe.node.wait_dependencies()
503 ### Factory information ###
505 connector_types = dict({
507 "help": "Connector from node to applications",
513 "help": "Connector from node to network interfaces",
519 "help": "Connector from node to application dependencies "
520 "(packages and applications that need to be installed)",
526 "help": "Connector from network interfaces to the internet",
532 "help": "Connector to a Node",
538 "help": "Connector to a NetPipe",
545 "help": "ip-ip tunneling over TCP link",
551 "help": "ip-ip tunneling over UDP datagrams",
557 "help": "IP or Ethernet tunneling using the GRE protocol",
563 "help": "TUN device file descriptor provider",
569 "help": "TUN device file descriptor slot",
578 "from": (TESTBED_ID, NODE, "devs"),
579 "to": (TESTBED_ID, NODEIFACE, "node"),
580 "init_code": connect_node_iface_node,
584 "from": (TESTBED_ID, NODE, "devs"),
585 "to": (TESTBED_ID, TUNIFACE, "node"),
586 "init_code": connect_tun_iface_node,
590 "from": (TESTBED_ID, NODE, "devs"),
591 "to": (TESTBED_ID, TAPIFACE, "node"),
592 "init_code": connect_tun_iface_node,
596 "from": (TESTBED_ID, NODEIFACE, "inet"),
597 "to": (TESTBED_ID, INTERNET, "devs"),
598 "init_code": connect_node_iface_inet,
602 "from": (TESTBED_ID, NODE, "apps"),
603 "to": (TESTBED_ID, APPLICATION, "node"),
604 "init_code": connect_dep,
608 "from": (TESTBED_ID, NODE, "deps"),
609 "to": (TESTBED_ID, DEPENDENCY, "node"),
610 "init_code": connect_dep,
614 "from": (TESTBED_ID, NODE, "deps"),
615 "to": (TESTBED_ID, NEPIDEPENDENCY, "node"),
616 "init_code": connect_dep,
620 "from": (TESTBED_ID, NODE, "deps"),
621 "to": (TESTBED_ID, NS3DEPENDENCY, "node"),
622 "init_code": connect_dep,
626 "from": (TESTBED_ID, NODE, "pipes"),
627 "to": (TESTBED_ID, NETPIPE, "node"),
628 "init_code": connect_node_netpipe,
632 "from": (TESTBED_ID, TUNIFACE, "tcp"),
633 "to": (TESTBED_ID, TUNIFACE, "tcp"),
634 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
638 "from": (TESTBED_ID, TUNIFACE, "udp"),
639 "to": (TESTBED_ID, TUNIFACE, "udp"),
640 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
644 "from": (TESTBED_ID, TUNIFACE, "gre"),
645 "to": (TESTBED_ID, TUNIFACE, "gre"),
646 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
650 "from": (TESTBED_ID, TUNIFACE, "fd->"),
651 "to": (TESTBED_ID, TUNFILTER, "->fd"),
652 "init_code": connect_tun_iface_filter,
656 "from": (TESTBED_ID, TUNFILTER, "tcp"),
657 "to": (TESTBED_ID, TUNIFACE, "tcp"),
658 "init_code": functools.partial(connect_filter_peer,"tcp"),
662 "from": (TESTBED_ID, TUNFILTER, "udp"),
663 "to": (TESTBED_ID, TUNIFACE, "udp"),
664 "init_code": functools.partial(connect_filter_peer,"udp"),
668 "from": (TESTBED_ID, TAPIFACE, "tcp"),
669 "to": (TESTBED_ID, TAPIFACE, "tcp"),
670 "init_code": functools.partial(connect_tun_iface_peer,"tcp"),
674 "from": (TESTBED_ID, TAPIFACE, "udp"),
675 "to": (TESTBED_ID, TAPIFACE, "udp"),
676 "init_code": functools.partial(connect_tun_iface_peer,"udp"),
680 "from": (TESTBED_ID, TAPIFACE, "gre"),
681 "to": (TESTBED_ID, TAPIFACE, "gre"),
682 "init_code": functools.partial(connect_tun_iface_peer,"gre"),
686 "from": (TESTBED_ID, TAPIFACE, "fd->"),
687 "to": (TESTBED_ID, TUNFILTER, "->fd"),
688 "init_code": connect_tun_iface_filter,
692 "from": (TESTBED_ID, TUNFILTER, "tcp"),
693 "to": (TESTBED_ID, TAPIFACE, "tcp"),
694 "init_code": functools.partial(connect_filter_peer,"tcp"),
698 "from": (TESTBED_ID, TUNFILTER, "udp"),
699 "to": (TESTBED_ID, TAPIFACE, "udp"),
700 "init_code": functools.partial(connect_filter_peer,"udp"),
704 "from": (TESTBED_ID, TUNFILTER, "tcp"),
705 "to": (TESTBED_ID, TUNFILTER, "tcp"),
706 "init_code": functools.partial(connect_filter_filter,"tcp"),
710 "from": (TESTBED_ID, TUNFILTER, "udp"),
711 "to": (TESTBED_ID, TUNFILTER, "udp"),
712 "init_code": functools.partial(connect_filter_filter,"udp"),
716 "from": (TESTBED_ID, TUNIFACE, "tcp"),
717 "to": (None, None, "tcp"),
718 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
719 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
723 "from": (TESTBED_ID, TUNIFACE, "udp"),
724 "to": (None, None, "udp"),
725 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
726 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
730 "from": (TESTBED_ID, TUNIFACE, "fd->"),
731 "to": (None, None, "->fd"),
732 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
736 "from": (TESTBED_ID, TUNIFACE, "gre"),
737 "to": (None, None, "gre"),
738 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
742 "from": (TESTBED_ID, TAPIFACE, "tcp"),
743 "to": (None, None, "tcp"),
744 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"tcp"),
745 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"tcp"),
749 "from": (TESTBED_ID, TAPIFACE, "udp"),
750 "to": (None, None, "udp"),
751 "init_code": functools.partial(crossconnect_tun_iface_peer_init,"udp"),
752 "compl_code": functools.partial(crossconnect_tun_iface_peer_compl,"udp"),
756 "from": (TESTBED_ID, TAPIFACE, "fd->"),
757 "to": (None, None, "->fd"),
758 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"fd"),
761 # EGRE is an extension of PlanetLab, so we can't connect externally
762 # if the other testbed isn't another PlanetLab
764 "from": (TESTBED_ID, TAPIFACE, "gre"),
765 "to": (TESTBED_ID, None, "gre"),
766 "compl_code": functools.partial(crossconnect_tun_iface_peer_both,"gre"),
770 "from": (TESTBED_ID, TUNFILTER, "tcp"),
771 "to": (None, None, "tcp"),
772 "init_code": functools.partial(crossconnect_filter_peer_init,"tcp"),
773 "compl_code": functools.partial(crossconnect_filter_peer_compl,"tcp"),
777 "from": (TESTBED_ID, TUNFILTER, "udp"),
778 "to": (None, None, "udp"),
779 "init_code": functools.partial(crossconnect_filter_peer_init,"udp"),
780 "compl_code": functools.partial(crossconnect_filter_peer_compl,"udp"),
786 "forward_X11": dict({
787 "name": "forward_X11",
788 "help": "Forward x11 from main namespace to the node",
789 "type": Attribute.BOOL,
791 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
792 "validation_function": validation.is_bool,
796 "help": "Constrain hostname during resource discovery. May use wildcards.",
797 "type": Attribute.STRING,
798 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
799 "validation_function": validation.is_string,
803 "help": "Constrain location (city) during resource discovery. May use wildcards.",
804 "type": Attribute.STRING,
805 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
806 "validation_function": validation.is_string,
810 "help": "Constrain location (country) during resource discovery. May use wildcards.",
811 "type": Attribute.STRING,
812 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
813 "validation_function": validation.is_string,
817 "help": "Constrain location (region) during resource discovery. May use wildcards.",
818 "type": Attribute.STRING,
819 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
820 "validation_function": validation.is_string,
822 "architecture": dict({
823 "name": "architecture",
824 "help": "Constrain architexture during resource discovery.",
825 "type": Attribute.ENUM,
826 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
827 "allowed": ["x86_64",
829 "validation_function": validation.is_enum,
831 "operating_system": dict({
832 "name": "operatingSystem",
833 "help": "Constrain operating system during resource discovery.",
834 "type": Attribute.ENUM,
835 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
841 "validation_function": validation.is_enum,
845 "help": "Constrain the PlanetLab site this node should reside on.",
846 "type": Attribute.ENUM,
847 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
851 "validation_function": validation.is_enum,
853 "min_reliability": dict({
854 "name": "minReliability",
855 "help": "Constrain reliability while picking PlanetLab nodes. Specifies a lower acceptable bound.",
856 "type": Attribute.DOUBLE,
858 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
859 "validation_function": validation.is_number,
861 "max_reliability": dict({
862 "name": "maxReliability",
863 "help": "Constrain reliability while picking PlanetLab nodes. Specifies an upper acceptable bound.",
864 "type": Attribute.DOUBLE,
866 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
867 "validation_function": validation.is_number,
869 "min_bandwidth": dict({
870 "name": "minBandwidth",
871 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies a lower acceptable bound.",
872 "type": Attribute.DOUBLE,
874 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
875 "validation_function": validation.is_number,
877 "max_bandwidth": dict({
878 "name": "maxBandwidth",
879 "help": "Constrain available bandwidth while picking PlanetLab nodes. Specifies an upper acceptable bound.",
880 "type": Attribute.DOUBLE,
882 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
883 "validation_function": validation.is_number,
887 "help": "Constrain node load average while picking PlanetLab nodes. Specifies a lower acceptable bound.",
888 "type": Attribute.DOUBLE,
890 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
891 "validation_function": validation.is_number,
895 "help": "Constrain node load average while picking PlanetLab nodes. Specifies an upper acceptable bound.",
896 "type": Attribute.DOUBLE,
898 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
899 "validation_function": validation.is_number,
903 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies a lower acceptable bound.",
904 "type": Attribute.DOUBLE,
906 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
907 "validation_function": validation.is_number,
911 "help": "Constrain available cpu time while picking PlanetLab nodes. Specifies an upper acceptable bound.",
912 "type": Attribute.DOUBLE,
914 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
915 "validation_function": validation.is_number,
921 "type": Attribute.BOOL,
923 "validation_function": validation.is_bool
927 "help": "This is the primary interface for the attached node",
928 "type": Attribute.BOOL,
930 "validation_function": validation.is_bool
934 "help": "Device name",
935 "type": Attribute.STRING,
936 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
937 "validation_function": validation.is_string
941 "help": "Maximum transmition unit for device",
942 "type": Attribute.INTEGER,
944 "validation_function": validation.is_integer_range(0,1500)
948 "help": "Network mask for the device (eg: 24 for /24 network)",
949 "type": Attribute.INTEGER,
950 "validation_function": validation.is_integer_range(8,24)
954 "help": "Enable SNAT (source NAT to the internet) no this device",
955 "type": Attribute.BOOL,
957 "validation_function": validation.is_bool
959 "pointopoint": dict({
960 "name": "pointopoint",
961 "help": "If the interface is a P2P link, the remote endpoint's IP "
962 "should be set on this attribute.",
963 "type": Attribute.STRING,
964 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
965 "validation_function": validation.is_string
968 "name": "txqueuelen",
969 "help": "Transmission queue length (in packets)",
970 "type": Attribute.INTEGER,
972 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
974 "validation_function": validation.is_integer
979 "help": "Command line string",
980 "type": Attribute.STRING,
981 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
982 "validation_function": validation.is_string
986 "help": "Run with root privileges",
987 "type": Attribute.BOOL,
988 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
990 "validation_function": validation.is_bool
994 "help": "Standard input",
995 "type": Attribute.STRING,
996 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
997 "validation_function": validation.is_string
1002 "help": "Space-separated list of packages required to run the application",
1003 "type": Attribute.STRING,
1004 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1005 "validation_function": validation.is_string
1007 "build-depends": dict({
1008 "name": "buildDepends",
1009 "help": "Space-separated list of packages required to build the application",
1010 "type": Attribute.STRING,
1011 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1012 "validation_function": validation.is_string
1014 "rpm-fusion": dict({
1015 "name": "rpmFusion",
1016 "help": "True if required packages can be found in the RpmFusion repository",
1017 "type": Attribute.BOOL,
1018 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1020 "validation_function": validation.is_bool
1024 "help": "Space-separated list of regular files to be deployed in the working path prior to building. "
1025 "Archives won't be expanded automatically.",
1026 "type": Attribute.STRING,
1027 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1028 "validation_function": validation.is_string
1032 "help": "Build commands to execute after deploying the sources. "
1033 "Sources will be in the ${SOURCES} folder. "
1034 "Example: tar xzf ${SOURCES}/my-app.tgz && cd my-app && ./configure && make && make clean.\n"
1035 "Try to make the commands return with a nonzero exit code on error.\n"
1036 "Also, do not install any programs here, use the 'install' attribute. This will "
1037 "help keep the built files constrained to the build folder (which may "
1038 "not be the home folder), and will result in faster deployment. Also, "
1039 "make sure to clean up temporary files, to reduce bandwidth usage between "
1040 "nodes when transferring built packages.",
1041 "type": Attribute.STRING,
1042 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1043 "validation_function": validation.is_string
1047 "help": "Commands to transfer built files to their final destinations. "
1048 "Sources will be in the initial working folder, and a special "
1049 "tag ${SOURCES} can be used to reference the experiment's "
1050 "home folder (where the application commands will run).\n"
1051 "ALL sources and targets needed for execution must be copied there, "
1052 "if building has been enabled.\n"
1053 "That is, 'slave' nodes will not automatically get any source files. "
1054 "'slave' nodes don't get build dependencies either, so if you need "
1055 "make and other tools to install, be sure to provide them as "
1056 "actual dependencies instead.",
1057 "type": Attribute.STRING,
1058 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1059 "validation_function": validation.is_string
1062 "netpipe_mode": dict({
1064 "help": "Link mode:\n"
1065 " * SERVER: applies to incoming connections\n"
1066 " * CLIENT: applies to outgoing connections\n"
1067 " * SERVICE: applies to both",
1068 "type": Attribute.ENUM,
1069 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1070 "allowed": ["SERVER",
1073 "validation_function": validation.is_enum,
1077 "help": "Port list or range. Eg: '22', '22,23,27', '20-2000'",
1078 "type": Attribute.STRING,
1079 "validation_function": is_portlist,
1083 "help": "Address list or range. Eg: '127.0.0.1', '127.0.0.1,127.0.1.1', '127.0.0.1/8'",
1084 "type": Attribute.STRING,
1085 "validation_function": is_addrlist,
1089 "help": "Inbound bandwidth limit (in Mbit/s)",
1090 "type": Attribute.DOUBLE,
1091 "validation_function": validation.is_number,
1095 "help": "Outbound bandwidth limit (in Mbit/s)",
1096 "type": Attribute.DOUBLE,
1097 "validation_function": validation.is_number,
1101 "help": "Inbound packet loss rate (0 = no loss, 1 = 100% loss)",
1102 "type": Attribute.DOUBLE,
1103 "validation_function": validation.is_number,
1107 "help": "Outbound packet loss rate (0 = no loss, 1 = 100% loss)",
1108 "type": Attribute.DOUBLE,
1109 "validation_function": validation.is_number,
1113 "help": "Inbound packet delay (in milliseconds)",
1114 "type": Attribute.INTEGER,
1116 "validation_function": validation.is_integer,
1120 "help": "Outbound packet delay (in milliseconds)",
1121 "type": Attribute.INTEGER,
1123 "validation_function": validation.is_integer,
1127 "help": "Path to a .c or .py source for a filter module, or a binary .so",
1128 "type": Attribute.STRING,
1129 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1130 "validation_function": validation.is_string
1134 "help": "Module arguments - comma-separated list of name=value pairs",
1135 "type": Attribute.STRING,
1136 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1137 "validation_function": validation.is_string
1144 "help": "Standard output stream"
1148 "help": "Application standard error",
1152 "help": "Output of the build process",
1155 "netpipe_stats": dict({
1156 "name": "netpipeStats",
1157 "help": "Information about rule match counters, packets dropped, etc.",
1162 "help": "Detailled log of all packets going through the interface",
1166 "help": "PCAP trace of all packets going through the interface",
1170 "help": "Extra output trace for applications. When activated this trace can be referenced with wildcard a reference from an Application command line. Ex: command: 'tcpdump -w {#[elemet-label].trace[trace-id].[name|path]#}' ",
1174 create_order = [ INTERNET, NODE, NODEIFACE, TUNFILTER, TAPIFACE, TUNIFACE, NETPIPE, NEPIDEPENDENCY, NS3DEPENDENCY, DEPENDENCY, APPLICATION ]
1176 configure_order = [ INTERNET, Parallel(NODE), NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1178 # Start (and prestart) node after ifaces, because the node needs the ifaces in order to set up routes
1179 start_order = [ INTERNET, NODEIFACE, Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NODE), NETPIPE, Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), Parallel(APPLICATION) ]
1182 shutdown_order = [ Parallel(APPLICATION), Parallel(TAPIFACE), Parallel(TUNIFACE), Parallel(NETPIPE), Parallel(NEPIDEPENDENCY), Parallel(NS3DEPENDENCY), Parallel(DEPENDENCY), NODEIFACE, Parallel(NODE) ]
1184 factories_info = dict({
1186 "help": "Virtualized Node (V-Server style)",
1187 "category": FC.CATEGORY_NODES,
1188 "create_function": create_node,
1189 "preconfigure_function": configure_node,
1190 "prestart_function": configure_node_routes,
1202 # NEPI-in-NEPI attributes
1203 ATTR_NEPI_TESTBED_ENVIRONMENT_SETUP,
1205 "connector_types": ["devs", "apps", "pipes", "deps"],
1206 "tags": [tags.NODE, tags.ALLOW_ROUTES],
1209 "help": "External network interface - they cannot be brought up or down, and they MUST be connected to the internet.",
1210 "category": FC.CATEGORY_DEVICES,
1211 "create_function": create_nodeiface,
1212 "preconfigure_function": configure_nodeiface,
1213 "box_attributes": [ ],
1214 "connector_types": ["node", "inet"],
1215 "tags": [tags.INTERFACE, tags.HAS_ADDRESSES],
1218 "help": "Virtual TUN network interface (layer 3)",
1219 "category": FC.CATEGORY_DEVICES,
1220 "create_function": create_tuniface,
1221 "preconfigure_function": preconfigure_tuniface,
1222 "configure_function": postconfigure_tuniface,
1223 "prestart_function": wait_tuniface,
1225 "up", "if_name", "mtu", "snat", "pointopoint",
1227 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1229 "traces": ["packets", "pcap"],
1230 "connector_types": ["node","udp","tcp","fd->","gre"],
1231 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1234 "help": "Virtual TAP network interface (layer 2)",
1235 "category": FC.CATEGORY_DEVICES,
1236 "create_function": create_tapiface,
1237 "preconfigure_function": preconfigure_tuniface,
1238 "configure_function": postconfigure_tuniface,
1239 "prestart_function": wait_tuniface,
1241 "up", "if_name", "mtu", "snat", "pointopoint",
1243 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1245 "traces": ["packets", "pcap"],
1246 "connector_types": ["node","udp","tcp","fd->","gre"],
1247 "tags": [tags.INTERFACE, tags.ALLOW_ADDRESSES],
1250 "help": "TUN/TAP stream filter\n\n"
1251 "If specified, it should be either a .py or .so module. "
1252 "It will be loaded, and all incoming and outgoing packets "
1253 "will be routed through it. The filter will not be responsible "
1254 "for buffering, packet queueing is performed in tun_connect "
1255 "already, so it should not concern itself with it. It should "
1256 "not, however, block in one direction if the other is congested.\n"
1258 "Modules are expected to have the following methods:\n"
1260 "\t\tIf arguments are given, this method will be called with the\n"
1261 "\t\tgiven arguments (as keyword args in python modules, or a single\n"
1262 "\taccept_packet(packet, direction):\n"
1263 "\t\tDecide whether to drop the packet. Direction is 0 for packets "
1264 "coming from the local side to the remote, and 1 is for packets "
1265 "coming from the remote side to the local. Return a boolean, "
1266 "true if the packet is not to be dropped.\n"
1267 "\tfilter_init():\n"
1268 "\t\tInitializes a filtering pipe (filter_run). It should "
1269 "return two file descriptors to use as a bidirectional "
1270 "pipe: local and remote. 'local' is where packets from the "
1271 "local side will be written to. After filtering, those packets "
1272 "should be written to 'remote', where tun_connect will read "
1273 "from, and it will forward them to the remote peer. "
1274 "Packets from the remote peer will be written to 'remote', "
1275 "where the filter is expected to read from, and eventually "
1276 "forward them to the local side. If the file descriptors are "
1277 "not nonblocking, they will be set to nonblocking. So it's "
1278 "better to set them from the start like that.\n"
1279 "\tfilter_run(local, remote):\n"
1280 "\t\tIf filter_init is provided, it will be called repeatedly, "
1281 "in a separate thread until the process is killed. It should "
1282 "sleep at most for a second.\n"
1283 "\tfilter_close(local, remote):\n"
1284 "\t\tCalled then the process is killed, if filter_init was provided. "
1285 "It should, among other things, close the file descriptors.\n"
1287 "Python modules are expected to return a tuple in filter_init, "
1288 "either of file descriptors or file objects, while native ones "
1289 "will receive two int*.\n"
1291 "Python modules can additionally contain a custom queue class "
1292 "that will replace the FIFO used by default. The class should "
1293 "be named 'queueclass' and contain an interface compatible with "
1294 "collections.deque. That is, indexing (especiall for q[0]), "
1295 "bool(q), popleft, appendleft, pop (right), append (right), "
1296 "len(q) and clear.",
1297 "category": FC.CATEGORY_CHANNELS,
1298 "create_function": create_tunfilter,
1301 "tun_proto", "tun_addr", "tun_port", "tun_key", "tun_cipher",
1303 "connector_types": ["->fd","udp","tcp"],
1306 "help": "Generic executable command line application",
1307 "category": FC.CATEGORY_APPLICATIONS,
1308 "create_function": create_application,
1309 "start_function": start_application,
1310 "status_function": status_application,
1311 "stop_function": stop_application,
1312 "configure_function": configure_application,
1313 "box_attributes": ["command", "sudo", "stdin",
1314 "depends", "build-depends", "build", "install",
1315 "sources", "rpm-fusion" ],
1316 "connector_types": ["node"],
1317 "traces": ["stdout", "stderr", "buildlog", "output"],
1318 "tags": [tags.APPLICATION],
1321 "help": "Requirement for package or application to be installed on some node",
1322 "category": FC.CATEGORY_APPLICATIONS,
1323 "create_function": create_dependency,
1324 "preconfigure_function": configure_dependency,
1325 "box_attributes": ["depends", "build-depends", "build", "install",
1326 "sources", "rpm-fusion" ],
1327 "connector_types": ["node"],
1328 "traces": ["buildlog"],
1330 NEPIDEPENDENCY: dict({
1331 "help": "Requirement for NEPI inside NEPI - required to run testbed instances inside a node",
1332 "category": FC.CATEGORY_APPLICATIONS,
1333 "create_function": create_nepi_dependency,
1334 "preconfigure_function": configure_dependency,
1335 "box_attributes": [],
1336 "connector_types": ["node"],
1337 "traces": ["buildlog"],
1339 NS3DEPENDENCY: dict({
1340 "help": "Requirement for NS3 inside NEPI - required to run NS3 testbed instances inside a node. It also needs NepiDependency.",
1341 "category": FC.CATEGORY_APPLICATIONS,
1342 "create_function": create_ns3_dependency,
1343 "preconfigure_function": configure_dependency,
1344 "box_attributes": [ ],
1345 "connector_types": ["node"],
1346 "traces": ["buildlog"],
1349 "help": "Internet routing",
1350 "category": FC.CATEGORY_CHANNELS,
1351 "create_function": create_internet,
1352 "connector_types": ["devs"],
1353 "tags": [tags.INTERNET],
1356 "help": "Link emulation",
1357 "category": FC.CATEGORY_CHANNELS,
1358 "create_function": create_netpipe,
1359 "configure_function": configure_netpipe,
1360 "box_attributes": ["netpipe_mode",
1361 "addr_list", "port_list",
1362 "bw_in","plr_in","delay_in",
1363 "bw_out","plr_out","delay_out"],
1364 "connector_types": ["node"],
1365 "traces": ["netpipe_stats"],
1369 testbed_attributes = dict({
1372 "help": "The name of the PlanetLab slice to use",
1373 "type": Attribute.STRING,
1374 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1375 "validation_function": validation.is_string
1379 "help": "The name of the PlanetLab user to use for API calls - it must have at least a User role.",
1380 "type": Attribute.STRING,
1381 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1382 "validation_function": validation.is_string
1386 "help": "The PlanetLab user's password.",
1387 "type": Attribute.STRING,
1388 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1389 "validation_function": validation.is_string
1393 "help": "The PlanetLab PLC API host",
1394 "type": Attribute.STRING,
1395 "value": "www.planet-lab.eu",
1396 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1397 "validation_function": validation.is_string
1401 "help": "The PlanetLab PLC API url pattern - %(hostname)s is replaced by plcHost.",
1402 "type": Attribute.STRING,
1403 "value": "https://%(hostname)s:443/PLCAPI/",
1404 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1405 "validation_function": validation.is_string
1407 "p2p_deployment": dict({
1408 "name": "p2pDeployment",
1409 "help": "Enable peer-to-peer deployment of applications and dependencies. "
1410 "When enabled, dependency packages and applications are "
1411 "deployed in a P2P fashion, picking a single node to do "
1412 "the building or repo download, while all the others "
1413 "cooperatively exchange resulting binaries or rpms. "
1414 "When deploying to many nodes, this is a far more efficient "
1415 "use of resources. It does require re-encrypting and distributing "
1416 "the slice's private key. Though it is implemented in a secure "
1417 "fashion, if they key's sole purpose is not PlanetLab, then this "
1418 "feature should be disabled.",
1419 "type": Attribute.BOOL,
1421 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1422 "validation_function": validation.is_bool
1424 "slice_ssh_key": dict({
1425 "name": "sliceSSHKey",
1426 "help": "The controller-local path to the slice user's ssh private key. "
1427 "It is the user's responsability to deploy this file where the controller "
1428 "will run, it won't be done automatically because it's sensitive information. "
1429 "It is recommended that a NEPI-specific user be created for this purpose and "
1430 "this purpose alone.",
1431 "type": Attribute.STRING,
1432 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable | Attribute.NoDefaultValue,
1433 "validation_function": validation.is_string
1435 "pl_log_level": dict({
1436 "name": "plLogLevel",
1437 "help": "Verbosity of logging of planetlab events.",
1439 "type": Attribute.ENUM,
1440 "allowed": ["DEBUG",
1445 "validation_function": validation.is_enum,
1447 "tap_port_base": dict({
1448 "name": "tapPortBase",
1449 "help": "Base port to use when connecting TUN/TAPs. Effective port will be BASE + GUID.",
1450 "type": Attribute.INTEGER,
1452 "range": (2000,30000),
1453 "validation_function": validation.is_integer_range(2000,30000)
1455 "dedicated_slice": dict({
1456 "name": "dedicatedSlice",
1457 "help": "Set to True if the slice will be dedicated to this experiment. "
1458 "NEPI will perform node and slice cleanup, making sure slices are "
1459 "in a clean, repeatable state before running the experiment.",
1460 "type": Attribute.BOOL,
1462 "flags": Attribute.ExecReadOnly | Attribute.ExecImmutable,
1463 "validation_function": validation.is_bool
1467 supported_recovery_policies = [
1473 class MetadataInfo(metadata.MetadataInfo):
1475 def connector_types(self):
1476 return connector_types
1479 def connections(self):
1483 def attributes(self):
1491 def create_order(self):
1495 def configure_order(self):
1496 return configure_order
1499 def prestart_order(self):
1503 def start_order(self):
1507 def factories_info(self):
1508 return factories_info
1511 def testbed_attributes(self):
1512 return testbed_attributes
1515 def testbed_id(self):
1519 def testbed_version(self):
1520 return TESTBED_VERSION
1523 def supported_recovery_policies(self):
1524 return supported_recovery_policies