fe216250d46a7e28951daaacdc9169e84b64f7de
[nepi.git] / test / testbeds / planetlab / integration.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 import getpass
5 import logging
6 from nepi.core.design import ExperimentDescription, FactoriesProvider
7 from nepi.core.execute import ExperimentController
8 from nepi.util import proxy
9 from nepi.util.constants import DeploymentConfiguration as DC
10 import os
11 import re
12 import shutil
13 import sys
14 import tempfile
15 import test_util
16 import time
17 import unittest
18
19 class PlanetLabIntegrationTestCase(unittest.TestCase):
20     testbed_id = "planetlab"
21     slicename = "inria_nepi"
22     slicehrn = "nepi.inria.nepi"
23     plchost = "nepiplc.pl.sophia.inria.fr"
24     
25     host1 = "nepi1.pl.sophia.inria.fr"
26     host2 = "nepi2.pl.sophia.inria.fr"
27     host3 = "nepi3.pl.sophia.inria.fr"
28     host4 = "nepi5.pl.sophia.inria.fr"
29
30     port_base = 2000 + (os.getpid() % 1000) * 13
31     
32     def setUp(self):
33         self.root_dir = tempfile.mkdtemp()
34         self.__class__.port_base = self.port_base + 100
35
36     def tearDown(self):
37         try:
38             shutil.rmtree(self.root_dir)
39         except:
40             # retry
41             time.sleep(0.1)
42             shutil.rmtree(self.root_dir)
43
44     def make_experiment_desc(self, use_sfa = False):
45         testbed_id = self.testbed_id
46         slicename = self.slicename
47         plchost = self.plchost
48         pl_ssh_key = os.environ.get(
49             "PL_SSH_KEY",
50             "%s/.ssh/id_rsa_planetlab" % (os.environ['HOME'],) )
51         pl_user, pl_pwd = test_util.pl_auth()
52
53         exp_desc = ExperimentDescription()
54         pl_provider = FactoriesProvider(testbed_id)
55         pl_desc = exp_desc.add_testbed_description(pl_provider)
56         pl_desc.set_attribute_value("homeDirectory", self.root_dir)
57         pl_desc.set_attribute_value("slice", slicename)
58         pl_desc.set_attribute_value("sliceSSHKey", pl_ssh_key)
59         pl_desc.set_attribute_value("authUser", pl_user)
60         pl_desc.set_attribute_value("authPass", pl_pwd)
61         pl_desc.set_attribute_value("plcHost", plchost)
62         pl_desc.set_attribute_value("tapPortBase", self.port_base)
63         pl_desc.set_attribute_value("p2pDeployment", False) # it's interactive, we don't want it in tests
64         pl_desc.set_attribute_value("dedicatedSlice", True)
65         pl_desc.set_attribute_value("plLogLevel", "DEBUG")
66         if use_sfa:
67             pl_desc.set_attribute_value("sfa", True)
68             pl_desc.set_attribute_value("sliceHrn", self.slicehrn)
69         
70         return pl_desc, exp_desc
71     
72     def _test_simple(self, daemonize_testbed, controller_access_configuration,
73             environ = None, use_sfa = False):
74         pl, exp = self.make_experiment_desc(use_sfa)
75         
76         node1 = pl.create("Node")
77         node2 = pl.create("Node")
78         node1.set_attribute_value("hostname", self.host1)
79         node2.set_attribute_value("hostname", self.host2)
80         iface1 = pl.create("NodeInterface")
81         iface2 = pl.create("NodeInterface")
82         iface2.set_attribute_value("label", "node2iface")
83         inet = pl.create("Internet")
84         node1.connector("devs").connect(iface1.connector("node"))
85         node2.connector("devs").connect(iface2.connector("node"))
86         iface1.connector("inet").connect(inet.connector("devs"))
87         iface2.connector("inet").connect(inet.connector("devs"))
88         app = pl.create("Application")
89         app.set_attribute_value("command", "ping -qc1 {#[node2iface].addr[0].[Address]#}")
90         app.enable_trace("stdout")
91         app.connector("node").connect(node1.connector("apps"))
92
93         if daemonize_testbed:
94             pl.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
95             inst_root_dir = os.path.join(self.root_dir, "instance")
96             os.mkdir(inst_root_dir)
97             pl.set_attribute_value(DC.ROOT_DIRECTORY, inst_root_dir)
98             pl.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
99
100             if environ:
101                 pl.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP, environ)
102
103         xml = exp.to_xml()
104
105         if controller_access_configuration:
106             controller = proxy.create_experiment_controller(xml, 
107                 controller_access_configuration)
108         else:
109             controller = ExperimentController(xml, self.root_dir)
110         
111         try:
112             controller.start()
113             while not controller.is_finished(app.guid):
114                 time.sleep(0.5)
115             ping_result = controller.trace(app.guid, "stdout")
116             comp_result = r"""PING .* \(.*\) \d*\(\d*\) bytes of data.
117
118 --- .* ping statistics ---
119 1 packets transmitted, 1 received, 0% packet loss, time \d*ms.*
120 """
121             self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
122                 "Unexpected trace:\n" + ping_result)
123         
124         finally:
125             try:
126                 controller.stop()
127             except:
128                 import traceback
129                 traceback.print_exc()
130             try:
131                 controller.shutdown()
132             except:
133                 import traceback
134                 traceback.print_exc()
135
136     def _test_spanning_deployment(self, use_sfa = False):
137         pl, exp = self.make_experiment_desc(use_sfa)
138
139         pl.set_attribute_value("p2pDeployment", True) # we do want it here - even if interactive
140         
141         from nepi.testbeds import planetlab as plpackage
142         
143         nodes = [ pl.create("Node") for i in xrange(4) ]
144         ifaces = [ pl.create("NodeInterface") for node in nodes ]
145         inet = pl.create("Internet")
146         for node, iface in zip(nodes,ifaces):
147             node.connector("devs").connect(iface.connector("node"))
148             iface.connector("inet").connect(inet.connector("devs"))
149         
150         apps = []
151         for node in nodes:
152             app = pl.create("Application")
153             app.set_attribute_value("command", "./consts")
154             app.set_attribute_value("buildDepends", "gcc")
155             app.set_attribute_value("build", "gcc ${SOURCES}/consts.c -o consts")
156             app.set_attribute_value("install", "cp consts ${SOURCES}/consts")
157             app.set_attribute_value("sources", os.path.join(
158                 os.path.dirname(plpackage.__file__),'scripts','consts.c'))
159             app.enable_trace("stdout")
160             app.enable_trace("stderr")
161             app.enable_trace("buildlog")
162             node.connector("apps").connect(app.connector("node"))
163             apps.append(app)
164
165         comp_result = \
166 r""".*ETH_P_ALL = 0x[0-9a-fA-F]{8}
167 ETH_P_IP = 0x[0-9a-fA-F]{8}
168 TUNGETIFF = 0x[0-9a-fA-F]{8}
169 TUNSETIFF = 0x[0-9a-fA-F]{8}
170 IFF_NO_PI = 0x[0-9a-fA-F]{8}
171 IFF_TAP = 0x[0-9a-fA-F]{8}
172 IFF_TUN = 0x[0-9a-fA-F]{8}
173 IFF_VNET_HDR = 0x[0-9a-fA-F]{8}
174 TUN_PKT_STRIP = 0x[0-9a-fA-F]{8}
175 IFHWADDRLEN = 0x[0-9a-fA-F]{8}
176 IFNAMSIZ = 0x[0-9a-fA-F]{8}
177 IFREQ_SZ = 0x[0-9a-fA-F]{8}
178 FIONREAD = 0x[0-9a-fA-F]{8}.*
179 """
180
181         comp_build = r".*(Identity added|gcc).*"
182
183         xml = exp.to_xml()
184
185         controller = ExperimentController(xml, self.root_dir)
186         try:
187             controller.start()
188             while not all(controller.is_finished(app.guid) for app in apps):
189                 time.sleep(0.5)
190             
191             for app in apps:
192                 app_result = controller.trace(app.guid, "stdout") or ""
193                 self.assertTrue(re.match(comp_result, app_result, re.MULTILINE),
194                     "Unexpected trace:\n" + app_result)
195
196                 build_result = controller.trace(app.guid, "buildlog") or ""
197                 self.assertTrue(re.match(comp_build, build_result, re.MULTILINE | re.DOTALL),
198                     "Unexpected trace:\n" + build_result)
199         
200         finally:
201             try:
202                 controller.stop()
203             except:
204                 import traceback
205                 traceback.print_exc()
206             try:
207                 controller.shutdown()
208             except:
209                 import traceback
210                 traceback.print_exc()
211
212     def _test_recover(self, daemonize_testbed, controller_access_configuration, 
213             environ = None, use_sfa = False):
214         pl, exp = self.make_experiment_desc(use_sfa)
215         
216         pl.set_attribute_value(DC.RECOVERY_POLICY, DC.POLICY_RECOVER)
217         
218         node1 = pl.create("Node")
219         node2 = pl.create("Node")
220         node1.set_attribute_value("hostname", self.host1)
221         node2.set_attribute_value("hostname", self.host2)
222         
223         iface1 = pl.create("NodeInterface")
224         iface2 = pl.create("NodeInterface")
225         inet = pl.create("Internet")
226         node1.connector("devs").connect(iface1.connector("node"))
227         node2.connector("devs").connect(iface2.connector("node"))
228         iface1.connector("inet").connect(inet.connector("devs"))
229         iface2.connector("inet").connect(inet.connector("devs"))
230         
231         tap1 = pl.create("TapInterface")
232         tap2 = pl.create("TapInterface")
233         node1.connector("devs").connect(tap1.connector("node"))
234         node2.connector("devs").connect(tap2.connector("node"))
235         tap1.connector("udp").connect(tap2.connector("udp"))
236         
237         tap1ip = tap1.add_address()
238         tap1ip.set_attribute_value("Address", "192.168.2.2")
239         tap1ip.set_attribute_value("NetPrefix", 24)
240         tap1ip.set_attribute_value("Broadcast", False)
241
242         tap2ip = tap2.add_address()
243         tap2ip.set_attribute_value("Address", "192.168.2.3")
244         tap2ip.set_attribute_value("NetPrefix", 24)
245         tap2ip.set_attribute_value("Broadcast", False)
246         
247         app = pl.create("Application")
248         app.set_attribute_value("command", "ping -qc10 192.168.2.3")
249         app.enable_trace("stdout")
250         app.connector("node").connect(node1.connector("apps"))
251
252         if daemonize_testbed:
253             pl.set_attribute_value(DC.DEPLOYMENT_MODE, DC.MODE_DAEMON)
254             inst_root_dir = os.path.join(self.root_dir, "instance")
255             os.mkdir(inst_root_dir)
256             pl.set_attribute_value(DC.ROOT_DIRECTORY, inst_root_dir)
257             pl.set_attribute_value(DC.LOG_LEVEL, DC.DEBUG_LEVEL)
258
259             if environ:
260                 pl.set_attribute_value(DC.DEPLOYMENT_ENVIRONMENT_SETUP, environ)
261
262         xml = exp.to_xml()
263
264         if controller_access_configuration:
265             controller = proxy.create_experiment_controller(xml, 
266                 controller_access_configuration)
267         else:
268             controller = ExperimentController(xml, self.root_dir)
269         
270         try:
271             controller.start()
272             
273             # purposedly break connection
274             controller = None
275             
276             # recover
277             if controller_access_configuration:
278                 controller_access_configuration.set_attribute_value(
279                     DC.RECOVER, True)
280                 controller = proxy.create_experiment_controller(None, 
281                     controller_access_configuration)
282             else:
283                 controller = ExperimentController(None, self.root_dir)
284                 controller.recover()
285             
286             while not controller.is_finished(app.guid):
287                 time.sleep(0.5)
288             ping_result = controller.trace(app.guid, "stdout")
289             comp_result = r"""PING .* \(.*\) \d*\(\d*\) bytes of data.
290
291 --- .* ping statistics ---
292 10 packets transmitted, 10 received, 0% packet loss, time \d*ms.*
293 """
294             self.assertTrue(re.match(comp_result, ping_result, re.MULTILINE),
295                 "Unexpected trace:\n" + ping_result)
296         
297         finally:
298             if controller is not None:
299                 try:
300                     controller.stop()
301                 except:
302                     import traceback
303                     traceback.print_exc()
304                 try:
305                     controller.shutdown()
306                 except:
307                     import traceback
308                     traceback.print_exc()
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_simple(self):
312         self._test_simple(
313             daemonize_testbed = False,
314             controller_access_configuration = None)
315
316     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
317     def test_simple_sfa(self):
318         self._test_simple(
319             daemonize_testbed = False,
320             controller_access_configuration = None,
321             use_sfa = True)
322
323     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
324     @test_util.skipUnless(os.environ.get('NEPI_FULL_TESTS','').lower() in ('1','yes','true','on'),
325         "Test is interactive, requires NEPI_FULL_TESTS=yes")
326     def test_spanning_deployment(self):
327         self._test_spanning_deployment()
328
329     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
330     @test_util.skipUnless(os.environ.get('NEPI_FULL_TESTS','').lower() in ('1','yes','true','on'),
331         "Test is interactive, requires NEPI_FULL_TESTS=yes")
332     def test_spanning_deployment_sfa(self):
333         self._test_spanning_deployment(use_sfa = True)
334
335     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
336     def test_simple_daemonized(self):
337         access_config = proxy.AccessConfiguration({
338             DC.DEPLOYMENT_MODE : DC.MODE_DAEMON,
339             DC.ROOT_DIRECTORY : self.root_dir,
340             DC.LOG_LEVEL : DC.DEBUG_LEVEL,
341         })
342
343         self._test_simple(
344             daemonize_testbed = False,
345             controller_access_configuration = access_config)
346
347     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
348     def test_simple_daemonized_sfa(self):
349         access_config = proxy.AccessConfiguration({
350             DC.DEPLOYMENT_MODE : DC.MODE_DAEMON,
351             DC.ROOT_DIRECTORY : self.root_dir,
352             DC.LOG_LEVEL : DC.DEBUG_LEVEL,
353         })
354
355         self._test_simple(
356             daemonize_testbed = False,
357             controller_access_configuration = access_config,
358             use_sfa = True)
359
360     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
361     def test_z_simple_ssh(self): # _z_ cause we want it last - it messes up the process :(
362         # Recreate environment
363         environ = ' ; '.join( map("export %s=%r".__mod__, os.environ.iteritems()) )
364
365         env = test_util.test_environment()
366
367         access_config = proxy.AccessConfiguration({
368             DC.DEPLOYMENT_MODE : DC.MODE_DAEMON,
369             DC.ROOT_DIRECTORY : self.root_dir,
370             DC.LOG_LEVEL : DC.DEBUG_LEVEL,
371             DC.DEPLOYMENT_COMMUNICATION : DC.ACCESS_SSH,
372             DC.DEPLOYMENT_PORT : env.port,
373             DC.USE_AGENT : True,
374             DC.DEPLOYMENT_ENVIRONMENT_SETUP : environ,
375         })
376
377         self._test_simple(
378             daemonize_testbed = False,
379             controller_access_configuration = access_config,
380             environ = environ)
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_recover(self):
384         self._test_recover(
385             daemonize_testbed = False,
386             controller_access_configuration = None)
387
388     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
389     def test_recover_sfa(self):
390         self._test_recover(
391             daemonize_testbed = False,
392             controller_access_configuration = None,
393             use_sfa = True)
394
395     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
396     def test_recover_daemonized(self):
397         access_config = proxy.AccessConfiguration({
398             DC.DEPLOYMENT_MODE : DC.MODE_DAEMON,
399             DC.ROOT_DIRECTORY : self.root_dir,
400             DC.LOG_LEVEL : DC.DEBUG_LEVEL,
401         })
402
403         self._test_recover(
404             daemonize_testbed = False,
405             controller_access_configuration = access_config)
406
407     @test_util.skipUnless(test_util.pl_auth() is not None, "Test requires PlanetLab authentication info (PL_USER and PL_PASS environment variables)")
408     def test_recover_daemonized_sfa(self):
409         access_config = proxy.AccessConfiguration({
410             DC.DEPLOYMENT_MODE : DC.MODE_DAEMON,
411             DC.ROOT_DIRECTORY : self.root_dir,
412             DC.LOG_LEVEL : DC.DEBUG_LEVEL,
413         })
414
415         self._test_recover(
416             daemonize_testbed = False,
417             controller_access_configuration = access_config,
418             use_sfa = True)
419
420
421 if __name__ == '__main__':
422     unittest.main()
423