You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
224 lines
6.9 KiB
224 lines
6.9 KiB
7 months ago
|
# Copyright 2019 - The Android Open Source Project
|
||
|
#
|
||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
# you may not use this file except in compliance with the License.
|
||
|
# You may obtain a copy of the License at
|
||
|
#
|
||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||
|
#
|
||
|
# Unless required by applicable law or agreed to in writing, software
|
||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
|
# See the License for the specific language governing permissions and
|
||
|
# limitations under the License.
|
||
|
r"""Pull entry point.
|
||
|
|
||
|
This command will pull the log files from a remote instance for AVD troubleshooting.
|
||
|
"""
|
||
|
|
||
|
from __future__ import print_function
|
||
|
import logging
|
||
|
import os
|
||
|
import subprocess
|
||
|
import tempfile
|
||
|
|
||
|
from acloud import errors
|
||
|
from acloud.internal import constants
|
||
|
from acloud.internal.lib import utils
|
||
|
from acloud.internal.lib.ssh import Ssh
|
||
|
from acloud.internal.lib.ssh import IP
|
||
|
from acloud.list import list as list_instances
|
||
|
from acloud.public import config
|
||
|
from acloud.public import report
|
||
|
|
||
|
|
||
|
logger = logging.getLogger(__name__)
|
||
|
|
||
|
_FIND_LOG_FILE_CMD = "find -L %s -type f" % constants.REMOTE_LOG_FOLDER
|
||
|
# Black list for log files.
|
||
|
_KERNEL = "kernel"
|
||
|
_IMG_FILE_EXTENSION = ".img"
|
||
|
|
||
|
|
||
|
def PullFileFromInstance(cfg, instance, file_name=None, no_prompts=False):
|
||
|
"""Pull file from remote CF instance.
|
||
|
|
||
|
1. Download log files to temp folder.
|
||
|
2. If only one file selected, display it on screen.
|
||
|
3. Show the download folder for users.
|
||
|
|
||
|
Args:
|
||
|
cfg: AcloudConfig object.
|
||
|
instance: list.Instance() object.
|
||
|
file_name: String of file name.
|
||
|
no_prompts: Boolean, True to skip the prompt about file streaming.
|
||
|
|
||
|
Returns:
|
||
|
A Report instance.
|
||
|
"""
|
||
|
ssh = Ssh(ip=IP(ip=instance.ip),
|
||
|
user=constants.GCE_USER,
|
||
|
ssh_private_key_path=cfg.ssh_private_key_path,
|
||
|
extra_args_ssh_tunnel=cfg.extra_args_ssh_tunnel)
|
||
|
log_files = SelectLogFileToPull(ssh, file_name)
|
||
|
download_folder = GetDownloadLogFolder(instance.name)
|
||
|
PullLogs(ssh, log_files, download_folder)
|
||
|
if len(log_files) == 1:
|
||
|
DisplayLog(ssh, log_files[0], no_prompts)
|
||
|
return report.Report(command="pull")
|
||
|
|
||
|
|
||
|
def PullLogs(ssh, log_files, download_folder):
|
||
|
"""Pull log files from remote instance.
|
||
|
|
||
|
Args:
|
||
|
ssh: Ssh object.
|
||
|
log_files: List of file path in the remote instance.
|
||
|
download_folder: String of download folder path.
|
||
|
"""
|
||
|
for log_file in log_files:
|
||
|
target_file = os.path.join(download_folder, os.path.basename(log_file))
|
||
|
ssh.ScpPullFile(log_file, target_file)
|
||
|
_DisplayPullResult(download_folder)
|
||
|
|
||
|
|
||
|
def DisplayLog(ssh, log_file, no_prompts=False):
|
||
|
"""Display the content of log file in the screen.
|
||
|
|
||
|
Args:
|
||
|
ssh: Ssh object.
|
||
|
log_file: String of the log file path.
|
||
|
no_prompts: Boolean, True to skip all prompts.
|
||
|
"""
|
||
|
warning_msg = ("It will stream log to show on screen. If you want to stop "
|
||
|
"streaming, please press CTRL-C to exit.\nPress 'y' to show "
|
||
|
"log or read log by myself[y/N]:")
|
||
|
if no_prompts or utils.GetUserAnswerYes(warning_msg):
|
||
|
ssh.Run("tail -f -n +1 %s" % log_file, show_output=True)
|
||
|
|
||
|
|
||
|
def _DisplayPullResult(download_folder):
|
||
|
"""Display messages to user after pulling log files.
|
||
|
|
||
|
Args:
|
||
|
download_folder: String of download folder path.
|
||
|
"""
|
||
|
utils.PrintColorString(
|
||
|
"Download logs to folder: %s \nYou can look into log files to check "
|
||
|
"AVD issues." % download_folder)
|
||
|
|
||
|
|
||
|
def GetDownloadLogFolder(instance):
|
||
|
"""Get the download log folder accroding to instance name.
|
||
|
|
||
|
Args:
|
||
|
instance: String, the name of instance.
|
||
|
|
||
|
Returns:
|
||
|
String of the download folder path.
|
||
|
"""
|
||
|
log_folder = os.path.join(tempfile.gettempdir(), instance)
|
||
|
if not os.path.exists(log_folder):
|
||
|
os.makedirs(log_folder)
|
||
|
logger.info("Download logs to folder: %s", log_folder)
|
||
|
return log_folder
|
||
|
|
||
|
|
||
|
def SelectLogFileToPull(ssh, file_name=None):
|
||
|
"""Select one log file or all log files to downalod.
|
||
|
|
||
|
1. Get all log file paths as selection list
|
||
|
2. Get user selected file path or user provided file name.
|
||
|
|
||
|
Args:
|
||
|
ssh: Ssh object.
|
||
|
file_name: String of file name.
|
||
|
|
||
|
Returns:
|
||
|
List of selected file paths.
|
||
|
|
||
|
Raises:
|
||
|
errors.CheckPathError: Can't find log files.
|
||
|
"""
|
||
|
log_files = GetAllLogFilePaths(ssh)
|
||
|
if file_name:
|
||
|
file_path = os.path.join(constants.REMOTE_LOG_FOLDER, file_name)
|
||
|
if file_path in log_files:
|
||
|
return [file_path]
|
||
|
raise errors.CheckPathError("Can't find this log file(%s) from remote "
|
||
|
"instance." % file_path)
|
||
|
|
||
|
if len(log_files) == 1:
|
||
|
return log_files
|
||
|
|
||
|
if len(log_files) > 1:
|
||
|
print("Multiple log files detected, choose any one to proceed:")
|
||
|
return utils.GetAnswerFromList(log_files, enable_choose_all=True)
|
||
|
|
||
|
raise errors.CheckPathError("Can't find any log file in folder(%s) from "
|
||
|
"remote instance." % constants.REMOTE_LOG_FOLDER)
|
||
|
|
||
|
|
||
|
def GetAllLogFilePaths(ssh):
|
||
|
"""Get the file paths of all log files.
|
||
|
|
||
|
Args:
|
||
|
ssh: Ssh object.
|
||
|
|
||
|
Returns:
|
||
|
List of all log file paths.
|
||
|
"""
|
||
|
ssh_cmd = [ssh.GetBaseCmd(constants.SSH_BIN), _FIND_LOG_FILE_CMD]
|
||
|
log_files = []
|
||
|
try:
|
||
|
files_output = utils.CheckOutput(" ".join(ssh_cmd), shell=True)
|
||
|
log_files = FilterLogfiles(files_output.splitlines())
|
||
|
except subprocess.CalledProcessError:
|
||
|
logger.debug("The folder(%s) that running launch_cvd doesn't exist.",
|
||
|
constants.REMOTE_LOG_FOLDER)
|
||
|
return log_files
|
||
|
|
||
|
|
||
|
def FilterLogfiles(files):
|
||
|
"""Filter some unused files.
|
||
|
|
||
|
Two rules to filter out files.
|
||
|
1. File name is "kernel".
|
||
|
2. File type is image "*.img".
|
||
|
|
||
|
Args:
|
||
|
files: List of file paths in the remote instance.
|
||
|
|
||
|
Return:
|
||
|
List of log files.
|
||
|
"""
|
||
|
log_files = list(files)
|
||
|
for file_path in files:
|
||
|
file_name = os.path.basename(file_path)
|
||
|
if file_name == _KERNEL or file_name.endswith(_IMG_FILE_EXTENSION):
|
||
|
log_files.remove(file_path)
|
||
|
return log_files
|
||
|
|
||
|
|
||
|
def Run(args):
|
||
|
"""Run pull.
|
||
|
|
||
|
After pull command executed, tool will return one Report instance.
|
||
|
If there is no instance to pull, just return empty Report.
|
||
|
|
||
|
Args:
|
||
|
args: Namespace object from argparse.parse_args.
|
||
|
|
||
|
Returns:
|
||
|
A Report instance.
|
||
|
"""
|
||
|
cfg = config.GetAcloudConfig(args)
|
||
|
if args.instance_name:
|
||
|
instance = list_instances.GetInstancesFromInstanceNames(
|
||
|
cfg, [args.instance_name])
|
||
|
return PullFileFromInstance(cfg, instance[0], args.file_name, args.no_prompt)
|
||
|
return PullFileFromInstance(cfg,
|
||
|
list_instances.ChooseOneRemoteInstance(cfg),
|
||
|
args.file_name,
|
||
|
args.no_prompt)
|