Home | History | Annotate | Download | only in lxc
      1 #!/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 
      6 import os
      7 import random
      8 import shutil
      9 import tempfile
     10 import unittest
     11 from contextlib import contextmanager
     12 
     13 import common
     14 from autotest_lib.client.bin import utils
     15 from autotest_lib.client.common_lib import error
     16 from autotest_lib.site_utils import lxc
     17 from autotest_lib.site_utils.lxc import constants
     18 from autotest_lib.site_utils.lxc import container as container_module
     19 from autotest_lib.site_utils.lxc import unittest_http
     20 from autotest_lib.site_utils.lxc import unittest_setup
     21 from autotest_lib.site_utils.lxc import utils as lxc_utils
     22 
     23 
     24 class ContainerTests(unittest.TestCase):
     25     """Unit tests for the Container class."""
     26 
     27     @classmethod
     28     def setUpClass(cls):
     29         cls.test_dir = tempfile.mkdtemp(dir=lxc.DEFAULT_CONTAINER_PATH,
     30                                         prefix='container_unittest_')
     31 
     32         # Check if a base container exists on this machine and download one if
     33         # necessary.
     34         image = lxc.BaseImage()
     35         try:
     36             cls.base_container = image.get()
     37             cls.cleanup_base_container = False
     38         except error.ContainerError:
     39             image.setup()
     40             cls.base_container = image.get()
     41             cls.cleanup_base_container = True
     42         assert(cls.base_container is not None)
     43 
     44 
     45     @classmethod
     46     def tearDownClass(cls):
     47         cls.base_container = None
     48         if not unittest_setup.config.skip_cleanup:
     49             if cls.cleanup_base_container:
     50                 lxc.BaseImage().cleanup()
     51             utils.run('sudo rm -r %s' % cls.test_dir)
     52 
     53 
     54     def testInit(self):
     55         """Verifies that containers initialize correctly."""
     56         # Make a container that just points to the base container.
     57         container = lxc.Container.create_from_existing_dir(
     58             self.base_container.container_path,
     59             self.base_container.name)
     60         # Calling is_running triggers an lxc-ls call, which should verify that
     61         # the on-disk container is valid.
     62         self.assertFalse(container.is_running())
     63 
     64 
     65     def testInitInvalid(self):
     66         """Verifies that invalid containers can still be instantiated,
     67         if not used.
     68         """
     69         with tempfile.NamedTemporaryFile(dir=self.test_dir) as tmpfile:
     70             name = os.path.basename(tmpfile.name)
     71             container = lxc.Container.create_from_existing_dir(self.test_dir,
     72                                                                name)
     73             with self.assertRaises(error.ContainerError):
     74                 container.refresh_status()
     75 
     76 
     77     def testInvalidId(self):
     78         """Verifies that corrupted ID files do not raise exceptions."""
     79         with self.createContainer() as container:
     80             # Create a container with an empty ID file.
     81             id_path = os.path.join(container.container_path,
     82                                    container.name,
     83                                    container_module._CONTAINER_ID_FILENAME)
     84             utils.run('sudo touch %s' % id_path)
     85             try:
     86                 # Verify that container creation doesn't raise exceptions.
     87                 test_container = lxc.Container.create_from_existing_dir(
     88                         self.test_dir, container.name)
     89                 self.assertIsNone(test_container.id)
     90             except Exception:
     91                 self.fail('Unexpected exception:\n%s' % error.format_error())
     92 
     93 
     94     def testDefaultHostname(self):
     95         """Verifies that the zygote starts up with a default hostname that is
     96         the lxc container name."""
     97         test_name = 'testHostname'
     98         with self.createContainer(name=test_name) as container:
     99             container.start(wait_for_network=True)
    100             hostname = container.attach_run('hostname').stdout.strip()
    101             self.assertEqual(test_name, hostname)
    102 
    103 
    104     def testSetHostnameRunning(self):
    105         """Verifies that the hostname can be set on a running container."""
    106         with self.createContainer() as container:
    107             expected_hostname = 'my-new-hostname'
    108             container.start(wait_for_network=True)
    109             container.set_hostname(expected_hostname)
    110             hostname = container.attach_run('hostname -f').stdout.strip()
    111             self.assertEqual(expected_hostname, hostname)
    112 
    113 
    114     def testSetHostnameNotRunningRaisesException(self):
    115         """Verifies that set_hostname on a stopped container raises an error.
    116 
    117         The lxc.utsname config setting is unreliable (it only works if the
    118         original container name is not a valid RFC-952 hostname, e.g. if it has
    119         underscores).
    120 
    121         A more reliable method exists for setting the hostname but it requires
    122         the container to be running.  To avoid confusion, setting the hostname
    123         on a stopped container is disallowed.
    124 
    125         This test verifies that the operation raises a ContainerError.
    126         """
    127         with self.createContainer() as container:
    128             with self.assertRaises(error.ContainerError):
    129                 # Ensure the container is not running
    130                 if container.is_running():
    131                     raise RuntimeError('Container should not be running.')
    132                 container.set_hostname('foobar')
    133 
    134 
    135     def testClone(self):
    136         """Verifies that cloning a container works as expected."""
    137         clone = lxc.Container.clone(src=self.base_container,
    138                                     new_name="testClone",
    139                                     new_path=self.test_dir,
    140                                     snapshot=True)
    141         try:
    142             # Throws an exception if the container is not valid.
    143             clone.refresh_status()
    144         finally:
    145             clone.destroy()
    146 
    147 
    148     def testCloneWithoutCleanup(self):
    149         """Verifies that cloning a container to an existing name will fail as
    150         expected.
    151         """
    152         lxc.Container.clone(src=self.base_container,
    153                             new_name="testCloneWithoutCleanup",
    154                             new_path=self.test_dir,
    155                             snapshot=True)
    156         with self.assertRaises(error.ContainerError):
    157             lxc.Container.clone(src=self.base_container,
    158                                 new_name="testCloneWithoutCleanup",
    159                                 new_path=self.test_dir,
    160                                 snapshot=True)
    161 
    162 
    163     def testCloneWithCleanup(self):
    164         """Verifies that cloning a container with cleanup works properly."""
    165         clone0 = lxc.Container.clone(src=self.base_container,
    166                                      new_name="testClone",
    167                                      new_path=self.test_dir,
    168                                      snapshot=True)
    169         clone0.start(wait_for_network=False)
    170         tmpfile = clone0.attach_run('mktemp').stdout
    171         # Verify that our tmpfile exists
    172         clone0.attach_run('test -f %s' % tmpfile)
    173 
    174         # Clone another container in place of the existing container.
    175         clone1 = lxc.Container.clone(src=self.base_container,
    176                                      new_name="testClone",
    177                                      new_path=self.test_dir,
    178                                      snapshot=True,
    179                                      cleanup=True)
    180         with self.assertRaises(error.CmdError):
    181             clone1.attach_run('test -f %s' % tmpfile)
    182 
    183 
    184     def testInstallSsp(self):
    185         """Verifies that installing the ssp in the container works."""
    186         # Hard-coded path to some golden data for this test.
    187         test_ssp = os.path.join(
    188                 common.autotest_dir,
    189                 'site_utils', 'lxc', 'test', 'test_ssp.tar.bz2')
    190         # Create a container, install the self-served ssp, then check that it is
    191         # installed into the container correctly.
    192         with self.createContainer() as container:
    193             with unittest_http.serve_locally(test_ssp) as url:
    194                 container.install_ssp(url)
    195             container.start(wait_for_network=False)
    196 
    197             # The test ssp just contains a couple of text files, in known
    198             # locations.  Verify the location and content of those files in the
    199             # container.
    200             cat = lambda path: container.attach_run('cat %s' % path).stdout
    201             test0 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
    202                                      'test.0'))
    203             test1 = cat(os.path.join(constants.CONTAINER_AUTOTEST_DIR,
    204                                      'dir0', 'test.1'))
    205             self.assertEquals('the five boxing wizards jumped quickly',
    206                               test0)
    207             self.assertEquals('the quick brown fox jumps over the lazy dog',
    208                               test1)
    209 
    210 
    211     def testInstallControlFile(self):
    212         """Verifies that installing a control file in the container works."""
    213         _unused, tmpfile = tempfile.mkstemp()
    214         with self.createContainer() as container:
    215             container.install_control_file(tmpfile)
    216             container.start(wait_for_network=False)
    217             # Verify that the file is found in the container.
    218             container.attach_run(
    219                 'test -f %s' % os.path.join(lxc.CONTROL_TEMP_PATH,
    220                                             os.path.basename(tmpfile)))
    221 
    222 
    223     def testCopyFile(self):
    224         """Verifies that files are correctly copied into the container."""
    225         control_string = 'amazingly few discotheques provide jukeboxes'
    226         with tempfile.NamedTemporaryFile() as tmpfile:
    227             tmpfile.write(control_string)
    228             tmpfile.flush()
    229 
    230             with self.createContainer() as container:
    231                 dst = os.path.join(constants.CONTAINER_AUTOTEST_DIR,
    232                                    os.path.basename(tmpfile.name))
    233                 container.copy(tmpfile.name, dst)
    234                 container.start(wait_for_network=False)
    235                 # Verify the file content.
    236                 test_string = container.attach_run('cat %s' % dst).stdout
    237                 self.assertEquals(control_string, test_string)
    238 
    239 
    240     def testCopyDirectory(self):
    241         """Verifies that directories are correctly copied into the container."""
    242         control_string = 'pack my box with five dozen liquor jugs'
    243         with lxc_utils.TempDir() as tmpdir:
    244             fd, tmpfile = tempfile.mkstemp(dir=tmpdir)
    245             f = os.fdopen(fd, 'w')
    246             f.write(control_string)
    247             f.close()
    248 
    249             with self.createContainer() as container:
    250                 dst = os.path.join(constants.CONTAINER_AUTOTEST_DIR,
    251                                    os.path.basename(tmpdir))
    252                 container.copy(tmpdir, dst)
    253                 container.start(wait_for_network=False)
    254                 # Verify the file content.
    255                 test_file = os.path.join(dst, os.path.basename(tmpfile))
    256                 test_string = container.attach_run('cat %s' % test_file).stdout
    257                 self.assertEquals(control_string, test_string)
    258 
    259 
    260     def testMountDirectory(self):
    261         """Verifies that read-write mounts work."""
    262         with lxc_utils.TempDir() as tmpdir, self.createContainer() as container:
    263             dst = '/testMountDirectory/testMount'
    264             container.mount_dir(tmpdir, dst, readonly=False)
    265             container.start(wait_for_network=False)
    266 
    267             # Verify that the mount point is correctly bound, and is read-write.
    268             self.verifyBindMount(container, dst, tmpdir)
    269             container.attach_run('test -r %s -a -w %s' % (dst, dst))
    270 
    271 
    272     def testMountDirectoryReadOnly(self):
    273         """Verifies that read-only mounts work."""
    274         with lxc_utils.TempDir() as tmpdir, self.createContainer() as container:
    275             dst = '/testMountDirectoryReadOnly/testMount'
    276             container.mount_dir(tmpdir, dst, readonly=True)
    277             container.start(wait_for_network=False)
    278 
    279             # Verify that the mount point is correctly bound, and is read-only.
    280             self.verifyBindMount(container, dst, tmpdir)
    281             container.attach_run('test -r %s -a ! -w %s' % (dst, dst))
    282 
    283 
    284     def testMountDirectoryRelativePath(self):
    285         """Verifies that relative-path mounts work."""
    286         with lxc_utils.TempDir() as tmpdir, self.createContainer() as container:
    287             dst = 'testMountDirectoryRelativePath/testMount'
    288             container.mount_dir(tmpdir, dst, readonly=True)
    289             container.start(wait_for_network=False)
    290 
    291             # Verify that the mount points is correctly bound..
    292             self.verifyBindMount(container, dst, tmpdir)
    293 
    294 
    295     def testContainerIdPersistence(self):
    296         """Verifies that container IDs correctly persist.
    297 
    298         When a Container is instantiated on top of an existing container dir,
    299         check that it picks up the correct ID.
    300         """
    301         with self.createContainer() as container:
    302             test_id = random_container_id()
    303             container.id = test_id
    304 
    305             # Set up another container and verify that its ID matches.
    306             test_container = lxc.Container.create_from_existing_dir(
    307                     container.container_path, container.name)
    308 
    309             self.assertEqual(test_id, test_container.id)
    310 
    311 
    312     def testContainerIdIsNone_newContainer(self):
    313         """Verifies that newly created/cloned containers have no ID."""
    314         with self.createContainer() as container:
    315             self.assertIsNone(container.id)
    316             # Set an ID, clone the container, and verify the clone has no ID.
    317             container.id = random_container_id()
    318             clone = lxc.Container.clone(src=container,
    319                                         new_name=container.name + '_clone',
    320                                         snapshot=True)
    321             self.assertIsNotNone(container.id)
    322             self.assertIsNone(clone.id)
    323 
    324 
    325     @contextmanager
    326     def createContainer(self, name=None):
    327         """Creates a container from the base container, for testing.
    328         Use this to ensure that containers get properly cleaned up after each
    329         test.
    330 
    331         @param name: An optional name for the new container.
    332         """
    333         if name is None:
    334             name = self.id().split('.')[-1]
    335         container = lxc.Container.clone(src=self.base_container,
    336                                         new_name=name,
    337                                         new_path=self.test_dir,
    338                                         snapshot=True)
    339         try:
    340             yield container
    341         finally:
    342             if not unittest_setup.config.skip_cleanup:
    343                 container.destroy()
    344 
    345 
    346     def verifyBindMount(self, container, container_path, host_path):
    347         """Verifies that a given path in a container is bind-mounted to a given
    348         path in the host system.
    349 
    350         @param container: The Container instance to be tested.
    351         @param container_path: The path in the container to compare.
    352         @param host_path: The path in the host system to compare.
    353         """
    354         container_inode = (container.attach_run('ls -id %s' % container_path)
    355                            .stdout.split()[0])
    356         host_inode = utils.run('ls -id %s' % host_path).stdout.split()[0]
    357         # Compare the container and host inodes - they should match.
    358         self.assertEqual(container_inode, host_inode)
    359 
    360 
    361 class ContainerIdTests(unittest.TestCase):
    362     """Unit tests for the ContainerId class."""
    363 
    364     def setUp(self):
    365         self.test_dir = tempfile.mkdtemp()
    366 
    367 
    368     def tearDown(self):
    369         shutil.rmtree(self.test_dir)
    370 
    371 
    372     def testPickle(self):
    373         """Verifies the ContainerId persistence code."""
    374         # Create a random ID, then save and load it and compare them.
    375         control = random_container_id()
    376         control.save(self.test_dir)
    377 
    378         test_data = lxc.ContainerId.load(self.test_dir)
    379         self.assertEqual(control, test_data)
    380 
    381 
    382 def random_container_id():
    383     """Generate a random container ID for testing."""
    384     return lxc.ContainerId.create(random.randint(0, 1000))
    385 
    386 
    387 if __name__ == '__main__':
    388     unittest_setup.setup()
    389     unittest.main()
    390