1# Copyright 2019 - The Android Open Source Project 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); 4# you may not use this file except in compliance with the License. 5# You may obtain a copy of the License at 6# 7# http://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, 11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12# See the License for the specific language governing permissions and 13# limitations under the License. 14r"""Pull entry point. 15 16This command will pull the log files from a remote instance for AVD troubleshooting. 17""" 18 19from __future__ import print_function 20import logging 21import os 22import tempfile 23 24from acloud import errors 25from acloud.internal import constants 26from acloud.internal.lib import utils 27from acloud.internal.lib.ssh import Ssh 28from acloud.internal.lib.ssh import IP 29from acloud.list import list as list_instances 30from acloud.public import config 31from acloud.public import report 32 33 34logger = logging.getLogger(__name__) 35 36# Black list for log files. 37_KERNEL = "kernel" 38_IMG_FILE_EXTENSION = ".img" 39 40 41def PullFileFromInstance(cfg, instance, file_name=None, no_prompts=False): 42 """Pull file from remote CF instance. 43 44 1. Download log files to temp folder. 45 2. If only one file selected, display it on screen. 46 3. Show the download folder for users. 47 48 Args: 49 cfg: AcloudConfig object. 50 instance: list.Instance() object. 51 file_name: String of file name. 52 no_prompts: Boolean, True to skip the prompt about file streaming. 53 54 Returns: 55 A Report instance. 56 """ 57 ssh = Ssh(ip=IP(ip=instance.ip), 58 user=constants.GCE_USER, 59 ssh_private_key_path=cfg.ssh_private_key_path, 60 extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel) 61 log_files = SelectLogFileToPull(ssh, file_name) 62 PullLogs(ssh, log_files, instance.name) 63 if len(log_files) == 1: 64 DisplayLog(ssh, log_files[0], no_prompts) 65 return report.Report(command="pull") 66 67 68def PullLogs(ssh, log_files, instance_name): 69 """Pull log files from remote instance. 70 71 Args: 72 ssh: Ssh object. 73 log_files: List of file path in the remote instance. 74 instance_name: The instance name that is used to create the download 75 folder. 76 77 Returns: 78 The download folder path. 79 """ 80 download_folder = _GetDownloadLogFolder(instance_name) 81 for log_file in log_files: 82 target_file = os.path.join(download_folder, os.path.basename(log_file)) 83 ssh.ScpPullFile(log_file, target_file) 84 _DisplayPullResult(download_folder) 85 return download_folder 86 87 88def DisplayLog(ssh, log_file, no_prompts=False): 89 """Display the content of log file in the screen. 90 91 Args: 92 ssh: Ssh object. 93 log_file: String of the log file path. 94 no_prompts: Boolean, True to skip all prompts. 95 """ 96 warning_msg = ("It will stream log to show on screen. If you want to stop " 97 "streaming, please press CTRL-C to exit.\nPress 'y' to show " 98 "log or read log by myself[y/N]:") 99 if no_prompts or utils.GetUserAnswerYes(warning_msg): 100 ssh.Run("tail -f -n +1 %s" % log_file, show_output=True) 101 102 103def _DisplayPullResult(download_folder): 104 """Display messages to user after pulling log files. 105 106 Args: 107 download_folder: String of download folder path. 108 """ 109 utils.PrintColorString( 110 "Download logs to folder: %s \nYou can look into log files to check " 111 "AVD issues." % download_folder) 112 113 114def _GetDownloadLogFolder(instance): 115 """Get the download log folder accroding to instance name. 116 117 Args: 118 instance: String, the name of instance. 119 120 Returns: 121 String of the download folder path. 122 """ 123 log_folder = os.path.join(tempfile.gettempdir(), instance) 124 if not os.path.exists(log_folder): 125 os.makedirs(log_folder) 126 logger.info("Download logs to folder: %s", log_folder) 127 return log_folder 128 129 130def SelectLogFileToPull(ssh, file_name=None): 131 """Select one log file or all log files to downalod. 132 133 1. Get all log file paths as selection list 134 2. Get user selected file path or user provided file name. 135 136 Args: 137 ssh: Ssh object. 138 file_name: String of file name. 139 140 Returns: 141 List of selected file paths. 142 143 Raises: 144 errors.CheckPathError: Can't find log files. 145 """ 146 log_files = GetAllLogFilePaths(ssh, constants.REMOTE_LOG_FOLDER) 147 if file_name: 148 file_path = os.path.join(constants.REMOTE_LOG_FOLDER, file_name) 149 if file_path in log_files: 150 return [file_path] 151 raise errors.CheckPathError("Can't find this log file(%s) from remote " 152 "instance." % file_path) 153 154 if len(log_files) == 1: 155 return log_files 156 157 if len(log_files) > 1: 158 print("Multiple log files detected, choose any one to proceed:") 159 return utils.GetAnswerFromList(log_files, enable_choose_all=True) 160 161 raise errors.CheckPathError("Can't find any log file in folder(%s) from " 162 "remote instance." % constants.REMOTE_LOG_FOLDER) 163 164 165def GetAllLogFilePaths(ssh, remote_log_folder): 166 """Get all file paths under the log folder. 167 168 Two rules to filter out files. 169 1. File name is "kernel". 170 2. File type is image "*.img". 171 172 Args: 173 ssh: Ssh object. 174 remote_log_folder: The path to the remote log folder. 175 176 Returns: 177 List of strings, the log file paths. 178 """ 179 files = utils.FindRemoteFiles(ssh, [remote_log_folder]) 180 log_files = list(files) 181 for file_path in files: 182 file_name = os.path.basename(file_path) 183 if file_name == _KERNEL or file_name.endswith(_IMG_FILE_EXTENSION): 184 log_files.remove(file_path) 185 return log_files 186 187 188def Run(args): 189 """Run pull. 190 191 After pull command executed, tool will return one Report instance. 192 If there is no instance to pull, just return empty Report. 193 194 Args: 195 args: Namespace object from argparse.parse_args. 196 197 Returns: 198 A Report instance. 199 """ 200 cfg = config.GetAcloudConfig(args) 201 if args.instance_name: 202 instance = list_instances.GetInstancesFromInstanceNames( 203 cfg, [args.instance_name]) 204 return PullFileFromInstance(cfg, instance[0], args.file_name, args.no_prompt) 205 return PullFileFromInstance(cfg, 206 list_instances.ChooseOneRemoteInstance(cfg), 207 args.file_name, 208 args.no_prompt) 209