1#!/usr/bin/env python 2# 3# Copyright 2018 - The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16r"""host setup runner 17 18A setup sub task runner to support setting up the local host for AVD local 19instance. 20""" 21 22from __future__ import print_function 23 24import getpass 25import logging 26import os 27import shutil 28import sys 29import tempfile 30 31from acloud.internal import constants 32from acloud.internal.lib import utils 33from acloud.setup import base_task_runner 34from acloud.setup import setup_common 35 36 37logger = logging.getLogger(__name__) 38 39# Packages "devscripts" and "equivs" are required for "mk-build-deps". 40_AVD_REQUIRED_PKGS = [ 41 "devscripts", "equivs", "libvirt-clients", "libvirt-daemon-system"] 42_BASE_REQUIRED_PKGS = ["ssvnc", "lzop", "python3-tk"] 43_CUTTLEFISH_COMMOM_PKG = "cuttlefish-common" 44_CF_COMMOM_FOLDER = "cf-common" 45_LIST_OF_MODULES = ["kvm_intel", "kvm"] 46_UPDATE_APT_GET_CMD = "sudo apt-get update" 47_INSTALL_CUTTLEFISH_COMMOM_CMD = [ 48 "git clone https://github.com/google/android-cuttlefish.git {git_folder}", 49 "cd {git_folder}", 50 "yes | sudo mk-build-deps -i -r -B", 51 "dpkg-buildpackage -uc -us", 52 "sudo apt-get install -y -f ../cuttlefish-common_*_amd64.deb"] 53 54 55class BasePkgInstaller(base_task_runner.BaseTaskRunner): 56 """Subtask base runner class for installing packages.""" 57 58 # List of packages for child classes to override. 59 PACKAGES = [] 60 61 def ShouldRun(self): 62 """Check if required packages are all installed. 63 64 Returns: 65 Boolean, True if required packages are not installed. 66 """ 67 if not utils.IsSupportedPlatform(): 68 return False 69 70 # Any required package is not installed or not up-to-date will need to 71 # run installation task. 72 for pkg_name in self.PACKAGES: 73 if not setup_common.PackageInstalled(pkg_name): 74 return True 75 76 return False 77 78 def _Run(self): 79 """Install specified packages.""" 80 cmd = "\n".join( 81 [setup_common.PKG_INSTALL_CMD % pkg 82 for pkg in self.PACKAGES 83 if not setup_common.PackageInstalled(pkg)]) 84 85 if not utils.GetUserAnswerYes("\nStart to install package(s):\n%s" 86 "\nPress 'y' to continue or anything " 87 "else to do it myself and run acloud " 88 "again[y/N]: " % cmd): 89 sys.exit(constants.EXIT_BY_USER) 90 91 setup_common.CheckCmdOutput(_UPDATE_APT_GET_CMD, shell=True) 92 for pkg in self.PACKAGES: 93 setup_common.InstallPackage(pkg) 94 95 logger.info("All package(s) installed now.") 96 97 98class AvdPkgInstaller(BasePkgInstaller): 99 """Subtask runner class for installing packages for local instances.""" 100 101 WELCOME_MESSAGE_TITLE = ("Install required packages for host setup for " 102 "local instances") 103 WELCOME_MESSAGE = ("This step will walk you through the required packages " 104 "installation for running Android cuttlefish devices " 105 "on your host.") 106 PACKAGES = _AVD_REQUIRED_PKGS 107 108 109class HostBasePkgInstaller(BasePkgInstaller): 110 """Subtask runner class for installing base host packages.""" 111 112 WELCOME_MESSAGE_TITLE = "Install base packages on the host" 113 WELCOME_MESSAGE = ("This step will walk you through the base packages " 114 "installation for your host.") 115 PACKAGES = _BASE_REQUIRED_PKGS 116 117 118class CuttlefishCommonPkgInstaller(base_task_runner.BaseTaskRunner): 119 """Subtask base runner class for installing cuttlefish-common.""" 120 121 WELCOME_MESSAGE_TITLE = "Install cuttlefish-common packages on the host" 122 WELCOME_MESSAGE = ("This step will walk you through the cuttlefish-common " 123 "packages installation for your host.") 124 125 def ShouldRun(self): 126 """Check if cuttlefish-common package is installed. 127 128 Returns: 129 Boolean, True if cuttlefish-common is not installed. 130 """ 131 if not utils.IsSupportedPlatform(): 132 return False 133 134 # Any required package is not installed or not up-to-date will need to 135 # run installation task. 136 if not setup_common.PackageInstalled(_CUTTLEFISH_COMMOM_PKG): 137 return True 138 return False 139 140 def _Run(self): 141 """Install cuttlefilsh-common packages.""" 142 cf_common_path = os.path.join(tempfile.mkdtemp(), _CF_COMMOM_FOLDER) 143 logger.debug("cuttlefish-common path: %s", cf_common_path) 144 cmd = "\n".join(sub_cmd.format(git_folder=cf_common_path) 145 for sub_cmd in _INSTALL_CUTTLEFISH_COMMOM_CMD) 146 147 if not utils.GetUserAnswerYes("\nStart to install cuttlefish-common :\n%s" 148 "\nPress 'y' to continue or anything " 149 "else to do it myself and run acloud " 150 "again[y/N]: " % cmd): 151 sys.exit(constants.EXIT_BY_USER) 152 try: 153 setup_common.CheckCmdOutput(cmd, shell=True) 154 finally: 155 shutil.rmtree(os.path.dirname(cf_common_path)) 156 logger.info("Cuttlefish-common package installed now.") 157 158 159class CuttlefishHostSetup(base_task_runner.BaseTaskRunner): 160 """Subtask class that setup host for cuttlefish.""" 161 162 WELCOME_MESSAGE_TITLE = "Host Enviornment Setup" 163 WELCOME_MESSAGE = ( 164 "This step will help you to setup enviornment for running Android " 165 "cuttlefish devices on your host. That includes adding user to kvm " 166 "related groups and checking required linux modules." 167 ) 168 169 def ShouldRun(self): 170 """Check host user groups and modules. 171 172 Returns: 173 Boolean: False if user is in all required groups and all modules 174 are reloaded. 175 """ 176 if not utils.IsSupportedPlatform(): 177 return False 178 179 return not (utils.CheckUserInGroups(constants.LIST_CF_USER_GROUPS) 180 and self._CheckLoadedModules(_LIST_OF_MODULES)) 181 182 @staticmethod 183 def _CheckLoadedModules(module_list): 184 """Check if the modules are all in use. 185 186 Args: 187 module_list: The list of module name. 188 Returns: 189 True if all modules are in use. 190 """ 191 logger.info("Checking if modules are loaded: %s", module_list) 192 lsmod_output = setup_common.CheckCmdOutput("lsmod", print_cmd=False) 193 current_modules = [r.split()[0] for r in lsmod_output.splitlines()] 194 all_modules_present = True 195 for module in module_list: 196 if module not in current_modules: 197 logger.info("missing module: %s", module) 198 all_modules_present = False 199 return all_modules_present 200 201 def _Run(self): 202 """Setup host environment for local cuttlefish instance support.""" 203 # TODO: provide --uid args to let user use prefered username 204 username = getpass.getuser() 205 setup_cmds = [ 206 "sudo rmmod kvm_intel", 207 "sudo rmmod kvm", 208 "sudo modprobe kvm", 209 "sudo modprobe kvm_intel"] 210 for group in constants.LIST_CF_USER_GROUPS: 211 setup_cmds.append("sudo usermod -aG %s % s" % (group, username)) 212 213 print("Below commands will be run:") 214 for setup_cmd in setup_cmds: 215 print(setup_cmd) 216 217 if self._ConfirmContinue(): 218 for setup_cmd in setup_cmds: 219 setup_common.CheckCmdOutput(setup_cmd, shell=True) 220 print("Host environment setup has done!") 221 222 @staticmethod 223 def _ConfirmContinue(): 224 """Ask user if they want to continue. 225 226 Returns: 227 True if user answer yes. 228 """ 229 answer_client = utils.InteractWithQuestion( 230 "\nPress 'y' to continue or anything else to do it myself[y/N]: ", 231 utils.TextColors.WARNING) 232 return answer_client in constants.USER_ANSWER_YES 233