1*9c5db199SXin Li# Copyright 2015 The Chromium Authors. All rights reserved. 2*9c5db199SXin Li# Use of this source code is governed by a BSD-style license that can be 3*9c5db199SXin Li# found in the LICENSE file. 4*9c5db199SXin Li 5*9c5db199SXin Lifrom __future__ import absolute_import 6*9c5db199SXin Lifrom __future__ import division 7*9c5db199SXin Lifrom __future__ import print_function 8*9c5db199SXin Li 9*9c5db199SXin Liimport logging 10*9c5db199SXin Liimport os 11*9c5db199SXin Liimport tempfile 12*9c5db199SXin Li 13*9c5db199SXin Liimport common 14*9c5db199SXin Lifrom autotest_lib.client.bin import utils as common_utils 15*9c5db199SXin Lifrom autotest_lib.client.common_lib import error 16*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import dev_server 17*9c5db199SXin Lifrom autotest_lib.client.common_lib.cros import retry 18*9c5db199SXin Lifrom autotest_lib.server import utils as server_utils 19*9c5db199SXin Lifrom autotest_lib.site_utils.lxc import constants 20*9c5db199SXin Liimport six 21*9c5db199SXin Lifrom six.moves import zip 22*9c5db199SXin Li 23*9c5db199SXin Litry: 24*9c5db199SXin Li from autotest_lib.utils.frozen_chromite.lib import metrics 25*9c5db199SXin Liexcept ImportError: 26*9c5db199SXin Li metrics = common_utils.metrics_mock 27*9c5db199SXin Li 28*9c5db199SXin Li 29*9c5db199SXin Lidef get_container_info(container_path, **filters): 30*9c5db199SXin Li """Get a collection of container information in the given container path. 31*9c5db199SXin Li 32*9c5db199SXin Li This method parse the output of lxc-ls to get a list of container 33*9c5db199SXin Li information. The lxc-ls command output looks like: 34*9c5db199SXin Li NAME STATE IPV4 IPV6 AUTOSTART PID MEMORY RAM SWAP 35*9c5db199SXin Li -------------------------------------------------------------------------- 36*9c5db199SXin Li base STOPPED - - NO - - - - 37*9c5db199SXin Li test_123 RUNNING 10.0.3.27 - NO 8359 6.28MB 6.28MB 0.0MB 38*9c5db199SXin Li 39*9c5db199SXin Li @param container_path: Path to look for containers. 40*9c5db199SXin Li @param filters: Key value to filter the containers, e.g., name='base' 41*9c5db199SXin Li 42*9c5db199SXin Li @return: A list of dictionaries that each dictionary has the information of 43*9c5db199SXin Li a container. The keys are defined in ATTRIBUTES. 44*9c5db199SXin Li """ 45*9c5db199SXin Li cmd = 'sudo lxc-ls -P %s -f -F %s' % (os.path.realpath(container_path), 46*9c5db199SXin Li ','.join(constants.ATTRIBUTES)) 47*9c5db199SXin Li output = common_utils.run(cmd).stdout 48*9c5db199SXin Li info_collection = [] 49*9c5db199SXin Li 50*9c5db199SXin Li logging.info('cmd [%s] output:\n%s', cmd, output) 51*9c5db199SXin Li 52*9c5db199SXin Li for line in output.splitlines()[1:]: 53*9c5db199SXin Li # Only LXC 1.x has the second line of '-' as a separator. 54*9c5db199SXin Li if line.startswith('------'): 55*9c5db199SXin Li continue 56*9c5db199SXin Li info_collection.append( 57*9c5db199SXin Li dict(list(zip(constants.ATTRIBUTES, line.split())))) 58*9c5db199SXin Li if filters: 59*9c5db199SXin Li filtered_collection = [] 60*9c5db199SXin Li for key, value in six.iteritems(filters): 61*9c5db199SXin Li for info in info_collection: 62*9c5db199SXin Li if key in info and info[key] == value: 63*9c5db199SXin Li filtered_collection.append(info) 64*9c5db199SXin Li info_collection = filtered_collection 65*9c5db199SXin Li return info_collection 66*9c5db199SXin Li 67*9c5db199SXin Li 68*9c5db199SXin Lidef download_extract(url, target, extract_dir): 69*9c5db199SXin Li """Download the file from given url and save it to the target, then extract. 70*9c5db199SXin Li 71*9c5db199SXin Li @param url: Url to download the file. 72*9c5db199SXin Li @param target: Path of the file to save to. 73*9c5db199SXin Li @param extract_dir: Directory to extract the content of the file to. 74*9c5db199SXin Li """ 75*9c5db199SXin Li remote_url = dev_server.DevServer.get_server_url(url) 76*9c5db199SXin Li # This can be run in multiple threads, pick a unique tmp_file.name. 77*9c5db199SXin Li with tempfile.NamedTemporaryFile(prefix=os.path.basename(target) + '_', 78*9c5db199SXin Li delete=False) as tmp_file: 79*9c5db199SXin Li if remote_url in dev_server.ImageServerBase.servers(): 80*9c5db199SXin Li _download_via_devserver(url, tmp_file.name) 81*9c5db199SXin Li else: 82*9c5db199SXin Li _download_via_curl(url, tmp_file.name) 83*9c5db199SXin Li common_utils.run('sudo mv %s %s' % (tmp_file.name, target)) 84*9c5db199SXin Li common_utils.run('sudo tar -xvf %s -C %s' % (target, extract_dir)) 85*9c5db199SXin Li 86*9c5db199SXin Li 87*9c5db199SXin Li# Make sure retries only happen in the non-timeout case. 88*9c5db199SXin Li@retry.retry((error.CmdError), 89*9c5db199SXin Li raiselist=[error.CmdTimeoutError], 90*9c5db199SXin Li timeout_min=3*2, 91*9c5db199SXin Li delay_sec=10) 92*9c5db199SXin Lidef _download_via_curl(url, target_file_path): 93*9c5db199SXin Li # We do not want to retry on CmdTimeoutError but still retry on 94*9c5db199SXin Li # CmdError. Hence we can't use curl --timeout=... 95*9c5db199SXin Li common_utils.run('sudo curl -s %s -o %s' % (url, target_file_path), 96*9c5db199SXin Li stderr_tee=common_utils.TEE_TO_LOGS, timeout=3*60) 97*9c5db199SXin Li 98*9c5db199SXin Li 99*9c5db199SXin Li# Make sure retries only happen in the non-timeout case. 100*9c5db199SXin Li@retry.retry((error.CmdError), 101*9c5db199SXin Li raiselist=[error.CmdTimeoutError], 102*9c5db199SXin Li timeout_min=(constants.DEVSERVER_CALL_TIMEOUT * 103*9c5db199SXin Li constants.DEVSERVER_CALL_RETRY / 60), 104*9c5db199SXin Li delay_sec=constants.DEVSERVER_CALL_DELAY) 105*9c5db199SXin Lidef _download_via_devserver(url, target_file_path): 106*9c5db199SXin Li dev_server.ImageServerBase.download_file( 107*9c5db199SXin Li url, target_file_path, timeout=constants.DEVSERVER_CALL_TIMEOUT) 108*9c5db199SXin Li 109*9c5db199SXin Li 110*9c5db199SXin Lidef _install_package_precheck(packages): 111*9c5db199SXin Li """If SSP is not enabled or the test is running in chroot (using test_that), 112*9c5db199SXin Li packages installation should be skipped. 113*9c5db199SXin Li 114*9c5db199SXin Li The check does not raise exception so tests started by test_that or running 115*9c5db199SXin Li in an Autotest setup with SSP disabled can continue. That assume the running 116*9c5db199SXin Li environment, chroot or a machine, has the desired packages installed 117*9c5db199SXin Li already. 118*9c5db199SXin Li 119*9c5db199SXin Li @param packages: A list of names of the packages to install. 120*9c5db199SXin Li 121*9c5db199SXin Li @return: True if package installation can continue. False if it should be 122*9c5db199SXin Li skipped. 123*9c5db199SXin Li 124*9c5db199SXin Li """ 125*9c5db199SXin Li if server_utils.is_inside_chroot(): 126*9c5db199SXin Li logging.info('Test is running inside chroot. Install package %s is ' 127*9c5db199SXin Li 'skipped.', packages) 128*9c5db199SXin Li return False 129*9c5db199SXin Li 130*9c5db199SXin Li if not common_utils.is_in_container(): 131*9c5db199SXin Li raise error.ContainerError('Package installation is only supported ' 132*9c5db199SXin Li 'when test is running inside container.') 133*9c5db199SXin Li 134*9c5db199SXin Li return True 135*9c5db199SXin Li 136*9c5db199SXin Li 137*9c5db199SXin Lidef _remove_banned_packages(packages, banned_packages): 138*9c5db199SXin Li """Filter out packages. 139*9c5db199SXin Li 140*9c5db199SXin Li @param packages: A set of packages names that have been requested. 141*9c5db199SXin Li @param items: A list of package names that are not to be installed. 142*9c5db199SXin Li 143*9c5db199SXin Li @return: A sanatized set of packages names to install. 144*9c5db199SXin Li """ 145*9c5db199SXin Li return {package for package in packages if package not in banned_packages} 146*9c5db199SXin Li 147*9c5db199SXin Li 148*9c5db199SXin Lidef _ensure_pip(target_setting): 149*9c5db199SXin Li """ Ensure pip is installed, if not install it. 150*9c5db199SXin Li 151*9c5db199SXin Li @param target_setting: target command param specifying the path to where 152*9c5db199SXin Li python packages should be installed. 153*9c5db199SXin Li """ 154*9c5db199SXin Li try: 155*9c5db199SXin Li import pip 156*9c5db199SXin Li except ImportError: 157*9c5db199SXin Li common_utils.run( 158*9c5db199SXin Li 'wget https://bootstrap.pypa.io/get-pip.py -O /tmp/get-pip.py') 159*9c5db199SXin Li common_utils.run('python /tmp/get-pip.py %s' % target_setting) 160*9c5db199SXin Li 161*9c5db199SXin Li 162*9c5db199SXin Li@metrics.SecondsTimerDecorator( 163*9c5db199SXin Li '%s/install_packages_duration' % constants.STATS_KEY) 164*9c5db199SXin Li@retry.retry(error.CmdError, timeout_min=30) 165*9c5db199SXin Lidef install_packages(packages=[], python_packages=[], force_latest=False): 166*9c5db199SXin Li """Install the given package inside container. 167*9c5db199SXin Li 168*9c5db199SXin Li !!! WARNING !!! 169*9c5db199SXin Li This call may introduce several minutes of delay in test run. The best way 170*9c5db199SXin Li to avoid such delay is to update the base container used for the test run. 171*9c5db199SXin Li File a bug for infra deputy to update the base container with the new 172*9c5db199SXin Li package a test requires. 173*9c5db199SXin Li 174*9c5db199SXin Li @param packages: A list of names of the packages to install. 175*9c5db199SXin Li @param python_packages: A list of names of the python packages to install 176*9c5db199SXin Li using pip. 177*9c5db199SXin Li @param force_latest: True to force to install the latest version of the 178*9c5db199SXin Li package. Default to False, which means skip installing 179*9c5db199SXin Li the package if it's installed already, even with an old 180*9c5db199SXin Li version. 181*9c5db199SXin Li 182*9c5db199SXin Li @raise error.ContainerError: If package is attempted to be installed outside 183*9c5db199SXin Li a container. 184*9c5db199SXin Li @raise error.CmdError: If the package doesn't exist or failed to install. 185*9c5db199SXin Li 186*9c5db199SXin Li """ 187*9c5db199SXin Li if not _install_package_precheck(packages or python_packages): 188*9c5db199SXin Li return 189*9c5db199SXin Li 190*9c5db199SXin Li # If force_latest is False, only install packages that are not already 191*9c5db199SXin Li # installed. 192*9c5db199SXin Li if not force_latest: 193*9c5db199SXin Li packages = [p for p in packages 194*9c5db199SXin Li if not common_utils.is_package_installed(p)] 195*9c5db199SXin Li python_packages = [p for p in python_packages 196*9c5db199SXin Li if not common_utils.is_python_package_installed(p)] 197*9c5db199SXin Li if not packages and not python_packages: 198*9c5db199SXin Li logging.debug( 199*9c5db199SXin Li 'All packages are installed already, skip reinstall.') 200*9c5db199SXin Li return 201*9c5db199SXin Li 202*9c5db199SXin Li # Always run apt-get update before installing any container. The base 203*9c5db199SXin Li # container may have outdated cache. 204*9c5db199SXin Li common_utils.run('sudo apt-get update') 205*9c5db199SXin Li 206*9c5db199SXin Li # Make sure the lists are not None for iteration. 207*9c5db199SXin Li packages = [] if not packages else packages 208*9c5db199SXin Li # Remove duplicates. 209*9c5db199SXin Li packages = set(packages) 210*9c5db199SXin Li 211*9c5db199SXin Li # Ubuntu distribution of pip is very old, do not use it as it causes 212*9c5db199SXin Li # segmentation faults. Some tests request these packages, ensure they 213*9c5db199SXin Li # are not installed. 214*9c5db199SXin Li packages = _remove_banned_packages(packages, ['python-pip', 'python-dev']) 215*9c5db199SXin Li 216*9c5db199SXin Li if packages: 217*9c5db199SXin Li common_utils.run( 218*9c5db199SXin Li 'sudo DEBIAN_FRONTEND=noninteractive apt-get install %s -y ' 219*9c5db199SXin Li '--force-yes' % ' '.join(packages)) 220*9c5db199SXin Li logging.debug('Packages are installed: %s.', packages) 221*9c5db199SXin Li 222*9c5db199SXin Li target_setting = '' 223*9c5db199SXin Li # For containers running in Moblab, /usr/local/lib/python2.7/dist-packages/ 224*9c5db199SXin Li # is a readonly mount from the host. Therefore, new python modules have to 225*9c5db199SXin Li # be installed in /usr/lib/python2.7/dist-packages/ 226*9c5db199SXin Li # Containers created in Moblab does not have autotest/site-packages folder. 227*9c5db199SXin Li if not os.path.exists('/usr/local/autotest/site-packages'): 228*9c5db199SXin Li target_setting = '--target="/usr/lib/python2.7/dist-packages/"' 229*9c5db199SXin Li # Pip should be installed in the base container, if not install it. 230*9c5db199SXin Li if python_packages: 231*9c5db199SXin Li _ensure_pip(target_setting) 232*9c5db199SXin Li common_utils.run('python -m pip install pip --upgrade') 233*9c5db199SXin Li common_utils.run('python -m pip install %s %s' % (target_setting, 234*9c5db199SXin Li ' '.join(python_packages))) 235*9c5db199SXin Li logging.debug('Python packages are installed: %s.', python_packages) 236