#!/usr/bin/python2 # Copyright 2017 The Chromium OS Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. import os import random import shutil import tempfile import unittest from contextlib import contextmanager import common from autotest_lib.client.bin import utils from autotest_lib.client.common_lib import error from autotest_lib.site_utils import lxc from autotest_lib.site_utils.lxc import constants from autotest_lib.site_utils.lxc import container as container_module from autotest_lib.site_utils.lxc import unittest_http from autotest_lib.site_utils.lxc import unittest_setup from autotest_lib.site_utils.lxc import utils as lxc_utils class ContainerTests(lxc_utils.LXCTests): """Unit tests for the Container class.""" @classmethod def setUpClass(cls): super(ContainerTests, cls).setUpClass() cls.test_dir = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH, prefix='container_unittest_') # Check if a base container exists on this machine and download one if # necessary. image = lxc.BaseImage() try: cls.base_container = image.get() cls.cleanup_base_container = False except error.ContainerError: image.setup() cls.base_container = image.get() cls.cleanup_base_container = True assert(cls.base_container is not None) @classmethod def tearDownClass(cls): cls.base_container = None if not unittest_setup.config.skip_cleanup: if cls.cleanup_base_container: lxc.BaseImage().cleanup() utils.run('sudo rm -r %s' % cls.test_dir) def testInit(self): """Verifies that containers initialize correctly.""" # Make a container that just points to the base container. container = lxc.Container.create_from_existing_dir( self.base_container.container_path, self.base_container.name) # Calling is_running triggers an lxc-ls call, which should verify that # the on-disk container is valid. self.assertFalse(container.is_running()) def testInitInvalid(self): """Verifies that invalid containers can still be instantiated, if not used. """ with tempfile.NamedTemporaryFile(dir=self.test_dir) as tmpfile: name = os.path.basename(tmpfile.name) container = lxc.Container.create_from_existing_dir(self.test_dir, name) with self.assertRaises(error.ContainerError): container.refresh_status() def testInvalidId(self): """Verifies that corrupted ID files do not raise exceptions.""" with self.createContainer() as container: # Create a container with an empty ID file. id_path = os.path.join(container.container_path, container.name, container_module._CONTAINER_ID_FILENAME) utils.run('sudo touch %s' % id_path) try: # Verify that container creation doesn't raise exceptions. test_container = lxc.Container.create_from_existing_dir( self.test_dir, container.name) self.assertIsNone(test_container.id) except Exception: self.fail('Unexpected exception:\n%s' % error.format_error()) def testDefaultHostname(self): """Verifies that the zygote starts up with a default hostname that is the lxc container name.""" test_name = 'testHostname' with self.createContainer(name=test_name) as container: container.start(wait_for_network=True) hostname = container.attach_run('hostname').stdout.strip() self.assertEqual(test_name, hostname) def testSetHostnameRunning(self): """Verifies that the hostname can be set on a running container.""" with self.createContainer() as container: expected_hostname = 'my-new-hostname' container.start(wait_for_network=True) container.set_hostname(expected_hostname) hostname = container.attach_run('hostname -f').stdout.strip() self.assertEqual(expected_hostname, hostname) def testSetHostnameNotRunningRaisesException(self): """Verifies that set_hostname on a stopped container raises an error. The lxc.utsname config setting is unreliable (it only works if the original container name is not a valid RFC-952 hostname, e.g. if it has underscores). A more reliable method exists for setting the hostname but it requires the container to be running. To avoid confusion, setting the hostname on a stopped container is disallowed. This test verifies that the operation raises a ContainerError. """ with self.createContainer() as container: with self.assertRaises(error.ContainerError): # Ensure the container is not running if container.is_running(): raise RuntimeError('Container should not be running.') container.set_hostname('foobar') def testClone(self): """Verifies that cloning a container works as expected.""" clone = lxc.Container.clone(src=self.base_container, new_name="testClone", new_path=self.test_dir, snapshot=True) try: # Throws an exception if the container is not valid. clone.refresh_status() finally: clone.destroy() def testCloneWithoutCleanup(self): """Verifies that cloning a container to an existing name will fail as expected. """ lxc.Container.clone(src=self.base_container, new_name="testCloneWithoutCleanup", new_path=self.test_dir, snapshot=True) with self.assertRaises(error.ContainerError): lxc.Container.clone(src=self.base_container, new_name="testCloneWithoutCleanup", new_path=self.test_dir, snapshot=True) def testCloneWithCleanup(self): """Verifies that cloning a container with cleanup works properly.""" clone0 = lxc.Container.clone(src=self.base_container, new_name="testClone", new_path=self.test_dir, snapshot=True) clone0.start(wait_for_network=False) tmpfile = clone0.attach_run('mktemp').stdout # Verify that our tmpfile exists clone0.attach_run('test -f %s' % tmpfile) # Clone another container in place of the existing container. clone1 = lxc.Container.clone(src=self.base_container, new_name="testClone", new_path=self.test_dir, snapshot=True, cleanup=True) with self.assertRaises(error.CmdError): clone1.attach_run('test -f %s' % tmpfile) def testInstallSsp(self): """Verifies that installing the ssp in the container works.""" # Hard-coded path to some golden data for this test. test_ssp = os.path.join( common.autotest_dir, 'site_utils', 'lxc', 'test', 'test_ssp.tar.bz2') # Create a container, install the self-served ssp, then check that it is # installed into the container correctly. with self.createContainer() as container: with unittest_http.serve_locally(test_ssp) as url: container.install_ssp(url) container.start(wait_for_network=False) # The test ssp just contains a couple of text files, in known # locations. Verify the location and content of those files in the # container. cat = lambda path: container.attach_run('cat %s' % path).stdout test0 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR, 'test.0')) test1 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR, 'dir0', 'test.1')) self.assertEquals('the five boxing wizards jumped quickly', test0) self.assertEquals('the quick brown fox jumps over the lazy dog', test1) def testInstallControlFile(self): """Verifies that installing a control file in the container works.""" _unused, tmpfile = tempfile.mkstemp() with self.createContainer() as container: container.install_control_file(tmpfile) container.start(wait_for_network=False) # Verify that the file is found in the container. container.attach_run( 'test -f %s' % os.path.join(lxc.CONTROL_TEMP_PATH, os.path.basename(tmpfile))) def testCopyFile(self): """Verifies that files are correctly copied into the container.""" control_string = 'amazingly few discotheques provide jukeboxes' with tempfile.NamedTemporaryFile() as tmpfile: tmpfile.write(control_string) tmpfile.flush() with self.createContainer() as container: dst = os.path.join(constants.CONTAINER_AUTOTEST_DIR, os.path.basename(tmpfile.name)) container.copy(tmpfile.name, dst) container.start(wait_for_network=False) # Verify the file content. test_string = container.attach_run('cat %s' % dst).stdout self.assertEquals(control_string, test_string) def testCopyDirectory(self): """Verifies that directories are correctly copied into the container.""" control_string = 'pack my box with five dozen liquor jugs' with lxc_utils.TempDir() as tmpdir: fd, tmpfile = tempfile.mkstemp(dir=tmpdir) f = os.fdopen(fd, 'w') f.write(control_string) f.close() with self.createContainer() as container: dst = os.path.join(constants.CONTAINER_AUTOTEST_DIR, os.path.basename(tmpdir)) container.copy(tmpdir, dst) container.start(wait_for_network=False) # Verify the file content. test_file = os.path.join(dst, os.path.basename(tmpfile)) test_string = container.attach_run('cat %s' % test_file).stdout self.assertEquals(control_string, test_string) def testMountDirectory(self): """Verifies that read-write mounts work.""" with lxc_utils.TempDir() as tmpdir, self.createContainer() as container: dst = '/testMountDirectory/testMount' container.mount_dir(tmpdir, dst, readonly=False) container.start(wait_for_network=False) # Verify that the mount point is correctly bound, and is read-write. self.verifyBindMount(container, dst, tmpdir) container.attach_run('test -r %s -a -w %s' % (dst, dst)) def testMountDirectoryReadOnly(self): """Verifies that read-only mounts work.""" with lxc_utils.TempDir() as tmpdir, self.createContainer() as container: dst = '/testMountDirectoryReadOnly/testMount' container.mount_dir(tmpdir, dst, readonly=True) container.start(wait_for_network=False) # Verify that the mount point is correctly bound, and is read-only. self.verifyBindMount(container, dst, tmpdir) container.attach_run('test -r %s -a ! -w %s' % (dst, dst)) def testMountDirectoryRelativePath(self): """Verifies that relative-path mounts work.""" with lxc_utils.TempDir() as tmpdir, self.createContainer() as container: dst = 'testMountDirectoryRelativePath/testMount' container.mount_dir(tmpdir, dst, readonly=True) container.start(wait_for_network=False) # Verify that the mount points is correctly bound.. self.verifyBindMount(container, dst, tmpdir) def testContainerIdPersistence(self): """Verifies that container IDs correctly persist. When a Container is instantiated on top of an existing container dir, check that it picks up the correct ID. """ with self.createContainer() as container: test_id = random_container_id() container.id = test_id # Set up another container and verify that its ID matches. test_container = lxc.Container.create_from_existing_dir( container.container_path, container.name) self.assertEqual(test_id, test_container.id) def testContainerIdIsNone_newContainer(self): """Verifies that newly created/cloned containers have no ID.""" with self.createContainer() as container: self.assertIsNone(container.id) # Set an ID, clone the container, and verify the clone has no ID. container.id = random_container_id() clone = lxc.Container.clone(src=container, new_name=container.name + '_clone', snapshot=True) self.assertIsNotNone(container.id) self.assertIsNone(clone.id) @contextmanager def createContainer(self, name=None): """Creates a container from the base container, for testing. Use this to ensure that containers get properly cleaned up after each test. @param name: An optional name for the new container. """ if name is None: name = self.id().split('.')[-1] container = lxc.Container.clone(src=self.base_container, new_name=name, new_path=self.test_dir, snapshot=True) try: yield container finally: if not unittest_setup.config.skip_cleanup: container.destroy() def verifyBindMount(self, container, container_path, host_path): """Verifies that a given path in a container is bind-mounted to a given path in the host system. @param container: The Container instance to be tested. @param container_path: The path in the container to compare. @param host_path: The path in the host system to compare. """ container_inode = (container.attach_run('ls -id %s' % container_path) .stdout.split()[0]) host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0] # Compare the container and host inodes - they should match. self.assertEqual(container_inode, host_inode) class ContainerIdTests(lxc_utils.LXCTests): """Unit tests for the ContainerId class.""" def setUp(self): self.test_dir = tempfile.mkdtemp() def tearDown(self): shutil.rmtree(self.test_dir) def testPickle(self): """Verifies the ContainerId persistence code.""" # Create a random ID, then save and load it and compare them. control = random_container_id() control.save(self.test_dir) test_data = lxc.ContainerId.load(self.test_dir) self.assertEqual(control, test_data) def random_container_id(): """Generate a random container ID for testing.""" return lxc.ContainerId.create(random.randint(0, 1000)) if __name__ == '__main__': unittest.main()