• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
14 r"""Pull entry point.
15 
16 This command will pull the log files from a remote instance for AVD troubleshooting.
17 """
18 
19 from __future__ import print_function
20 import logging
21 import os
22 import tempfile
23 
24 from acloud import errors
25 from acloud.internal import constants
26 from acloud.internal.lib import utils
27 from acloud.internal.lib.ssh import Ssh
28 from acloud.internal.lib.ssh import IP
29 from acloud.list import list as list_instances
30 from acloud.public import config
31 from acloud.public import report
32 
33 
34 logger = logging.getLogger(__name__)
35 
36 # Black list for log files.
37 _KERNEL = "kernel"
38 _IMG_FILE_EXTENSION = ".img"
39 
40 
41 def 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 
68 def 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 
88 def 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 
103 def _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 
114 def _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 
130 def 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 
165 def 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 
188 def 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