blob: 8a102b321f3e14291e39a2bdd170848dd656277d [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)
182 yield zygote
183 if not options.skip_cleanup:
184 zygote.destroy()
185
186
187 def verifyBindMount(self, container, container_path, host_path):
188 """Verifies that a given path in a container is bind-mounted to a given
189 path in the host system.
190
191 @param container: The Container instance to be tested.
192 @param container_path: The path in the container to compare.
193 @param host_path: The path in the host system to compare.
194 """
195 container_inode = (container.attach_run('ls -id %s' % container_path)
196 .stdout.split()[0])
197 host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0]
198 # Compare the container and host inodes - they should match.
199 self.assertEqual(container_inode, host_inode)
200
201
202def parse_options():
203 """Parse command line inputs.
204 """
205 parser = argparse.ArgumentParser()
206 parser.add_argument('-v', '--verbose', action='store_true',
207 help='Print out ALL entries.')
208 parser.add_argument('--skip_cleanup', action='store_true',
209 help='Skip deleting test containers.')
210 args, argv = parser.parse_known_args()
211
212 # Hack: python unittest also processes args. Construct an argv to pass to
213 # it, that filters out the options it won't recognize.
214 if args.verbose:
215 argv.append('-v')
216 argv.insert(0, sys.argv[0])
217
218 return args, argv
219
220
221if __name__ == '__main__':
222 options, unittest_argv = parse_options()
223
Ben Kwac0ce5452017-07-12 12:12:46 +0800224 log_level=(logging.DEBUG if options.verbose else logging.INFO)
225 unittest_logging.setup(log_level)
226
227 unittest.main(argv=unittest_argv)