blob: 566855605e3f5ba97a3228b9d3da18a411c51270 [file] [log] [blame]
Ben Kwac0ce5452017-07-12 12:12:46 +08001#!/usr/bin/python
2# Copyright 2017 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import argparse
7import logging
8import os
9import tempfile
10import shutil
11import sys
12import unittest
13from contextlib import contextmanager
14
15import common
16from autotest_lib.client.bin import utils
17from autotest_lib.site_utils import lxc
Ben Kwac6395232017-07-17 14:44:08 +080018from autotest_lib.site_utils.lxc import constants
19from autotest_lib.site_utils.lxc import unittest_http
Ben Kwac0ce5452017-07-12 12:12:46 +080020from autotest_lib.site_utils.lxc import unittest_logging
21from autotest_lib.site_utils.lxc import utils as lxc_utils
22
23
24options = None
25
Ben Kwa36952eb2017-07-12 23:41:40 +080026@unittest.skipIf(lxc.IS_MOBLAB, 'Zygotes are not supported on moblab.')
Ben Kwac0ce5452017-07-12 12:12:46 +080027class ZygoteTests(unittest.TestCase):
28 """Unit tests for the Zygote class."""
29
30 @classmethod
31 def setUpClass(cls):
32 cls.test_dir = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
33 prefix='zygote_unittest_')
34 cls.shared_host_path = os.path.join(cls.test_dir, 'host')
35
36 # Use a container bucket just to download and set up the base image.
37 cls.bucket = lxc.ContainerBucket(cls.test_dir, cls.shared_host_path)
38
39 if cls.bucket.base_container is None:
40 logging.debug('Base container not found - reinitializing')
41 cls.bucket.setup_base()
42
43 cls.base_container = cls.bucket.base_container
44 assert(cls.base_container is not None)
45
46
47 @classmethod
48 def tearDownClass(cls):
49 cls.base_container = None
50 if not options.skip_cleanup:
51 cls.bucket.destroy_all()
52 shutil.rmtree(cls.test_dir)
53
54 def tearDown(self):
55 # Ensure host dirs from each test are completely destroyed.
56 for host_dir in os.listdir(self.shared_host_path):
57 host_dir = os.path.realpath(os.path.join(self.shared_host_path,
58 host_dir))
59 lxc_utils.cleanup_host_mount(host_dir);
60
61
62 def testCleanup(self):
63 """Verifies that the zygote cleans up after itself."""
64 with self.createZygote() as zygote:
65 host_path = zygote.host_path
66
67 self.assertTrue(os.path.isdir(host_path))
68
69 # Start/stop the zygote to exercise the host mounts.
70 zygote.start(wait_for_network=False)
71 zygote.stop()
72
73 # After the zygote is destroyed, verify that the host path is cleaned
74 # up.
75 self.assertFalse(os.path.isdir(host_path))
76
77
78 def testCleanupWithUnboundHostDir(self):
79 """Verifies that cleanup works when the host dir is unbound."""
80 with self.createZygote() as zygote:
81 host_path = zygote.host_path
82
83 self.assertTrue(os.path.isdir(host_path))
84 # Don't start the zygote, so the host mount is not bound.
85
86 # After the zygote is destroyed, verify that the host path is cleaned
87 # up.
88 self.assertFalse(os.path.isdir(host_path))
89
90
91 def testCleanupWithNoHostDir(self):
92 """Verifies that cleanup works when the host dir is missing."""
93 with self.createZygote() as zygote:
94 host_path = zygote.host_path
95
96 utils.run('sudo rmdir %s' % zygote.host_path)
97 self.assertFalse(os.path.isdir(host_path))
98 # Zygote destruction should yield no errors if the host path is
99 # missing.
100
101
Ben Kwac0ce5452017-07-12 12:12:46 +0800102 def testSetHostnameRunning(self):
103 """Verifies that the hostname can be set on a running container."""
104 with self.createZygote() as zygote:
105 expected_hostname = 'my-new-hostname'
106 zygote.start(wait_for_network=True)
107 zygote.set_hostname(expected_hostname)
108 hostname = zygote.attach_run('hostname -f').stdout.strip()
109 self.assertEqual(expected_hostname, hostname)
110
111
112 def testHostDir(self):
113 """Verifies that the host dir on the container is created, and correctly
114 bind-mounted."""
115 with self.createZygote() as zygote:
116 self.assertIsNotNone(zygote.host_path)
117 self.assertTrue(os.path.isdir(zygote.host_path))
118
119 zygote.start(wait_for_network=False)
120
121 self.verifyBindMount(
122 zygote,
Ben Kwa36952eb2017-07-12 23:41:40 +0800123 container_path=lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800124 host_path=zygote.host_path)
125
126
127 def testHostDirExists(self):
128 """Verifies that the host dir is just mounted if it already exists."""
129 # Pre-create the host dir and put a file in it.
130 test_host_path = os.path.join(self.shared_host_path,
131 'testHostDirExists')
132 test_filename = 'test_file'
133 test_host_file = os.path.join(test_host_path, test_filename)
134 test_string = 'jackdaws love my big sphinx of quartz.'
135 os.mkdir(test_host_path)
136 with open(test_host_file, 'w+') as f:
137 f.write(test_string)
138
139 # Sanity check
140 self.assertTrue(lxc_utils.path_exists(test_host_file))
141
142 with self.createZygote(host_path=test_host_path) as zygote:
143 zygote.start(wait_for_network=False)
144
145 self.verifyBindMount(
146 zygote,
Ben Kwa36952eb2017-07-12 23:41:40 +0800147 container_path=lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800148 host_path=zygote.host_path)
149
150 # Verify that the old directory contents was preserved.
Ben Kwa36952eb2017-07-12 23:41:40 +0800151 cmd = 'cat %s' % os.path.join(lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800152 test_filename)
153 test_output = zygote.attach_run(cmd).stdout.strip()
154 self.assertEqual(test_string, test_output)
155
156
Ben Kwac6395232017-07-17 14:44:08 +0800157 def testInstallSsp(self):
158 """Verifies that installing the ssp in the container works."""
159 # Hard-coded path to some golden data for this test.
160 test_ssp = os.path.join(
161 common.autotest_dir,
162 'site_utils', 'lxc', 'test', 'test_ssp.tar.bz2')
163 # Create a container, install the self-served ssp, then check that it is
164 # installed into the container correctly.
165 with self.createZygote() as zygote:
166 # Note: start the zygote first, then install the SSP. This mimics
167 # the way things would work in the production environment.
168 zygote.start(wait_for_network=False)
169 with unittest_http.serve_locally(test_ssp) as url:
170 zygote.install_ssp(url)
171
172 # The test ssp just contains a couple of text files, in known
173 # locations. Verify the location and content of those files in the
174 # container.
175 cat = lambda path: zygote.attach_run('cat %s' % path).stdout
176 test0 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
177 'test.0'))
178 test1 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
179 'dir0', 'test.1'))
180 self.assertEquals('the five boxing wizards jumped quickly',
181 test0)
182 self.assertEquals('the quick brown fox jumps over the lazy dog',
183 test1)
184
185
186 def testInstallControlFile(self):
187 """Verifies that installing a control file in the container works."""
188 _unused, tmpfile = tempfile.mkstemp()
189 with self.createZygote() as zygote:
190 # Note: start the zygote first. This mimics the way things would
191 # work in the production environment.
192 zygote.start(wait_for_network=False)
193 zygote.install_control_file(tmpfile)
194 # Verify that the file is found in the zygote.
195 zygote.attach_run(
196 'test -f %s' % os.path.join(lxc.CONTROL_TEMP_PATH,
197 os.path.basename(tmpfile)))
198
199
Ben Kwac0ce5452017-07-12 12:12:46 +0800200 @contextmanager
201 def createZygote(self,
202 name = None,
203 attribute_values = None,
204 snapshot = True,
205 host_path = None):
206 """Clones a zygote from the test base container.
207 Use this to ensure that zygotes got properly cleaned up after each test.
208
209 @param container_path: The LXC path for the new container.
210 @param host_path: The host path for the new container.
211 @param name: The name of the new container.
212 @param attribute_values: Any attribute values for the new container.
213 @param snapshot: Whether to create a snapshot clone.
214 """
215 if name is None:
216 name = self.id().split('.')[-1]
217 if host_path is None:
218 host_path = os.path.join(self.shared_host_path, name)
219 if attribute_values is None:
220 attribute_values = {}
221 zygote = lxc.Zygote(self.test_dir,
222 name,
223 attribute_values,
224 self.base_container,
225 snapshot,
226 host_path)
Ben Kwa5a2cac12017-07-17 13:18:59 +0800227 try:
228 yield zygote
229 finally:
230 if not options.skip_cleanup:
231 zygote.destroy()
Ben Kwac0ce5452017-07-12 12:12:46 +0800232
233
234 def verifyBindMount(self, container, container_path, host_path):
235 """Verifies that a given path in a container is bind-mounted to a given
236 path in the host system.
237
238 @param container: The Container instance to be tested.
239 @param container_path: The path in the container to compare.
240 @param host_path: The path in the host system to compare.
241 """
242 container_inode = (container.attach_run('ls -id %s' % container_path)
243 .stdout.split()[0])
244 host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0]
245 # Compare the container and host inodes - they should match.
246 self.assertEqual(container_inode, host_inode)
247
248
249def parse_options():
250 """Parse command line inputs.
251 """
252 parser = argparse.ArgumentParser()
253 parser.add_argument('-v', '--verbose', action='store_true',
254 help='Print out ALL entries.')
255 parser.add_argument('--skip_cleanup', action='store_true',
256 help='Skip deleting test containers.')
257 args, argv = parser.parse_known_args()
258
259 # Hack: python unittest also processes args. Construct an argv to pass to
260 # it, that filters out the options it won't recognize.
261 if args.verbose:
Ben Kwac6395232017-07-17 14:44:08 +0800262 argv.insert(0, '-v')
Ben Kwac0ce5452017-07-12 12:12:46 +0800263 argv.insert(0, sys.argv[0])
264
265 return args, argv
266
267
268if __name__ == '__main__':
269 options, unittest_argv = parse_options()
270
Ben Kwac0ce5452017-07-12 12:12:46 +0800271 log_level=(logging.DEBUG if options.verbose else logging.INFO)
272 unittest_logging.setup(log_level)
273
274 unittest.main(argv=unittest_argv)