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