blob: e84f3254773d6fd347da4ad1f1a9d3a292d05887 [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 Kwac0ce5452017-07-12 12:12:46 +080018from autotest_lib.site_utils.lxc import unittest_logging
19from autotest_lib.site_utils.lxc import utils as lxc_utils
20
21
22options = None
23
Ben Kwa36952eb2017-07-12 23:41:40 +080024@unittest.skipIf(lxc.IS_MOBLAB, 'Zygotes are not supported on moblab.')
Ben Kwac0ce5452017-07-12 12:12:46 +080025class 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
Ben Kwac0ce5452017-07-12 12:12:46 +0800100 def testSetHostnameRunning(self):
101 """Verifies that the hostname can be set on a running container."""
102 with self.createZygote() as zygote:
103 expected_hostname = 'my-new-hostname'
104 zygote.start(wait_for_network=True)
105 zygote.set_hostname(expected_hostname)
106 hostname = zygote.attach_run('hostname -f').stdout.strip()
107 self.assertEqual(expected_hostname, hostname)
108
109
110 def testHostDir(self):
111 """Verifies that the host dir on the container is created, and correctly
112 bind-mounted."""
113 with self.createZygote() as zygote:
114 self.assertIsNotNone(zygote.host_path)
115 self.assertTrue(os.path.isdir(zygote.host_path))
116
117 zygote.start(wait_for_network=False)
118
119 self.verifyBindMount(
120 zygote,
Ben Kwa36952eb2017-07-12 23:41:40 +0800121 container_path=lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800122 host_path=zygote.host_path)
123
124
125 def testHostDirExists(self):
126 """Verifies that the host dir is just mounted if it already exists."""
127 # Pre-create the host dir and put a file in it.
128 test_host_path = os.path.join(self.shared_host_path,
129 'testHostDirExists')
130 test_filename = 'test_file'
131 test_host_file = os.path.join(test_host_path, test_filename)
132 test_string = 'jackdaws love my big sphinx of quartz.'
133 os.mkdir(test_host_path)
134 with open(test_host_file, 'w+') as f:
135 f.write(test_string)
136
137 # Sanity check
138 self.assertTrue(lxc_utils.path_exists(test_host_file))
139
140 with self.createZygote(host_path=test_host_path) as zygote:
141 zygote.start(wait_for_network=False)
142
143 self.verifyBindMount(
144 zygote,
Ben Kwa36952eb2017-07-12 23:41:40 +0800145 container_path=lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800146 host_path=zygote.host_path)
147
148 # Verify that the old directory contents was preserved.
Ben Kwa36952eb2017-07-12 23:41:40 +0800149 cmd = 'cat %s' % os.path.join(lxc.CONTAINER_AUTOTEST_DIR,
Ben Kwac0ce5452017-07-12 12:12:46 +0800150 test_filename)
151 test_output = zygote.attach_run(cmd).stdout.strip()
152 self.assertEqual(test_string, test_output)
153
154
155 @contextmanager
156 def createZygote(self,
157 name = None,
158 attribute_values = None,
159 snapshot = True,
160 host_path = None):
161 """Clones a zygote from the test base container.
162 Use this to ensure that zygotes got properly cleaned up after each test.
163
164 @param container_path: The LXC path for the new container.
165 @param host_path: The host path for the new container.
166 @param name: The name of the new container.
167 @param attribute_values: Any attribute values for the new container.
168 @param snapshot: Whether to create a snapshot clone.
169 """
170 if name is None:
171 name = self.id().split('.')[-1]
172 if host_path is None:
173 host_path = os.path.join(self.shared_host_path, name)
174 if attribute_values is None:
175 attribute_values = {}
176 zygote = lxc.Zygote(self.test_dir,
177 name,
178 attribute_values,
179 self.base_container,
180 snapshot,
181 host_path)
Ben Kwa5a2cac12017-07-17 13:18:59 +0800182 try:
183 yield zygote
184 finally:
185 if not options.skip_cleanup:
186 zygote.destroy()
Ben Kwac0ce5452017-07-12 12:12:46 +0800187
188
189 def verifyBindMount(self, container, container_path, host_path):
190 """Verifies that a given path in a container is bind-mounted to a given
191 path in the host system.
192
193 @param container: The Container instance to be tested.
194 @param container_path: The path in the container to compare.
195 @param host_path: The path in the host system to compare.
196 """
197 container_inode = (container.attach_run('ls -id %s' % container_path)
198 .stdout.split()[0])
199 host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0]
200 # Compare the container and host inodes - they should match.
201 self.assertEqual(container_inode, host_inode)
202
203
204def parse_options():
205 """Parse command line inputs.
206 """
207 parser = argparse.ArgumentParser()
208 parser.add_argument('-v', '--verbose', action='store_true',
209 help='Print out ALL entries.')
210 parser.add_argument('--skip_cleanup', action='store_true',
211 help='Skip deleting test containers.')
212 args, argv = parser.parse_known_args()
213
214 # Hack: python unittest also processes args. Construct an argv to pass to
215 # it, that filters out the options it won't recognize.
216 if args.verbose:
217 argv.append('-v')
218 argv.insert(0, sys.argv[0])
219
220 return args, argv
221
222
223if __name__ == '__main__':
224 options, unittest_argv = parse_options()
225
Ben Kwac0ce5452017-07-12 12:12:46 +0800226 log_level=(logging.DEBUG if options.verbose else logging.INFO)
227 unittest_logging.setup(log_level)
228
229 unittest.main(argv=unittest_argv)