Lots of cross-connection fixes, TUN synchronization, etc
[nepi.git] / test / testbeds / planetlab / execute.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import getpass
5 from nepi.util.constants import STATUS_FINISHED
6 from nepi.testbeds import planetlab
7 import os
8 import shutil
9 import tempfile
10 import time
11 import unittest
12 import re
13 import test_util
14 import sys
15
16 class PlanetLabExecuteTestCase(unittest.TestCase):
17     testbed_id = "planetlab"
18     testbed_version = "01"
19     slicename = "inria_nepi"
20     plchost = "nepiplc.pl.sophia.inria.fr"
21     
22     host1 = "nepi1.pl.sophia.inria.fr"
23     host2 = "nepi2.pl.sophia.inria.fr"
24
25     def setUp(self):
26         self.root_dir = tempfile.mkdtemp()
27         
28     def tearDown(self):
29         try:
30             shutil.rmtree(self.root_dir)
31         except:
32             # retry
33             time.sleep(0.1)
34             shutil.rmtree(self.root_dir)
35
36     def make_instance(self):
37         testbed_id = self.testbed_id
38         testbed_version = self.testbed_version
39         slicename = self.slicename
40         plchost = self.plchost
41         
42         instance = planetlab.TestbedController(testbed_version)
43         pl_ssh_key = os.environ.get(
44             "PL_SSH_KEY",
45             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
46         pl_user, pl_pwd = test_util.pl_auth()
47         
48         instance.defer_configure("homeDirectory", self.root_dir)
49         instance.defer_configure("slice", slicename)
50         instance.defer_configure("sliceSSHKey", pl_ssh_key)
51         instance.defer_configure("authUser", pl_user)
52         instance.defer_configure("authPass", pl_pwd)
53         instance.defer_configure("plcHost", plchost)
54         
55         return instance
56
57     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
58     def test_simple(self):
59         instance = self.make_instance()
60         
61         instance.defer_create(2, "Node")
62         instance.defer_create_set(2, "hostname", self.host1)
63         instance.defer_create(3, "Node")
64         instance.defer_create_set(3, "hostname", self.host2)
65         instance.defer_create(4, "NodeInterface")
66         instance.defer_connect(2, "devs", 4, "node")
67         instance.defer_create(5, "NodeInterface")
68         instance.defer_connect(3, "devs", 5, "node")
69         instance.defer_create(6, "Internet")
70         instance.defer_connect(4, "inet", 6, "devs")
71         instance.defer_connect(5, "inet", 6, "devs")
72         instance.defer_create(7, "Application")
73         instance.defer_create_set(7, "command", "ping -qc1 {#[GUID-5].addr[0].[Address]#}")
74         instance.defer_add_trace(7, "stdout")
75         instance.defer_add_trace(7, "stderr")
76         instance.defer_connect(7, "node", 2, "apps")
77
78         comp_result = r"""PING .* \(.*\) \d*\(\d*\) bytes of data.
79
80 --- .* ping statistics ---
81 1 packets transmitted, 1 received, 0% packet loss, time \d*ms.*
82 """
83
84         try:
85             instance.do_setup()
86             instance.do_create()
87             instance.do_connect_init()
88             instance.do_connect_compl()
89             instance.do_preconfigure()
90             
91             # Manually replace netref
92             instance.set(7, "command",
93                 instance.get(7, "command")
94                     .replace("{#[GUID-5].addr[0].[Address]#}", 
95                         instance.get_address(5, 0, "Address") )
96             )
97
98             instance.do_configure()
99             
100             instance.start()
101             while instance.status(7) != STATUS_FINISHED:
102                 time.sleep(0.5)
103             ping_result = instance.trace(7, "stdout") or ""
104             instance.stop()
105         finally:
106             instance.shutdown()
107
108         # asserts at the end, to make sure there's proper cleanup
109         self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
110             "Unexpected trace:\n" + ping_result)
111         
112     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
113     def test_depends(self):
114         instance = self.make_instance()
115         
116         instance.defer_create(2, "Node")
117         instance.defer_create_set(2, "hostname", self.host1)
118         instance.defer_create(3, "NodeInterface")
119         instance.defer_connect(2, "devs", 3, "node")
120         instance.defer_create(4, "Internet")
121         instance.defer_connect(3, "inet", 4, "devs")
122         instance.defer_create(5, "Application")
123         instance.defer_create_set(5, "command", "gfortran --version")
124         instance.defer_create_set(5, "depends", "gcc-gfortran")
125         instance.defer_add_trace(5, "stdout")
126         instance.defer_add_trace(5, "stderr")
127         instance.defer_connect(5, "node", 2, "apps")
128
129         try:
130             instance.do_setup()
131             instance.do_create()
132             instance.do_connect_init()
133             instance.do_connect_compl()
134             instance.do_preconfigure()
135             instance.do_configure()
136             
137             instance.start()
138             while instance.status(5) != STATUS_FINISHED:
139                 time.sleep(0.5)
140             ping_result = instance.trace(5, "stdout") or ""
141             comp_result = r".*GNU Fortran \(GCC\).*"
142             instance.stop()
143         finally:
144             instance.shutdown()
145
146         # asserts at the end, to make sure there's proper cleanup
147         self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
148             "Unexpected trace:\n" + ping_result)
149         
150     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
151     def test_build(self):
152         instance = self.make_instance()
153         
154         instance.defer_create(2, "Node")
155         instance.defer_create_set(2, "hostname", self.host1)
156         instance.defer_create(3, "NodeInterface")
157         instance.defer_connect(2, "devs", 3, "node")
158         instance.defer_create(4, "Internet")
159         instance.defer_connect(3, "inet", 4, "devs")
160         instance.defer_create(10, "Application")
161         instance.defer_create_set(10, "command", "./consts")
162         instance.defer_create_set(10, "buildDepends", "gcc")
163         instance.defer_create_set(10, "build", "gcc ${SOURCES}/consts.c -o consts")
164         instance.defer_create_set(10, "install", "cp consts ${SOURCES}/consts")
165         instance.defer_create_set(10, "sources", os.path.join(os.path.dirname(planetlab.__file__),'scripts','consts.c'))
166         instance.defer_add_trace(10, "stdout")
167         instance.defer_add_trace(10, "stderr")
168         instance.defer_connect(10, "node", 2, "apps")
169
170         comp_result = \
171 r""".*ETH_P_ALL = 0x[0-9a-fA-F]{8}
172 ETH_P_IP = 0x[0-9a-fA-F]{8}
173 TUNSETIFF = 0x[0-9a-fA-F]{8}
174 IFF_NO_PI = 0x[0-9a-fA-F]{8}
175 IFF_TAP = 0x[0-9a-fA-F]{8}
176 IFF_TUN = 0x[0-9a-fA-F]{8}
177 IFF_VNET_HDR = 0x[0-9a-fA-F]{8}
178 TUN_PKT_STRIP = 0x[0-9a-fA-F]{8}
179 IFHWADDRLEN = 0x[0-9a-fA-F]{8}
180 IFNAMSIZ = 0x[0-9a-fA-F]{8}
181 IFREQ_SZ = 0x[0-9a-fA-F]{8}
182 FIONREAD = 0x[0-9a-fA-F]{8}.*
183 """
184
185         try:
186             instance.do_setup()
187             instance.do_create()
188             instance.do_connect_init()
189             instance.do_connect_compl()
190             instance.do_preconfigure()
191             instance.do_configure()
192             
193             instance.start()
194             while instance.status(10) != STATUS_FINISHED:
195                 time.sleep(0.5)
196             ping_result = instance.trace(10, "stdout") or ""
197             instance.stop()
198         finally:
199             instance.shutdown()
200
201         # asserts at the end, to make sure there's proper cleanup
202         self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
203             "Unexpected trace:\n" + ping_result)
204         
205     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
206     def test_simple_vsys(self):
207         instance = self.make_instance()
208         
209         instance.defer_create(2, "Node")
210         instance.defer_create_set(2, "hostname", self.host1)
211         instance.defer_create_set(2, "emulation", True) # require emulation
212         instance.defer_create(3, "NodeInterface")
213         instance.defer_connect(2, "devs", 3, "node")
214         instance.defer_create(4, "Internet")
215         instance.defer_connect(3, "inet", 4, "devs")
216         instance.defer_create(5, "TunInterface")
217         instance.defer_add_address(5, "192.168.2.2", 24, False)
218         instance.defer_connect(2, "devs", 5, "node")
219         instance.defer_create(6, "Application")
220         instance.defer_create_set(6, "command", """
221 set -e
222 netconfig help > /dev/null
223 test -e /vsys/vif_up.in > /dev/null
224 test -e /vsys/vif_up.out > /dev/null
225 test -e /vsys/fd_tuntap.control > /dev/null
226 echo 'OKIDOKI'
227 """)
228         instance.defer_create_set(6, "sudo", True) # only sudo has access to /vsys
229         instance.defer_add_trace(6, "stdout")
230         instance.defer_add_trace(6, "stderr")
231         instance.defer_connect(6, "node", 2, "apps")
232
233         try:
234             instance.do_setup()
235             instance.do_create()
236             instance.do_connect_init()
237             instance.do_connect_compl()
238             instance.do_preconfigure()
239             instance.do_configure()
240             
241             instance.start()
242             while instance.status(6) != STATUS_FINISHED:
243                 time.sleep(0.5)
244             test_result = (instance.trace(6, "stdout") or "").strip()
245             comp_result = "OKIDOKI"
246             instance.stop()
247         finally:
248             instance.shutdown()
249
250         # asserts at the end, to make sure there's proper cleanup
251         self.assertEqual(comp_result, test_result)
252
253     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
254     def test_emulation(self):
255         instance = self.make_instance()
256         
257         instance.defer_create(2, "Node")
258         instance.defer_create_set(2, "hostname", self.host1)
259         instance.defer_create_set(2, "emulation", True) # require emulation
260         instance.defer_create(3, "NodeInterface")
261         instance.defer_connect(2, "devs", 3, "node")
262         instance.defer_create(4, "Internet")
263         instance.defer_connect(3, "inet", 4, "devs")
264         instance.defer_create(7, "NetPipe")
265         instance.defer_create_set(7, "mode", "CLIENT")
266         instance.defer_create_set(7, "portList", "80")
267         instance.defer_create_set(7, "bwOut", 12.0/1024.0) # 12kbps
268         instance.defer_create_set(7, "bwIn", 64.0/1024.0) # 64kbps
269         instance.defer_create_set(7, "plrOut", 0.01) # 1% plr outbound - high loss
270         instance.defer_create_set(7, "plrIn", 0.001) # 0.1% plr inbound - regular loss
271         instance.defer_create_set(7, "delayOut", int(1500 * 8 / (12.0/1024.0) / 1000)) # tx delay at 12kbps in ms
272         instance.defer_create_set(7, "delayIn", int(1500 * 8 / (64.0/1024.0) / 1000)) # rx delay at 64kbps in ms
273         instance.defer_add_trace(7, "netpipeStats")
274         instance.defer_connect(2, "pipes", 7, "node")
275         instance.defer_create(8, "Application")
276         instance.defer_create_set(8, "command", "time wget -q -O /dev/null http://www.google.com/") # Fetch ~10kb
277         instance.defer_add_trace(8, "stdout")
278         instance.defer_add_trace(8, "stderr")
279         instance.defer_connect(8, "node", 2, "apps")
280
281         try:
282             instance.do_setup()
283             instance.do_create()
284             instance.do_connect_init()
285             instance.do_connect_compl()
286             instance.do_preconfigure()
287             instance.do_configure()
288             
289             instance.start()
290             while instance.status(8) != STATUS_FINISHED:
291                 time.sleep(0.5)
292             test_result = (instance.trace(8, "stderr") or "").strip()
293             comp_result = r".*real\s*(?P<min>[0-9]+)m(?P<sec>[0-9]+[.][0-9]+)s.*"
294             netpipe_stats = instance.trace(7, "netpipeStats")
295             
296             instance.stop()
297         finally:
298             instance.shutdown()
299
300         # asserts at the end, to make sure there's proper cleanup
301         match = re.match(comp_result, test_result, re.MULTILINE)
302         self.assertTrue(match, "Unexpected output: %s" % (test_result,))
303         
304         minutes = int(match.group("min"))
305         seconds = float(match.group("sec"))
306         self.assertTrue((minutes * 60 + seconds) > 1.0, "Emulation not effective: %s" % (test_result,))
307
308         self.assertTrue(netpipe_stats, "Unavailable netpipe stats")
309
310     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
311     def test_tun_emulation_requirement(self):
312         instance = self.make_instance()
313         
314         instance.defer_create(2, "Node")
315         instance.defer_create_set(2, "hostname", self.host1)
316         instance.defer_create(3, "NodeInterface")
317         instance.defer_connect(2, "devs", 3, "node")
318         instance.defer_create(4, "Internet")
319         instance.defer_connect(3, "inet", 4, "devs")
320         instance.defer_create(5, "TunInterface")
321         instance.defer_add_address(5, "192.168.2.2", 24, False)
322         instance.defer_connect(2, "devs", 5, "node")
323         instance.defer_create(6, "Application")
324         instance.defer_create_set(6, "command", "false")
325         instance.defer_add_trace(6, "stdout")
326         instance.defer_add_trace(6, "stderr")
327         instance.defer_connect(6, "node", 2, "apps")
328
329         try:
330             instance.do_setup()
331             instance.do_create()
332             instance.do_connect_init()
333             instance.do_connect_compl()
334             instance.do_preconfigure()
335             instance.do_configure()
336             self.fail("Usage of TUN without emulation should fail")
337         except Exception,e:
338             pass
339
340     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
341     def _pingtest(self, TunClass, ConnectionProto):
342         instance = self.make_instance()
343         
344         instance.defer_create(2, "Node")
345         instance.defer_create_set(2, "hostname", self.host1)
346         instance.defer_create_set(2, "emulation", True) # require emulation
347         instance.defer_create(3, "Node")
348         instance.defer_create_set(3, "hostname", self.host2)
349         instance.defer_create_set(3, "emulation", True) # require emulation
350         instance.defer_create(4, "NodeInterface")
351         instance.defer_connect(2, "devs", 4, "node")
352         instance.defer_create(5, "Internet")
353         instance.defer_connect(4, "inet", 5, "devs")
354         instance.defer_create(6, "NodeInterface")
355         instance.defer_connect(3, "devs", 6, "node")
356         instance.defer_connect(6, "inet", 5, "devs")
357         instance.defer_create(7, TunClass)
358         instance.defer_add_trace(7, "packets")
359         instance.defer_add_address(7, "192.168.2.2", 24, False)
360         instance.defer_connect(2, "devs", 7, "node")
361         instance.defer_create(8, TunClass)
362         instance.defer_add_trace(8, "packets")
363         instance.defer_add_address(8, "192.168.2.3", 24, False)
364         instance.defer_connect(3, "devs", 8, "node")
365         instance.defer_connect(7, ConnectionProto, 8, ConnectionProto)
366         instance.defer_create(9, "Application")
367         instance.defer_create_set(9, "command", "ping -qc1 {#[GUID-8].addr[0].[Address]#}")
368         instance.defer_add_trace(9, "stdout")
369         instance.defer_add_trace(9, "stderr")
370         instance.defer_connect(9, "node", 2, "apps")
371
372         comp_result = r"""PING .* \(.*\) \d*\(\d*\) bytes of data.
373
374 --- .* ping statistics ---
375 1 packets transmitted, 1 received, 0% packet loss, time \d*ms.*
376 """
377
378         try:
379             instance.do_setup()
380             instance.do_create()
381             instance.do_connect_init()
382             instance.do_connect_compl()
383             instance.do_preconfigure()
384             
385             # Manually replace netref
386             instance.set(9, "command",
387                 instance.get(9, "command")
388                     .replace("{#[GUID-8].addr[0].[Address]#}", 
389                         instance.get_address(8, 0, "Address") )
390             )
391             
392             instance.do_configure()
393             
394             instance.start()
395             while instance.status(9) != STATUS_FINISHED:
396                 time.sleep(0.5)
397             ping_result = instance.trace(9, "stdout") or ""
398             packets1 = instance.trace(7, "packets") or ""
399             packets2 = instance.trace(8, "packets") or ""
400             instance.stop()
401         finally:
402             instance.shutdown()
403
404         # asserts at the end, to make sure there's proper cleanup
405         self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
406             "Unexpected trace:\n%s\nPackets @ source:\n%s\nPackets @ target:\n%s" % (
407                 ping_result,
408                 packets1,
409                 packets2))
410
411     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
412     def test_tun_ping(self):
413         self._pingtest("TunInterface", "tcp")
414
415     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
416     def test_tun_ping_udp(self):
417         self._pingtest("TunInterface", "udp")
418
419     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
420     def test_tap_ping(self):
421         self._pingtest("TapInterface", "tcp")
422
423     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
424     def test_tap_ping_udp(self):
425         self._pingtest("TapInterface", "udp")
426
427     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
428     def test_nepi_depends(self):
429         instance = self.make_instance()
430         
431         instance.defer_create(2, "Node")
432         instance.defer_create_set(2, "hostname", self.host1)
433         instance.defer_create(3, "NodeInterface")
434         instance.defer_connect(2, "devs", 3, "node")
435         instance.defer_create(4, "Internet")
436         instance.defer_connect(3, "inet", 4, "devs")
437         instance.defer_create(5, "NepiDependency")
438         instance.defer_connect(5, "node", 2, "deps")
439         instance.defer_create(12, "Application")
440         instance.defer_connect(12, "node", 2, "apps")
441         instance.defer_create_set(12, "command", "python -c 'import nepi'")
442         instance.defer_add_trace(12, "stderr")
443
444         try:
445             instance.do_setup()
446             instance.do_create()
447             instance.do_connect_init()
448             instance.do_connect_compl()
449             instance.do_preconfigure()
450             instance.do_configure()
451             
452             instance.start()
453             while instance.status(12) != STATUS_FINISHED:
454                 time.sleep(0.5)
455             ping_result = (instance.trace(12, "stderr") or "").strip()
456             instance.stop()
457         finally:
458             instance.shutdown()
459         
460         # asserts at the end, to make sure there's proper cleanup
461         self.assertEqual(ping_result, "")
462
463     @test_util.skipUnless(test_util.pl_auth() is not None, 
464         "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
465     @test_util.skipUnless(os.environ.get('NEPI_FULL_TESTS','').lower() in ('1','yes','true','on'),
466         "Test is expensive, requires NEPI_FULL_TESTS=yes")
467     def test_ns3_depends(self):
468         instance = self.make_instance()
469         
470         instance.defer_create(2, "Node")
471         instance.defer_create_set(2, "hostname", self.host1)
472         instance.defer_create(3, "NodeInterface")
473         instance.defer_connect(2, "devs", 3, "node")
474         instance.defer_create(4, "Internet")
475         instance.defer_connect(3, "inet", 4, "devs")
476         instance.defer_create(5, "NepiDependency")
477         instance.defer_connect(5, "node", 2, "deps")
478         instance.defer_create(6, "NS3Dependency")
479         instance.defer_connect(6, "node", 2, "deps")
480         instance.defer_create(12, "Application")
481         instance.defer_connect(12, "node", 2, "apps")
482         instance.defer_create_set(12, "command", "python -c 'import nepi.testbeds.ns3.execute ; tb = nepi.testbeds.ns3.execute.TestbedController(\"3_9_RC3\") ; mod = tb._load_ns3_module()'")
483         instance.defer_add_trace(12, "stderr")
484
485         try:
486             instance.do_setup()
487             instance.do_create()
488             instance.do_connect_init()
489             instance.do_connect_compl()
490             instance.do_preconfigure()
491             instance.do_configure()
492             
493             instance.start()
494             while instance.status(12) != STATUS_FINISHED:
495                 time.sleep(0.5)
496             ping_result = (instance.trace(12, "stderr") or "").strip()
497             instance.stop()
498         finally:
499             instance.shutdown()
500         
501         # asserts at the end, to make sure there's proper cleanup
502         self.assertEqual(ping_result, "")
503         
504
505 if __name__ == '__main__':
506     unittest.main()
507