1# 2# Copyright (C) 2018 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 17import os 18import datetime 19import logging 20import stat 21import threading 22import zipfile 23 24from host_controller import common 25from host_controller.command_processor import base_command_processor 26 27_REPACKAGE_ADDITIONAL_FILE_LIST = [ 28 "android-vtslab/testcases/DATA/app/WifiUtil/WifiUtil.apk", 29 "android-vtslab/testcases/DATA/vtslab-gcs.json", 30 "android-vtslab/testcases/DATA/xml/media_profiles_vendor.xml", 31 "android-vtslab/testcases/host_controller/build/client_secrets.json", 32 "android-vtslab/testcases/host_controller/build/credentials", 33] 34 35_REPACKAGE_ADDITIONAL_BIN_LIST = [ 36 "android-vtslab/bin/adb", 37] 38 39# Path to the version.txt file in the fetched vtslab package zip. 40_VERSION_INFO_FILE_PATH = "android-vtslab/testcases/version.txt" 41 42# List of strings for supported ak versions. 43AK_VERSIONS = ["8.0.0", "8.0.1", "8.1.0", "9", "O", "OMR1", "P", "Q"] 44 45for version in AK_VERSIONS: 46 file_path = "android-vtslab/testcases/DATA/ak/.%s.ak" % version 47 _REPACKAGE_ADDITIONAL_FILE_LIST.append(file_path) 48 file_path += ".pub" 49 _REPACKAGE_ADDITIONAL_FILE_LIST.append(file_path) 50 51 52class CommandRelease(base_command_processor.BaseCommandProcessor): 53 """Command processor for update command. 54 55 Attributes: 56 arg_parser: ConsoleArgumentParser object, argument parser. 57 console: cmd.Cmd console object. 58 command: string, command name which this processor will handle. 59 command_detail: string, detailed explanation for the command. 60 _timers: dict, instances of scheduled threading.Timer. 61 Uses timestamp("%H:%M") string as a key. 62 _vtslab_package_version: string, version information of the fetched 63 vtslab package. 64 (<git commit timestamp>:<git commit hash value>) 65 """ 66 67 command = "release" 68 command_detail = "Release HC. Used for fetching HC package from PAB and uploading to GCS." 69 70 # @Override 71 def SetUp(self): 72 """Initializes the parser for update command.""" 73 self._timers = {} 74 self._vtslab_package_version = "" 75 self.arg_parser.add_argument( 76 "--schedule-for", 77 default="17:00", 78 help="Schedule to update HC package at the given time every day. " 79 "Example: --schedule-for=%%H:%%M") 80 self.arg_parser.add_argument( 81 "--account_id", 82 default=common._DEFAULT_ACCOUNT_ID, 83 help="Partner Android Build account_id to use.") 84 self.arg_parser.add_argument( 85 "--branch", help="Branch to grab the artifact from.") 86 self.arg_parser.add_argument( 87 "--target", 88 help="a comma-separate list of build target product(s).") 89 self.arg_parser.add_argument( 90 "--dest", 91 help="Google Cloud Storage URL to which the file is uploaded.") 92 self.arg_parser.add_argument( 93 "--cancel", help="Cancel all scheduled release if given.") 94 self.arg_parser.add_argument( 95 "--print-all", help="Print all scheduled timers.") 96 self.arg_parser.add_argument( 97 "--additional_files_bucket", 98 default="gs://vtslab-release", 99 help="GCS bucket URL from where to fetch the additional files " 100 "required for HC to run properly.") 101 102 # @Override 103 def Run(self, arg_line): 104 """Schedule a host_constroller package release at a certain time.""" 105 args = self.arg_parser.ParseLine(arg_line) 106 107 if args.print_all: 108 logging.info(self._timers) 109 return 110 111 if not args.cancel: 112 if args.schedule_for == "now": 113 self.ReleaseCallback(args.schedule_for, args.account_id, 114 args.branch, args.target, args.dest, 115 args.additional_files_bucket) 116 return 117 118 elif len(args.schedule_for.split(":")) != 2: 119 logging.error("The format of --schedule-for flag is %H:%M") 120 return False 121 122 if (int(args.schedule_for.split(":")[0]) not in range(24) 123 or int(args.schedule_for.split(":")[-1]) not in range(60)): 124 logging.error("The value of --schedule-for flag must be in " 125 "\"00:00\"..\"23:59\" inclusive") 126 return False 127 128 if not args.schedule_for in self._timers: 129 delta_time = datetime.datetime.now().replace( 130 hour=int(args.schedule_for.split(":")[0]), 131 minute=int(args.schedule_for.split(":")[-1]), 132 second=0, 133 microsecond=0) - datetime.datetime.now() 134 135 if delta_time <= datetime.timedelta(0): 136 delta_time += datetime.timedelta(days=1) 137 138 self._timers[args.schedule_for] = threading.Timer( 139 delta_time.total_seconds(), self.ReleaseCallback, 140 (args.schedule_for, args.account_id, args.branch, 141 args.target, args.dest, args.additional_files_bucket)) 142 self._timers[args.schedule_for].daemon = True 143 self._timers[args.schedule_for].start() 144 logging.info("Release job scheduled for {}".format( 145 datetime.datetime.now() + delta_time)) 146 else: 147 self.CancelAllEvents() 148 149 def FetchVtslab(self, account_id, branch, target, bucket): 150 """Fetchs android-vtslab.zip and return the fetched file path. 151 152 Args: 153 account_id: string, Partner Android Build account_id to use. 154 branch: string, branch to grab the artifact from. 155 targets: string, a comma-separate list of build target product(s). 156 bucket: string, GCS bucket URL from where to fetch the additional 157 files. 158 159 Returns: 160 path to the fetched android-vtslab.zip file. None if the fetching 161 has failed. 162 """ 163 self.console.build_provider["pab"].Authenticate() 164 fetched_path = self.console.build_provider[ 165 "pab"].FetchLatestBuiltHCPackage(account_id, branch, target) 166 167 with zipfile.ZipFile(fetched_path, mode="a") as vtslab_package: 168 if _VERSION_INFO_FILE_PATH in vtslab_package.namelist(): 169 self._vtslab_package_version = vtslab_package.open( 170 _VERSION_INFO_FILE_PATH).readline().strip() 171 else: 172 self._vtslab_package_version = "" 173 174 for path in _REPACKAGE_ADDITIONAL_FILE_LIST: 175 additional_file = os.path.join(bucket, path) 176 self.console.build_provider["gcs"].Fetch(additional_file) 177 try: 178 logging.info("Adding file %s into %s" % 179 (os.path.basename(path), 180 os.path.basename(fetched_path))) 181 additional_file_path = self.console.build_provider[ 182 "gcs"].GetAdditionalFile(os.path.basename(path)) 183 vtslab_package.write(additional_file_path, path) 184 except KeyError as e: 185 logging.exception(e) 186 187 for bin in _REPACKAGE_ADDITIONAL_BIN_LIST: 188 additional_bin = os.path.join(bucket, bin) 189 self.console.build_provider["gcs"].Fetch(additional_bin) 190 try: 191 logging.info("Adding executable %s into %s" % 192 (os.path.basename(bin), 193 os.path.basename(fetched_path))) 194 additional_bin_path = self.console.build_provider[ 195 "gcs"].GetAdditionalFile(os.path.basename(bin)) 196 bin_mode = os.stat(additional_bin_path).st_mode 197 os.chmod( 198 additional_bin_path, 199 bin_mode | stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH) 200 vtslab_package.write(additional_bin_path, bin) 201 except KeyError as e: 202 logging.exception(e) 203 204 return fetched_path 205 206 def UploadVtslab(self, package_file_path, dest_path): 207 """upload repackaged vtslab package to GCS. 208 209 Args: 210 package_file_path: string, path to the vtslab package file. 211 dest_path: string, URL to GCS. 212 """ 213 if dest_path and dest_path.endswith("/"): 214 split_list = os.path.basename(package_file_path).split(".") 215 if self._vtslab_package_version: 216 try: 217 timestamp, hash = self._vtslab_package_version.split(":") 218 split_list[0] += "-%s-%s" % (timestamp, hash) 219 except ValueError as e: 220 logging.exception(e) 221 split_list[0] += "-{timestamp_date}" 222 else: 223 split_list[0] += "-{timestamp_date}" 224 dest_path += ".".join(split_list) 225 226 upload_command = "upload --src %s --dest %s" % (package_file_path, 227 dest_path) 228 self.console.onecmd(upload_command) 229 230 def ReleaseCallback(self, schedule_for, account_id, branch, target, dest, 231 bucket): 232 """Target function for the scheduled Timer. 233 234 Args: 235 schedule_for: string, scheduled time for this Timer. 236 Format: "%H:%M" (from "00:00" to "23:59" inclusive) 237 account_id: string, Partner Android Build account_id to use. 238 branch: string, branch to grab the artifact from. 239 targets: string, a comma-separate list of build target product(s). 240 dest: string, URL to GCS. 241 bucket: string, GCS bucket URL from where to fetch the additional 242 files. 243 """ 244 fetched_path = self.FetchVtslab(account_id, branch, target, bucket) 245 if fetched_path: 246 self.UploadVtslab(fetched_path, dest) 247 248 if schedule_for != "now": 249 delta_time = datetime.datetime.now().replace( 250 hour=int(schedule_for.split(":")[0]), 251 minute=int(schedule_for.split(":")[-1]), 252 second=0, 253 microsecond=0) - datetime.datetime.now() + datetime.timedelta( 254 days=1) 255 self._timers[schedule_for] = threading.Timer( 256 delta_time.total_seconds(), self.ReleaseCallback, 257 (schedule_for, account_id, branch, target, dest, bucket)) 258 self._timers[schedule_for].daemon = True 259 self._timers[schedule_for].start() 260 logging.info("Release job scheduled for {}".format( 261 datetime.datetime.now() + delta_time)) 262 263 def CancelAllEvents(self): 264 """Cancel all scheduled Timer.""" 265 for scheduled_time in self._timers: 266 self._timers[scheduled_time].cancel() 267 self._timers = {} 268