1# -*- coding: utf-8 -*- 2# Copyright 2016 The Chromium OS Authors. All rights reserved. 3# Use of this source code is governed by a BSD-style license that can be 4# found in the LICENSE file. 5 6"""Library containing functions to execute auto-update on a remote device. 7 8ChromiumOSUpdater includes: 9 ----Check----- 10 * Check functions, including kernel/version/cgpt check. 11 12 ----Precheck--- 13 * Pre-check if the device can run its nebraska. 14 * Pre-check for stateful/rootfs update/whole update. 15 16 ----Tranfer---- 17 * This step is carried out by Transfer subclasses in 18 auto_updater_transfer.py. 19 20 ----Auto-Update--- 21 * Do rootfs partition update if it's required. 22 * Do stateful partition update if it's required. 23 * Do reboot for device if it's required. 24 25 ----Verify---- 26 * Do verification if it's required. 27 * Disable rootfs verification in device if it's required. 28 * Post-check stateful/rootfs update/whole update. 29""" 30 31from __future__ import print_function 32 33import json 34import os 35import re 36import subprocess 37import tempfile 38import time 39 40import six 41 42from autotest_lib.utils.frozen_chromite.cli import command 43from autotest_lib.utils.frozen_chromite.lib import auto_update_util 44from autotest_lib.utils.frozen_chromite.lib import auto_updater_transfer 45from autotest_lib.utils.frozen_chromite.lib import cros_build_lib 46from autotest_lib.utils.frozen_chromite.lib import cros_logging as logging 47from autotest_lib.utils.frozen_chromite.lib import nebraska_wrapper 48from autotest_lib.utils.frozen_chromite.lib import operation 49from autotest_lib.utils.frozen_chromite.lib import osutils 50from autotest_lib.utils.frozen_chromite.lib import remote_access 51from autotest_lib.utils.frozen_chromite.lib import retry_util 52from autotest_lib.utils.frozen_chromite.lib import stateful_updater 53from autotest_lib.utils.frozen_chromite.lib import timeout_util 54 55from autotest_lib.utils.frozen_chromite.utils import key_value_store 56 57# Naming conventions for global variables: 58# File on remote host without slash: REMOTE_XXX_FILENAME 59# File on remote host with slash: REMOTE_XXX_FILE_PATH 60# Path on remote host with slash: REMOTE_XXX_PATH 61# File on local server without slash: LOCAL_XXX_FILENAME 62 63# Update Status for remote device. 64UPDATE_STATUS_IDLE = 'UPDATE_STATUS_IDLE' 65UPDATE_STATUS_DOWNLOADING = 'UPDATE_STATUS_DOWNLOADING' 66UPDATE_STATUS_FINALIZING = 'UPDATE_STATUS_FINALIZING' 67UPDATE_STATUS_UPDATED_NEED_REBOOT = 'UPDATE_STATUS_UPDATED_NEED_REBOOT' 68 69# Max number of the times for retry: 70# 1. for transfer functions to be retried. 71# 2. for some retriable commands to be retried. 72MAX_RETRY = 5 73 74# The delay between retriable tasks. 75DELAY_SEC_FOR_RETRY = 5 76 77# Number of seconds to wait for the post check version to settle. 78POST_CHECK_SETTLE_SECONDS = 15 79 80# Number of seconds to delay between post check retries. 81POST_CHECK_RETRY_SECONDS = 5 82 83 84class ChromiumOSUpdateError(Exception): 85 """Thrown when there is a general ChromiumOS-specific update error.""" 86 87 88class PreSetupUpdateError(ChromiumOSUpdateError): 89 """Raised for the rootfs/stateful update pre-setup failures.""" 90 91 92class RootfsUpdateError(ChromiumOSUpdateError): 93 """Raised for the Rootfs partition update failures.""" 94 95 96class StatefulUpdateError(ChromiumOSUpdateError): 97 """Raised for the stateful partition update failures.""" 98 99 100class AutoUpdateVerifyError(ChromiumOSUpdateError): 101 """Raised for verification failures after auto-update.""" 102 103 104class RebootVerificationError(ChromiumOSUpdateError): 105 """Raised for failing to reboot errors.""" 106 107 108class BaseUpdater(object): 109 """The base updater class.""" 110 111 def __init__(self, device, payload_dir): 112 self.device = device 113 self.payload_dir = payload_dir 114 115 116class ChromiumOSUpdater(BaseUpdater): 117 """Used to update DUT with image.""" 118 119 # Nebraska files. 120 LOCAL_NEBRASKA_LOG_FILENAME = 'nebraska.log' 121 REMOTE_NEBRASKA_FILENAME = 'nebraska.py' 122 123 # rootfs update files. 124 REMOTE_UPDATE_ENGINE_BIN_FILENAME = 'update_engine_client' 125 REMOTE_UPDATE_ENGINE_LOGFILE_PATH = '/var/log/update_engine.log' 126 127 UPDATE_CHECK_INTERVAL_PROGRESSBAR = 0.5 128 UPDATE_CHECK_INTERVAL_NORMAL = 10 129 130 # `mode` parameter when copying payload files to the DUT. 131 PAYLOAD_MODE_PARALLEL = 'parallel' 132 PAYLOAD_MODE_SCP = 'scp' 133 134 # Related to crbug.com/276094: Restore to 5 mins once the 'host did not 135 # return from reboot' bug is solved. 136 REBOOT_TIMEOUT = 480 137 138 REMOTE_STATEFUL_PATH_TO_CHECK = ('/var', '/home', '/mnt/stateful_partition') 139 REMOTE_STATEFUL_TEST_FILENAME = '.test_file_to_be_deleted' 140 REMOTE_UPDATED_MARKERFILE_PATH = '/run/update_engine_autoupdate_completed' 141 REMOTE_LAB_MACHINE_FILE_PATH = '/mnt/stateful_partition/.labmachine' 142 KERNEL_A = {'name': 'KERN-A', 'kernel': 2, 'root': 3} 143 KERNEL_B = {'name': 'KERN-B', 'kernel': 4, 'root': 5} 144 KERNEL_UPDATE_TIMEOUT = 180 145 146 def __init__(self, device, build_name, payload_dir, transfer_class, 147 log_file=None, tempdir=None, clobber_stateful=True, 148 yes=False, do_rootfs_update=True, do_stateful_update=True, 149 reboot=True, disable_verification=False, 150 send_payload_in_parallel=False, payload_filename=None, 151 staging_server=None, clear_tpm_owner=False, ignore_appid=False): 152 """Initialize a ChromiumOSUpdater for auto-update a chromium OS device. 153 154 Args: 155 device: the ChromiumOSDevice to be updated. 156 build_name: the target update version for the device. 157 payload_dir: the directory of payload(s). 158 transfer_class: A reference to any subclass of 159 auto_updater_transfer.Transfer class. 160 log_file: The file to save running logs. 161 tempdir: the temp directory in caller, not in the device. For example, 162 the tempdir for cros flash is /tmp/cros-flash****/, used to 163 temporarily keep files when transferring update-utils package, and 164 reserve nebraska and update engine logs. 165 do_rootfs_update: whether to do rootfs partition update. The default is 166 True. 167 do_stateful_update: whether to do stateful partition update. The default 168 is True. 169 reboot: whether to reboot device after update. The default is True. 170 disable_verification: whether to disabling rootfs verification on the 171 device. The default is False. 172 clobber_stateful: whether to do a clean stateful update. The default is 173 False. 174 yes: Assume "yes" (True) for any prompt. The default is False. However, 175 it should be set as True if we want to disable all the prompts for 176 auto-update. 177 payload_filename: Filename of exact payload file to use for 178 update instead of the default: update.gz. Defaults to None. Use 179 only if you staged a payload by filename (i.e not artifact) first. 180 send_payload_in_parallel: whether to transfer payload in chunks 181 in parallel. The default is False. 182 staging_server: URL (str) of the server that's staging the payload files. 183 Assuming transfer_class is None, if value for staging_server is None 184 or empty, an auto_updater_transfer.LocalTransfer reference must be 185 passed through the transfer_class parameter. 186 clear_tpm_owner: If true, it will clear the TPM owner on reboot. 187 ignore_appid: True to tell Nebraska to ignore the update request's 188 App ID. This allows mismatching the source and target version boards. 189 One specific use case is updating between <board> and 190 <board>-kernelnext images which have different App IDs. 191 """ 192 super(ChromiumOSUpdater, self).__init__(device, payload_dir) 193 194 self.tempdir = (tempdir if tempdir is not None 195 else tempfile.mkdtemp(prefix='cros-update')) 196 self.inactive_kernel = None 197 self.update_version = build_name 198 199 # Update setting 200 self._cmd_kwargs = {} 201 self._cmd_kwargs_omit_error = {'check': False} 202 self._do_stateful_update = do_stateful_update 203 self._do_rootfs_update = do_rootfs_update 204 self._disable_verification = disable_verification 205 self._clobber_stateful = clobber_stateful 206 self._reboot = reboot 207 self._yes = yes 208 # Device's directories 209 self.device_dev_dir = os.path.join(self.device.work_dir, 'src') 210 self.device_payload_dir = os.path.join( 211 self.device.work_dir, 212 auto_updater_transfer.Transfer.PAYLOAD_DIR_NAME) 213 # autoupdate_EndToEndTest uses exact payload filename for update 214 self.payload_filename = payload_filename 215 if send_payload_in_parallel: 216 self.payload_mode = self.PAYLOAD_MODE_PARALLEL 217 else: 218 self.payload_mode = self.PAYLOAD_MODE_SCP 219 self.perf_id = None 220 221 if log_file: 222 log_kwargs = { 223 'stdout': log_file, 224 'append_to_file': True, 225 'stderr': subprocess.STDOUT, 226 } 227 self._cmd_kwargs.update(log_kwargs) 228 self._cmd_kwargs_omit_error.update(log_kwargs) 229 230 self._staging_server = staging_server 231 self._transfer_obj = self._CreateTransferObject(transfer_class) 232 233 self._clear_tpm_owner = clear_tpm_owner 234 self._ignore_appid = ignore_appid 235 236 @property 237 def is_au_endtoendtest(self): 238 return self.payload_filename is not None 239 240 @property 241 def request_logs_dir(self): 242 """Returns path to the nebraska request logfiles directory. 243 244 Returns: 245 A complete path to the logfiles directory. 246 """ 247 return self.tempdir 248 249 def _CreateTransferObject(self, transfer_class): 250 """Create the correct Transfer class. 251 252 Args: 253 transfer_class: A variable that contains a reference to one of the 254 Transfer classes in auto_updater_transfer. 255 """ 256 assert issubclass(transfer_class, auto_updater_transfer.Transfer) 257 258 # Determine if staging_server needs to be passed as an argument to 259 # class_ref. 260 cls_kwargs = {} 261 if self._staging_server: 262 cls_kwargs['staging_server'] = self._staging_server 263 264 return transfer_class( 265 device=self.device, payload_dir=self.payload_dir, 266 payload_name=self._GetRootFsPayloadFileName(), 267 cmd_kwargs=self._cmd_kwargs, 268 transfer_rootfs_update=self._do_rootfs_update, 269 transfer_stateful_update=self._do_rootfs_update, 270 device_payload_dir=self.device_payload_dir, tempdir=self.tempdir, 271 payload_mode=self.payload_mode, **cls_kwargs) 272 273 def CheckRestoreStateful(self): 274 """Check whether to restore stateful.""" 275 logging.debug('Checking whether to restore stateful...') 276 restore_stateful = False 277 try: 278 self._CheckNebraskaCanRun() 279 return restore_stateful 280 except nebraska_wrapper.NebraskaStartupError as e: 281 if self._do_rootfs_update: 282 msg = ('Cannot start nebraska! The stateful partition may be ' 283 'corrupted: %s' % e) 284 prompt = 'Attempt to restore the stateful partition?' 285 restore_stateful = self._yes or cros_build_lib.BooleanPrompt( 286 prompt=prompt, default=False, prolog=msg) 287 if not restore_stateful: 288 raise ChromiumOSUpdateError( 289 'Cannot continue to perform rootfs update!') 290 291 logging.debug('Restore stateful partition is%s required.', 292 ('' if restore_stateful else ' not')) 293 return restore_stateful 294 295 def _CheckNebraskaCanRun(self): 296 """We can run Nebraska on |device|.""" 297 nebraska_bin = os.path.join(self.device_dev_dir, 298 self.REMOTE_NEBRASKA_FILENAME) 299 nebraska = nebraska_wrapper.RemoteNebraskaWrapper( 300 self.device, nebraska_bin=nebraska_bin) 301 nebraska.CheckNebraskaCanRun() 302 303 @classmethod 304 def GetUpdateStatus(cls, device, keys=None): 305 """Returns the status of the update engine on the |device|. 306 307 Retrieves the status from update engine and confirms all keys are 308 in the status. 309 310 Args: 311 device: A ChromiumOSDevice object. 312 keys: the keys to look for in the status result (defaults to 313 ['CURRENT_OP']). 314 315 Returns: 316 A list of values in the order of |keys|. 317 """ 318 keys = keys or ['CURRENT_OP'] 319 result = device.run([cls.REMOTE_UPDATE_ENGINE_BIN_FILENAME, '--status'], 320 capture_output=True, log_output=True) 321 322 if not result.output: 323 raise Exception('Cannot get update status') 324 325 try: 326 status = key_value_store.LoadData(result.output) 327 except ValueError: 328 raise ValueError('Cannot parse update status') 329 330 values = [] 331 for key in keys: 332 if key not in status: 333 raise ValueError('Missing "%s" in the update engine status' % key) 334 335 values.append(status.get(key)) 336 337 return values 338 339 @classmethod 340 def GetRootDev(cls, device): 341 """Get the current root device on |device|. 342 343 Args: 344 device: a ChromiumOSDevice object, defines whose root device we 345 want to fetch. 346 """ 347 rootdev = device.run( 348 ['rootdev', '-s'], capture_output=True).output.strip() 349 logging.debug('Current root device is %s', rootdev) 350 return rootdev 351 352 def _StartUpdateEngineIfNotRunning(self, device): 353 """Starts update-engine service if it is not running. 354 355 Args: 356 device: a ChromiumOSDevice object, defines the target root device. 357 """ 358 try: 359 result = device.run(['start', 'update-engine'], 360 capture_output=True, log_output=True).stdout 361 if 'start/running' in result: 362 logging.info('update engine was not running, so we started it.') 363 except cros_build_lib.RunCommandError as e: 364 if e.result.returncode != 1 or 'is already running' not in e.result.error: 365 raise e 366 367 def SetupRootfsUpdate(self): 368 """Makes sure |device| is ready for rootfs update.""" 369 logging.info('Checking if update engine is idle...') 370 self._StartUpdateEngineIfNotRunning(self.device) 371 status = self.GetUpdateStatus(self.device)[0] 372 if status == UPDATE_STATUS_UPDATED_NEED_REBOOT: 373 logging.info('Device needs to reboot before updating...') 374 self._Reboot('setup of Rootfs Update') 375 status = self.GetUpdateStatus(self.device)[0] 376 377 if status != UPDATE_STATUS_IDLE: 378 raise RootfsUpdateError('Update engine is not idle. Status: %s' % status) 379 380 if self.is_au_endtoendtest: 381 # TODO(ahassani): This should only be done for jetsteam devices. 382 self._RetryCommand(['sudo', 'stop', 'ap-update-manager'], 383 **self._cmd_kwargs_omit_error) 384 385 self._RetryCommand(['rm', '-f', self.REMOTE_UPDATED_MARKERFILE_PATH], 386 **self._cmd_kwargs) 387 self._RetryCommand(['stop', 'ui'], **self._cmd_kwargs_omit_error) 388 389 390 def _GetDevicePythonSysPath(self): 391 """Get python sys.path of the given |device|.""" 392 sys_path = self.device.run( 393 ['python', '-c', '"import json, sys; json.dump(sys.path, sys.stdout)"'], 394 capture_output=True, log_output=True).output 395 return json.loads(sys_path) 396 397 def _FindDevicePythonPackagesDir(self): 398 """Find the python packages directory for the given |device|.""" 399 third_party_host_dir = '' 400 sys_path = self._GetDevicePythonSysPath() 401 for p in sys_path: 402 if p.endswith('site-packages') or p.endswith('dist-packages'): 403 third_party_host_dir = p 404 break 405 406 if not third_party_host_dir: 407 raise ChromiumOSUpdateError( 408 'Cannot find proper site-packages/dist-packages directory from ' 409 'sys.path for storing packages: %s' % sys_path) 410 411 return third_party_host_dir 412 413 def _GetRootFsPayloadFileName(self): 414 """Get the correct RootFs payload filename. 415 416 Returns: 417 The payload filename. (update.gz or a custom payload filename). 418 """ 419 if self.is_au_endtoendtest: 420 return self.payload_filename 421 else: 422 return auto_updater_transfer.ROOTFS_FILENAME 423 424 def ResetStatefulPartition(self): 425 """Clear any pending stateful update request.""" 426 logging.debug('Resetting stateful partition...') 427 try: 428 stateful_updater.StatefulUpdater(self.device).Reset() 429 except stateful_updater.Error as e: 430 raise StatefulUpdateError(e) 431 432 def RevertBootPartition(self): 433 """Revert the boot partition.""" 434 part = self.GetRootDev(self.device) 435 logging.warning('Reverting update; Boot partition will be %s', part) 436 try: 437 self.device.run(['/postinst', part], **self._cmd_kwargs) 438 except cros_build_lib.RunCommandError as e: 439 logging.warning('Reverting the boot partition failed: %s', e) 440 441 def UpdateRootfs(self): 442 """Update the rootfs partition of the device (utilizing nebraska).""" 443 logging.notice('Updating rootfs partition...') 444 nebraska_bin = os.path.join(self.device_dev_dir, 445 self.REMOTE_NEBRASKA_FILENAME) 446 447 nebraska = nebraska_wrapper.RemoteNebraskaWrapper( 448 self.device, nebraska_bin=nebraska_bin, 449 update_payloads_address='file://' + self.device_payload_dir, 450 update_metadata_dir=self.device_payload_dir, 451 ignore_appid=self._ignore_appid) 452 453 try: 454 nebraska.Start() 455 456 # Use the localhost IP address (default) to ensure that update engine 457 # client can connect to the nebraska. 458 nebraska_url = nebraska.GetURL(critical_update=True) 459 cmd = [self.REMOTE_UPDATE_ENGINE_BIN_FILENAME, '--check_for_update', 460 '--omaha_url="%s"' % nebraska_url] 461 462 self.device.run(cmd, **self._cmd_kwargs) 463 464 # If we are using a progress bar, update it every 0.5s instead of 10s. 465 if command.UseProgressBar(): 466 update_check_interval = self.UPDATE_CHECK_INTERVAL_PROGRESSBAR 467 oper = operation.ProgressBarOperation() 468 else: 469 update_check_interval = self.UPDATE_CHECK_INTERVAL_NORMAL 470 oper = None 471 end_message_not_printed = True 472 473 # Loop until update is complete. 474 while True: 475 # Number of times to retry `update_engine_client --status`. See 476 # crbug.com/744212. 477 update_engine_status_retry = 30 478 op, progress = retry_util.RetryException( 479 cros_build_lib.RunCommandError, 480 update_engine_status_retry, 481 self.GetUpdateStatus, 482 self.device, 483 ['CURRENT_OP', 'PROGRESS'], 484 delay_sec=DELAY_SEC_FOR_RETRY)[0:2] 485 logging.info('Waiting for update...status: %s at progress %s', 486 op, progress) 487 488 if op == UPDATE_STATUS_UPDATED_NEED_REBOOT: 489 logging.info('Update completed.') 490 break 491 492 if op == UPDATE_STATUS_IDLE: 493 # Something went wrong. Try to get last error code. 494 cmd = ['cat', self.REMOTE_UPDATE_ENGINE_LOGFILE_PATH] 495 log = self.device.run(cmd).stdout.strip().splitlines() 496 err_str = 'Updating payload state for error code: ' 497 targets = [line for line in log if err_str in line] 498 logging.debug('Error lines found: %s', targets) 499 if not targets: 500 raise RootfsUpdateError( 501 'Update failed with unexpected update status: %s' % op) 502 else: 503 # e.g 20 (ErrorCode::kDownloadStateInitializationError) 504 raise RootfsUpdateError(targets[-1].rpartition(err_str)[2]) 505 506 if oper is not None: 507 if op == UPDATE_STATUS_DOWNLOADING: 508 oper.ProgressBar(float(progress)) 509 elif end_message_not_printed and op == UPDATE_STATUS_FINALIZING: 510 oper.Cleanup() 511 logging.info('Finalizing image.') 512 end_message_not_printed = False 513 514 time.sleep(update_check_interval) 515 # TODO(ahassani): Scope the Exception to finer levels. For example we don't 516 # need to revert the boot partition if the Nebraska fails to start, etc. 517 except Exception as e: 518 logging.error('Rootfs update failed %s', e) 519 self.RevertBootPartition() 520 logging.warning(nebraska.PrintLog() or 'No nebraska log is available.') 521 raise RootfsUpdateError('Failed to perform rootfs update: %r' % e) 522 finally: 523 nebraska.Stop() 524 525 nebraska.CollectLogs(os.path.join(self.tempdir, 526 self.LOCAL_NEBRASKA_LOG_FILENAME)) 527 self.device.CopyFromDevice( 528 self.REMOTE_UPDATE_ENGINE_LOGFILE_PATH, 529 os.path.join(self.tempdir, os.path.basename( 530 self.REMOTE_UPDATE_ENGINE_LOGFILE_PATH)), 531 follow_symlinks=True, **self._cmd_kwargs_omit_error) 532 533 def UpdateStateful(self): 534 """Update the stateful partition of the device.""" 535 try: 536 stateful_update_payload = os.path.join( 537 self.device.work_dir, auto_updater_transfer.STATEFUL_FILENAME) 538 539 updater = stateful_updater.StatefulUpdater(self.device) 540 updater.Update( 541 stateful_update_payload, 542 update_type=(stateful_updater.StatefulUpdater.UPDATE_TYPE_CLOBBER if 543 self._clobber_stateful else None)) 544 545 # Delete the stateful update file on success so it doesn't occupy extra 546 # disk space. On failure it will get cleaned up. 547 self.device.DeletePath(stateful_update_payload) 548 except stateful_updater.Error as e: 549 error_msg = 'Stateful update failed with error: %s' % str(e) 550 logging.exception(error_msg) 551 self.ResetStatefulPartition() 552 raise StatefulUpdateError(error_msg) 553 554 def _FixPayloadPropertiesFile(self): 555 """Fix the update payload properties file so nebraska can use it. 556 557 Update the payload properties file to make sure that nebraska can use it. 558 The reason is that very old payloads are still being used for provisioning 559 the AU tests, but those properties files are not compatible with recent 560 nebraska protocols. 561 562 TODO(ahassani): Once we only test delta or full payload with 563 source image of M77 or higher, this function can be deprecated. 564 """ 565 logging.info('Fixing payload properties file.') 566 payload_properties_path = self._transfer_obj.GetPayloadPropsFile() 567 props = json.loads(osutils.ReadFile(payload_properties_path)) 568 props['appid'] = self.ResolveAPPIDMismatchIfAny(props.get('appid')) 569 values = self._transfer_obj.GetPayloadProps() 570 571 # TODO(ahassani): Use the keys form nebraska.py once it is moved to 572 # chromite. 573 valid_entries = { 574 # Since only old payloads don't have this and they are only used for 575 # provisioning, they will be full payloads. 576 'is_delta': False, 577 'size': values['size'], 578 'target_version': values['image_version'], 579 } 580 581 for key, value in valid_entries.items(): 582 if props.get(key) is None: 583 props[key] = value 584 585 with open(payload_properties_path, 'w') as fp: 586 json.dump(props, fp) 587 588 def RunUpdateRootfs(self): 589 """Run all processes needed by updating rootfs. 590 591 1. Check device's status to make sure it can be updated. 592 2. Copy files to remote device needed for rootfs update. 593 3. Do root updating. 594 """ 595 596 # Any call to self._transfer_obj.TransferRootfsUpdate() must be preceeded by 597 # a conditional call to self._FixPayloadPropertiesFile() as this handles the 598 # usecase in reported in crbug.com/1012520. Whenever 599 # self._FixPayloadPropertiesFile() gets deprecated, this call can be safely 600 # removed. For more details on TODOs, refer to self.TransferRootfsUpdate() 601 # docstrings. 602 603 self._FixPayloadPropertiesFile() 604 605 # SetupRootfsUpdate() may reboot the device and therefore should be called 606 # before any payloads are transferred to the device and only if rootfs 607 # update is required. 608 self.SetupRootfsUpdate() 609 610 # Copy payload for rootfs update. 611 self._transfer_obj.TransferRootfsUpdate() 612 613 self.UpdateRootfs() 614 615 if self.is_au_endtoendtest: 616 self.PostCheckRootfsUpdate() 617 618 # Delete the update file so it doesn't take much space on disk for the 619 # remainder of the update process. 620 self.device.DeletePath(self.device_payload_dir, recursive=True) 621 622 def RunUpdateStateful(self): 623 """Run all processes needed by updating stateful. 624 625 1. Copy files to remote device needed by stateful update. 626 2. Do stateful update. 627 """ 628 self._transfer_obj.TransferStatefulUpdate() 629 self.UpdateStateful() 630 631 def RebootAndVerify(self): 632 """Reboot and verify the remote device. 633 634 1. Reboot the remote device. If _clobber_stateful (--clobber-stateful) 635 is executed, the stateful partition is wiped, and the working directory 636 on the remote device no longer exists. So, recreate the working directory 637 for this remote device. 638 2. Verify the remote device, by checking that whether the root device 639 changed after reboot. 640 """ 641 logging.notice('Rebooting device...') 642 # Record the current root device. This must be done after SetupRootfsUpdate 643 # and before reboot, since SetupRootfsUpdate may reboot the device if there 644 # is a pending update, which changes the root device, and reboot will 645 # definitely change the root device if update successfully finishes. 646 old_root_dev = self.GetRootDev(self.device) 647 self.device.Reboot() 648 if self._clobber_stateful: 649 self.device.run(['mkdir', '-p', self.device.work_dir]) 650 651 if self._do_rootfs_update: 652 logging.notice('Verifying that the device has been updated...') 653 new_root_dev = self.GetRootDev(self.device) 654 if old_root_dev is None: 655 raise AutoUpdateVerifyError( 656 'Failed to locate root device before update.') 657 658 if new_root_dev is None: 659 raise AutoUpdateVerifyError( 660 'Failed to locate root device after update.') 661 662 if new_root_dev == old_root_dev: 663 raise AutoUpdateVerifyError( 664 'Failed to boot into the new version. Possibly there was a ' 665 'signing problem, or an automated rollback occurred because ' 666 'your new image failed to boot.') 667 668 def ResolveAPPIDMismatchIfAny(self, payload_app_id): 669 """Resolves and APP ID mismatch between the payload and device. 670 671 If the APP ID of the payload is different than the device, then the nebraska 672 will fail. We empty the payload's AppID so nebraska can do partial APP ID 673 matching. 674 """ 675 if ((self.device.app_id and self.device.app_id == payload_app_id) or 676 payload_app_id == ''): 677 return payload_app_id 678 679 logging.warning('You are installing an image with a different release ' 680 'App ID than the device (%s vs %s), we are forcing the ' 681 'install!', payload_app_id, self.device.app_id) 682 return '' 683 684 def RunUpdate(self): 685 """Update the device with image of specific version.""" 686 self._transfer_obj.CheckPayloads() 687 688 self._transfer_obj.TransferUpdateUtilsPackage() 689 690 restore_stateful = self.CheckRestoreStateful() 691 if restore_stateful: 692 self.RestoreStateful() 693 694 # Perform device updates. 695 if self._do_rootfs_update: 696 self.RunUpdateRootfs() 697 logging.info('Rootfs update completed.') 698 699 if self._do_stateful_update and not restore_stateful: 700 self.RunUpdateStateful() 701 logging.info('Stateful update completed.') 702 703 if self._clear_tpm_owner: 704 self.SetClearTpmOwnerRequest() 705 706 if self._reboot: 707 self.RebootAndVerify() 708 709 if self.is_au_endtoendtest: 710 self.PostCheckCrOSUpdate() 711 712 if self._disable_verification: 713 logging.info('Disabling rootfs verification on the device...') 714 self.device.DisableRootfsVerification() 715 716 def _Reboot(self, error_stage, timeout=None): 717 try: 718 if timeout is None: 719 timeout = self.REBOOT_TIMEOUT 720 self.device.Reboot(timeout_sec=timeout) 721 except cros_build_lib.DieSystemExit: 722 raise ChromiumOSUpdateError('Could not recover from reboot at %s' % 723 error_stage) 724 except remote_access.SSHConnectionError: 725 raise ChromiumOSUpdateError('Failed to connect at %s' % error_stage) 726 727 def _cgpt(self, flag, kernel, dev='$(rootdev -s -d)'): 728 """Return numeric cgpt value for the specified flag, kernel, device.""" 729 cmd = ['cgpt', 'show', '-n', '-i', '%d' % kernel['kernel'], flag, dev] 730 return int(self._RetryCommand( 731 cmd, capture_output=True, log_output=True).output.strip()) 732 733 def _GetKernelPriority(self, kernel): 734 """Return numeric priority for the specified kernel. 735 736 Args: 737 kernel: information of the given kernel, KERNEL_A or KERNEL_B. 738 """ 739 return self._cgpt('-P', kernel) 740 741 def _GetKernelSuccess(self, kernel): 742 """Return boolean success flag for the specified kernel. 743 744 Args: 745 kernel: information of the given kernel, KERNEL_A or KERNEL_B. 746 """ 747 return self._cgpt('-S', kernel) != 0 748 749 def _GetKernelTries(self, kernel): 750 """Return tries count for the specified kernel. 751 752 Args: 753 kernel: information of the given kernel, KERNEL_A or KERNEL_B. 754 """ 755 return self._cgpt('-T', kernel) 756 757 def _GetKernelState(self): 758 """Returns the (<active>, <inactive>) kernel state as a pair.""" 759 active_root = int(re.findall(r'(\d+\Z)', self.GetRootDev(self.device))[0]) 760 if active_root == self.KERNEL_A['root']: 761 return self.KERNEL_A, self.KERNEL_B 762 elif active_root == self.KERNEL_B['root']: 763 return self.KERNEL_B, self.KERNEL_A 764 else: 765 raise ChromiumOSUpdateError('Encountered unknown root partition: %s' % 766 active_root) 767 768 def _GetReleaseVersion(self): 769 """Get release version of the device.""" 770 lsb_release_content = self._RetryCommand( 771 ['cat', '/etc/lsb-release'], 772 capture_output=True, log_output=True).output.strip() 773 regex = r'^CHROMEOS_RELEASE_VERSION=(.+)$' 774 return auto_update_util.GetChromeosBuildInfo( 775 lsb_release_content=lsb_release_content, regex=regex) 776 777 def _GetReleaseBuilderPath(self): 778 """Get release version of the device.""" 779 lsb_release_content = self._RetryCommand( 780 ['cat', '/etc/lsb-release'], 781 capture_output=True, log_output=True).output.strip() 782 regex = r'^CHROMEOS_RELEASE_BUILDER_PATH=(.+)$' 783 return auto_update_util.GetChromeosBuildInfo( 784 lsb_release_content=lsb_release_content, regex=regex) 785 786 def CheckVersion(self): 787 """Check the image running in DUT has the expected version. 788 789 Returns: 790 True if the DUT's image version matches the version that the 791 ChromiumOSUpdater tries to update to. 792 """ 793 if not self.update_version: 794 return False 795 796 # Use CHROMEOS_RELEASE_BUILDER_PATH to match the build version if it exists 797 # in lsb-release, otherwise, continue using CHROMEOS_RELEASE_VERSION. 798 release_builder_path = self._GetReleaseBuilderPath() 799 if release_builder_path: 800 return self.update_version == release_builder_path 801 802 return self.update_version.endswith(self._GetReleaseVersion()) 803 804 def _VerifyBootExpectations(self, expected_kernel_state, rollback_message): 805 """Verify that we fully booted given expected kernel state. 806 807 It verifies that we booted using the correct kernel state, and that the 808 OS has marked the kernel as good. 809 810 Args: 811 expected_kernel_state: kernel state that we're verifying with i.e. I 812 expect to be booted onto partition 4 etc. See output of _GetKernelState. 813 rollback_message: string to raise as a RootfsUpdateError if we booted 814 with the wrong partition. 815 """ 816 logging.debug('Start verifying boot expectations...') 817 # Figure out the newly active kernel 818 active_kernel_state = self._GetKernelState()[0] 819 820 # Rollback 821 if (expected_kernel_state and 822 active_kernel_state != expected_kernel_state): 823 logging.debug('Dumping partition table.') 824 self.device.run(['cgpt', 'show', '$(rootdev -s -d)'], 825 **self._cmd_kwargs) 826 logging.debug('Dumping crossystem for firmware debugging.') 827 self.device.run(['crossystem', '--all'], **self._cmd_kwargs) 828 raise RootfsUpdateError(rollback_message) 829 830 # Make sure chromeos-setgoodkernel runs 831 try: 832 timeout_util.WaitForReturnTrue( 833 lambda: (self._GetKernelTries(active_kernel_state) == 0 834 and self._GetKernelSuccess(active_kernel_state)), 835 self.KERNEL_UPDATE_TIMEOUT, 836 period=5) 837 except timeout_util.TimeoutError: 838 services_status = self.device.run( 839 ['status', 'system-services'], capture_output=True, 840 log_output=True).output 841 logging.debug('System services_status: %r', services_status) 842 if services_status != 'system-services start/running\n': 843 event = ('Chrome failed to reach login screen') 844 else: 845 event = ('update-engine failed to call ' 846 'chromeos-setgoodkernel') 847 raise RootfsUpdateError( 848 'After update and reboot, %s ' 849 'within %d seconds' % (event, self.KERNEL_UPDATE_TIMEOUT)) 850 851 def _CheckVersionToConfirmInstall(self): 852 logging.debug('Checking whether the new build is successfully installed...') 853 if not self.update_version: 854 logging.debug('No update_version is provided if test is executed with' 855 'local nebraska.') 856 return True 857 858 # Always try the default check_version method first, this prevents 859 # any backward compatibility issue. 860 if self.CheckVersion(): 861 return True 862 863 return auto_update_util.VersionMatch( 864 self.update_version, self._GetReleaseVersion()) 865 866 def _RetryCommand(self, cmd, **kwargs): 867 """Retry commands if SSHConnectionError happens. 868 869 Args: 870 cmd: the command to be run by device. 871 kwargs: the parameters for device to run the command. 872 873 Returns: 874 the output of running the command. 875 """ 876 return retry_util.RetryException( 877 remote_access.SSHConnectionError, 878 MAX_RETRY, 879 self.device.run, 880 cmd, delay_sec=DELAY_SEC_FOR_RETRY, 881 shell=isinstance(cmd, six.string_types), 882 **kwargs) 883 884 def PreSetupStatefulUpdate(self): 885 """Pre-setup for stateful update for CrOS host.""" 886 logging.debug('Start pre-setup for stateful update...') 887 if self._clobber_stateful: 888 for folder in self.REMOTE_STATEFUL_PATH_TO_CHECK: 889 touch_path = os.path.join(folder, self.REMOTE_STATEFUL_TEST_FILENAME) 890 self._RetryCommand(['touch', touch_path], **self._cmd_kwargs) 891 892 def PostCheckStatefulUpdate(self): 893 """Post-check for stateful update for CrOS host.""" 894 logging.debug('Start post check for stateful update...') 895 self._Reboot('post check of stateful update') 896 if self._clobber_stateful: 897 for folder in self.REMOTE_STATEFUL_PATH_TO_CHECK: 898 test_file_path = os.path.join(folder, 899 self.REMOTE_STATEFUL_TEST_FILENAME) 900 # If stateful update succeeds, these test files should not exist. 901 if self.device.IfFileExists(test_file_path, 902 **self._cmd_kwargs_omit_error): 903 raise StatefulUpdateError('failed to post-check stateful update.') 904 905 def _IsUpdateUtilsPackageInstalled(self): 906 """Check whether update-utils package is well installed. 907 908 There's a chance that nebraska package is removed in the middle of 909 auto-update process. This function double check it and transfer it if it's 910 removed. 911 """ 912 logging.info('Checking whether nebraska files are still on the device...') 913 try: 914 nebraska_bin = os.path.join(self.device_dev_dir, 915 self.REMOTE_NEBRASKA_FILENAME) 916 if not self.device.IfFileExists( 917 nebraska_bin, **self._cmd_kwargs_omit_error): 918 logging.info('Nebraska files not found on device. Resending them...') 919 920 self._transfer_obj.TransferUpdateUtilsPackage() 921 922 return True 923 except cros_build_lib.RunCommandError as e: 924 logging.warning('Failed to verify whether packages still exist: %s', e) 925 return False 926 927 def CheckNebraskaCanRun(self): 928 """Check if nebraska can successfully run for ChromiumOSUpdater.""" 929 self._IsUpdateUtilsPackageInstalled() 930 self._CheckNebraskaCanRun() 931 932 def RestoreStateful(self): 933 """Restore stateful partition for device.""" 934 logging.warning('Restoring the stateful partition.') 935 self.PreSetupStatefulUpdate() 936 self._transfer_obj.TransferStatefulUpdate() 937 self.ResetStatefulPartition() 938 self.UpdateStateful() 939 self.PostCheckStatefulUpdate() 940 try: 941 self.CheckNebraskaCanRun() 942 logging.info('Stateful partition restored.') 943 except nebraska_wrapper.NebraskaStartupError as e: 944 raise ChromiumOSUpdateError( 945 'Unable to restore stateful partition: %s' % e) 946 947 def SetClearTpmOwnerRequest(self): 948 """Set clear_tpm_owner_request flag.""" 949 # The issue is that certain AU tests leave the TPM in a bad state which 950 # most commonly shows up in provisioning. Executing this 'crossystem' 951 # command before rebooting clears the problem state during the reboot. 952 # It's also worth mentioning that this isn't a complete fix: The bad 953 # TPM state in theory might happen some time other than during 954 # provisioning. Also, the bad TPM state isn't supposed to happen at 955 # all; this change is just papering over the real bug. 956 logging.info('Setting clear_tpm_owner_request to 1.') 957 self._RetryCommand('crossystem clear_tpm_owner_request=1', 958 **self._cmd_kwargs_omit_error) 959 960 def PostCheckRootfsUpdate(self): 961 """Post-check for rootfs update for CrOS host.""" 962 logging.debug('Start post check for rootfs update...') 963 active_kernel, inactive_kernel = self._GetKernelState() 964 logging.debug('active_kernel= %s, inactive_kernel=%s', 965 active_kernel, inactive_kernel) 966 if (self._GetKernelPriority(inactive_kernel) < 967 self._GetKernelPriority(active_kernel)): 968 raise RootfsUpdateError('Update failed. The priority of the inactive ' 969 'kernel partition is less than that of the ' 970 'active kernel partition.') 971 self.inactive_kernel = inactive_kernel 972 973 def PostCheckCrOSUpdate(self): 974 """Post check for the whole auto-update process.""" 975 logging.debug('Post check for the whole CrOS update...') 976 start_time = time.time() 977 # Not use 'sh' here since current device.run cannot recognize 978 # the content of $FILE. 979 autoreboot_cmd = ('FILE="%s" ; [ -f "$FILE" ] || ' 980 '( touch "$FILE" ; start autoreboot )') 981 self._RetryCommand(autoreboot_cmd % self.REMOTE_LAB_MACHINE_FILE_PATH, 982 **self._cmd_kwargs) 983 984 # Loop in case the initial check happens before the reboot. 985 while True: 986 try: 987 start_verify_time = time.time() 988 self._VerifyBootExpectations( 989 self.inactive_kernel, rollback_message= 990 'Build %s failed to boot on %s; system rolled back to previous ' 991 'build' % (self.update_version, self.device.hostname)) 992 993 # Check that we've got the build we meant to install. 994 if not self._CheckVersionToConfirmInstall(): 995 raise ChromiumOSUpdateError( 996 'Failed to update %s to build %s; found build ' 997 '%s instead' % (self.device.hostname, 998 self.update_version, 999 self._GetReleaseVersion())) 1000 except RebootVerificationError as e: 1001 # If a minimum amount of time since starting the check has not 1002 # occurred, wait and retry. Use the start of the verification 1003 # time in case an SSH call takes a long time to return/fail. 1004 if start_verify_time - start_time < POST_CHECK_SETTLE_SECONDS: 1005 logging.warning('Delaying for re-check of %s to update to %s (%s)', 1006 self.device.hostname, self.update_version, e) 1007 time.sleep(POST_CHECK_RETRY_SECONDS) 1008 continue 1009 raise 1010 break 1011 1012 if not self._clobber_stateful: 1013 self.PostRebootUpdateCheckForAUTest() 1014 1015 def PostRebootUpdateCheckForAUTest(self): 1016 """Do another update check after reboot to get the post update hostlog. 1017 1018 This is only done with autoupdate_EndToEndTest. 1019 """ 1020 logging.debug('Doing one final update check to get post update hostlog.') 1021 nebraska_bin = os.path.join(self.device_dev_dir, 1022 self.REMOTE_NEBRASKA_FILENAME) 1023 nebraska = nebraska_wrapper.RemoteNebraskaWrapper( 1024 self.device, nebraska_bin=nebraska_bin, 1025 update_metadata_dir=self.device.work_dir) 1026 1027 try: 1028 nebraska.Start() 1029 1030 nebraska_url = nebraska.GetURL(critical_update=True, no_update=True) 1031 cmd = [self.REMOTE_UPDATE_ENGINE_BIN_FILENAME, '--check_for_update', 1032 '--omaha_url="%s"' % nebraska_url] 1033 self.device.run(cmd, **self._cmd_kwargs) 1034 op = self.GetUpdateStatus(self.device)[0] 1035 logging.info('Post update check status: %s', op) 1036 except Exception as err: 1037 logging.error('Post reboot update check failed: %s', str(err)) 1038 logging.warning(nebraska.PrintLog() or 'No nebraska log is available.') 1039 finally: 1040 nebraska.Stop() 1041