Home | History | Annotate | Download | only in lxc
      1 # Copyright 2017 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 import os
      7 
      8 import common
      9 from autotest_lib.client.common_lib import error
     10 from autotest_lib.client.bin import utils
     11 from autotest_lib.client.common_lib.cros import retry
     12 from autotest_lib.site_utils.lxc import constants
     13 from autotest_lib.site_utils.lxc import utils as lxc_utils
     14 
     15 
     16 # Cleaning up the bind mount can sometimes be blocked if a process is active in
     17 # the directory.  Give cleanup operations about 10 seconds to complete.  This is
     18 # only an approximate measure.
     19 _RETRY_MAX_SECONDS = 10
     20 
     21 
     22 class SharedHostDir(object):
     23     """A class that manages the shared host directory.
     24 
     25     Instantiating this class sets up a shared host directory at the specified
     26     path.  The directory is cleaned up and unmounted when cleanup is called.
     27     """
     28 
     29     def __init__(self,
     30                  path = constants.DEFAULT_SHARED_HOST_PATH,
     31                  force_delete = False):
     32         """Sets up the shared host directory.
     33 
     34         @param shared_host_path: The location of the shared host path.
     35         @param force_delete: If True, the host dir will be cleared and
     36                              reinitialized if it already exists.
     37         """
     38         self.path = os.path.realpath(path)
     39 
     40         # If the host dir exists and is valid and force_delete is not set, there
     41         # is nothing to do.  Otherwise, clear the host dir if it exists, then
     42         # recreate it.  Do not use lxc_utils.path_exists as that forces a sudo
     43         # call - the SharedHostDir is used all over the place, and
     44         # instantiatinng one should not cause the user to have to enter their
     45         # password if the host dir already exists.  The host dir is created with
     46         # open permissions so it should be accessible without sudo.
     47         if os.path.isdir(self.path):
     48             if not force_delete and self._host_dir_is_valid():
     49                 return
     50             else:
     51                 self.cleanup()
     52 
     53         utils.run('sudo mkdir "%(path)s" && '
     54                   'sudo chmod 777 "%(path)s" && '
     55                   'sudo mount --bind "%(path)s" "%(path)s" && '
     56                   'sudo mount --make-shared "%(path)s"' %
     57                   {'path': self.path})
     58 
     59 
     60     def cleanup(self, timeout=_RETRY_MAX_SECONDS):
     61         """Removes the shared host directory.
     62 
     63         This should only be called after all containers have been destroyed
     64         (i.e. all host mounts have been disconnected and removed, so the shared
     65         host directory should be empty).
     66 
     67         @param timeout: Unmounting and deleting the mount point can run into
     68                         race conditions vs the kernel sometimes.  This parameter
     69                         specifies the number of seconds for which to keep
     70                         waiting and retrying the umount/rm commands before
     71                         raising a CmdError.  The default of _RETRY_MAX_SECONDS
     72                         should work; this parameter is for tests to substitute a
     73                         different time out.
     74 
     75         @raises CmdError: If any of the commands involved in unmounting or
     76                           deleting the mount point fail even after retries.
     77         """
     78         if not os.path.exists(self.path):
     79             return
     80 
     81         # Unmount and delete everything in the host path.
     82         for info in lxc_utils.get_mount_info():
     83             if lxc_utils.is_subdir(self.path, info.mount_point):
     84                 utils.run('sudo umount "%s"' % info.mount_point)
     85 
     86         # It's possible that the directory is no longer mounted (e.g. if the
     87         # system was rebooted), so check before unmounting.
     88         if utils.run('findmnt %s > /dev/null' % self.path,
     89                      ignore_status=True).exit_status == 0:
     90             self._try_umount(timeout)
     91         self._try_rm(timeout)
     92 
     93 
     94     def _try_umount(self, timeout):
     95         """Tries to unmount the shared host dir.
     96 
     97         If the unmount fails, it is retried approximately once a second, for
     98         <timeout> seconds.  If the command still fails, a CmdError is raised.
     99 
    100         @param timeout: A timeout in seconds for which to retry the command.
    101 
    102         @raises CmdError: If the command has not succeeded after
    103                           _RETRY_MAX_SECONDS.
    104         """
    105         @retry.retry(error.CmdError, timeout_min=timeout/60.0,
    106                      delay_sec=1)
    107         def run_with_retry():
    108             """Actually unmounts the shared host dir.  Internal function."""
    109             utils.run('sudo umount %s' % self.path)
    110         run_with_retry()
    111 
    112 
    113     def _try_rm(self, timeout):
    114         """Tries to remove the shared host dir.
    115 
    116         If the rm command fails, it is retried approximately once a second, for
    117         <timeout> seconds.  If the command still fails, a CmdError is raised.
    118 
    119         @param timeout: A timeout in seconds for which to retry the command.
    120 
    121         @raises CmdError: If the command has not succeeded after
    122                           _RETRY_MAX_SECONDS.
    123         """
    124         @retry.retry(error.CmdError, timeout_min=timeout/60.0,
    125                      delay_sec=1)
    126         def run_with_retry():
    127             """Actually removes the shared host dir.  Internal function."""
    128             utils.run('sudo rm -r "%s"' % self.path)
    129         run_with_retry()
    130 
    131 
    132     def _host_dir_is_valid(self):
    133         """Verifies that the shared host directory is set up correctly."""
    134         logging.debug('Verifying existing host path: %s', self.path)
    135         host_mount = list(lxc_utils.get_mount_info(self.path))
    136 
    137         # Check that the host mount exists and is shared
    138         if host_mount:
    139             if 'shared' in host_mount[0].tags:
    140                 return True
    141             else:
    142                 logging.debug('Host mount not shared (%r).', host_mount)
    143         else:
    144             logging.debug('Host mount not found.')
    145 
    146         return False
    147