1# Copyright 2020 The Chromium OS Authors. All rights reserved. 2# Use of this source code is governed by a BSD-style license that can be 3# found in the LICENSE file. 4 5""" 6This file provides functions to implement bluetooth_PeerUpdate test 7which downloads chameleond bundle from google cloud storage and updates 8peer device associated with a DUT 9""" 10 11from __future__ import absolute_import 12 13import logging 14import os 15import sys 16import tempfile 17import time 18 19from datetime import datetime 20 21import common 22from autotest_lib.client.bin import utils 23from autotest_lib.client.common_lib import error 24 25 26# The location of the package in the cloud 27GS_PUBLIC = 'gs://chromeos-localmirror/distfiles/bluetooth_peer_bundle/' 28 29# NAME of the file that stores commit info in the cloud 30COMMIT_FILENAME = 'latest_bluetooth_commit' 31 32# The following needs to be kept in sync with values chameleond code 33BUNDLE_TEMPLATE='chameleond-0.0.2-{}.tar.gz' # Name of the chamleond package 34BUNDLE_DIR = 'chameleond-0.0.2' 35BUNDLE_VERSION = '9999' 36CHAMELEON_BOARD = 'fpga_tio' 37 38 39def run_cmd(peer, cmd): 40 """A wrapper around host.run().""" 41 try: 42 logging.info('executing command %s on peer',cmd) 43 result = peer.host.run(cmd) 44 logging.info('exit_status is %s', result.exit_status) 45 logging.info('stdout is %s stderr is %s', result.stdout, result.stderr) 46 output = result.stderr if result.stderr else result.stdout 47 if result.exit_status == 0: 48 return True, output 49 else: 50 return False, output 51 except error.AutoservRunError as e: 52 logging.error('Error while running cmd %s %s', cmd, e) 53 return False, None 54 55 56def is_update_needed(peer, latest_commit): 57 """ Check if update is required 58 59 Update if the commit hash doesn't match 60 61 @returns: True/False 62 """ 63 return not is_commit_hash_equal(peer, latest_commit) 64 65 66def is_commit_hash_equal(peer, latest_commit): 67 """ Check if chameleond commit hash is the expected one""" 68 try: 69 commit = peer.get_bt_commit_hash() 70 except: 71 logging.error('Getting the commit hash failed. Updating the peer %s', 72 sys.exc_info()) 73 return True 74 75 logging.debug('commit %s found on peer %s', commit, peer.host) 76 return commit == latest_commit 77 78 79def perform_update(peer, latest_commit): 80 """ Update the chameleond on the peer""" 81 82 logging.info('copy the file over to the peer') 83 try: 84 cur_dir = '/tmp/' 85 bundle = BUNDLE_TEMPLATE.format(latest_commit) 86 bundle_path = os.path.join(cur_dir, bundle) 87 logging.debug('package location is %s', bundle_path) 88 89 peer.host.send_file(bundle_path, '/tmp/') 90 except: 91 logging.error('copying the file failed %s ', sys.exc_info()) 92 logging.error(str(os.listdir(cur_dir))) 93 return False 94 95 HOST_NOW = datetime.strftime(datetime.now(), '%Y-%m-%d %H:%M:%S') 96 logging.info('running make on peer') 97 cmd = ('cd %s && rm -rf %s && tar zxf %s &&' 98 'cd %s && find -exec touch -c {} \; &&' 99 'make install REMOTE_INSTALL=TRUE ' 100 'HOST_NOW="%s" BUNDLE_VERSION=%s ' 101 'CHAMELEON_BOARD=%s && rm %s%s') % (cur_dir,BUNDLE_DIR, bundle, 102 BUNDLE_DIR, HOST_NOW, 103 BUNDLE_VERSION, 104 CHAMELEON_BOARD, cur_dir, 105 bundle) 106 logging.debug(cmd) 107 status, _ = run_cmd(peer, cmd) 108 if not status: 109 logging.info('make failed') 110 return False 111 112 logging.info('chameleond installed on peer') 113 return True 114 115 116def restart_check_chameleond(peer): 117 """restart chameleond and make sure it is running.""" 118 119 restart_cmd = 'sudo /etc/init.d/chameleond restart' 120 start_cmd = 'sudo /etc/init.d/chameleond start' 121 status_cmd = 'sudo /etc/init.d/chameleond status' 122 123 status, _ = run_cmd(peer, restart_cmd) 124 if not status: 125 status, _ = run_cmd(peer, start_cmd) 126 if not status: 127 logging.error('restarting/starting chamleond failed') 128 # 129 #TODO: Refactor so that we wait for all peer devices all together. 130 # 131 # Wait till chameleond initialization is complete 132 time.sleep(5) 133 134 status, output = run_cmd(peer, status_cmd) 135 expected_output = 'chameleond is running' 136 return status and expected_output in output 137 138 139def update_peer(peer, latest_commit): 140 """Update the chameleond on peer devices if required 141 142 @params peer: btpeer to be updated 143 @params latest_commit: target git commit 144 145 @returns: (True, None) if update succeeded 146 (False, reason) if update failed 147 """ 148 149 if peer.get_platform() != 'RASPI': 150 logging.error('Unsupported peer %s',str(peer.host)) 151 return False, 'Unsupported peer' 152 153 if not perform_update(peer, latest_commit): 154 return False, 'Update failed' 155 156 if not restart_check_chameleond(peer): 157 return False, 'Unable to start chameleond' 158 159 if is_update_needed(peer, latest_commit): 160 return False, 'Commit not updated after upgrade' 161 162 logging.info('updating chameleond succeded') 163 return True, '' 164 165 166def update_peers(host, latest_commit): 167 """Update the chameleond on alll peer devices of an host""" 168 169 if host.btpeer_list == []: 170 raise error.TestError('Bluetooth Peer not present') 171 172 status = {} 173 for peer in host.btpeer_list: 174 #TODO(b:160782273) Make this parallel 175 status[peer] = {} 176 status[peer]['update_needed'] = is_update_needed(peer,latest_commit) 177 178 logging.debug(status) 179 if not any([v['update_needed'] for v in status.values()]): 180 logging.info("Update not needed on any of the peers") 181 return 182 for peer in host.btpeer_list: 183 if status[peer]['update_needed']: 184 status[peer]['updated'], status[peer]['reason'] = \ 185 update_peer(peer, latest_commit) 186 187 logging.debug(status) 188 # If any of the peers failed update, raise failure with the reason 189 if not all([v['updated'] for v in status.values() if v['update_needed']]): 190 for peer, v in status.items(): 191 if v['update_needed']: 192 if not v['updated']: 193 logging.error('updating peer %s failed %s', str(peer.host), 194 v['reason']) 195 raise error.TestFail() 196 197 logging.info('%s peers updated',len([v['updated'] for v in status.values() 198 if v['update_needed']])) 199 200 201def get_latest_commit(): 202 """ Get the latest commit 203 204 Download the file containing the latest commit and 205 parse it contents, and cleanup. 206 @returns (True,commit) in case of success ; (False, None) in case of failure 207 """ 208 try: 209 commit = None 210 src = GS_PUBLIC + COMMIT_FILENAME 211 212 with tempfile.NamedTemporaryFile(suffix='bt_commit') as tmp_file: 213 tmp_filename = tmp_file.name 214 cmd = 'gsutil cp {} {}'.format(src, tmp_filename) 215 result = utils.run(cmd) 216 if result.exit_status != 0: 217 logging.error('Downloading commit file failed with %s', 218 result.exit_status) 219 return (False, None) 220 with open(tmp_filename) as f: 221 content = f.read() 222 logging.debug('content of the file is %s', content) 223 commit = content.strip('\n').strip() 224 225 logging.info('latest commit is %s', commit) 226 if commit is None: 227 return (False, None) 228 else: 229 return (True, commit) 230 except Exception as e: 231 logging.error('exception %s in get_latest_commit', str(e)) 232 return (False, None) 233 234 235def download_installation_files(host, commit): 236 """ Download the chameleond installation bundle""" 237 src_path = GS_PUBLIC + BUNDLE_TEMPLATE.format(commit) 238 dest_path = '/tmp/' + BUNDLE_TEMPLATE.format(commit) 239 logging.debug('chamelond bundle path is %s', src_path) 240 logging.debug('bundle path in DUT is %s', dest_path) 241 242 cmd = 'gsutil cp {} {}'.format(src_path, dest_path) 243 try: 244 result = utils.run(cmd) 245 if result.exit_status != 0: 246 logging.error('Downloading the chameleond bundle failed with %d', 247 result.exit_status) 248 return False 249 # Send file to DUT from the test server 250 host.send_file(dest_path, dest_path) 251 logging.debug('file send to %s %s',host, dest_path) 252 return True 253 except Exception as e: 254 logging.error('exception %s in download_installation_files', str(e)) 255 return False 256 257 258def cleanup(host, commit): 259 """ Cleanup the installation file from server.""" 260 261 dest_path = '/tmp/' + BUNDLE_TEMPLATE.format(commit) 262 # remove file from test server 263 if not os.path.exists(dest_path): 264 logging.debug('File %s not found', dest_path) 265 return True 266 267 try: 268 logging.debug('Remove file %s', dest_path) 269 os.remove(dest_path) 270 271 # remove file from the DUT 272 result = host.run('rm {}'.format(dest_path)) 273 if result.exit_status != 0: 274 logging.error('Unable to delete %s on dut', dest_path) 275 return False 276 return True 277 except Exception as e: 278 logging.error('Exception %s in cleanup', str(e)) 279 return False 280