blob: a8c09789ebe4af68c9c5252b39235780de379ce9 [file] [log] [blame]
xixuana4f4e712017-05-08 15:17:54 -07001# Copyright 2016 The Chromium OS Authors. All rights reserved.
xixuan52c2fba2016-05-20 17:02:48 -07002# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""An executable function cros-update for auto-update of a CrOS host.
6
7The reason to create this file is to let devserver to trigger a background
8process for CrOS auto-update. Therefore, when devserver service is restarted
9sometimes, the CrOS auto-update process is still running and the corresponding
10provision task won't claim failure.
11
12It includes two classes:
13 a. CrOSUpdateTrigger:
14 1. Includes all logics which identify which types of update need to be
15 performed in the current DUT.
16 2. Responsible for write current status of CrOS auto-update process into
17 progress_tracker.
18
19 b. CrOSAUParser:
20 1. Pre-setup the required args for CrOS auto-update.
21 2. Parse the input parameters for cmd that runs 'cros_update.py'.
22"""
23
24from __future__ import print_function
25
26import argparse
27import cros_update_logging
28import cros_update_progress
29import logging
30import os
31import sys
xixuan28d99072016-10-06 12:24:16 -070032import traceback
xixuan52c2fba2016-05-20 17:02:48 -070033
xixuancf58dd32016-08-24 13:57:06 -070034# only import setup_chromite before chromite import.
35import setup_chromite # pylint: disable=unused-import
xixuan52c2fba2016-05-20 17:02:48 -070036try:
37 from chromite.lib import auto_updater
38 from chromite.lib import remote_access
39 from chromite.lib import timeout_util
40except ImportError as e:
41 logging.debug('chromite cannot be imported: %r', e)
42 auto_updater = None
43 remote_access = None
xixuancf58dd32016-08-24 13:57:06 -070044 timeout_util = None
xixuan52c2fba2016-05-20 17:02:48 -070045
xixuanac89ce82016-11-30 16:48:20 -080046# The build channel for recovering host's stateful partition
47STABLE_BUILD_CHANNEL = 'stable-channel'
48
xixuan52c2fba2016-05-20 17:02:48 -070049# Timeout for CrOS auto-update process.
50CROS_UPDATE_TIMEOUT_MIN = 30
51
52# The preserved path in remote device, won't be deleted after rebooting.
53CROS_PRESERVED_PATH = ('/mnt/stateful_partition/unencrypted/'
54 'preserve/cros-update')
55
56# Standard error tmeplate to be written into status tracking log.
xixuan28d99072016-10-06 12:24:16 -070057CROS_ERROR_TEMPLATE = cros_update_progress.ERROR_TAG + ' %s'
xixuan52c2fba2016-05-20 17:02:48 -070058
xixuan27d50442017-08-09 10:38:25 -070059# Setting logging level
60logConfig = cros_update_logging.loggingConfig()
61logConfig.ConfigureLogging()
xixuan52c2fba2016-05-20 17:02:48 -070062
63class CrOSAUParser(object):
64 """Custom command-line options parser for cros-update."""
65 def __init__(self):
66 self.args = sys.argv[1:]
67 self.parser = argparse.ArgumentParser(
68 usage='%(prog)s [options] [control-file]')
69 self.SetupOptions()
70 self.removed_args = []
71
72 # parse an empty list of arguments in order to set self.options
73 # to default values.
74 self.options = self.parser.parse_args(args=[])
75
76 def SetupOptions(self):
77 """Setup options to call cros-update command."""
78 self.parser.add_argument('-d', action='store', type=str,
79 dest='host_name',
80 help='host_name of a DUT')
81 self.parser.add_argument('-b', action='store', type=str,
82 dest='build_name',
83 help='build name to be auto-updated')
84 self.parser.add_argument('--static_dir', action='store', type=str,
85 dest='static_dir',
86 help='static directory of the devserver')
87 self.parser.add_argument('--force_update', action='store_true',
88 dest='force_update', default=False,
89 help=('force an update even if the version '
90 'installed is the same'))
91 self.parser.add_argument('--full_update', action='store_true',
92 dest='full_update', default=False,
93 help=('force a rootfs update, skip stateful '
94 'update'))
xixuanac89ce82016-11-30 16:48:20 -080095 self.parser.add_argument('--original_build', action='store', type=str,
96 dest='original_build', default='',
97 help=('force stateful update with the same '
98 'version of previous rootfs partition'))
David Haddock90e49442017-04-07 19:14:09 -070099 self.parser.add_argument('--payload_filename', action='store', type=str,
Richard Barnettebff69702017-07-17 14:34:48 -0700100 dest='payload_filename', default=None,
David Haddock90e49442017-04-07 19:14:09 -0700101 help='A custom payload filename')
David Haddock20559612017-06-28 22:15:08 -0700102 self.parser.add_argument('--clobber_stateful', action='store_true',
103 dest='clobber_stateful', default=False,
104 help='Whether to clobber stateful')
xixuan52c2fba2016-05-20 17:02:48 -0700105
106 def ParseArgs(self):
107 """Parse and process command line arguments."""
108 # Positional arguments from the end of the command line will be included
109 # in the list of unknown_args.
110 self.options, unknown_args = self.parser.parse_known_args()
111 # Filter out none-positional arguments
112 while unknown_args and unknown_args[0][0] == '-':
113 self.removed_args.append(unknown_args.pop(0))
114 # Always assume the argument has a value.
115 if unknown_args:
116 self.removed_args.append(unknown_args.pop(0))
117 if self.removed_args:
118 logging.warn('Unknown arguments are removed from the options: %s',
119 self.removed_args)
120
121
122class CrOSUpdateTrigger(object):
123 """The class for CrOS auto-updater trigger.
124
125 This class is used for running all CrOS auto-update trigger logic.
126 """
127 def __init__(self, host_name, build_name, static_dir, progress_tracker=None,
xixuan3bc974e2016-10-18 17:21:43 -0700128 log_file=None, au_tempdir=None, force_update=False,
David Haddock20559612017-06-28 22:15:08 -0700129 full_update=False, original_build=None, payload_filename=None,
130 clobber_stateful=True):
xixuan52c2fba2016-05-20 17:02:48 -0700131 self.host_name = host_name
132 self.build_name = build_name
133 self.static_dir = static_dir
134 self.progress_tracker = progress_tracker
135 self.log_file = log_file
xixuan3bc974e2016-10-18 17:21:43 -0700136 self.au_tempdir = au_tempdir
xixuan52c2fba2016-05-20 17:02:48 -0700137 self.force_update = force_update
138 self.full_update = full_update
xixuanac89ce82016-11-30 16:48:20 -0800139 self.original_build = original_build
David Haddock90e49442017-04-07 19:14:09 -0700140 self.payload_filename = payload_filename
David Haddock20559612017-06-28 22:15:08 -0700141 self.clobber_stateful = clobber_stateful
xixuan52c2fba2016-05-20 17:02:48 -0700142
143 def _WriteAUStatus(self, content):
144 if self.progress_tracker:
145 self.progress_tracker.WriteStatus(content)
146
147 def _StatefulUpdate(self, cros_updater):
148 """The detailed process in stateful update.
149
150 Args:
151 cros_updater: The CrOS auto updater for auto-update.
152 """
153 self._WriteAUStatus('pre-setup stateful update')
154 cros_updater.PreSetupStatefulUpdate()
155 self._WriteAUStatus('perform stateful update')
156 cros_updater.UpdateStateful()
157 self._WriteAUStatus('post-check stateful update')
158 cros_updater.PostCheckStatefulUpdate()
159
160 def _RootfsUpdate(self, cros_updater):
161 """The detailed process in rootfs update.
162
163 Args:
164 cros_updater: The CrOS auto updater for auto-update.
165 """
xixuana4f4e712017-05-08 15:17:54 -0700166 self._WriteAUStatus('Check whether devserver can run before rootfs update')
167 cros_updater.CheckDevserverRun()
xixuan52c2fba2016-05-20 17:02:48 -0700168 self._WriteAUStatus('transfer rootfs update package')
169 cros_updater.TransferRootfsUpdate()
170 self._WriteAUStatus('pre-setup rootfs update')
171 cros_updater.PreSetupRootfsUpdate()
172 self._WriteAUStatus('rootfs update')
173 cros_updater.UpdateRootfs()
174 self._WriteAUStatus('post-check rootfs update')
175 cros_updater.PostCheckRootfsUpdate()
176
xixuanac89ce82016-11-30 16:48:20 -0800177 def _GetOriginalPayloadDir(self):
178 """Get the directory of original payload.
179
180 Returns:
181 The directory of original payload, whose format is like:
182 'static/stable-channel/link/3428.210.0'
183 """
184 if self.original_build:
185 return os.path.join(self.static_dir, '%s/%s' % (STABLE_BUILD_CHANNEL,
186 self.original_build))
187 else:
188 return None
189
xixuan52c2fba2016-05-20 17:02:48 -0700190 def TriggerAU(self):
191 """Execute auto update for cros_host.
192
193 The auto update includes 4 steps:
194 1. if devserver cannot run, restore the stateful partition.
xixuan2aca0ac2016-07-29 12:02:06 -0700195 2. if possible, do stateful update first, but never raise errors, except
196 for timeout_util.TimeoutError caused by system.signal.
xixuan52c2fba2016-05-20 17:02:48 -0700197 3. If required or stateful_update fails, first do rootfs update, then do
198 stateful_update.
199 4. Post-check for the whole update.
200 """
201 try:
202 with remote_access.ChromiumOSDeviceHandler(
203 self.host_name, port=None,
204 base_dir=CROS_PRESERVED_PATH,
Luigi Semenzato8db8b3c2017-01-26 18:02:19 -0800205 ping=False) as device:
xixuan52c2fba2016-05-20 17:02:48 -0700206
207 logging.debug('Remote device %s is connected', self.host_name)
208 payload_dir = os.path.join(self.static_dir, self.build_name)
xixuanac89ce82016-11-30 16:48:20 -0800209 original_payload_dir = self._GetOriginalPayloadDir()
210
xixuan52c2fba2016-05-20 17:02:48 -0700211 chromeos_AU = auto_updater.ChromiumOSUpdater(
xixuan2a0970a2016-08-10 12:12:44 -0700212 device, self.build_name, payload_dir,
213 dev_dir=os.path.abspath(os.path.dirname(__file__)),
xixuan3bc974e2016-10-18 17:21:43 -0700214 tempdir=self.au_tempdir,
xixuan2a0970a2016-08-10 12:12:44 -0700215 log_file=self.log_file,
xixuanac89ce82016-11-30 16:48:20 -0800216 original_payload_dir=original_payload_dir,
David Haddock90e49442017-04-07 19:14:09 -0700217 yes=True,
David Haddock20559612017-06-28 22:15:08 -0700218 payload_filename=self.payload_filename,
219 clobber_stateful=self.clobber_stateful)
xixuan52c2fba2016-05-20 17:02:48 -0700220 chromeos_AU.CheckPayloads()
221
xixuanfdfd1e32016-10-06 14:55:17 -0700222 version_match = chromeos_AU.PreSetupCrOSUpdate()
xixuan52c2fba2016-05-20 17:02:48 -0700223 self._WriteAUStatus('Transfer Devserver/Stateful Update Package')
224 chromeos_AU.TransferDevServerPackage()
225 chromeos_AU.TransferStatefulUpdate()
226
227 restore_stateful = chromeos_AU.CheckRestoreStateful()
228 do_stateful_update = (not self.full_update) and (
xixuanfdfd1e32016-10-06 14:55:17 -0700229 version_match and self.force_update)
xixuan52c2fba2016-05-20 17:02:48 -0700230 stateful_update_complete = False
231 logging.debug('Start CrOS update process...')
232 try:
233 if restore_stateful:
234 self._WriteAUStatus('Restore Stateful Partition')
235 chromeos_AU.RestoreStateful()
236 stateful_update_complete = True
237 else:
238 # Whether to execute stateful update depends on:
239 # a. full_update=False: No full reimage is required.
240 # b. The update version is matched to the current version, And
241 # force_update=True: Update is forced even if the version
242 # installed is the same.
243 if do_stateful_update:
244 self._StatefulUpdate(chromeos_AU)
245 stateful_update_complete = True
246
xixuan2aca0ac2016-07-29 12:02:06 -0700247 except timeout_util.TimeoutError:
248 raise
xixuan52c2fba2016-05-20 17:02:48 -0700249 except Exception as e:
250 logging.debug('Error happens in stateful update: %r', e)
251
252 # Whether to execute rootfs update depends on:
253 # a. stateful update is not completed, or completed by
254 # update action 'restore_stateful'.
255 # b. force_update=True: Update is forced no matter what the current
256 # version is. Or, the update version is not matched to the current
257 # version.
258 require_rootfs_update = self.force_update or (
259 not chromeos_AU.CheckVersion())
260 if (not (do_stateful_update and stateful_update_complete)
261 and require_rootfs_update):
262 self._RootfsUpdate(chromeos_AU)
263 self._StatefulUpdate(chromeos_AU)
264
265 self._WriteAUStatus('post-check for CrOS auto-update')
266 chromeos_AU.PostCheckCrOSUpdate()
267 self._WriteAUStatus(cros_update_progress.FINISHED)
268 except Exception as e:
269 logging.debug('Error happens in CrOS auto-update: %r', e)
xixuan28d99072016-10-06 12:24:16 -0700270 self._WriteAUStatus(CROS_ERROR_TEMPLATE % str(traceback.format_exc()))
xixuan52c2fba2016-05-20 17:02:48 -0700271 raise
272
273
274def main():
xixuan52c2fba2016-05-20 17:02:48 -0700275 # Create one cros_update_parser instance for parsing CrOS auto-update cmd.
276 AU_parser = CrOSAUParser()
277 try:
278 AU_parser.ParseArgs()
279 except Exception as e:
280 logging.error('Error in Parsing Args: %r', e)
281 raise
282
283 if len(sys.argv) == 1:
284 AU_parser.parser.print_help()
285 sys.exit(1)
286
xixuanac89ce82016-11-30 16:48:20 -0800287 options = AU_parser.options
xixuan52c2fba2016-05-20 17:02:48 -0700288
xixuan2a0970a2016-08-10 12:12:44 -0700289 # Use process group id as the unique id in track and log files, since
290 # os.setsid is executed before the current process is run.
xixuan52c2fba2016-05-20 17:02:48 -0700291 pid = os.getpid()
xixuan2a0970a2016-08-10 12:12:44 -0700292 pgid = os.getpgid(pid)
xixuan52c2fba2016-05-20 17:02:48 -0700293
294 # Setting log files for CrOS auto-update process.
295 # Log file: file to record every details of CrOS auto-update process.
xixuanac89ce82016-11-30 16:48:20 -0800296 log_file = cros_update_progress.GetExecuteLogFile(options.host_name, pgid)
xixuan52c2fba2016-05-20 17:02:48 -0700297 logging.info('Writing executing logs into file: %s', log_file)
298 logConfig.SetFileHandler(log_file)
299
300 # Create a progress_tracker for tracking CrOS auto-update progress.
xixuanac89ce82016-11-30 16:48:20 -0800301 progress_tracker = cros_update_progress.AUProgress(options.host_name, pgid)
xixuan52c2fba2016-05-20 17:02:48 -0700302
xixuan3bc974e2016-10-18 17:21:43 -0700303 # Create a dir for temporarily storing devserver codes and logs.
xixuanac89ce82016-11-30 16:48:20 -0800304 au_tempdir = cros_update_progress.GetAUTempDirectory(options.host_name, pgid)
xixuan3bc974e2016-10-18 17:21:43 -0700305
xixuan52c2fba2016-05-20 17:02:48 -0700306 # Create cros_update instance to run CrOS auto-update.
xixuanac89ce82016-11-30 16:48:20 -0800307 cros_updater_trigger = CrOSUpdateTrigger(
308 options.host_name, options.build_name, options.static_dir,
309 progress_tracker=progress_tracker,
310 log_file=log_file,
311 au_tempdir=au_tempdir,
312 force_update=options.force_update,
313 full_update=options.full_update,
David Haddock90e49442017-04-07 19:14:09 -0700314 original_build=options.original_build,
David Haddock20559612017-06-28 22:15:08 -0700315 payload_filename=options.payload_filename,
316 clobber_stateful=options.clobber_stateful)
xixuan52c2fba2016-05-20 17:02:48 -0700317
318 # Set timeout the cros-update process.
319 try:
320 with timeout_util.Timeout(CROS_UPDATE_TIMEOUT_MIN*60):
321 cros_updater_trigger.TriggerAU()
322 except timeout_util.TimeoutError as e:
323 error_msg = ('%s. The CrOS auto-update process is timed out, thus will be '
324 'terminated' % str(e))
325 progress_tracker.WriteStatus(CROS_ERROR_TEMPLATE % error_msg)
326
327
328if __name__ == '__main__':
329 main()