blob: d4ffdc073eb444ca5acaf0f796e206d5bae0327a [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
18from autotest_lib.site_utils.lxc import config as lxc_config
19from autotest_lib.site_utils.lxc import unittest_logging
20from autotest_lib.site_utils.lxc import utils as lxc_utils
21
22
23options = None
24
25class ZygoteTests(unittest.TestCase):
26 """Unit tests for the Zygote class."""
27
28 @classmethod
29 def setUpClass(cls):
30 cls.test_dir = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
31 prefix='zygote_unittest_')
32 cls.shared_host_path = os.path.join(cls.test_dir, 'host')
33
34 # Use a container bucket just to download and set up the base image.
35 cls.bucket = lxc.ContainerBucket(cls.test_dir, cls.shared_host_path)
36
37 if cls.bucket.base_container is None:
38 logging.debug('Base container not found - reinitializing')
39 cls.bucket.setup_base()
40
41 cls.base_container = cls.bucket.base_container
42 assert(cls.base_container is not None)
43
44
45 @classmethod
46 def tearDownClass(cls):
47 cls.base_container = None
48 if not options.skip_cleanup:
49 cls.bucket.destroy_all()
50 shutil.rmtree(cls.test_dir)
51
52 def tearDown(self):
53 # Ensure host dirs from each test are completely destroyed.
54 for host_dir in os.listdir(self.shared_host_path):
55 host_dir = os.path.realpath(os.path.join(self.shared_host_path,
56 host_dir))
57 lxc_utils.cleanup_host_mount(host_dir);
58
59
60 def testCleanup(self):
61 """Verifies that the zygote cleans up after itself."""
62 with self.createZygote() as zygote:
63 host_path = zygote.host_path
64
65 self.assertTrue(os.path.isdir(host_path))
66
67 # Start/stop the zygote to exercise the host mounts.
68 zygote.start(wait_for_network=False)
69 zygote.stop()
70
71 # After the zygote is destroyed, verify that the host path is cleaned
72 # up.
73 self.assertFalse(os.path.isdir(host_path))
74
75
76 def testCleanupWithUnboundHostDir(self):
77 """Verifies that cleanup works when the host dir is unbound."""
78 with self.createZygote() as zygote:
79 host_path = zygote.host_path
80
81 self.assertTrue(os.path.isdir(host_path))
82 # Don't start the zygote, so the host mount is not bound.
83
84 # After the zygote is destroyed, verify that the host path is cleaned
85 # up.
86 self.assertFalse(os.path.isdir(host_path))
87
88
89 def testCleanupWithNoHostDir(self):
90 """Verifies that cleanup works when the host dir is missing."""
91 with self.createZygote() as zygote:
92 host_path = zygote.host_path
93
94 utils.run('sudo rmdir %s' % zygote.host_path)
95 self.assertFalse(os.path.isdir(host_path))
96 # Zygote destruction should yield no errors if the host path is
97 # missing.
98
99
100 def testHostname(self):
101 """Verifies that the zygote starts up with a default hostname that is
102 the lxc container name."""
103 test_name = 'testHostname'
104 with self.createZygote(name=test_name) as zygote:
105 zygote.start(wait_for_network=True)
106 hostname = zygote.attach_run('hostname -f').stdout.strip()
107 self.assertEqual(test_name, hostname)
108
109
110 @unittest.skip('Setting the container hostname using lxc.utsname does not'
111 'work on goobuntu.')
112 def testSetHostnameNotRunning(self):
113 """Verifies that the hostname can be set on a stopped container."""
114 with self.createZygote() as zygote:
115 expected_hostname = 'my-new-hostname'
116 zygote.set_hostname(expected_hostname)
117 zygote.start(wait_for_network=True)
118 hostname = zygote.attach_run('hostname -f').stdout.strip()
119 self.assertEqual(expected_hostname, hostname)
120
121
122 def testSetHostnameRunning(self):
123 """Verifies that the hostname can be set on a running container."""
124 with self.createZygote() as zygote:
125 expected_hostname = 'my-new-hostname'
126 zygote.start(wait_for_network=True)
127 zygote.set_hostname(expected_hostname)
128 hostname = zygote.attach_run('hostname -f').stdout.strip()
129 self.assertEqual(expected_hostname, hostname)
130
131
132 def testHostDir(self):
133 """Verifies that the host dir on the container is created, and correctly
134 bind-mounted."""
135 with self.createZygote() as zygote:
136 self.assertIsNotNone(zygote.host_path)
137 self.assertTrue(os.path.isdir(zygote.host_path))
138
139 zygote.start(wait_for_network=False)
140
141 self.verifyBindMount(
142 zygote,
143 container_path=lxc_config.CONTAINER_AUTOTEST_DIR,
144 host_path=zygote.host_path)
145
146
147 def testHostDirExists(self):
148 """Verifies that the host dir is just mounted if it already exists."""
149 # Pre-create the host dir and put a file in it.
150 test_host_path = os.path.join(self.shared_host_path,
151 'testHostDirExists')
152 test_filename = 'test_file'
153 test_host_file = os.path.join(test_host_path, test_filename)
154 test_string = 'jackdaws love my big sphinx of quartz.'
155 os.mkdir(test_host_path)
156 with open(test_host_file, 'w+') as f:
157 f.write(test_string)
158
159 # Sanity check
160 self.assertTrue(lxc_utils.path_exists(test_host_file))
161
162 with self.createZygote(host_path=test_host_path) as zygote:
163 zygote.start(wait_for_network=False)
164
165 self.verifyBindMount(
166 zygote,
167 container_path=lxc_config.CONTAINER_AUTOTEST_DIR,
168 host_path=zygote.host_path)
169
170 # Verify that the old directory contents was preserved.
171 cmd = 'cat %s' % os.path.join(lxc_config.CONTAINER_AUTOTEST_DIR,
172 test_filename)
173 test_output = zygote.attach_run(cmd).stdout.strip()
174 self.assertEqual(test_string, test_output)
175
176
177 @contextmanager
178 def createZygote(self,
179 name = None,
180 attribute_values = None,
181 snapshot = True,
182 host_path = None):
183 """Clones a zygote from the test base container.
184 Use this to ensure that zygotes got properly cleaned up after each test.
185
186 @param container_path: The LXC path for the new container.
187 @param host_path: The host path for the new container.
188 @param name: The name of the new container.
189 @param attribute_values: Any attribute values for the new container.
190 @param snapshot: Whether to create a snapshot clone.
191 """
192 if name is None:
193 name = self.id().split('.')[-1]
194 if host_path is None:
195 host_path = os.path.join(self.shared_host_path, name)
196 if attribute_values is None:
197 attribute_values = {}
198 zygote = lxc.Zygote(self.test_dir,
199 name,
200 attribute_values,
201 self.base_container,
202 snapshot,
203 host_path)
204 yield zygote
205 if not options.skip_cleanup:
206 zygote.destroy()
207
208
209 def verifyBindMount(self, container, container_path, host_path):
210 """Verifies that a given path in a container is bind-mounted to a given
211 path in the host system.
212
213 @param container: The Container instance to be tested.
214 @param container_path: The path in the container to compare.
215 @param host_path: The path in the host system to compare.
216 """
217 container_inode = (container.attach_run('ls -id %s' % container_path)
218 .stdout.split()[0])
219 host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0]
220 # Compare the container and host inodes - they should match.
221 self.assertEqual(container_inode, host_inode)
222
223
224def parse_options():
225 """Parse command line inputs.
226 """
227 parser = argparse.ArgumentParser()
228 parser.add_argument('-v', '--verbose', action='store_true',
229 help='Print out ALL entries.')
230 parser.add_argument('--skip_cleanup', action='store_true',
231 help='Skip deleting test containers.')
232 args, argv = parser.parse_known_args()
233
234 # Hack: python unittest also processes args. Construct an argv to pass to
235 # it, that filters out the options it won't recognize.
236 if args.verbose:
237 argv.append('-v')
238 argv.insert(0, sys.argv[0])
239
240 return args, argv
241
242
243if __name__ == '__main__':
244 options, unittest_argv = parse_options()
245
246
247 log_level=(logging.DEBUG if options.verbose else logging.INFO)
248 unittest_logging.setup(log_level)
249
250 unittest.main(argv=unittest_argv)