Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1 | # |
| 2 | # Copyright (C) 2017 The Android Open Source Project |
| 3 | # |
| 4 | # Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | # you may not use this file except in compliance with the License. |
| 6 | # You may obtain a copy of the License at |
| 7 | # |
| 8 | # http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | # |
| 10 | # Unless required by applicable law or agreed to in writing, software |
| 11 | # distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | # See the License for the specific language governing permissions and |
| 14 | # limitations under the License. |
| 15 | # |
| 16 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 17 | import cmd |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 18 | import ctypes |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 19 | import datetime |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 20 | import imp # Python v2 compatibility |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 21 | import importlib |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 22 | import logging |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 23 | import multiprocessing |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 24 | import os |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 25 | import queue |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 26 | import re |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 27 | import shutil |
| 28 | import socket |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 29 | import stat |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 30 | import subprocess |
| 31 | import sys |
| 32 | import threading |
Keun Soo Yim | 7d95b50 | 2018-01-21 21:53:54 -0800 | [diff] [blame] | 33 | import tempfile |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 34 | import time |
Keun Soo Yim | 7d95b50 | 2018-01-21 21:53:54 -0800 | [diff] [blame] | 35 | import zipfile |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 36 | |
| 37 | import httplib2 |
Keun Soo Yim | 594a332 | 2018-01-21 21:16:17 -0800 | [diff] [blame] | 38 | from googleapiclient import errors |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 39 | import urlparse |
| 40 | |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 41 | from google.protobuf import text_format |
| 42 | |
| 43 | from vti.test_serving.proto import TestLabConfigMessage_pb2 as LabCfgMsg |
| 44 | from vti.test_serving.proto import TestScheduleConfigMessage_pb2 as SchedCfgMsg |
| 45 | |
Yuexi Ma | 1dac95d | 2018-01-11 18:39:50 -0800 | [diff] [blame] | 46 | from host_controller.console_argument_parser import ConsoleArgumentError |
| 47 | from host_controller.console_argument_parser import ConsoleArgumentParser |
Yuexi Ma | a01c0c7 | 2018-01-11 19:28:04 -0800 | [diff] [blame] | 48 | from host_controller.command_processor import command_info |
Hsin-Yi Chen | 0259291 | 2018-01-17 19:44:17 +0800 | [diff] [blame] | 49 | from host_controller.command_processor import command_test |
Keun Soo Yim | d115f92 | 2018-01-05 09:41:27 -0800 | [diff] [blame] | 50 | from host_controller.tfc import request |
| 51 | from host_controller.build import build_flasher |
| 52 | from host_controller.build import build_provider |
| 53 | from host_controller.build import build_provider_ab |
| 54 | from host_controller.build import build_provider_gcs |
| 55 | from host_controller.build import build_provider_local_fs |
| 56 | from host_controller.tradefed import remote_operation |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 57 | from host_controller.utils.gsi import img_utils |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 58 | from vts.utils.python.common import cmd_utils |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 59 | |
| 60 | # The default Partner Android Build (PAB) public account. |
| 61 | # To obtain access permission, please reach out to Android partner engineering |
| 62 | # department of Google LLC. |
| 63 | _DEFAULT_ACCOUNT_ID = '543365459' |
| 64 | |
| 65 | # The default value for "flash --current". |
| 66 | _DEFAULT_FLASH_IMAGES = [ |
| 67 | build_provider.FULL_ZIPFILE, |
Keun Soo Yim | 7d95b50 | 2018-01-21 21:53:54 -0800 | [diff] [blame] | 68 | "bootloader.img", |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 69 | "boot.img", |
| 70 | "cache.img", |
Keun Soo Yim | 7d95b50 | 2018-01-21 21:53:54 -0800 | [diff] [blame] | 71 | "radio.img", |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 72 | "system.img", |
| 73 | "userdata.img", |
| 74 | "vbmeta.img", |
| 75 | "vendor.img", |
| 76 | ] |
| 77 | |
| 78 | # The environment variable for default serial numbers. |
| 79 | _ANDROID_SERIAL = "ANDROID_SERIAL" |
| 80 | |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 81 | DEVICE_STATUS_DICT = { |
| 82 | "unknown": 0, |
| 83 | "fastboot": 1, |
| 84 | "online": 2, |
| 85 | "ready": 3, |
| 86 | "use": 4, |
| 87 | "error": 5} |
| 88 | |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 89 | _SPL_DEFAULT_DAY = 5 |
| 90 | |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 91 | # Maximum number of leased jobs per host. |
| 92 | _MAX_LEASED_JOBS = 14 |
| 93 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 94 | |
Yuexi Ma | a01c0c7 | 2018-01-11 19:28:04 -0800 | [diff] [blame] | 95 | COMMAND_PROCESSORS = [ |
| 96 | command_info.CommandInfo, |
Hsin-Yi Chen | 0259291 | 2018-01-17 19:44:17 +0800 | [diff] [blame] | 97 | command_test.CommandTest, |
Yuexi Ma | a01c0c7 | 2018-01-11 19:28:04 -0800 | [diff] [blame] | 98 | ] |
| 99 | |
| 100 | |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 101 | def ExecJobOnProcessPoolWrapper(arg, **kwargs): |
| 102 | """Passes argument to Console.ExecjobOnProcessPool. |
| 103 | |
| 104 | need to invoke a global function from a process pool |
| 105 | since methods are not picklable. |
| 106 | |
| 107 | Args: |
| 108 | arg: tuple, consists of Console class instance from the main process |
| 109 | and a dict containing the leased job information. |
| 110 | kwargs: dict, currently not in use. |
| 111 | """ |
| 112 | return Console.ExecJobOnProcessPool(*arg, **kwargs) |
| 113 | |
| 114 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 115 | class Console(cmd.Cmd): |
| 116 | """The console for host controllers. |
| 117 | |
| 118 | Attributes: |
Yuexi Ma | a01c0c7 | 2018-01-11 19:28:04 -0800 | [diff] [blame] | 119 | command_processors: dict of string:BaseCommandProcessor, |
| 120 | map between command string and command processors. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 121 | device_image_info: dict containing info about device image files. |
| 122 | prompt: The prompt string at the beginning of each command line. |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 123 | test_result: dict containing info about the last test result. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 124 | test_suite_info: dict containing info about test suite package files. |
| 125 | tools_info: dict containing info about custom tool files. |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 126 | build_thread: dict containing threading.Thread instances(s) that |
| 127 | update build info regularly. |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 128 | scheduler_thread: dict containing threading.Thread instances(s) that |
| 129 | update configs regularly. |
| 130 | update_thread: threading.Thread that updates device state regularly. |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 131 | leased_job_process: Process. Fetches leased jobs from leased_job_queue |
| 132 | and maps them to process pool. |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 133 | leased_job_running: multiprocessing.Value, keeps running leased_job_process |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 134 | if equal to 1. |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 135 | leased_job_queue: multiprocessing.Queue. Jobs leased will be pushed form |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 136 | the main process, popped and executed on process pool. |
Keun Soo Yim | 4faa360 | 2018-01-11 15:00:08 -0800 | [diff] [blame] | 137 | _build_provider_pab: The BuildProviderPAB used to download artifacts. |
Keun Soo Yim | 164798b | 2018-02-10 17:40:53 -0800 | [diff] [blame^] | 138 | _vti_address: string, VTI service URI. |
Keun Soo Yim | a170308 | 2018-01-05 16:59:43 -0800 | [diff] [blame] | 139 | _vti_client: VtiEndpoewrClient, used to upload data to a test |
| 140 | scheduling infrastructure. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 141 | _tfc_client: The TfcClient that the host controllers connect to. |
| 142 | _hosts: A list of HostController objects. |
| 143 | _in_file: The input file object. |
| 144 | _out_file: The output file object. |
| 145 | _serials: A list of string where each string is a device serial. |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 146 | _config_parser: The parser for config command. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 147 | _copy_parser: The parser for copy command. |
| 148 | _device_parser: The parser for device command. |
| 149 | _fetch_parser: The parser for fetch command. |
| 150 | _flash_parser: The parser for flash command. |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 151 | _gsispl_parser: The parser for gsispl command. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 152 | _lease_parser: The parser for lease command. |
| 153 | _list_parser: The parser for list command. |
| 154 | _request_parser: The parser for request command. |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 155 | _upload_parser: The parser for upload command. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 156 | """ |
| 157 | |
| 158 | def __init__(self, |
Keun Soo Yim | a170308 | 2018-01-05 16:59:43 -0800 | [diff] [blame] | 159 | vti_endpoint_client, |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 160 | tfc, |
| 161 | pab, |
| 162 | host_controllers, |
Keun Soo Yim | 164798b | 2018-02-10 17:40:53 -0800 | [diff] [blame^] | 163 | vti_address=None, |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 164 | in_file=sys.stdin, |
| 165 | out_file=sys.stdout): |
| 166 | """Initializes the attributes and the parsers.""" |
| 167 | # cmd.Cmd is old-style class. |
| 168 | cmd.Cmd.__init__(self, stdin=in_file, stdout=out_file) |
| 169 | self._build_provider = {} |
| 170 | self._build_provider["pab"] = pab |
| 171 | self._build_provider[ |
| 172 | "local_fs"] = build_provider_local_fs.BuildProviderLocalFS() |
| 173 | self._build_provider["gcs"] = build_provider_gcs.BuildProviderGCS() |
| 174 | self._build_provider["ab"] = build_provider_ab.BuildProviderAB() |
Keun Soo Yim | a170308 | 2018-01-05 16:59:43 -0800 | [diff] [blame] | 175 | self._vti_endpoint_client = vti_endpoint_client |
Keun Soo Yim | 164798b | 2018-02-10 17:40:53 -0800 | [diff] [blame^] | 176 | self._vti_address = vti_address |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 177 | self._tfc_client = tfc |
| 178 | self._hosts = host_controllers |
| 179 | self._in_file = in_file |
| 180 | self._out_file = out_file |
| 181 | self.prompt = "> " |
Hsin-Yi Chen | c57a6cf | 2018-01-18 12:57:06 +0800 | [diff] [blame] | 182 | self.command_processors = {} |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 183 | self.device_image_info = {} |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 184 | self.test_result = {} |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 185 | self.test_suite_info = {} |
| 186 | self.tools_info = {} |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 187 | self.build_thread = {} |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 188 | self.schedule_thread = {} |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 189 | self.update_thread = None |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 190 | self.leased_job_process = None |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 191 | self.leased_job_running = None |
| 192 | self.leased_job_queue = None |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 193 | self.fetch_info = {} |
Keun Soo Yim | c00832e | 2018-02-10 17:59:46 -0800 | [diff] [blame] | 194 | self.test_results = {} |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 195 | |
Hsin-Yi Chen | 18ee53c | 2018-01-22 17:47:07 +0800 | [diff] [blame] | 196 | if _ANDROID_SERIAL in os.environ: |
| 197 | self._serials = [os.environ[_ANDROID_SERIAL]] |
| 198 | else: |
| 199 | self._serials = [] |
| 200 | |
Yuexi Ma | 52e7684 | 2018-01-11 10:36:55 -0800 | [diff] [blame] | 201 | self.InitCommandModuleParsers() |
Hsin-Yi Chen | c57a6cf | 2018-01-18 12:57:06 +0800 | [diff] [blame] | 202 | self.SetUpCommandProcessors() |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 203 | self.StartLeasedJobExecutionProcess() |
Yuexi Ma | 52e7684 | 2018-01-11 10:36:55 -0800 | [diff] [blame] | 204 | |
| 205 | def InitCommandModuleParsers(self): |
| 206 | """Init all console command modules""" |
| 207 | for name in dir(self): |
| 208 | if name.startswith('_Init') and name.endswith('Parser'): |
| 209 | attr_func = getattr(self, name) |
| 210 | if hasattr(attr_func, '__call__'): |
| 211 | attr_func() |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 212 | |
Hsin-Yi Chen | c57a6cf | 2018-01-18 12:57:06 +0800 | [diff] [blame] | 213 | def SetUpCommandProcessors(self): |
| 214 | """Sets up all command processors.""" |
| 215 | for command_processor in COMMAND_PROCESSORS: |
| 216 | cp = command_processor() |
| 217 | cp._SetUp(self) |
| 218 | do_text = "do_%s" % cp.command |
| 219 | help_text = "help_%s" % cp.command |
| 220 | setattr(self, do_text, cp._Run) |
| 221 | setattr(self, help_text, cp._Help) |
| 222 | self.command_processors[cp.command] = cp |
| 223 | |
| 224 | def TearDown(self): |
| 225 | """Removes all command processors.""" |
| 226 | for command_processor in self.command_processors.itervalues(): |
| 227 | command_processor._TearDown() |
| 228 | self.command_processors.clear() |
| 229 | |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 230 | def FormatString(self, format_string): |
| 231 | """Replaces variables with the values in the console's dictionaries. |
| 232 | |
| 233 | Args: |
| 234 | format_string: The string containing variables enclosed in {}. |
| 235 | |
| 236 | Returns: |
| 237 | The formatted string. |
| 238 | |
| 239 | Raises: |
| 240 | KeyError if a variable is not found in the dictionaries or the |
| 241 | value is empty. |
| 242 | """ |
| 243 | def ReplaceVariable(match): |
| 244 | name = match.group(1) |
| 245 | if name in ("build_id", "branch", "target"): |
| 246 | value = self.fetch_info[name] |
| 247 | elif name in ("result_zip", "suite_plan"): |
| 248 | value = self.test_result[name] |
Hyunwoo Ko | 3556b19 | 2018-02-08 19:29:12 +0900 | [diff] [blame] | 249 | elif name in ("timestamp"): |
| 250 | value = datetime.datetime.now().strftime("%Y%m%d-%H%M%S") |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 251 | else: |
| 252 | value = None |
| 253 | |
| 254 | if not value: |
| 255 | raise KeyError(name) |
| 256 | |
| 257 | return value |
| 258 | |
| 259 | return re.sub("{([^}]+)}", ReplaceVariable, format_string) |
| 260 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 261 | def _InitRequestParser(self): |
| 262 | """Initializes the parser for request command.""" |
| 263 | self._request_parser = ConsoleArgumentParser( |
| 264 | "request", "Send TFC a request to execute a command.") |
| 265 | self._request_parser.add_argument( |
| 266 | "--cluster", |
| 267 | required=True, |
| 268 | help="The cluster to which the request is submitted.") |
| 269 | self._request_parser.add_argument( |
| 270 | "--run-target", |
| 271 | required=True, |
| 272 | help="The target device to run the command.") |
| 273 | self._request_parser.add_argument( |
| 274 | "--user", |
| 275 | required=True, |
| 276 | help="The name of the user submitting the request.") |
| 277 | self._request_parser.add_argument( |
| 278 | "command", |
| 279 | metavar="COMMAND", |
| 280 | nargs="+", |
| 281 | help='The command to be executed. If the command contains ' |
| 282 | 'arguments starting with "-", place the command after ' |
| 283 | '"--" at end of line.') |
| 284 | |
| 285 | def ProcessScript(self, script_file_path): |
| 286 | """Processes a .py script file. |
| 287 | |
| 288 | A script file implements a function which emits a list of console |
| 289 | commands to execute. That function emits an empty list or None if |
| 290 | no more command needs to be processed. |
| 291 | |
| 292 | Args: |
| 293 | script_file_path: string, the path of a script file (.py file). |
| 294 | |
| 295 | Returns: |
| 296 | True if successful; False otherwise |
| 297 | """ |
| 298 | if not script_file_path.endswith(".py"): |
| 299 | print("Script file is not .py file: %s" % script_file_path) |
| 300 | return False |
| 301 | |
| 302 | script_module = imp.load_source('script_module', script_file_path) |
| 303 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 304 | commands = script_module.EmitConsoleCommands() |
| 305 | if commands: |
| 306 | for command in commands: |
| 307 | self.onecmd(command) |
| 308 | return True |
| 309 | |
Keun Soo Yim | a8ce7de | 2018-01-08 15:18:03 -0800 | [diff] [blame] | 310 | def ProcessConfigurableScript(self, script_file_path, **kwargs): |
| 311 | """Processes a .py script file. |
| 312 | |
| 313 | A script file implements a function which emits a list of console |
| 314 | commands to execute. That function emits an empty list or None if |
| 315 | no more command needs to be processed. |
| 316 | |
| 317 | Args: |
| 318 | script_file_path: string, the path of a script file (.py file). |
| 319 | kwargs: extra args for the interface function defined in |
| 320 | the script file. |
| 321 | |
| 322 | Returns: |
| 323 | True if successful; False otherwise |
| 324 | """ |
Keun Soo Yim | 4faa360 | 2018-01-11 15:00:08 -0800 | [diff] [blame] | 325 | if script_file_path and "." not in script_file_path: |
| 326 | script_file_path += ".py" |
| 327 | |
Keun Soo Yim | a8ce7de | 2018-01-08 15:18:03 -0800 | [diff] [blame] | 328 | if not script_file_path.endswith(".py"): |
| 329 | print("Script file is not .py file: %s" % script_file_path) |
| 330 | return False |
| 331 | |
| 332 | script_module = imp.load_source('script_module', script_file_path) |
| 333 | |
Hyunwoo Ko | d0f8b91 | 2018-02-07 17:36:00 +0900 | [diff] [blame] | 334 | commands = script_module.EmitConsoleCommands(**kwargs) |
Keun Soo Yim | a8ce7de | 2018-01-08 15:18:03 -0800 | [diff] [blame] | 335 | if commands: |
| 336 | for command in commands: |
| 337 | self.onecmd(command) |
Hyunwoo Ko | d6e7d68 | 2018-02-05 17:01:48 +0900 | [diff] [blame] | 338 | else: |
| 339 | return False |
Keun Soo Yim | a8ce7de | 2018-01-08 15:18:03 -0800 | [diff] [blame] | 340 | return True |
| 341 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 342 | def do_request(self, line): |
| 343 | """Sends TFC a request to execute a command.""" |
| 344 | args = self._request_parser.ParseLine(line) |
| 345 | req = request.Request( |
| 346 | cluster=args.cluster, |
| 347 | command_line=" ".join(args.command), |
| 348 | run_target=args.run_target, |
| 349 | user=args.user) |
| 350 | self._tfc_client.NewRequest(req) |
| 351 | |
| 352 | def help_request(self): |
| 353 | """Prints help message for request command.""" |
| 354 | self._request_parser.print_help(self._out_file) |
| 355 | |
| 356 | def _InitListParser(self): |
| 357 | """Initializes the parser for list command.""" |
| 358 | self._list_parser = ConsoleArgumentParser( |
| 359 | "list", "Show information about the hosts.") |
| 360 | self._list_parser.add_argument( |
| 361 | "--host", type=int, help="The index of the host.") |
| 362 | self._list_parser.add_argument( |
| 363 | "type", |
| 364 | choices=("hosts", "devices"), |
| 365 | help="The type of the shown objects.") |
| 366 | |
| 367 | def _Print(self, string): |
| 368 | """Prints a string and a new line character. |
| 369 | |
| 370 | Args: |
| 371 | string: The string to be printed. |
| 372 | """ |
| 373 | self._out_file.write(string + "\n") |
| 374 | |
| 375 | def do_list(self, line): |
| 376 | """Shows information about the hosts.""" |
| 377 | args = self._list_parser.ParseLine(line) |
| 378 | if args.host is None: |
| 379 | hosts = enumerate(self._hosts) |
| 380 | else: |
| 381 | hosts = [(args.host, self._hosts[args.host])] |
| 382 | if args.type == "hosts": |
| 383 | self._PrintHosts(self._hosts) |
| 384 | elif args.type == "devices": |
| 385 | for ind, host in hosts: |
| 386 | devices = host.ListDevices() |
| 387 | self._Print("[%3d] %s" % (ind, host.hostname)) |
| 388 | self._PrintDevices(devices) |
| 389 | |
| 390 | def help_list(self): |
| 391 | """Prints help message for list command.""" |
| 392 | self._list_parser.print_help(self._out_file) |
| 393 | |
| 394 | def _PrintHosts(self, hosts): |
| 395 | """Shows a list of host controllers. |
| 396 | |
| 397 | Args: |
| 398 | hosts: A list of HostController objects. |
| 399 | """ |
| 400 | self._Print("index name") |
| 401 | for ind, host in enumerate(hosts): |
| 402 | self._Print("[%3d] %s" % (ind, host.hostname)) |
| 403 | |
| 404 | def _PrintDevices(self, devices): |
| 405 | """Shows a list of devices. |
| 406 | |
| 407 | Args: |
| 408 | devices: A list of DeviceInfo objects. |
| 409 | """ |
| 410 | attr_names = ("device_serial", "state", "run_target", "build_id", |
| 411 | "sdk_version", "stub") |
| 412 | self._PrintObjects(devices, attr_names) |
| 413 | |
| 414 | def _PrintObjects(self, objects, attr_names): |
| 415 | """Shows objects as a table. |
| 416 | |
| 417 | Args: |
| 418 | object: The objects to be shown, one object in a row. |
| 419 | attr_names: The attributes to be shown, one attribute in a column. |
| 420 | """ |
| 421 | width = [len(name) for name in attr_names] |
| 422 | rows = [attr_names] |
| 423 | for dev_info in objects: |
| 424 | attrs = [ |
| 425 | _ToPrintString(getattr(dev_info, name, "")) |
| 426 | for name in attr_names |
| 427 | ] |
| 428 | rows.append(attrs) |
| 429 | for index, attr in enumerate(attrs): |
| 430 | width[index] = max(width[index], len(attr)) |
| 431 | |
| 432 | for row in rows: |
| 433 | self._Print(" ".join( |
| 434 | attr.ljust(width[index]) for index, attr in enumerate(row))) |
| 435 | |
| 436 | def _InitLeaseParser(self): |
| 437 | """Initializes the parser for lease command.""" |
| 438 | self._lease_parser = ConsoleArgumentParser( |
| 439 | "lease", "Make a host lease command tasks from TFC.") |
| 440 | self._lease_parser.add_argument( |
| 441 | "--host", type=int, help="The index of the host.") |
| 442 | |
| 443 | def do_lease(self, line): |
| 444 | """Makes a host lease command tasks from TFC.""" |
| 445 | args = self._lease_parser.ParseLine(line) |
| 446 | if args.host is None: |
| 447 | if len(self._hosts) > 1: |
| 448 | raise ConsoleArgumentError("More than one hosts.") |
| 449 | args.host = 0 |
| 450 | tasks = self._hosts[args.host].LeaseCommandTasks() |
| 451 | self._PrintTasks(tasks) |
| 452 | |
| 453 | def help_lease(self): |
| 454 | """Prints help message for lease command.""" |
| 455 | self._lease_parser.print_help(self._out_file) |
| 456 | |
| 457 | def _InitFetchParser(self): |
| 458 | """Initializes the parser for fetch command.""" |
| 459 | self._fetch_parser = ConsoleArgumentParser("fetch", |
| 460 | "Fetch a build artifact.") |
| 461 | self._fetch_parser.add_argument( |
| 462 | '--type', |
| 463 | default='pab', |
| 464 | choices=('local_fs', 'gcs', 'pab', 'ab'), |
| 465 | help='Build provider type') |
| 466 | self._fetch_parser.add_argument( |
| 467 | '--method', |
| 468 | default='GET', |
| 469 | choices=('GET', 'POST'), |
| 470 | help='Method for fetching') |
| 471 | self._fetch_parser.add_argument( |
| 472 | "--path", # required for local_fs |
| 473 | help="The path of a local directory which keeps the artifacts.") |
| 474 | self._fetch_parser.add_argument( |
| 475 | "--branch", # required for pab |
| 476 | help="Branch to grab the artifact from.") |
| 477 | self._fetch_parser.add_argument( |
| 478 | "--target", # required for pab |
| 479 | help="Target product to grab the artifact from.") |
| 480 | # TODO(lejonathan): find a way to not specify this? |
| 481 | self._fetch_parser.add_argument( |
| 482 | "--account_id", |
| 483 | default=_DEFAULT_ACCOUNT_ID, |
| 484 | help="Partner Android Build account_id to use.") |
| 485 | self._fetch_parser.add_argument( |
| 486 | '--build_id', |
| 487 | default='latest', |
| 488 | help='Build ID to use default latest.') |
| 489 | self._fetch_parser.add_argument( |
| 490 | "--artifact_name", # required for pab |
| 491 | help= |
| 492 | "Name of the artifact to be fetched. {id} replaced with build id.") |
| 493 | self._fetch_parser.add_argument( |
Keun Soo Yim | 8a68d5b | 2018-01-21 21:36:41 -0800 | [diff] [blame] | 494 | "--userinfo-file", |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 495 | help= |
| 496 | "Location of file containing email and password, if using POST.") |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 497 | self._fetch_parser.add_argument( |
| 498 | "--noauth_local_webserver", |
| 499 | default=False, |
| 500 | type=bool, |
| 501 | help="True to not use a local webserver for authentication.") |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 502 | |
| 503 | def do_fetch(self, line): |
| 504 | """Makes the host download a build artifact from PAB.""" |
| 505 | args = self._fetch_parser.ParseLine(line) |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 506 | |
| 507 | if args.type not in self._build_provider: |
| 508 | print("ERROR: uninitialized fetch type %s" % args.type) |
| 509 | return |
| 510 | |
| 511 | provider = self._build_provider[args.type] |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 512 | if args.type == "pab": |
| 513 | # do we want this somewhere else? No harm in doing multiple times |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 514 | provider.Authenticate(args.userinfo_file, |
| 515 | args.noauth_local_webserver) |
Keun Soo Yim | 12872ff | 2018-01-09 15:05:42 -0800 | [diff] [blame] | 516 | (device_images, test_suites, |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 517 | fetch_environment, _) = provider.GetArtifact( |
| 518 | account_id=args.account_id, |
| 519 | branch=args.branch, |
| 520 | target=args.target, |
| 521 | artifact_name=args.artifact_name, |
| 522 | build_id=args.build_id, |
| 523 | method=args.method) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 524 | self.fetch_info["build_id"] = fetch_environment["build_id"] |
| 525 | elif args.type == "local_fs": |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 526 | device_images, test_suites = provider.Fetch(args.path) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 527 | self.fetch_info["build_id"] = None |
| 528 | elif args.type == "gcs": |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 529 | device_images, test_suites, tools = provider.Fetch(args.path) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 530 | self.fetch_info["build_id"] = None |
| 531 | elif args.type == "ab": |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 532 | device_images, test_suites, fetch_environment = provider.Fetch( |
| 533 | branch=args.branch, |
| 534 | target=args.target, |
| 535 | artifact_name=args.artifact_name, |
| 536 | build_id=args.build_id) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 537 | self.fetch_info["build_id"] = fetch_environment["build_id"] |
| 538 | else: |
| 539 | print("ERROR: unknown fetch type %s" % args.type) |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 540 | return |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 541 | |
| 542 | self.fetch_info["branch"] = args.branch |
| 543 | self.fetch_info["target"] = args.target |
| 544 | |
| 545 | self.device_image_info.update(device_images) |
| 546 | self.test_suite_info.update(test_suites) |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 547 | self.tools_info.update(provider.GetAdditionalFile()) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 548 | |
| 549 | if self.device_image_info: |
| 550 | logging.info("device images:\n%s", "\n".join( |
| 551 | image + ": " + path |
| 552 | for image, path in self.device_image_info.iteritems())) |
| 553 | if self.test_suite_info: |
| 554 | logging.info("test suites:\n%s", "\n".join( |
| 555 | suite + ": " + path |
| 556 | for suite, path in self.test_suite_info.iteritems())) |
Hsin-Yi Chen | 2f3cb9a | 2018-01-02 16:05:18 +0800 | [diff] [blame] | 557 | if self.tools_info: |
| 558 | logging.info("additional files:\n%s", "\n".join( |
| 559 | rel_path + ": " + full_path |
| 560 | for rel_path, full_path in self.tools_info.iteritems())) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 561 | |
| 562 | def help_fetch(self): |
| 563 | """Prints help message for fetch command.""" |
| 564 | self._fetch_parser.print_help(self._out_file) |
| 565 | |
| 566 | def DownloadTestResources(self, request_id): |
| 567 | """Download all of the test resources for a TFC request id. |
| 568 | |
| 569 | Args: |
| 570 | request_id: int, TFC request id |
| 571 | """ |
| 572 | resources = self._tfc_client.TestResourceList(request_id) |
| 573 | for resource in resources: |
| 574 | self.DownloadTestResource(resource['url']) |
| 575 | |
| 576 | def DownloadTestResource(self, url): |
| 577 | """Download a test resource with build provider, given a url. |
| 578 | |
| 579 | Args: |
| 580 | url: a resource locator (not necessarily HTTP[s]) |
| 581 | with the scheme specifying the build provider. |
| 582 | """ |
| 583 | parsed = urlparse.urlparse(url) |
| 584 | path = (parsed.netloc + parsed.path).split('/') |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 585 | if parsed.scheme == "pab": |
| 586 | if len(path) != 5: |
| 587 | print("Invalid pab resource locator: %s" % url) |
| 588 | return |
| 589 | account_id, branch, target, build_id, artifact_name = path |
| 590 | cmd = ("fetch" |
| 591 | " --type=pab" |
| 592 | " --account_id=%s" |
| 593 | " --branch=%s" |
| 594 | " --target=%s" |
| 595 | " --build_id=%s" |
| 596 | " --artifact_name=%s") % (account_id, branch, target, |
| 597 | build_id, artifact_name) |
| 598 | self.onecmd(cmd) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 599 | elif parsed.scheme == "ab": |
| 600 | if len(path) != 4: |
| 601 | print("Invalid ab resource locator: %s" % url) |
| 602 | return |
| 603 | branch, target, build_id, artifact_name = path |
| 604 | cmd = ("fetch" |
| 605 | "--type=ab" |
| 606 | " --branch=%s" |
| 607 | " --target=%s" |
| 608 | " --build_id=%s" |
| 609 | " --artifact_name=%s") % (branch, target, build_id, |
| 610 | artifact_name) |
| 611 | self.onecmd(cmd) |
| 612 | elif parsed.scheme == gcs: |
| 613 | cmd = "fetch --type=gcs --path=%s" % url |
| 614 | self.onecmd(cmd) |
| 615 | else: |
| 616 | print "Invalid URL: %s" % url |
| 617 | |
| 618 | def _InitFlashParser(self): |
| 619 | """Initializes the parser for flash command.""" |
| 620 | self._flash_parser = ConsoleArgumentParser("flash", |
| 621 | "Flash images to a device.") |
| 622 | self._flash_parser.add_argument( |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 623 | "--image", |
| 624 | help=("The file name of an image to flash." |
| 625 | " Used to flash a single image.")) |
| 626 | self._flash_parser.add_argument( |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 627 | "--current", |
| 628 | metavar="PARTITION_IMAGE", |
| 629 | nargs="*", |
| 630 | type=lambda x: x.split("="), |
| 631 | help="The partitions and images to be flashed. The format is " |
| 632 | "<partition>=<image>. If PARTITION_IMAGE list is empty, " |
| 633 | "currently fetched " + ", ".join(_DEFAULT_FLASH_IMAGES) + |
| 634 | " will be flashed.") |
| 635 | self._flash_parser.add_argument( |
| 636 | "--serial", default="", help="Serial number for device.") |
| 637 | self._flash_parser.add_argument( |
| 638 | "--build_dir", |
| 639 | help="Directory containing build images to be flashed.") |
| 640 | self._flash_parser.add_argument( |
| 641 | "--gsi", help="Path to generic system image") |
| 642 | self._flash_parser.add_argument( |
| 643 | "--vbmeta", help="Path to vbmeta image") |
| 644 | self._flash_parser.add_argument( |
| 645 | "--flasher_type", |
| 646 | default="fastboot", |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 647 | help="Flasher type. Valid arguments are \"fastboot\", \"custom\", " |
| 648 | "and full module name followed by class name. The class must " |
| 649 | "inherit build_flasher.BuildFlasher, and implement " |
| 650 | "__init__(serial, flasher_path) and " |
| 651 | "Flash(device_images, additional_files, *flasher_args).") |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 652 | self._flash_parser.add_argument( |
Keun Soo Yim | feb66eb | 2018-01-08 15:15:09 -0800 | [diff] [blame] | 653 | "--flasher_path", |
| 654 | default=None, |
| 655 | help="Path to a flasher binary") |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 656 | self._flash_parser.add_argument( |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 657 | "flasher_args", |
| 658 | metavar="ARGUMENTS", |
| 659 | nargs="*", |
| 660 | help="The arguments passed to the flasher binary. If any argument " |
| 661 | "starts with \"-\", place all of them after \"--\" at end of " |
| 662 | "line.") |
| 663 | self._flash_parser.add_argument( |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 664 | "--reboot_mode", |
| 665 | default="bootloader", |
| 666 | choices=("bootloader", "download"), |
| 667 | help="Reboot device to bootloader/download mode") |
| 668 | self._flash_parser.add_argument( |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 669 | "--repackage", |
| 670 | default="tar.md5", |
| 671 | choices=("tar.md5"), |
| 672 | help="Repackage artifacts into given format before flashing.") |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 673 | self._flash_parser.add_argument( |
| 674 | "--wait-for-boot", |
| 675 | default="true", |
| 676 | help="false to not wait for devie booting.") |
| 677 | self._flash_parser.add_argument( |
| 678 | "--reboot", |
| 679 | default="false", |
| 680 | help="true to reboot the device(s).") |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 681 | |
| 682 | def do_flash(self, line): |
| 683 | """Flash GSI or build images to a device connected with ADB.""" |
| 684 | args = self._flash_parser.ParseLine(line) |
| 685 | |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 686 | # path |
| 687 | if (self.tools_info is not None and |
| 688 | args.flasher_path in self.tools_info): |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 689 | flasher_path = self.tools_info[args.flasher_path] |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 690 | elif args.flasher_path: |
| 691 | flasher_path = args.flasher_path |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 692 | else: |
Keun Soo Yim | feb66eb | 2018-01-08 15:15:09 -0800 | [diff] [blame] | 693 | flasher_path = "" |
Hyunwoo Ko | d6e7d68 | 2018-02-05 17:01:48 +0900 | [diff] [blame] | 694 | if os.path.exists(flasher_path): |
| 695 | flasher_mode = os.stat(flasher_path).st_mode |
| 696 | os.chmod(flasher_path, |
| 697 | flasher_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 698 | |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 699 | # serial numbers |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 700 | if args.serial: |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 701 | flasher_serials = [args.serial] |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 702 | elif self._serials: |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 703 | flasher_serials = self._serials |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 704 | else: |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 705 | flasher_serials = [""] |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 706 | |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 707 | # images |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 708 | if args.image: |
| 709 | partition_image = {} |
| 710 | partition_image[args.image] = self.device_image_info[args.image] |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 711 | else: |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 712 | if args.current: |
| 713 | partition_image = dict((partition, self.device_image_info[image]) |
| 714 | for partition, image in args.current) |
| 715 | else: |
| 716 | partition_image = dict((image.rsplit(".img", 1)[0], |
| 717 | self.device_image_info[image]) |
| 718 | for image in _DEFAULT_FLASH_IMAGES |
| 719 | if image in self.device_image_info) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 720 | |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 721 | # type |
| 722 | if args.flasher_type in ("fastboot", "custom"): |
| 723 | flasher_class = build_flasher.BuildFlasher |
| 724 | else: |
| 725 | class_path = args.flasher_type.rsplit(".", 1) |
| 726 | flasher_module = importlib.import_module(class_path[0]) |
| 727 | flasher_class = getattr(flasher_module, class_path[1]) |
| 728 | if not issubclass(flasher_class, build_flasher.BuildFlasher): |
| 729 | raise TypeError("%s is not a subclass of BuildFlasher." % |
| 730 | class_path[1]) |
| 731 | |
| 732 | flashers = [flasher_class(s, flasher_path) for s in flasher_serials] |
| 733 | |
| 734 | # Can be parallelized as long as that's proven reliable. |
| 735 | for flasher in flashers: |
| 736 | if args.flasher_type == "fastboot": |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 737 | if args.image is not None: |
| 738 | flasher.FlashImage( |
| 739 | partition_image, |
| 740 | True if args.reboot == "true" else False) |
| 741 | elif args.current is not None: |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 742 | flasher.Flash(partition_image) |
| 743 | else: |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 744 | if args.gsi is None and args.build_dir is None: |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 745 | self._flash_parser.error( |
Hsin-Yi Chen | 9e27bfc | 2017-12-28 17:16:19 +0800 | [diff] [blame] | 746 | "Nothing requested: " |
| 747 | "specify --gsi or --build_dir") |
| 748 | if args.build_dir is not None: |
| 749 | flasher.Flashall(args.build_dir) |
| 750 | if args.gsi is not None: |
| 751 | flasher.FlashGSI(args.gsi, args.vbmeta) |
| 752 | elif args.flasher_type == "custom": |
| 753 | if flasher_path is not None: |
| 754 | if args.repackage is not None: |
| 755 | flasher.RepackageArtifacts( |
| 756 | self.device_image_info, args.repackage) |
| 757 | flasher.FlashUsingCustomBinary( |
| 758 | self.device_image_info, args.reboot_mode, |
| 759 | args.flasher_args, 300) |
| 760 | else: |
| 761 | self._flash_parser.error( |
| 762 | "Please specify the path to custom flash tool.") |
| 763 | else: |
| 764 | flasher.Flash( |
| 765 | partition_image, self.tools_info, *args.flasher_args) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 766 | |
Keun Soo Yim | 55c6d05 | 2018-01-27 16:33:07 -0800 | [diff] [blame] | 767 | if args.wait_for_boot == "true": |
| 768 | for flasher in flashers: |
| 769 | flasher.WaitForDevice() |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 770 | |
| 771 | def help_flash(self): |
| 772 | """Prints help message for flash command.""" |
| 773 | self._flash_parser.print_help(self._out_file) |
| 774 | |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 775 | def _InitBuildParser(self): |
| 776 | """Initializes the parser for build command.""" |
| 777 | self._build_parser = ConsoleArgumentParser( |
| 778 | "build", "Specifies branches and targets to monitor.") |
| 779 | self._build_parser.add_argument( |
| 780 | "--update", |
| 781 | choices=("single", "start", "stop", "list"), |
| 782 | default="start", |
| 783 | help="Update build info") |
| 784 | self._build_parser.add_argument( |
| 785 | "--id", |
| 786 | default=None, |
| 787 | help="session ID only required for 'stop' update command") |
| 788 | self._build_parser.add_argument( |
| 789 | "--interval", |
| 790 | type=int, |
| 791 | default=30, |
| 792 | help="Interval (seconds) to repeat build update.") |
| 793 | self._build_parser.add_argument( |
| 794 | "--artifact-type", |
| 795 | choices=("device", "gsi", "test"), |
| 796 | default="device", |
| 797 | help="The type of an artifact to update") |
| 798 | self._build_parser.add_argument( |
| 799 | "--branch", |
| 800 | required=True, |
| 801 | help="Branch to grab the artifact from.") |
| 802 | self._build_parser.add_argument( |
| 803 | "--target", |
| 804 | required=True, |
| 805 | help="a comma-separate list of build target product(s).") |
| 806 | self._build_parser.add_argument( |
| 807 | "--account_id", |
| 808 | default=_DEFAULT_ACCOUNT_ID, |
| 809 | help="Partner Android Build account_id to use.") |
Keun Soo Yim | b691267 | 2018-01-21 21:44:02 -0800 | [diff] [blame] | 810 | self._build_parser.add_argument( |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 811 | "--method", |
| 812 | default="GET", |
| 813 | choices=("GET", "POST"), |
| 814 | help="Method for getting build information") |
| 815 | self._build_parser.add_argument( |
| 816 | "--userinfo-file", |
| 817 | help= |
| 818 | "Location of file containing email and password, if using POST.") |
| 819 | self._build_parser.add_argument( |
Keun Soo Yim | b691267 | 2018-01-21 21:44:02 -0800 | [diff] [blame] | 820 | "--noauth_local_webserver", |
| 821 | default=False, |
| 822 | type=bool, |
| 823 | help="True to not use a local webserver for authentication.") |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 824 | |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 825 | def UpdateBuild(self, account_id, branch, targets, artifact_type, method, |
| 826 | userinfo_file, noauth_local_webserver): |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 827 | """Updates the build state. |
| 828 | |
| 829 | Args: |
| 830 | account_id: string, Partner Android Build account_id to use. |
| 831 | branch: string, branch to grab the artifact from. |
| 832 | targets: string, a comma-separate list of build target product(s). |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 833 | artifact_type: string, artifact type (`device`, 'gsi' or `test'). |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 834 | method: string, method for getting build information. |
| 835 | userinfo_file: string, the path of a file containing email and |
| 836 | password (if method == POST). |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 837 | noauth_local_webserver: boolean, True to not use a local websever. |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 838 | """ |
| 839 | builds = [] |
| 840 | |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 841 | self._build_provider["pab"].Authenticate( |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 842 | userinfo_file=userinfo_file, |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 843 | noauth_local_webserver=noauth_local_webserver) |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 844 | for target in targets.split(","): |
| 845 | listed_builds = self._build_provider[ |
| 846 | "pab"].GetBuildList( |
| 847 | account_id=account_id, |
| 848 | branch=branch, |
| 849 | target=target, |
| 850 | page_token="", |
| 851 | max_results=100, |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 852 | method=method) |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 853 | |
| 854 | for listed_build in listed_builds: |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 855 | if method == "GET": |
| 856 | if "successful" in listed_build: |
| 857 | if listed_build["successful"]: |
| 858 | build = {} |
| 859 | build["manifest_branch"] = branch |
| 860 | build["build_id"] = listed_build["build_id"] |
| 861 | if "-" in target: |
| 862 | build["build_target"], build["build_type"] = target.split("-") |
| 863 | else: |
| 864 | build["build_target"] = target |
| 865 | build["build_type"] = "" |
| 866 | build["artifact_type"] = artifact_type |
| 867 | build["artifacts"] = [] |
| 868 | builds.append(build) |
| 869 | else: |
| 870 | print("Error: listed_build %s" % listed_build) |
| 871 | else: # POST |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 872 | build = {} |
| 873 | build["manifest_branch"] = branch |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 874 | build["build_id"] = listed_build[u"1"] |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 875 | if "-" in target: |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 876 | (build["build_target"], |
| 877 | build["build_type"]) = target.split("-") |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 878 | else: |
| 879 | build["build_target"] = target |
| 880 | build["build_type"] = "" |
| 881 | build["artifact_type"] = artifact_type |
| 882 | build["artifacts"] = [] |
| 883 | builds.append(build) |
| 884 | self._vti_endpoint_client.UploadBuildInfo(builds) |
| 885 | |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 886 | def UpdateBuildLoop(self, account_id, branch, target, artifact_type, method, |
| 887 | userinfo_file, noauth_local_webserver, update_interval): |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 888 | """Regularly updates the build information. |
| 889 | |
| 890 | Args: |
| 891 | account_id: string, Partner Android Build account_id to use. |
| 892 | branch: string, branch to grab the artifact from. |
| 893 | targets: string, a comma-separate list of build target product(s). |
| 894 | artifact_type: string, artifcat type (`device`, 'gsi' or `test). |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 895 | method: string, method for getting build information. |
| 896 | userinfo_file: string, the path of a file containing email and |
| 897 | password (if method == POST). |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 898 | noauth_local_webserver: boolean, True to not use a local websever. |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 899 | update_interval: int, number of seconds before repeating |
| 900 | """ |
| 901 | thread = threading.currentThread() |
| 902 | while getattr(thread, 'keep_running', True): |
| 903 | try: |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 904 | self.UpdateBuild(account_id, branch, target, |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 905 | artifact_type, method, userinfo_file, |
| 906 | noauth_local_webserver) |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 907 | except (socket.error, remote_operation.RemoteOperationException, |
| 908 | httplib2.HttpLib2Error, errors.HttpError) as e: |
| 909 | logging.exception(e) |
| 910 | time.sleep(update_interval) |
| 911 | |
| 912 | def do_build(self, line): |
| 913 | """Updates build info.""" |
| 914 | args = self._build_parser.ParseLine(line) |
| 915 | if args.update == "single": |
Jongmok Hong | 95bc86c | 2018-01-10 18:36:43 +0900 | [diff] [blame] | 916 | self.UpdateBuild( |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 917 | args.account_id, |
| 918 | args.branch, |
| 919 | args.target, |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 920 | args.artifact_type, |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 921 | args.method, |
| 922 | args.userinfo_file, |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 923 | args.noauth_local_webserver) |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 924 | elif args.update == "list": |
| 925 | print("Running build update sessions:") |
| 926 | for id in self.build_thread: |
| 927 | print(" ID %d", id) |
| 928 | elif args.update == "start": |
| 929 | if args.interval <= 0: |
| 930 | raise ConsoleArgumentError( |
| 931 | "update interval must be positive") |
| 932 | # do not allow user to create new |
| 933 | # thread if one is currently running |
| 934 | if args.id is None: |
| 935 | if not self.build_thread: |
| 936 | args.id = 1 |
| 937 | else: |
| 938 | args.id = max(self.build_thread) + 1 |
| 939 | else: |
| 940 | args.id = int(args.id) |
| 941 | if args.id in self.build_thread and not hasattr( |
| 942 | self.build_thread[args.id], 'keep_running'): |
| 943 | print( |
| 944 | 'build update (session ID: %s) already running. ' |
| 945 | 'run build --update stop first.' % args.id |
| 946 | ) |
| 947 | return |
| 948 | self.build_thread[args.id] = threading.Thread( |
| 949 | target=self.UpdateBuildLoop, |
| 950 | args=( |
| 951 | args.account_id, |
| 952 | args.branch, |
| 953 | args.target, |
| 954 | args.artifact_type, |
Keun Soo Yim | a981ec0 | 2018-01-21 21:48:58 -0800 | [diff] [blame] | 955 | args.method, |
| 956 | args.userinfo_file, |
Keun Soo Yim | 54f71d1 | 2018-01-21 21:40:11 -0800 | [diff] [blame] | 957 | args.noauth_local_webserver, |
Keun Soo Yim | 0e3b008 | 2018-01-08 15:10:02 -0800 | [diff] [blame] | 958 | args.interval, )) |
| 959 | self.build_thread[args.id].daemon = True |
| 960 | self.build_thread[args.id].start() |
| 961 | elif args.update == "stop": |
| 962 | if args.id is None: |
| 963 | print("--id must be set for stop") |
| 964 | else: |
| 965 | self.build_thread[int(args.id)].keep_running = False |
| 966 | |
| 967 | def help_build(self): |
| 968 | """Prints help message for build command.""" |
| 969 | self._build_parser.print_help(self._out_file) |
| 970 | print("Sample: build --target=aosp_sailfish-userdebug " |
| 971 | "--branch=<branch name> --artifact-type=device") |
| 972 | |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 973 | def _InitConfigParser(self): |
| 974 | """Initializes the parser for config command.""" |
| 975 | self._config_parser = ConsoleArgumentParser( |
| 976 | "config", "Specifies a global config type to monitor.") |
| 977 | self._config_parser.add_argument( |
| 978 | "--update", |
| 979 | choices=("single", "start", "stop", "list"), |
| 980 | default="start", |
| 981 | help="Update build info") |
| 982 | self._config_parser.add_argument( |
| 983 | "--id", |
| 984 | default=None, |
| 985 | help="session ID only required for 'stop' update command") |
| 986 | self._config_parser.add_argument( |
| 987 | "--interval", |
| 988 | type=int, |
| 989 | default=60, |
| 990 | help="Interval (seconds) to repeat build update.") |
| 991 | self._config_parser.add_argument( |
| 992 | "--config-type", |
| 993 | choices=("prod", "test"), |
| 994 | default="prod", |
| 995 | help="Whether it's for prod") |
| 996 | self._config_parser.add_argument( |
| 997 | "--branch", |
| 998 | required=True, |
| 999 | help="Branch to grab the artifact from.") |
| 1000 | self._config_parser.add_argument( |
| 1001 | "--target", |
| 1002 | required=True, |
| 1003 | help="a comma-separate list of build target product(s).") |
| 1004 | self._config_parser.add_argument( |
| 1005 | "--account_id", |
| 1006 | default=_DEFAULT_ACCOUNT_ID, |
| 1007 | help="Partner Android Build account_id to use.") |
| 1008 | self._config_parser.add_argument( |
| 1009 | '--method', |
| 1010 | default='GET', |
| 1011 | choices=('GET', 'POST'), |
| 1012 | help='Method for fetching') |
| 1013 | |
| 1014 | def UpdateConfig(self, account_id, branch, targets, config_type, method): |
| 1015 | """Updates the global configuration data. |
| 1016 | |
| 1017 | Args: |
| 1018 | account_id: string, Partner Android Build account_id to use. |
| 1019 | branch: string, branch to grab the artifact from. |
| 1020 | targets: string, a comma-separate list of build target product(s). |
| 1021 | config_type: string, config type (`prod` or `test'). |
| 1022 | method: string, HTTP method for fetching. |
| 1023 | """ |
| 1024 | |
| 1025 | self._build_provider["pab"].Authenticate() |
| 1026 | for target in targets.split(","): |
| 1027 | listed_builds = self._build_provider[ |
| 1028 | "pab"].GetBuildList( |
| 1029 | account_id=account_id, |
| 1030 | branch=branch, |
| 1031 | target=target, |
| 1032 | page_token="", |
| 1033 | max_results=1, |
| 1034 | method="GET") |
| 1035 | |
| 1036 | if listed_builds and len(listed_builds) > 0: |
| 1037 | listed_build = listed_builds[0] |
| 1038 | if listed_build["successful"]: |
| 1039 | device_images, test_suites, artifacts, configs = self._build_provider[ |
| 1040 | "pab"].GetArtifact( |
| 1041 | account_id=account_id, |
| 1042 | branch=branch, |
| 1043 | target=target, |
| 1044 | artifact_name=("vti-global-config-%s.zip" % config_type), |
| 1045 | build_id=listed_build["build_id"], |
| 1046 | method=method) |
| 1047 | base_path = os.path.dirname(configs[config_type]) |
| 1048 | schedules_pbs = [] |
| 1049 | lab_pbs = [] |
| 1050 | for root, dirs, files in os.walk(base_path): |
| 1051 | for config_file in files: |
| 1052 | full_path = os.path.join(root, config_file) |
Jongmok Hong | 938ac2b | 2018-01-17 19:42:04 +0900 | [diff] [blame] | 1053 | if config_file.endswith(".schedule_config"): |
Keun Soo Yim | ed07bdf | 2018-01-08 14:34:14 -0800 | [diff] [blame] | 1054 | with open(full_path, "r") as fd: |
| 1055 | context = fd.read() |
| 1056 | sched_cfg_msg = SchedCfgMsg.ScheduleConfigMessage() |
| 1057 | text_format.Merge(context, sched_cfg_msg) |
| 1058 | schedules_pbs.append(sched_cfg_msg) |
| 1059 | print sched_cfg_msg.manifest_branch |
| 1060 | elif config_file.endswith(".lab_config"): |
| 1061 | with open(full_path, "r") as fd: |
| 1062 | context = fd.read() |
| 1063 | lab_cfg_msg = LabCfgMsg.LabConfigMessage() |
| 1064 | text_format.Merge(context, lab_cfg_msg) |
| 1065 | lab_pbs.append(lab_cfg_msg) |
| 1066 | self._vti_endpoint_client.UploadScheduleInfo(schedules_pbs) |
| 1067 | self._vti_endpoint_client.UploadLabInfo(lab_pbs) |
| 1068 | |
| 1069 | def UpdateConfigLoop(self, account_id, branch, target, config_type, method, update_interval): |
| 1070 | """Regularly updates the global configuration. |
| 1071 | |
| 1072 | Args: |
| 1073 | account_id: string, Partner Android Build account_id to use. |
| 1074 | branch: string, branch to grab the artifact from. |
| 1075 | targets: string, a comma-separate list of build target product(s). |
| 1076 | config_type: string, config type (`prod` or `test'). |
| 1077 | method: string, HTTP method for fetching. |
| 1078 | update_interval: int, number of seconds before repeating |
| 1079 | """ |
| 1080 | thread = threading.currentThread() |
| 1081 | while getattr(thread, 'keep_running', True): |
| 1082 | try: |
| 1083 | self.UpdateConfig(account_id, branch, target, config_type, method) |
| 1084 | except (socket.error, remote_operation.RemoteOperationException, |
| 1085 | httplib2.HttpLib2Error, errors.HttpError) as e: |
| 1086 | logging.exception(e) |
| 1087 | time.sleep(update_interval) |
| 1088 | |
| 1089 | def do_config(self, line): |
| 1090 | """Updates global config.""" |
| 1091 | args = self._config_parser.ParseLine(line) |
| 1092 | if args.update == "single": |
| 1093 | self.UpdateConfig( |
| 1094 | args.account_id, |
| 1095 | args.branch, |
| 1096 | args.target, |
| 1097 | args.config_type, |
| 1098 | args.method) |
| 1099 | elif args.update == "list": |
| 1100 | print("Running config update sessions:") |
| 1101 | for id in self.schedule_thread: |
| 1102 | print(" ID %d", id) |
| 1103 | elif args.update == "start": |
| 1104 | if args.interval <= 0: |
| 1105 | raise ConsoleArgumentError( |
| 1106 | "update interval must be positive") |
| 1107 | # do not allow user to create new |
| 1108 | # thread if one is currently running |
| 1109 | if args.id is None: |
| 1110 | if not self.schedule_thread: |
| 1111 | args.id = 1 |
| 1112 | else: |
| 1113 | args.id = max(self.schedule_thread) + 1 |
| 1114 | else: |
| 1115 | args.id = int(args.id) |
| 1116 | if args.id in self.schedule_thread and not hasattr( |
| 1117 | self.schedule_thread[args.id], 'keep_running'): |
| 1118 | print( |
| 1119 | 'config update already running. ' |
| 1120 | 'run config --update=stop --id=%s first.' % args.id |
| 1121 | ) |
| 1122 | return |
| 1123 | self.schedule_thread[args.id] = threading.Thread( |
| 1124 | target=self.UpdateConfigLoop, |
| 1125 | args=( |
| 1126 | args.account_id, |
| 1127 | args.branch, |
| 1128 | args.target, |
| 1129 | args.config_type, |
| 1130 | args.method, |
| 1131 | args.interval, )) |
| 1132 | self.schedule_thread[args.id].daemon = True |
| 1133 | self.schedule_thread[args.id].start() |
| 1134 | elif args.update == "stop": |
| 1135 | if args.id is None: |
| 1136 | print("--id must be set for stop") |
| 1137 | else: |
| 1138 | self.schedule_thread[int(args.id)].keep_running = False |
| 1139 | |
| 1140 | def help_config(self): |
| 1141 | """Prints help message for config command.""" |
| 1142 | self._config_parser.print_help(self._out_file) |
| 1143 | print("Sample: schedule --target=aosp_sailfish-userdebug " |
| 1144 | "--branch=git_oc-release") |
| 1145 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1146 | def _InitCopyParser(self): |
| 1147 | """Initializes the parser for copy command.""" |
| 1148 | self._copy_parser = ConsoleArgumentParser("copy", "Copy a file.") |
| 1149 | |
| 1150 | def do_copy(self, line): |
| 1151 | """Copy a file from source to destination path.""" |
| 1152 | src, dst = line.split() |
| 1153 | if dst == "{vts_tf_home}": |
| 1154 | dst = os.path.dirname(self.test_suite_info["vts"]) |
| 1155 | elif "{" in dst: |
| 1156 | print("unknown dst %s" % dst) |
| 1157 | return |
| 1158 | shutil.copy(src, dst) |
| 1159 | |
| 1160 | def help_copy(self): |
| 1161 | """Prints help message for copy command.""" |
| 1162 | self._copy_parser.print_help(self._out_file) |
| 1163 | |
| 1164 | def _InitDeviceParser(self): |
| 1165 | """Initializes the parser for device command.""" |
| 1166 | self._device_parser = ConsoleArgumentParser( |
| 1167 | "device", "Selects device(s) under test.") |
| 1168 | self._device_parser.add_argument( |
| 1169 | "--set_serial", |
| 1170 | default="", |
| 1171 | help="Serial number for device. Can be a comma-separated list.") |
| 1172 | self._device_parser.add_argument( |
| 1173 | "--update", |
| 1174 | choices=("single", "start", "stop"), |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1175 | default="start", |
| 1176 | help="Update device info on cloud scheduler") |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1177 | self._device_parser.add_argument( |
| 1178 | "--interval", |
| 1179 | type=int, |
| 1180 | default=30, |
| 1181 | help="Interval (seconds) to repeat device update.") |
| 1182 | self._device_parser.add_argument( |
| 1183 | "--host", type=int, help="The index of the host.") |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1184 | self._device_parser.add_argument( |
| 1185 | "--server_type", |
| 1186 | choices=("vti", "tfc"), |
| 1187 | default="vti", |
| 1188 | help="The type of a cloud-based test scheduler server.") |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1189 | self._device_parser.add_argument( |
| 1190 | "--lease", |
| 1191 | default=False, |
| 1192 | type=bool, |
| 1193 | help="Whether to lease jobs and execute them.") |
Hsin-Yi Chen | 18ee53c | 2018-01-22 17:47:07 +0800 | [diff] [blame] | 1194 | |
| 1195 | def SetSerials(self, serials): |
| 1196 | """Sets the default serial numbers for flashing and testing. |
| 1197 | |
| 1198 | Args: |
| 1199 | serials: A list of strings, the serial numbers. |
| 1200 | """ |
| 1201 | self._serials = serials |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1202 | |
Hsin-Yi Chen | 0259291 | 2018-01-17 19:44:17 +0800 | [diff] [blame] | 1203 | def GetSerials(self): |
| 1204 | """Returns the serial numbers saved in the console. |
| 1205 | |
| 1206 | Returns: |
| 1207 | A list of strings, the serial numbers. |
| 1208 | """ |
| 1209 | return self._serials |
| 1210 | |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1211 | def StartLeasedJobExecutionProcess(self, map_interval=1): |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1212 | """Executes process that runs the leased job. |
| 1213 | |
| 1214 | Args: |
| 1215 | map_interval: int, time between mapping the leased jobs to process |
| 1216 | pool in seconds. Default value is 30 |
| 1217 | """ |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 1218 | leased_job_queue = multiprocessing.Queue() |
| 1219 | leased_job_running = multiprocessing.Value("b", 0) |
| 1220 | process = multiprocessing.Process( |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1221 | target=self.ExecLeasedJobs, |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1222 | args=(leased_job_queue, |
| 1223 | leased_job_running, |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1224 | map_interval,) |
| 1225 | ) |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1226 | process.start() |
| 1227 | |
| 1228 | self.leased_job_process = process |
| 1229 | self.leased_job_queue = leased_job_queue |
| 1230 | self.leased_job_running = leased_job_running |
| 1231 | |
| 1232 | def StopLeasedJobExecutionProcess(self): |
| 1233 | """Terminates the process that runs the leased job.""" |
| 1234 | self.leased_job_running = 0 |
| 1235 | self.leased_job_process.terminate() |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1236 | |
| 1237 | def ExecJobOnProcessPool(self, job): |
| 1238 | """Executes a job from job queue. |
| 1239 | |
| 1240 | Args: |
| 1241 | job: dict, contains the information on the leased job passed to this |
| 1242 | process. |
| 1243 | """ |
| 1244 | print("Executing a leased job on process {}".format(os.getpid())) |
| 1245 | filepath, kwargs = self._vti_endpoint_client.ExecuteJob(job) |
| 1246 | if filepath: |
| 1247 | ret = self.ProcessConfigurableScript( |
| 1248 | os.path.join(os.getcwd(), "host_controller", "campaigns", |
| 1249 | filepath), |
| 1250 | **kwargs) |
| 1251 | if ret: |
Hyunwoo Ko | a75a333 | 2018-02-07 18:08:21 +0900 | [diff] [blame] | 1252 | job_status = "complete" |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1253 | else: |
Hyunwoo Ko | a75a333 | 2018-02-07 18:08:21 +0900 | [diff] [blame] | 1254 | job_status = "infra_error" |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1255 | |
| 1256 | self._vti_endpoint_client.StopHeartbeat(job_status) |
| 1257 | print("Job execution complete. " |
| 1258 | "Setting job status to {}".format(job_status)) |
| 1259 | |
| 1260 | def ExecLeasedJobs(self, job_queue, process_running, map_interval): |
| 1261 | """Pops jobs from job_queue and execute them on process pool. |
| 1262 | |
| 1263 | Args: |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 1264 | job_queue: multiprocesing.Queue, a queue from which to get the |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1265 | leased jobs. Shared with the main process. |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1266 | process_running: byte, continue to run the while loop if equal to 1. |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1267 | Shared with the main process. |
| 1268 | map_interval: int, time between mapping the leased jobs to process |
| 1269 | pool in seconds. |
| 1270 | """ |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 1271 | leased_job_exec_pool = multiprocessing.Pool(processes=_MAX_LEASED_JOBS) |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1272 | |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1273 | while True: |
| 1274 | if process_running: |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1275 | arg_list = [] |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1276 | while not job_queue.empty(): |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1277 | leased_job = job_queue.get() |
| 1278 | pair = (self, leased_job) |
| 1279 | arg_list.append(pair) |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1280 | |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1281 | if len(arg_list) > 0: |
| 1282 | leased_job_exec_pool.map_async(ExecJobOnProcessPoolWrapper, |
| 1283 | arg_list) |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1284 | time.sleep(map_interval) |
| 1285 | |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1286 | def UpdateDevice(self, server_type, host, lease): |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1287 | """Updates the device state of all devices on a given host. |
| 1288 | |
| 1289 | Args: |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1290 | server_type: string, the type of a test secheduling server. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1291 | host: HostController object |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1292 | lease: boolean, True to lease and execute jobs. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1293 | """ |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1294 | if server_type == "vti": |
| 1295 | devices = [] |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1296 | |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1297 | stdout, stderr, returncode = cmd_utils.ExecuteOneShellCommand( |
| 1298 | "adb devices") |
| 1299 | |
| 1300 | lines = stdout.split("\n")[1:] |
| 1301 | for line in lines: |
| 1302 | if len(line.strip()): |
| 1303 | device = {} |
| 1304 | device["serial"] = line.split()[0] |
| 1305 | stdout, _, retcode = cmd_utils.ExecuteOneShellCommand( |
| 1306 | "adb -s %s shell getprop ro.product.board" % device["serial"]) |
| 1307 | if retcode == 0: |
| 1308 | device["product"] = stdout.strip() |
| 1309 | else: |
| 1310 | device["product"] = "error" |
| 1311 | device["status"] = DEVICE_STATUS_DICT["online"] |
| 1312 | devices.append(device) |
| 1313 | |
| 1314 | stdout, stderr, returncode = cmd_utils.ExecuteOneShellCommand( |
| 1315 | "fastboot devices") |
| 1316 | lines = stdout.split("\n") |
| 1317 | for line in lines: |
| 1318 | if len(line.strip()): |
| 1319 | device = {} |
| 1320 | device["serial"] = line.split()[0] |
Hyunwoo Ko | ee6ec27 | 2018-01-18 16:04:03 +0900 | [diff] [blame] | 1321 | _, stderr, retcode = cmd_utils.ExecuteOneShellCommand( |
| 1322 | "fastboot -s %s getvar product" % device["serial"]) |
| 1323 | if retcode == 0: |
| 1324 | res = stderr.splitlines()[0].rstrip() |
Hyunwoo Ko | d0f8b91 | 2018-02-07 17:36:00 +0900 | [diff] [blame] | 1325 | print(res) |
| 1326 | if ":" in res: |
| 1327 | device["product"] = res.split(":")[1].strip() |
| 1328 | else: |
| 1329 | device["product"] = "error" |
Hyunwoo Ko | ee6ec27 | 2018-01-18 16:04:03 +0900 | [diff] [blame] | 1330 | else: |
| 1331 | device["product"] = "error" |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1332 | device["status"] = DEVICE_STATUS_DICT["fastboot"] |
| 1333 | devices.append(device) |
| 1334 | |
| 1335 | self._vti_endpoint_client.UploadDeviceInfo( |
| 1336 | host.hostname, devices) |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1337 | |
| 1338 | if lease: |
| 1339 | filepath, kwargs = self._vti_endpoint_client.LeaseJob( |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1340 | socket.gethostname(), False) |
Hyunwoo Ko | d0f8b91 | 2018-02-07 17:36:00 +0900 | [diff] [blame] | 1341 | |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1342 | if filepath is not None: |
| 1343 | self.leased_job_queue.put(kwargs) |
| 1344 | |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1345 | elif server_type == "tfc": |
| 1346 | devices = host.ListDevices() |
| 1347 | for device in devices: |
| 1348 | device.Extend(['sim_state', 'sim_operator', 'mac_address']) |
| 1349 | snapshots = self._tfc_client.CreateDeviceSnapshot( |
| 1350 | host._cluster_ids[0], host.hostname, devices) |
| 1351 | self._tfc_client.SubmitHostEvents([snapshots]) |
| 1352 | else: |
| 1353 | print "Error: unknown server_type %s for UpdateDevice" % server_type |
| 1354 | |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1355 | def UpdateDeviceRepeat(self, server_type, host, lease, update_interval): |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1356 | """Regularly updates the device state of devices on a given host. |
| 1357 | |
| 1358 | Args: |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1359 | server_type: string, the type of a test secheduling server. |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1360 | host: HostController object |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1361 | lease: boolean, True to lease and execute jobs. |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1362 | update_interval: int, number of seconds before repeating |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1363 | """ |
| 1364 | thread = threading.currentThread() |
| 1365 | while getattr(thread, 'keep_running', True): |
| 1366 | try: |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1367 | self.UpdateDevice(server_type, host, lease) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1368 | except (socket.error, remote_operation.RemoteOperationException, |
| 1369 | httplib2.HttpLib2Error, errors.HttpError) as e: |
| 1370 | logging.exception(e) |
| 1371 | time.sleep(update_interval) |
| 1372 | |
| 1373 | def do_device(self, line): |
| 1374 | """Sets device info such as serial number.""" |
| 1375 | args = self._device_parser.ParseLine(line) |
| 1376 | if args.set_serial: |
Hsin-Yi Chen | 18ee53c | 2018-01-22 17:47:07 +0800 | [diff] [blame] | 1377 | self.SetSerials(args.set_serial.split(",")) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1378 | print("serials: %s" % self._serials) |
| 1379 | if args.update: |
| 1380 | if args.host is None: |
| 1381 | if len(self._hosts) > 1: |
| 1382 | raise ConsoleArgumentError("More than one host.") |
| 1383 | args.host = 0 |
| 1384 | host = self._hosts[args.host] |
| 1385 | if args.update == "single": |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1386 | self.UpdateDevice(args.server_type, host, args.lease) |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1387 | elif args.update == "start": |
| 1388 | if args.interval <= 0: |
| 1389 | raise ConsoleArgumentError( |
| 1390 | "update interval must be positive") |
| 1391 | # do not allow user to create new |
| 1392 | # thread if one is currently running |
| 1393 | if self.update_thread is not None and not hasattr( |
| 1394 | self.update_thread, 'keep_running'): |
| 1395 | print('device update already running. ' |
| 1396 | 'run device --update stop first.') |
| 1397 | return |
| 1398 | self.update_thread = threading.Thread( |
| 1399 | target=self.UpdateDeviceRepeat, |
| 1400 | args=( |
Keun Soo Yim | cb51325 | 2018-01-08 14:17:14 -0800 | [diff] [blame] | 1401 | args.server_type, |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1402 | host, |
Keun Soo Yim | 4b3fc31 | 2018-01-09 15:37:52 -0800 | [diff] [blame] | 1403 | args.lease, |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1404 | args.interval, |
| 1405 | )) |
| 1406 | self.update_thread.daemon = True |
| 1407 | self.update_thread.start() |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1408 | if args.lease: |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1409 | self.leased_job_running = 1 |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1410 | elif args.update == "stop": |
| 1411 | self.update_thread.keep_running = False |
Hyunwoo Ko | cdda4e8 | 2018-01-25 19:44:25 +0900 | [diff] [blame] | 1412 | if self.leased_job_running: |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1413 | self.leased_job_running = 0 |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1414 | |
| 1415 | def help_device(self): |
| 1416 | """Prints help message for device command.""" |
| 1417 | self._device_parser.print_help(self._out_file) |
| 1418 | |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1419 | def _InitGsiSplParser(self): |
| 1420 | """Initializes the parser for device command.""" |
| 1421 | self._gsisplParser = ConsoleArgumentParser( |
| 1422 | "gsispl", "Changes security patch level on a selected GSI file.") |
| 1423 | self._gsisplParser.add_argument( |
| 1424 | "--gsi", |
| 1425 | help="Path to GSI image to change security patch level. " |
| 1426 | "If path is not given, the most recently fetched system.img " |
| 1427 | "kept in device_image_info dictionary is used and then " |
| 1428 | "device_image_info will be updated with the new GSI file.") |
| 1429 | self._gsisplParser.add_argument( |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 1430 | "--version", help="New version ID. It should be YYYY-mm-dd format") |
| 1431 | self._gsisplParser.add_argument( |
| 1432 | "--version_from_path", |
| 1433 | help="Path to vendor provided image file to retrieve SPL version. " |
| 1434 | "If just a file name is given, the most recently fetched .img " |
| 1435 | "file will be used.") |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1436 | |
| 1437 | def do_gsispl(self, line): |
| 1438 | """Changes security patch level on a selected GSI file.""" |
| 1439 | args = self._gsisplParser.ParseLine(line) |
| 1440 | if args.gsi: |
| 1441 | if os.path.isfile(args.gsi): |
| 1442 | gsi_path = args.gsi |
| 1443 | else: |
| 1444 | print "Cannot find system image in given path" |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 1445 | return |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1446 | elif "system.img" in self.device_image_info: |
| 1447 | gsi_path = self.device_image_info["system.img"] |
| 1448 | else: |
| 1449 | print "Cannot find system image." |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 1450 | return |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1451 | |
| 1452 | if args.version: |
| 1453 | try: |
| 1454 | version_date = datetime.datetime.strptime( |
| 1455 | args.version, "%Y-%m-%d") |
| 1456 | version = "{:04d}-{:02d}-{:02d}".format( |
| 1457 | version_date.year, version_date.month, version_date.day) |
| 1458 | except ValueError as e: |
| 1459 | print "version ID should be YYYY-mm-dd format." |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 1460 | return |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 1461 | elif args.version_from_path: |
| 1462 | if os.path.isabs(args.version_from_path) and os.path.exists( |
| 1463 | args.version_from_path): |
| 1464 | img_path = args.version_from_path |
| 1465 | elif args.version_from_path in self.device_image_info: |
| 1466 | img_path = self.device_image_info[args.version_from_path] |
Keun Soo Yim | 7d95b50 | 2018-01-21 21:53:54 -0800 | [diff] [blame] | 1467 | elif (args.version_from_path == "boot.img" and |
| 1468 | "full-zipfile" in self.device_image_info): |
| 1469 | tempdir_base = os.path.join(os.getcwd(), "tmp") |
| 1470 | if not os.path.exists(tempdir_base): |
| 1471 | os.mkdir(tempdir_base) |
| 1472 | dest_path = tempfile.mkdtemp(dir=tempdir_base) |
| 1473 | |
| 1474 | with zipfile.ZipFile(self.device_image_info["full-zipfile"], 'r') as zip_ref: |
| 1475 | zip_ref.extractall(dest_path) |
| 1476 | img_path = os.path.join(dest_path, "boot.img") |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 1477 | else: |
| 1478 | print("Cannot find %s file." % args.version_from_path) |
| 1479 | return |
| 1480 | |
| 1481 | version_dict = img_utils.GetSPLVersionFromBootImg(img_path) |
| 1482 | if "year" in version_dict and "month" in version_dict: |
| 1483 | version = "{:04d}-{:02d}-{:02d}".format( |
| 1484 | version_dict["year"], version_dict["month"], |
| 1485 | _SPL_DEFAULT_DAY) |
| 1486 | else: |
| 1487 | print("Failed to fetch SPL version from %s file." % img_path) |
| 1488 | return |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1489 | else: |
Hyunwoo Ko | 432ccc6 | 2018-01-10 17:51:38 +0900 | [diff] [blame] | 1490 | print("version ID or path of .img file must be given.") |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 1491 | return |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1492 | |
Jongmok Hong | 32930e8 | 2018-01-10 18:31:16 +0900 | [diff] [blame] | 1493 | output_path = os.path.join( |
| 1494 | os.path.dirname(os.path.abspath(gsi_path)), |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1495 | "system-{}.img".format(version)) |
Jongmok Hong | e22f32f | 2018-01-25 14:21:58 +0900 | [diff] [blame] | 1496 | stdout, _, err_code = cmd_utils.ExecuteOneShellCommand( |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1497 | "{} {} {} {}".format( |
Jongmok Hong | 32930e8 | 2018-01-10 18:31:16 +0900 | [diff] [blame] | 1498 | os.path.join(os.getcwd(), "host_controller", "gsi", |
| 1499 | "change_security_patch_ver.sh"), gsi_path, |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1500 | output_path, version)) |
| 1501 | if err_code is 0: |
| 1502 | if not args.gsi: |
Jongmok Hong | 32930e8 | 2018-01-10 18:31:16 +0900 | [diff] [blame] | 1503 | print("system.img path is updated to : {}".format(output_path)) |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1504 | self.device_image_info["system.img"] = output_path |
| 1505 | else: |
Jongmok Hong | e22f32f | 2018-01-25 14:21:58 +0900 | [diff] [blame] | 1506 | print "gsispl error: {}".format(stdout) |
Jongmok Hong | daffd2b | 2018-01-08 14:05:11 +0900 | [diff] [blame] | 1507 | return |
Jongmok Hong | 71258da | 2018-01-05 19:17:58 +0900 | [diff] [blame] | 1508 | |
| 1509 | def help_gsispl(self): |
| 1510 | """Prints help message for gsispl command.""" |
| 1511 | self._gsisplParser.print_help(self._out_file) |
| 1512 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1513 | def _PrintTasks(self, tasks): |
| 1514 | """Shows a list of command tasks. |
| 1515 | |
| 1516 | Args: |
| 1517 | devices: A list of DeviceInfo objects. |
| 1518 | """ |
| 1519 | attr_names = ("request_id", "command_id", "task_id", "device_serials", |
| 1520 | "command_line") |
| 1521 | self._PrintObjects(tasks, attr_names) |
| 1522 | |
| 1523 | def do_exit(self, line): |
| 1524 | """Terminates the console. |
| 1525 | |
| 1526 | Returns: |
| 1527 | True, which stops the cmdloop. |
| 1528 | """ |
Hyunwoo Ko | dfd17d9 | 2018-02-01 14:58:56 +0900 | [diff] [blame] | 1529 | self.StopLeasedJobExecutionProcess() |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1530 | return True |
| 1531 | |
| 1532 | def help_exit(self): |
| 1533 | """Prints help message for exit command.""" |
| 1534 | self._Print("Terminate the console.") |
| 1535 | |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1536 | def _InitUploadParser(self): |
| 1537 | """Initializes the parser for upload command.""" |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 1538 | self._upload_parser = ConsoleArgumentParser( |
| 1539 | "upload", |
| 1540 | "Upload <src> file to <dest> Google Cloud Storage. " |
| 1541 | "In <src> and <dest>, variables enclosed in {} are replaced " |
| 1542 | "with the values stored in the console.") |
Hsin-Yi Chen | 0259291 | 2018-01-17 19:44:17 +0800 | [diff] [blame] | 1543 | self._upload_parser.add_argument( |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1544 | "--src", |
| 1545 | required=True, |
Jongmok Hong | 32930e8 | 2018-01-10 18:31:16 +0900 | [diff] [blame] | 1546 | default="latest-system.img", |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1547 | help="Path to a source file to upload. Only single file can be " |
| 1548 | "uploaded per once. Use 'latest- prefix to upload the latest " |
| 1549 | "fetch images. e.g. --src=latest-system.img If argument " |
| 1550 | "value is not given, the recently fetched system.img will be " |
| 1551 | "uploaded.") |
| 1552 | self._upload_parser.add_argument( |
| 1553 | "--dest", |
| 1554 | required=True, |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 1555 | help="Google Cloud Storage URL to which the file is uploaded.") |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1556 | |
| 1557 | def do_upload(self, line): |
| 1558 | """Upload args.src file to args.dest Google Cloud Storage.""" |
| 1559 | args = self._upload_parser.ParseLine(line) |
| 1560 | |
| 1561 | gsutil_path = build_provider_gcs.BuildProviderGCS.GetGsutilPath() |
| 1562 | if not gsutil_path: |
| 1563 | print("Please check gsutil is installed and on your PATH") |
| 1564 | return |
| 1565 | |
| 1566 | if args.src.startswith("latest-"): |
| 1567 | src_name = args.src[7:] |
| 1568 | if src_name in self.device_image_info: |
| 1569 | src_path = self.device_image_info[src_name] |
| 1570 | else: |
| 1571 | print("Unable to find {} in device_image_info".format( |
| 1572 | src_name)) |
| 1573 | return |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1574 | else: |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 1575 | try: |
| 1576 | src_path = self.FormatString(args.src) |
| 1577 | except KeyError as e: |
| 1578 | print("Unknown or uninitialized variable in src: %s" % e) |
| 1579 | return |
| 1580 | |
| 1581 | if not os.path.isfile(src_path): |
| 1582 | print("Cannot find a file: {}".format(src_path)) |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1583 | return |
| 1584 | |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 1585 | try: |
| 1586 | dest_path = self.FormatString(args.dest) |
| 1587 | except KeyError as e: |
| 1588 | print("Unknown or uninitialized variable in dest: %s" % e) |
| 1589 | return |
| 1590 | |
| 1591 | if not dest_path.startswith("gs://"): |
| 1592 | print("{} is not correct GCS url.".format(dest_path)) |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1593 | return |
| 1594 | """ TODO(jongmok) : Before upload, login status, authorization, |
| 1595 | and dest check are required. """ |
Hsin-Yi Chen | 30d418f | 2018-01-29 15:36:39 +0800 | [diff] [blame] | 1596 | copy_command = "{} cp {} {}".format(gsutil_path, src_path, dest_path) |
Jongmok Hong | 35a4ee4 | 2018-01-09 15:22:18 +0900 | [diff] [blame] | 1597 | _, stderr, err_code = cmd_utils.ExecuteOneShellCommand( |
| 1598 | copy_command) |
| 1599 | |
| 1600 | if err_code: |
| 1601 | print stderr |
| 1602 | |
| 1603 | def help_upload(self): |
| 1604 | """Prints help message for upload command.""" |
| 1605 | self._upload_parser.print_help(self._out_file) |
| 1606 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1607 | # @Override |
Keun Soo Yim | 4d812fc | 2018-01-21 21:51:46 -0800 | [diff] [blame] | 1608 | def onecmd(self, line, depth=1): |
Keun Soo Yim | 4c2181a | 2018-01-05 16:22:47 -0800 | [diff] [blame] | 1609 | """Executes command(s) and prints any exception. |
| 1610 | |
Keun Soo Yim | 4d812fc | 2018-01-21 21:51:46 -0800 | [diff] [blame] | 1611 | Parallel execution only for 2nd-level list element. |
| 1612 | |
Keun Soo Yim | 4c2181a | 2018-01-05 16:22:47 -0800 | [diff] [blame] | 1613 | Args: |
| 1614 | line: a list of string or string which keeps the command to run. |
| 1615 | """ |
| 1616 | if type(line) == list: |
Keun Soo Yim | 4d812fc | 2018-01-21 21:51:46 -0800 | [diff] [blame] | 1617 | if depth == 1: # 1 to use multi-threading |
| 1618 | jobs = [] |
| 1619 | for sub_command in line: |
Keun Soo Yim | 4fa6524 | 2018-02-10 17:38:13 -0800 | [diff] [blame] | 1620 | p = multiprocessing.Process( |
Keun Soo Yim | 4d812fc | 2018-01-21 21:51:46 -0800 | [diff] [blame] | 1621 | target=self.onecmd, args=(sub_command, depth + 1,)) |
| 1622 | jobs.append(p) |
| 1623 | p.start() |
| 1624 | for job in jobs: |
| 1625 | job.join() |
| 1626 | return |
| 1627 | else: |
| 1628 | for sub_command in line: |
| 1629 | self.onecmd(sub_command, depth + 1) |
Keun Soo Yim | 4c2181a | 2018-01-05 16:22:47 -0800 | [diff] [blame] | 1630 | |
Keun Soo Yim | 8cdfb52 | 2018-01-04 15:52:16 -0800 | [diff] [blame] | 1631 | if line: |
| 1632 | print("Command: %s" % line) |
| 1633 | try: |
| 1634 | return cmd.Cmd.onecmd(self, line) |
| 1635 | except Exception as e: |
| 1636 | self._Print("%s: %s" % (type(e).__name__, e)) |
| 1637 | return None |
| 1638 | |
| 1639 | # @Override |
| 1640 | def emptyline(self): |
| 1641 | """Ignores empty lines.""" |
| 1642 | pass |
| 1643 | |
| 1644 | # @Override |
| 1645 | def default(self, line): |
| 1646 | """Handles unrecognized commands. |
| 1647 | |
| 1648 | Returns: |
| 1649 | True if receives EOF; otherwise delegates to default handler. |
| 1650 | """ |
| 1651 | if line == "EOF": |
| 1652 | return self.do_exit(line) |
| 1653 | return cmd.Cmd.default(self, line) |
| 1654 | |
| 1655 | |
| 1656 | def _ToPrintString(obj): |
| 1657 | """Converts an object to printable string on console. |
| 1658 | |
| 1659 | Args: |
| 1660 | obj: The object to be printed. |
| 1661 | """ |
| 1662 | if isinstance(obj, (list, tuple, set)): |
| 1663 | return ",".join(str(x) for x in obj) |
| 1664 | return str(obj) |