blob: 03372e084a0bb6a28028b6c7c49fa4368da04daa [file] [log] [blame]
Don Garrett8db752c2014-10-17 16:56:55 -07001#!/usr/bin/python
2# Copyright (c) 2014 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"""Runs on autotest servers from a cron job to self update them.
7
8This script is designed to run on all autotest servers to allow them to
9automatically self-update based on the manifests used to create their (existing)
10repos.
11"""
12
13from __future__ import print_function
14
15import ConfigParser
Don Garrett03432d62014-11-19 18:18:35 -080016import argparse
Don Garrett8db752c2014-10-17 16:56:55 -070017import os
Don Garrett699b4b32014-12-11 13:10:15 -080018import re
Don Garrett8db752c2014-10-17 16:56:55 -070019import subprocess
20import sys
21import time
22
23import common
24
25from autotest_lib.client.common_lib import global_config
Dan Shiac6fdbf2016-04-09 17:36:28 -070026from autotest_lib.server import utils as server_utils
Dan Shicf278042016-04-06 21:16:34 -070027from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Don Garrett8db752c2014-10-17 16:56:55 -070028
Dan Shiac6fdbf2016-04-09 17:36:28 -070029
Don Garrettd0321722014-11-18 16:03:33 -080030# How long after restarting a service do we watch it to see if it's stable.
Prathmesh Prabhua62f92d2017-03-16 14:30:05 -070031SERVICE_STABILITY_TIMER = 60
Don Garrettd0321722014-11-18 16:03:33 -080032
Shuqian Zhaoa3438a52016-09-20 15:11:02 -070033# A dict to map update_commands defined in config file to repos or files that
34# decide whether need to update these commands. E.g. if no changes under
35# frontend repo, no need to update afe.
Aviv Keshet85622032016-10-03 03:17:26 -070036COMMANDS_TO_REPOS_DICT = {'afe': 'frontend/',
37 'tko': 'tko/'}
Shuqian Zhao697f7ed2016-10-21 14:58:28 -070038BUILD_EXTERNALS_COMMAND = 'build_externals'
Allen Lia2749cd2017-10-31 18:03:19 -070039
40_RESTART_SERVICES_FILE = os.path.join(os.environ['HOME'],
41 'push_restart_services')
Dan Shicf278042016-04-06 21:16:34 -070042
Dan Shiac6fdbf2016-04-09 17:36:28 -070043AFE = frontend_wrappers.RetryingAFE(
44 server=server_utils.get_global_afe_hostname(), timeout_min=5,
45 delay_sec=10)
Don Garrett8db752c2014-10-17 16:56:55 -070046
47class DirtyTreeException(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080048 """Raised when the tree has been modified in an unexpected way."""
Don Garrett8db752c2014-10-17 16:56:55 -070049
50
51class UnknownCommandException(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080052 """Raised when we try to run a command name with no associated command."""
Don Garrett8db752c2014-10-17 16:56:55 -070053
54
55class UnstableServices(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080056 """Raised if a service appears unstable after restart."""
Don Garrett8db752c2014-10-17 16:56:55 -070057
58
Don Garrett35711212014-12-18 14:33:41 -080059def strip_terminal_codes(text):
60 """This function removes all terminal formatting codes from a string.
61
62 @param text: String of text to cleanup.
63 @returns String with format codes removed.
64 """
65 ESC = '\x1b'
66 return re.sub(ESC+r'\[[^m]*m', '', text)
67
68
Richard Barnette488ac8b2017-09-01 09:28:55 -070069def _clean_pyc_files():
70 print('Removing .pyc files')
71 try:
72 subprocess.check_output([
73 'find', '.',
74 '(',
75 # These are ignored to reduce IO load (crbug.com/759780).
76 '-path', './site-packages',
77 '-o', '-path', './containers',
78 '-o', '-path', './logs',
79 '-o', '-path', './results',
80 ')',
81 '-prune',
82 '-o', '-name', '*.pyc',
83 '-exec', 'rm', '-f', '{}', '+'])
84 except Exception as e:
85 print('Warning: fail to remove .pyc! %s' % e)
86
87
Don Garrett8db752c2014-10-17 16:56:55 -070088def verify_repo_clean():
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070089 """This function cleans the current repo then verifies that it is valid.
Don Garrett8db752c2014-10-17 16:56:55 -070090
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070091 @raises DirtyTreeException if the repo is still not clean.
Don Garrett8db752c2014-10-17 16:56:55 -070092 @raises subprocess.CalledProcessError on a repo command failure.
93 """
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070094 subprocess.check_output(['git', 'reset', '--hard'])
Aviv Keshet69700142017-05-03 18:20:29 -070095 # Forcefully blow away any non-gitignored files in the tree.
96 subprocess.check_output(['git', 'clean', '-fd'])
Don Garrett8db752c2014-10-17 16:56:55 -070097 out = subprocess.check_output(['repo', 'status'], stderr=subprocess.STDOUT)
Don Garrett35711212014-12-18 14:33:41 -080098 out = strip_terminal_codes(out).strip()
Don Garrett699b4b32014-12-11 13:10:15 -080099
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700100 if not 'working directory clean' in out:
Dan Shicf278042016-04-06 21:16:34 -0700101 raise DirtyTreeException(out)
Don Garrett8db752c2014-10-17 16:56:55 -0700102
Don Garrett8db752c2014-10-17 16:56:55 -0700103
Aviv Keshetccbf6432017-10-19 12:03:34 -0700104def _clean_externals():
105 """Clean untracked files within ExternalSource and site-packages/
106
107 @raises subprocess.CalledProcessError on a git command failure.
108 """
109 dirs_to_clean = ['site-packages/', 'ExternalSource/']
110 cmd = ['git', 'clean', '-fxd'] + dirs_to_clean
111 subprocess.check_output(cmd)
112
113
Don Garrett8db752c2014-10-17 16:56:55 -0700114def repo_versions():
115 """This function collects the versions of all git repos in the general repo.
116
Don Garrettfa2c1c42014-12-11 12:11:49 -0800117 @returns A dictionary mapping project names to git hashes for HEAD.
Don Garrett8db752c2014-10-17 16:56:55 -0700118 @raises subprocess.CalledProcessError on a repo command failure.
119 """
Don Garrettfa2c1c42014-12-11 12:11:49 -0800120 cmd = ['repo', 'forall', '-p', '-c', 'pwd && git log -1 --format=%h']
Don Garrett35711212014-12-18 14:33:41 -0800121 output = strip_terminal_codes(subprocess.check_output(cmd))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800122
123 # The expected output format is:
124
125 # project chrome_build/
126 # /dir/holding/chrome_build
127 # 73dee9d
128 #
129 # project chrome_release/
130 # /dir/holding/chrome_release
131 # 9f3a5d8
132
133 lines = output.splitlines()
134
135 PROJECT_PREFIX = 'project '
136
137 project_heads = {}
138 for n in range(0, len(lines), 4):
139 project_line = lines[n]
140 project_dir = lines[n+1]
141 project_hash = lines[n+2]
142 # lines[n+3] is a blank line, but doesn't exist for the final block.
143
144 # Convert 'project chrome_build/' -> 'chrome_build'
145 assert project_line.startswith(PROJECT_PREFIX)
146 name = project_line[len(PROJECT_PREFIX):].rstrip('/')
147
148 project_heads[name] = (project_dir, project_hash)
149
150 return project_heads
Don Garrett8db752c2014-10-17 16:56:55 -0700151
152
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700153def repo_versions_to_decide_whether_run_cmd_update():
154 """Collect versions of repos/files defined in COMMANDS_TO_REPOS_DICT.
155
156 For the update_commands defined in config files, no need to run the command
157 every time. Only run it when the repos/files related to the commands have
158 been changed.
159
160 @returns A set of tuples: {(cmd, repo_version), ()...}
161 """
162 results = set()
163 for cmd, repo in COMMANDS_TO_REPOS_DICT.iteritems():
164 version = subprocess.check_output(
165 ['git', 'log', '-1', '--pretty=tformat:%h',
166 '%s/%s' % (common.autotest_dir, repo)])
167 results.add((cmd, version.strip()))
168 return results
169
170
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700171def repo_sync(update_push_servers=False):
Don Garrett8db752c2014-10-17 16:56:55 -0700172 """Perform a repo sync.
173
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700174 @param update_push_servers: If True, then update test_push servers to ToT.
175 Otherwise, update server to prod branch.
Don Garrett8db752c2014-10-17 16:56:55 -0700176 @raises subprocess.CalledProcessError on a repo command failure.
177 """
Don Garrettd0321722014-11-18 16:03:33 -0800178 subprocess.check_output(['repo', 'sync'])
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700179 if update_push_servers:
180 print('Updating push servers, checkout cros/master')
Shuqian Zhao9febd452017-01-31 15:36:40 -0800181 subprocess.check_output(['git', 'checkout', 'cros/master'],
182 stderr=subprocess.STDOUT)
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700183 else:
184 print('Updating server to prod branch')
Shuqian Zhao9febd452017-01-31 15:36:40 -0800185 subprocess.check_output(['git', 'checkout', 'cros/prod'],
186 stderr=subprocess.STDOUT)
Richard Barnette488ac8b2017-09-01 09:28:55 -0700187 _clean_pyc_files()
188
Don Garrett8db752c2014-10-17 16:56:55 -0700189
Don Garrettd0321722014-11-18 16:03:33 -0800190def discover_update_commands():
191 """Lookup the commands to run on this server.
Don Garrett8db752c2014-10-17 16:56:55 -0700192
Don Garrettd0321722014-11-18 16:03:33 -0800193 These commonly come from shadow_config.ini, since they vary by server type.
Don Garrett8db752c2014-10-17 16:56:55 -0700194
Don Garrettd0321722014-11-18 16:03:33 -0800195 @returns List of command names in string format.
Don Garrett8db752c2014-10-17 16:56:55 -0700196 """
Don Garrett8db752c2014-10-17 16:56:55 -0700197 try:
Don Garrettd0321722014-11-18 16:03:33 -0800198 return global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700199 'UPDATE', 'commands', type=list)
200
201 except (ConfigParser.NoSectionError, global_config.ConfigError):
Don Garrettd0321722014-11-18 16:03:33 -0800202 return []
Don Garrett8db752c2014-10-17 16:56:55 -0700203
Don Garrettd0321722014-11-18 16:03:33 -0800204
Allen Lia2749cd2017-10-31 18:03:19 -0700205def get_restart_services():
Don Garrettd0321722014-11-18 16:03:33 -0800206 """Find the services that need restarting on the current server.
207
208 These commonly come from shadow_config.ini, since they vary by server type.
209
Allen Lia2749cd2017-10-31 18:03:19 -0700210 @returns Iterable of service names in string format.
Don Garrettd0321722014-11-18 16:03:33 -0800211 """
Allen Lia2749cd2017-10-31 18:03:19 -0700212 with open(_RESTART_SERVICES_FILE) as f:
213 for line in f:
214 yield line.rstrip()
Don Garrett8db752c2014-10-17 16:56:55 -0700215
Don Garrett8db752c2014-10-17 16:56:55 -0700216
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700217def update_command(cmd_tag, dryrun=False, use_chromite_master=False):
Don Garrettd0321722014-11-18 16:03:33 -0800218 """Restart a command.
Don Garrett8db752c2014-10-17 16:56:55 -0700219
Don Garrettd0321722014-11-18 16:03:33 -0800220 The command name is looked up in global_config.ini to find the full command
221 to run, then it's executed.
Don Garrett8db752c2014-10-17 16:56:55 -0700222
Don Garrettd0321722014-11-18 16:03:33 -0800223 @param cmd_tag: Which command to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800224 @param dryrun: If true print the command that would have been run.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700225 @param use_chromite_master: True if updating chromite to master, rather
226 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700227
Don Garrettd0321722014-11-18 16:03:33 -0800228 @raises UnknownCommandException If cmd_tag can't be looked up.
229 @raises subprocess.CalledProcessError on a command failure.
230 """
231 # Lookup the list of commands to consider. They are intended to be
232 # in global_config.ini so that they can be shared everywhere.
233 cmds = dict(global_config.global_config.config.items(
234 'UPDATE_COMMANDS'))
Don Garrett8db752c2014-10-17 16:56:55 -0700235
Don Garrettd0321722014-11-18 16:03:33 -0800236 if cmd_tag not in cmds:
237 raise UnknownCommandException(cmd_tag, cmds)
Don Garrett8db752c2014-10-17 16:56:55 -0700238
Don Garrettd0321722014-11-18 16:03:33 -0800239 expanded_command = cmds[cmd_tag].replace('AUTOTEST_REPO',
240 common.autotest_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700241 # When updating push servers, pass an arg to build_externals to update
242 # chromite to master branch for testing
243 if use_chromite_master and cmd_tag == BUILD_EXTERNALS_COMMAND:
244 expanded_command += ' --use_chromite_master'
Don Garrett8db752c2014-10-17 16:56:55 -0700245
Don Garrett699b4b32014-12-11 13:10:15 -0800246 print('Running: %s: %s' % (cmd_tag, expanded_command))
Don Garrett03432d62014-11-19 18:18:35 -0800247 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800248 print('Skip: %s' % expanded_command)
Don Garrett03432d62014-11-19 18:18:35 -0800249 else:
Don Garrett4769c902015-01-05 15:58:56 -0800250 try:
251 subprocess.check_output(expanded_command, shell=True,
252 stderr=subprocess.STDOUT)
253 except subprocess.CalledProcessError as e:
254 print('FAILED:')
255 print(e.output)
256 raise
Don Garrett8db752c2014-10-17 16:56:55 -0700257
Don Garrett8db752c2014-10-17 16:56:55 -0700258
Don Garrett03432d62014-11-19 18:18:35 -0800259def restart_service(service_name, dryrun=False):
Don Garrettd0321722014-11-18 16:03:33 -0800260 """Restart a service.
261
262 Restarts the standard service with "service <name> restart".
263
264 @param service_name: The name of the service to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800265 @param dryrun: Don't really run anything, just print out the command.
Don Garrettd0321722014-11-18 16:03:33 -0800266
267 @raises subprocess.CalledProcessError on a command failure.
268 """
Don Garrett03432d62014-11-19 18:18:35 -0800269 cmd = ['sudo', 'service', service_name, 'restart']
Don Garrett699b4b32014-12-11 13:10:15 -0800270 print('Restarting: %s' % service_name)
Don Garrett03432d62014-11-19 18:18:35 -0800271 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800272 print('Skip: %s' % ' '.join(cmd))
Don Garrett03432d62014-11-19 18:18:35 -0800273 else:
Shuqian Zhao9febd452017-01-31 15:36:40 -0800274 subprocess.check_call(cmd, stderr=subprocess.STDOUT)
Don Garrettd0321722014-11-18 16:03:33 -0800275
276
277def service_status(service_name):
278 """Return the results "status <name>" for a given service.
279
280 This string is expected to contain the pid, and so to change is the service
281 is shutdown or restarted for any reason.
282
283 @param service_name: The name of the service to check on.
Don Garrett03432d62014-11-19 18:18:35 -0800284
Don Garrettd0321722014-11-18 16:03:33 -0800285 @returns The output of the external command.
286 Ex: autofs start/running, process 1931
287
288 @raises subprocess.CalledProcessError on a command failure.
289 """
Shuqian Zhaodcaa6162017-11-09 10:58:45 -0800290 return subprocess.check_output(['sudo', 'service', service_name, 'status'])
Don Garrettd0321722014-11-18 16:03:33 -0800291
292
Dan Shi57d4c732015-01-22 18:38:50 -0800293def restart_services(service_names, dryrun=False, skip_service_status=False):
Don Garrettd0321722014-11-18 16:03:33 -0800294 """Restart services as needed for the current server type.
295
296 Restart the listed set of services, and watch to see if they are stable for
297 at least SERVICE_STABILITY_TIMER. It restarts all services quickly,
298 waits for that delay, then verifies the status of all of them.
299
300 @param service_names: The list of service to restart and monitor.
Don Garrett03432d62014-11-19 18:18:35 -0800301 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800302 @param skip_service_status: Set to True to skip service status check.
303 Default is False.
Don Garrettd0321722014-11-18 16:03:33 -0800304
305 @raises subprocess.CalledProcessError on a command failure.
Don Garrett03432d62014-11-19 18:18:35 -0800306 @raises UnstableServices if any services are unstable after restart.
Don Garrettd0321722014-11-18 16:03:33 -0800307 """
308 service_statuses = {}
309
Don Garrett03432d62014-11-19 18:18:35 -0800310 if dryrun:
311 for name in service_names:
312 restart_service(name, dryrun=True)
313 return
314
Don Garrettd0321722014-11-18 16:03:33 -0800315 # Restart each, and record the status (including pid).
316 for name in service_names:
317 restart_service(name)
Don Garrettd0321722014-11-18 16:03:33 -0800318
Dan Shi57d4c732015-01-22 18:38:50 -0800319 # Skip service status check if --skip-service-status is specified. Used for
320 # servers in backup status.
321 if skip_service_status:
322 print('--skip-service-status is specified, skip checking services.')
323 return
324
Don Garrettd0321722014-11-18 16:03:33 -0800325 # Wait for a while to let the services settle.
326 time.sleep(SERVICE_STABILITY_TIMER)
Prathmesh Prabhua62f92d2017-03-16 14:30:05 -0700327 service_statuses = {name: service_status(name) for name in service_names}
328 time.sleep(SERVICE_STABILITY_TIMER)
Don Garrettd0321722014-11-18 16:03:33 -0800329 # Look for any services that changed status.
330 unstable_services = [n for n in service_names
331 if service_status(n) != service_statuses[n]]
332
333 # Report any services having issues.
334 if unstable_services:
335 raise UnstableServices(unstable_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700336
337
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700338def run_deploy_actions(cmds_skip=set(), dryrun=False,
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700339 skip_service_status=False, use_chromite_master=False):
Don Garrettfa2c1c42014-12-11 12:11:49 -0800340 """Run arbitrary update commands specified in global.ini.
Don Garrett8db752c2014-10-17 16:56:55 -0700341
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700342 @param cmds_skip: cmds no need to run since the corresponding repo/file
343 does not change.
Don Garrett03432d62014-11-19 18:18:35 -0800344 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800345 @param skip_service_status: Set to True to skip service status check.
346 Default is False.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700347 @param use_chromite_master: True if updating chromite to master, rather
348 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700349
Don Garrett03432d62014-11-19 18:18:35 -0800350 @raises subprocess.CalledProcessError on a command failure.
351 @raises UnstableServices if any services are unstable after restart.
352 """
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700353 defined_cmds = set(discover_update_commands())
354 cmds = defined_cmds - cmds_skip
Don Garrettd0321722014-11-18 16:03:33 -0800355 if cmds:
356 print('Running update commands:', ', '.join(cmds))
357 for cmd in cmds:
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700358 update_command(cmd, dryrun=dryrun,
359 use_chromite_master=use_chromite_master)
Don Garrettd0321722014-11-18 16:03:33 -0800360
Allen Lia2749cd2017-10-31 18:03:19 -0700361 services = list(get_restart_services())
Don Garrettd0321722014-11-18 16:03:33 -0800362 if services:
Don Garrett03432d62014-11-19 18:18:35 -0800363 print('Restarting Services:', ', '.join(services))
Dan Shi57d4c732015-01-22 18:38:50 -0800364 restart_services(services, dryrun=dryrun,
365 skip_service_status=skip_service_status)
Don Garrett03432d62014-11-19 18:18:35 -0800366
367
Don Garrettfa2c1c42014-12-11 12:11:49 -0800368def report_changes(versions_before, versions_after):
369 """Produce a report describing what changed in all repos.
370
371 @param versions_before: Results of repo_versions() from before the update.
372 @param versions_after: Results of repo_versions() from after the update.
373
374 @returns string containing a human friendly changes report.
375 """
376 result = []
377
Don Garrett35711212014-12-18 14:33:41 -0800378 if versions_after:
379 for project in sorted(set(versions_before.keys() + versions_after.keys())):
380 result.append('%s:' % project)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800381
Don Garrett35711212014-12-18 14:33:41 -0800382 _, before_hash = versions_before.get(project, (None, None))
383 after_dir, after_hash = versions_after.get(project, (None, None))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800384
Don Garrett35711212014-12-18 14:33:41 -0800385 if project not in versions_before:
386 result.append('Added.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800387
Don Garrett35711212014-12-18 14:33:41 -0800388 elif project not in versions_after:
389 result.append('Removed.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800390
Don Garrett35711212014-12-18 14:33:41 -0800391 elif before_hash == after_hash:
392 result.append('No Change.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800393
Don Garrett35711212014-12-18 14:33:41 -0800394 else:
395 hashes = '%s..%s' % (before_hash, after_hash)
396 cmd = ['git', 'log', hashes, '--oneline']
397 out = subprocess.check_output(cmd, cwd=after_dir,
398 stderr=subprocess.STDOUT)
399 result.append(out.strip())
Don Garrettfa2c1c42014-12-11 12:11:49 -0800400
Don Garrett35711212014-12-18 14:33:41 -0800401 result.append('')
402 else:
403 for project in sorted(versions_before.keys()):
404 _, before_hash = versions_before[project]
405 result.append('%s: %s' % (project, before_hash))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800406 result.append('')
407
408 return '\n'.join(result)
409
410
Don Garrett03432d62014-11-19 18:18:35 -0800411def parse_arguments(args):
412 """Parse command line arguments.
413
414 @param args: The command line arguments to parse. (ususally sys.argsv[1:])
415
Don Garrett40036362014-12-08 15:52:44 -0800416 @returns An argparse.Namespace populated with argument values.
Don Garrett03432d62014-11-19 18:18:35 -0800417 """
418 parser = argparse.ArgumentParser(
419 description='Command to update an autotest server.')
420 parser.add_argument('--skip-verify', action='store_false',
421 dest='verify', default=True,
422 help='Disable verification of a clean repository.')
423 parser.add_argument('--skip-update', action='store_false',
424 dest='update', default=True,
425 help='Skip the repository source code update.')
426 parser.add_argument('--skip-actions', action='store_false',
427 dest='actions', default=True,
428 help='Skip the post update actions.')
429 parser.add_argument('--skip-report', action='store_false',
430 dest='report', default=True,
431 help='Skip the git version report.')
Don Garrette3718912014-12-05 13:11:44 -0800432 parser.add_argument('--actions-only', action='store_true',
433 help='Run the post update actions (restart services).')
Don Garrett03432d62014-11-19 18:18:35 -0800434 parser.add_argument('--dryrun', action='store_true',
435 help='Don\'t actually run any commands, just log.')
Dan Shi57d4c732015-01-22 18:38:50 -0800436 parser.add_argument('--skip-service-status', action='store_true',
437 help='Skip checking the service status.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700438 parser.add_argument('--update_push_servers', action='store_true',
439 help='Indicate to update test_push server. If not '
440 'specify, then update server to production.')
Aviv Keshetccbf6432017-10-19 12:03:34 -0700441 parser.add_argument('--force-clean-externals', action='store_true',
442 default=False,
443 help='Force a cleanup of all untracked files within '
444 'site-packages/ and ExternalSource/, so that '
445 'build_externals will build from scratch.')
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700446 parser.add_argument('--force_update', action='store_true',
447 help='Force to run the update commands for afe, tko '
448 'and build_externals')
Don Garrett03432d62014-11-19 18:18:35 -0800449
450 results = parser.parse_args(args)
451
Don Garrette3718912014-12-05 13:11:44 -0800452 if results.actions_only:
453 results.verify = False
454 results.update = False
455 results.report = False
456
Don Garrett03432d62014-11-19 18:18:35 -0800457 # TODO(dgarrett): Make these behaviors support dryrun.
458 if results.dryrun:
459 results.verify = False
460 results.update = False
Aviv Keshetccbf6432017-10-19 12:03:34 -0700461 results.force_clean_externals = False
Don Garrett03432d62014-11-19 18:18:35 -0800462
463 return results
464
465
Allen Lie8c4ea42016-10-05 18:08:29 -0700466class ChangeDir(object):
467
468 """Context manager for changing to a directory temporarily."""
469
470 def __init__(self, dir):
471 self.new_dir = dir
472 self.old_dir = None
473
474 def __enter__(self):
475 self.old_dir = os.getcwd()
476 os.chdir(self.new_dir)
477
478 def __exit__(self, exc_type, exc_val, exc_tb):
479 os.chdir(self.old_dir)
480
481
Allen Li8802a5e2017-01-17 15:04:15 -0800482def _sync_chromiumos_repo():
483 """Update ~chromeos-test/chromiumos repo."""
484 print('Updating ~chromeos-test/chromiumos')
Allen Li19c48eb2017-01-25 15:03:10 -0800485 with ChangeDir(os.path.expanduser('~chromeos-test/chromiumos')):
Shuqian Zhao9febd452017-01-31 15:36:40 -0800486 ret = subprocess.call(['repo', 'sync'], stderr=subprocess.STDOUT)
Richard Barnette488ac8b2017-09-01 09:28:55 -0700487 _clean_pyc_files()
Allen Lie8c4ea42016-10-05 18:08:29 -0700488 if ret != 0:
489 print('Update failed, exited with status: %d' % ret)
490
491
Don Garrett03432d62014-11-19 18:18:35 -0800492def main(args):
493 """Main method."""
494 os.chdir(common.autotest_dir)
495 global_config.global_config.parse_config_file()
496
497 behaviors = parse_arguments(args)
498
499 if behaviors.verify:
Prathmesh Prabhu32f6c222017-03-16 14:53:12 -0700500 print('Checking tree status:')
501 verify_repo_clean()
502 print('Tree status: clean')
Don Garrett8db752c2014-10-17 16:56:55 -0700503
Aviv Keshetccbf6432017-10-19 12:03:34 -0700504 if behaviors.force_clean_externals:
505 print('Cleaning all external packages and their cache...')
506 _clean_externals()
507 print('...done.')
508
Don Garrett35711212014-12-18 14:33:41 -0800509 versions_before = repo_versions()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700510 versions_after = set()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700511 cmd_versions_before = repo_versions_to_decide_whether_run_cmd_update()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700512 cmd_versions_after = set()
Don Garrettfa2c1c42014-12-11 12:11:49 -0800513
Don Garrett03432d62014-11-19 18:18:35 -0800514 if behaviors.update:
Don Garrett03432d62014-11-19 18:18:35 -0800515 print('Updating Repo.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700516 repo_sync(behaviors.update_push_servers)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800517 versions_after = repo_versions()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700518 cmd_versions_after = repo_versions_to_decide_whether_run_cmd_update()
Allen Li8802a5e2017-01-17 15:04:15 -0800519 _sync_chromiumos_repo()
Allen Lie8c4ea42016-10-05 18:08:29 -0700520
Don Garrett03432d62014-11-19 18:18:35 -0800521 if behaviors.actions:
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700522 # If the corresponding repo/file not change, no need to run the cmd.
523 cmds_skip = (set() if behaviors.force_update else
524 {t[0] for t in cmd_versions_before & cmd_versions_after})
Prathmesh Prabhu32f6c222017-03-16 14:53:12 -0700525 run_deploy_actions(
526 cmds_skip, behaviors.dryrun, behaviors.skip_service_status,
527 use_chromite_master=behaviors.update_push_servers)
Don Garrett03432d62014-11-19 18:18:35 -0800528
Don Garrett35711212014-12-18 14:33:41 -0800529 if behaviors.report:
Don Garrettfa2c1c42014-12-11 12:11:49 -0800530 print('Changes:')
531 print(report_changes(versions_before, versions_after))
Don Garrett8db752c2014-10-17 16:56:55 -0700532
533
534if __name__ == '__main__':
Don Garrett03432d62014-11-19 18:18:35 -0800535 sys.exit(main(sys.argv[1:]))