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 35from acloud.setup import mkcert 36 37 38logger = logging.getLogger(__name__) 39 40_CF_COMMOM_FOLDER = "cf-common" 41 42_INTEL = "intel" 43_AMD = "amd" 44_KVM_INTEL = "kvm_intel" 45_KVM_AMD = "kvm_amd" 46_LIST_OF_INTEL_MODULES = [_KVM_INTEL, "kvm"] 47_LIST_OF_AMD_MODULES = [_KVM_AMD, "kvm"] 48_DICT_MODULES = {_INTEL: _LIST_OF_INTEL_MODULES, _AMD: _LIST_OF_AMD_MODULES} 49_INTEL_COMMANDS = [ 50 "sudo rmmod kvm_intel || true", "sudo rmmod kvm || true", 51 "sudo modprobe kvm", "sudo modprobe kvm_intel"] 52_AMD_COMMANDS = [ 53 "sudo rmmod kvm_amd || true", "sudo rmmod kvm|| true", "sudo modprobe kvm", 54 "sudo modprobe kvm_amd"] 55_DICT_SETUP_CMDS = {_INTEL: _INTEL_COMMANDS, _AMD: _AMD_COMMANDS} 56_UPDATE_APT_GET_CMD = "sudo apt-get update" 57_INSTALL_CUTTLEFISH_COMMOM_CMD = [ 58 "git clone https://github.com/google/android-cuttlefish.git {git_folder}", 59 "cd {git_folder}/base", 60 "debuild -i -us -uc -b", 61 "cd ../frontend", 62 "debuild -i -us -uc -b", 63 "sudo dpkg -i ../cuttlefish-base_*_*64.deb || sudo apt-get install -f", 64 "sudo dpkg -i ../cuttlefish-user_*_*64.deb || sudo apt-get install -f", 65 "sudo dpkg -i ../cuttlefish-common_*_*64.deb || sudo apt-get install -f"] 66_INSTALL_CUTTLEFISH_COMMOM_MSG = ("\nStart to install cuttlefish-common :\n%s" 67 "\nEnter 'y' to continue, otherwise N or " 68 "enter to exit: ") 69 70 71class BasePkgInstaller(base_task_runner.BaseTaskRunner): 72 """Subtask base runner class for installing packages.""" 73 74 # List of packages for child classes to override. 75 PACKAGES = [] 76 77 def ShouldRun(self): 78 """Check if required packages are all installed. 79 80 Returns: 81 Boolean, True if required packages are not installed. 82 """ 83 if not utils.IsSupportedPlatform(): 84 return False 85 86 # Any required package is not installed or not up-to-date will need to 87 # run installation task. 88 for pkg_name in self.PACKAGES: 89 if not setup_common.PackageInstalled(pkg_name): 90 return True 91 92 return False 93 94 def _Run(self): 95 """Install specified packages.""" 96 cmd = "\n".join( 97 [setup_common.PKG_INSTALL_CMD % pkg 98 for pkg in self.PACKAGES 99 if not setup_common.PackageInstalled(pkg)]) 100 101 if not utils.GetUserAnswerYes("\nStart to install package(s):\n%s" 102 "\nEnter 'y' to continue, otherwise N or " 103 "enter to exit: " % cmd): 104 sys.exit(constants.EXIT_BY_USER) 105 106 setup_common.CheckCmdOutput(_UPDATE_APT_GET_CMD, shell=True) 107 for pkg in self.PACKAGES: 108 setup_common.InstallPackage(pkg) 109 110 logger.info("All package(s) installed now.") 111 112 113class AvdPkgInstaller(BasePkgInstaller): 114 """Subtask runner class for installing packages for local instances.""" 115 116 WELCOME_MESSAGE_TITLE = ("Install required packages for host setup for " 117 "local instances") 118 WELCOME_MESSAGE = ("This step will walk you through the required packages " 119 "installation for running Android cuttlefish devices " 120 "on your host.") 121 PACKAGES = constants.AVD_REQUIRED_PKGS 122 123 124class HostBasePkgInstaller(BasePkgInstaller): 125 """Subtask runner class for installing base host packages.""" 126 127 WELCOME_MESSAGE_TITLE = "Install base packages on the host" 128 WELCOME_MESSAGE = ("This step will walk you through the base packages " 129 "installation for your host.") 130 PACKAGES = constants.BASE_REQUIRED_PKGS 131 132 133class CuttlefishCommonPkgInstaller(base_task_runner.BaseTaskRunner): 134 """Subtask base runner class for installing cuttlefish-common.""" 135 136 WELCOME_MESSAGE_TITLE = "Install cuttlefish-common packages on the host" 137 WELCOME_MESSAGE = ("This step will walk you through the cuttlefish-common " 138 "packages installation for your host.") 139 140 def ShouldRun(self): 141 """Check if cuttlefish-common package is installed. 142 143 Returns: 144 Boolean, True if cuttlefish-common is not installed. 145 """ 146 if not utils.IsSupportedPlatform(): 147 return False 148 149 # Any required package is not installed or not up-to-date will need to 150 # run installation task. 151 if not setup_common.PackageInstalled(constants.CUTTLEFISH_COMMOM_PKG): 152 return True 153 return False 154 155 def _Run(self): 156 """Install cuttlefilsh-common packages.""" 157 if setup_common.IsPackageInAptList(constants.CUTTLEFISH_COMMOM_PKG): 158 cmd = setup_common.PKG_INSTALL_CMD % constants.CUTTLEFISH_COMMOM_PKG 159 if not utils.GetUserAnswerYes(_INSTALL_CUTTLEFISH_COMMOM_MSG % cmd): 160 sys.exit(constants.EXIT_BY_USER) 161 setup_common.InstallPackage(constants.CUTTLEFISH_COMMOM_PKG) 162 return 163 164 # Install cuttlefish-common from github. 165 cf_common_path = os.path.join(tempfile.mkdtemp(), _CF_COMMOM_FOLDER) 166 logger.debug("cuttlefish-common path: %s", cf_common_path) 167 cmd = "\n".join(sub_cmd.format(git_folder=cf_common_path) 168 for sub_cmd in _INSTALL_CUTTLEFISH_COMMOM_CMD) 169 try: 170 if not utils.GetUserAnswerYes(_INSTALL_CUTTLEFISH_COMMOM_MSG % cmd): 171 sys.exit(constants.EXIT_BY_USER) 172 setup_common.CheckCmdOutput(cmd, shell=True) 173 finally: 174 shutil.rmtree(os.path.dirname(cf_common_path)) 175 176 177class LocalCAHostSetup(base_task_runner.BaseTaskRunner): 178 """Subtask class that setup host for setup local CA.""" 179 180 WELCOME_MESSAGE_TITLE = "Local CA Host Environment Setup" 181 WELCOME_MESSAGE = ("This step will walk you through the local CA setup " 182 "to your host for assuring a secure localhost url " 183 "connection when launching an AVD over webrtc.") 184 185 def ShouldRun(self): 186 """Check if the local CA is setup or not. 187 188 Returns: 189 Boolean, True if local CA is ready. 190 """ 191 if not utils.IsSupportedPlatform(): 192 return False 193 194 return not mkcert.IsRootCAReady() 195 196 def _Run(self): 197 """Setup host environment for the local CA.""" 198 if not utils.GetUserAnswerYes("\nStart to setup the local CA:\n" 199 "\nEnter 'y' to continue, otherwise N or " 200 "enter to exit: "): 201 sys.exit(constants.EXIT_BY_USER) 202 203 mkcert.Install() 204 logger.info("The local CA '%s.pem' is installed now.", 205 constants.SSL_CA_NAME) 206 207 208class CuttlefishHostSetup(base_task_runner.BaseTaskRunner): 209 """Subtask class that setup host for cuttlefish.""" 210 211 WELCOME_MESSAGE_TITLE = "Host Environment Setup" 212 WELCOME_MESSAGE = ( 213 "This step will help you to setup environment for running Android " 214 "cuttlefish devices on your host. That includes adding user to kvm " 215 "related groups and checking required linux modules." 216 ) 217 218 def ShouldRun(self): 219 """Check host user groups and modules. 220 221 Returns: 222 Boolean: False if user is in all required groups and all modules 223 are reloaded. 224 """ 225 if not utils.IsSupportedPlatform(): 226 return False 227 228 return not (utils.CheckUserInGroups(constants.LIST_CF_USER_GROUPS) 229 and self._CheckLoadedModules( 230 _DICT_MODULES.get(self._GetProcessorType()))) 231 232 @staticmethod 233 def _GetProcessorType(): 234 """Get the processor type. 235 236 Returns: 237 The processor type of the host. e.g. amd, intel. 238 """ 239 lsmod_output = setup_common.CheckCmdOutput("lsmod", print_cmd=False) 240 current_modules = [r.split()[0] for r in lsmod_output.splitlines()] 241 if _KVM_AMD in current_modules: 242 return _AMD 243 return _INTEL 244 245 @staticmethod 246 def _CheckLoadedModules(module_list): 247 """Check if the modules are all in use. 248 249 Args: 250 module_list: The list of module name. 251 252 Returns: 253 True if all modules are in use. 254 """ 255 logger.info("Checking if modules are loaded: %s", module_list) 256 lsmod_output = setup_common.CheckCmdOutput("lsmod", print_cmd=False) 257 current_modules = [r.split()[0] for r in lsmod_output.splitlines()] 258 all_modules_present = True 259 for module in module_list: 260 if module not in current_modules: 261 logger.info("missing module: %s", module) 262 all_modules_present = False 263 return all_modules_present 264 265 def _Run(self): 266 """Setup host environment for local cuttlefish instance support.""" 267 # TODO: provide --uid args to let user use prefered username 268 username = getpass.getuser() 269 setup_cmds = _DICT_SETUP_CMDS.get(self._GetProcessorType()) 270 for group in constants.LIST_CF_USER_GROUPS: 271 setup_cmds.append("sudo usermod -aG %s % s" % (group, username)) 272 273 print("Below commands will be run:") 274 for setup_cmd in setup_cmds: 275 print(setup_cmd) 276 277 if self._ConfirmContinue(): 278 for setup_cmd in setup_cmds: 279 setup_common.CheckCmdOutput(setup_cmd, shell=True) 280 print("Host environment setup has done!") 281 282 @staticmethod 283 def _ConfirmContinue(): 284 """Ask user if they want to continue. 285 286 Returns: 287 True if user answer yes. 288 """ 289 answer_client = utils.InteractWithQuestion( 290 "\nEnter 'y' to continue, otherwise N or enter to exit: ", 291 utils.TextColors.WARNING) 292 return answer_client in constants.USER_ANSWER_YES 293