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