blob: dfa014157200499943ad4cf81a9f4b1b4c27b294 [file] [log] [blame]
xixuan52c2fba2016-05-20 17:02:48 -07001# Copyright (c) 2016 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"""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
46# Timeout for CrOS auto-update process.
47CROS_UPDATE_TIMEOUT_MIN = 30
48
49# The preserved path in remote device, won't be deleted after rebooting.
50CROS_PRESERVED_PATH = ('/mnt/stateful_partition/unencrypted/'
51 'preserve/cros-update')
52
53# Standard error tmeplate to be written into status tracking log.
xixuan28d99072016-10-06 12:24:16 -070054CROS_ERROR_TEMPLATE = cros_update_progress.ERROR_TAG + ' %s'
xixuan52c2fba2016-05-20 17:02:48 -070055
56
57class CrOSAUParser(object):
58 """Custom command-line options parser for cros-update."""
59 def __init__(self):
60 self.args = sys.argv[1:]
61 self.parser = argparse.ArgumentParser(
62 usage='%(prog)s [options] [control-file]')
63 self.SetupOptions()
64 self.removed_args = []
65
66 # parse an empty list of arguments in order to set self.options
67 # to default values.
68 self.options = self.parser.parse_args(args=[])
69
70 def SetupOptions(self):
71 """Setup options to call cros-update command."""
72 self.parser.add_argument('-d', action='store', type=str,
73 dest='host_name',
74 help='host_name of a DUT')
75 self.parser.add_argument('-b', action='store', type=str,
76 dest='build_name',
77 help='build name to be auto-updated')
78 self.parser.add_argument('--static_dir', action='store', type=str,
79 dest='static_dir',
80 help='static directory of the devserver')
81 self.parser.add_argument('--force_update', action='store_true',
82 dest='force_update', default=False,
83 help=('force an update even if the version '
84 'installed is the same'))
85 self.parser.add_argument('--full_update', action='store_true',
86 dest='full_update', default=False,
87 help=('force a rootfs update, skip stateful '
88 'update'))
89
90 def ParseArgs(self):
91 """Parse and process command line arguments."""
92 # Positional arguments from the end of the command line will be included
93 # in the list of unknown_args.
94 self.options, unknown_args = self.parser.parse_known_args()
95 # Filter out none-positional arguments
96 while unknown_args and unknown_args[0][0] == '-':
97 self.removed_args.append(unknown_args.pop(0))
98 # Always assume the argument has a value.
99 if unknown_args:
100 self.removed_args.append(unknown_args.pop(0))
101 if self.removed_args:
102 logging.warn('Unknown arguments are removed from the options: %s',
103 self.removed_args)
104
105
106class CrOSUpdateTrigger(object):
107 """The class for CrOS auto-updater trigger.
108
109 This class is used for running all CrOS auto-update trigger logic.
110 """
111 def __init__(self, host_name, build_name, static_dir, progress_tracker=None,
112 log_file=None, force_update=False, full_update=False):
113 self.host_name = host_name
114 self.build_name = build_name
115 self.static_dir = static_dir
116 self.progress_tracker = progress_tracker
117 self.log_file = log_file
118 self.force_update = force_update
119 self.full_update = full_update
120
121 def _WriteAUStatus(self, content):
122 if self.progress_tracker:
123 self.progress_tracker.WriteStatus(content)
124
125 def _StatefulUpdate(self, cros_updater):
126 """The detailed process in stateful update.
127
128 Args:
129 cros_updater: The CrOS auto updater for auto-update.
130 """
131 self._WriteAUStatus('pre-setup stateful update')
132 cros_updater.PreSetupStatefulUpdate()
133 self._WriteAUStatus('perform stateful update')
134 cros_updater.UpdateStateful()
135 self._WriteAUStatus('post-check stateful update')
136 cros_updater.PostCheckStatefulUpdate()
137
138 def _RootfsUpdate(self, cros_updater):
139 """The detailed process in rootfs update.
140
141 Args:
142 cros_updater: The CrOS auto updater for auto-update.
143 """
144 self._WriteAUStatus('transfer rootfs update package')
145 cros_updater.TransferRootfsUpdate()
146 self._WriteAUStatus('pre-setup rootfs update')
147 cros_updater.PreSetupRootfsUpdate()
148 self._WriteAUStatus('rootfs update')
149 cros_updater.UpdateRootfs()
150 self._WriteAUStatus('post-check rootfs update')
151 cros_updater.PostCheckRootfsUpdate()
152
153 def TriggerAU(self):
154 """Execute auto update for cros_host.
155
156 The auto update includes 4 steps:
157 1. if devserver cannot run, restore the stateful partition.
xixuan2aca0ac2016-07-29 12:02:06 -0700158 2. if possible, do stateful update first, but never raise errors, except
159 for timeout_util.TimeoutError caused by system.signal.
xixuan52c2fba2016-05-20 17:02:48 -0700160 3. If required or stateful_update fails, first do rootfs update, then do
161 stateful_update.
162 4. Post-check for the whole update.
163 """
164 try:
165 with remote_access.ChromiumOSDeviceHandler(
166 self.host_name, port=None,
167 base_dir=CROS_PRESERVED_PATH,
168 ping=True) as device:
169
170 logging.debug('Remote device %s is connected', self.host_name)
171 payload_dir = os.path.join(self.static_dir, self.build_name)
172 chromeos_AU = auto_updater.ChromiumOSUpdater(
xixuan2a0970a2016-08-10 12:12:44 -0700173 device, self.build_name, payload_dir,
174 dev_dir=os.path.abspath(os.path.dirname(__file__)),
175 log_file=self.log_file,
xixuan52c2fba2016-05-20 17:02:48 -0700176 yes=True)
177 chromeos_AU.CheckPayloads()
178
xixuanfdfd1e32016-10-06 14:55:17 -0700179 version_match = chromeos_AU.PreSetupCrOSUpdate()
xixuan52c2fba2016-05-20 17:02:48 -0700180 self._WriteAUStatus('Transfer Devserver/Stateful Update Package')
181 chromeos_AU.TransferDevServerPackage()
182 chromeos_AU.TransferStatefulUpdate()
183
184 restore_stateful = chromeos_AU.CheckRestoreStateful()
185 do_stateful_update = (not self.full_update) and (
xixuanfdfd1e32016-10-06 14:55:17 -0700186 version_match and self.force_update)
xixuan52c2fba2016-05-20 17:02:48 -0700187 stateful_update_complete = False
188 logging.debug('Start CrOS update process...')
189 try:
190 if restore_stateful:
191 self._WriteAUStatus('Restore Stateful Partition')
192 chromeos_AU.RestoreStateful()
193 stateful_update_complete = True
194 else:
195 # Whether to execute stateful update depends on:
196 # a. full_update=False: No full reimage is required.
197 # b. The update version is matched to the current version, And
198 # force_update=True: Update is forced even if the version
199 # installed is the same.
200 if do_stateful_update:
201 self._StatefulUpdate(chromeos_AU)
202 stateful_update_complete = True
203
xixuan2aca0ac2016-07-29 12:02:06 -0700204 except timeout_util.TimeoutError:
205 raise
xixuan52c2fba2016-05-20 17:02:48 -0700206 except Exception as e:
207 logging.debug('Error happens in stateful update: %r', e)
208
209 # Whether to execute rootfs update depends on:
210 # a. stateful update is not completed, or completed by
211 # update action 'restore_stateful'.
212 # b. force_update=True: Update is forced no matter what the current
213 # version is. Or, the update version is not matched to the current
214 # version.
215 require_rootfs_update = self.force_update or (
216 not chromeos_AU.CheckVersion())
217 if (not (do_stateful_update and stateful_update_complete)
218 and require_rootfs_update):
219 self._RootfsUpdate(chromeos_AU)
220 self._StatefulUpdate(chromeos_AU)
221
222 self._WriteAUStatus('post-check for CrOS auto-update')
223 chromeos_AU.PostCheckCrOSUpdate()
224 self._WriteAUStatus(cros_update_progress.FINISHED)
225 except Exception as e:
226 logging.debug('Error happens in CrOS auto-update: %r', e)
xixuan28d99072016-10-06 12:24:16 -0700227 self._WriteAUStatus(CROS_ERROR_TEMPLATE % str(traceback.format_exc()))
xixuan52c2fba2016-05-20 17:02:48 -0700228 raise
229
230
231def main():
232 # Setting logging level
233 logConfig = cros_update_logging.loggingConfig()
234 logConfig.ConfigureLogging()
235
236 # Create one cros_update_parser instance for parsing CrOS auto-update cmd.
237 AU_parser = CrOSAUParser()
238 try:
239 AU_parser.ParseArgs()
240 except Exception as e:
241 logging.error('Error in Parsing Args: %r', e)
242 raise
243
244 if len(sys.argv) == 1:
245 AU_parser.parser.print_help()
246 sys.exit(1)
247
248 host_name = AU_parser.options.host_name
249 build_name = AU_parser.options.build_name
250 static_dir = AU_parser.options.static_dir
251 force_update = AU_parser.options.force_update
252 full_update = AU_parser.options.full_update
253
xixuan2a0970a2016-08-10 12:12:44 -0700254 # Use process group id as the unique id in track and log files, since
255 # os.setsid is executed before the current process is run.
xixuan52c2fba2016-05-20 17:02:48 -0700256 pid = os.getpid()
xixuan2a0970a2016-08-10 12:12:44 -0700257 pgid = os.getpgid(pid)
xixuan52c2fba2016-05-20 17:02:48 -0700258
259 # Setting log files for CrOS auto-update process.
260 # Log file: file to record every details of CrOS auto-update process.
xixuan2a0970a2016-08-10 12:12:44 -0700261 log_file = cros_update_progress.GetExecuteLogFile(host_name, pgid)
xixuan52c2fba2016-05-20 17:02:48 -0700262 logging.info('Writing executing logs into file: %s', log_file)
263 logConfig.SetFileHandler(log_file)
264
265 # Create a progress_tracker for tracking CrOS auto-update progress.
xixuan2a0970a2016-08-10 12:12:44 -0700266 progress_tracker = cros_update_progress.AUProgress(host_name, pgid)
xixuan52c2fba2016-05-20 17:02:48 -0700267
268 # Create cros_update instance to run CrOS auto-update.
269 cros_updater_trigger = CrOSUpdateTrigger(host_name, build_name, static_dir,
270 progress_tracker=progress_tracker,
271 log_file=log_file,
272 force_update=force_update,
273 full_update=full_update)
274
275 # Set timeout the cros-update process.
276 try:
277 with timeout_util.Timeout(CROS_UPDATE_TIMEOUT_MIN*60):
278 cros_updater_trigger.TriggerAU()
279 except timeout_util.TimeoutError as e:
280 error_msg = ('%s. The CrOS auto-update process is timed out, thus will be '
281 'terminated' % str(e))
282 progress_tracker.WriteStatus(CROS_ERROR_TEMPLATE % error_msg)
283
284
285if __name__ == '__main__':
286 main()