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