blob: abff7dbc0814b480a9819cbf7e109d6d1d44cfb0 [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
Dan Shicf278042016-04-06 21:16:34 -070019import socket
Don Garrett8db752c2014-10-17 16:56:55 -070020import subprocess
21import sys
22import time
23
24import common
25
26from autotest_lib.client.common_lib import global_config
Dan Shiac6fdbf2016-04-09 17:36:28 -070027from autotest_lib.server import utils as server_utils
Dan Shicf278042016-04-06 21:16:34 -070028from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
Don Garrett8db752c2014-10-17 16:56:55 -070029
Dan Shiac6fdbf2016-04-09 17:36:28 -070030
Don Garrettd0321722014-11-18 16:03:33 -080031# How long after restarting a service do we watch it to see if it's stable.
Don Garrett6073ba92015-07-23 15:01:39 -070032SERVICE_STABILITY_TIMER = 120
Don Garrettd0321722014-11-18 16:03:33 -080033
Dan Shicf278042016-04-06 21:16:34 -070034# A list of commands that only applies to primary server. For example,
35# test_importer should only be run in primary master scheduler. If two servers
36# are both running test_importer, there is a chance to fail as both try to
37# update the same table.
38PRIMARY_ONLY_COMMANDS = ['test_importer']
Shuqian Zhaoa3438a52016-09-20 15:11:02 -070039# A dict to map update_commands defined in config file to repos or files that
40# decide whether need to update these commands. E.g. if no changes under
41# frontend repo, no need to update afe.
Aviv Keshet85622032016-10-03 03:17:26 -070042COMMANDS_TO_REPOS_DICT = {'afe': 'frontend/',
43 'tko': 'tko/'}
Shuqian Zhao697f7ed2016-10-21 14:58:28 -070044BUILD_EXTERNALS_COMMAND = 'build_externals'
Allen Li43b275a2016-10-04 15:14:11 -070045# Services present on all hosts.
Allen Li7c5918f2016-12-06 12:20:55 -080046UNIVERSAL_SERVICES = ['sysmon']
Dan Shicf278042016-04-06 21:16:34 -070047
Dan Shiac6fdbf2016-04-09 17:36:28 -070048AFE = frontend_wrappers.RetryingAFE(
49 server=server_utils.get_global_afe_hostname(), timeout_min=5,
50 delay_sec=10)
Don Garrett8db752c2014-10-17 16:56:55 -070051
52class DirtyTreeException(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080053 """Raised when the tree has been modified in an unexpected way."""
Don Garrett8db752c2014-10-17 16:56:55 -070054
55
56class UnknownCommandException(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080057 """Raised when we try to run a command name with no associated command."""
Don Garrett8db752c2014-10-17 16:56:55 -070058
59
60class UnstableServices(Exception):
Don Garrettd0321722014-11-18 16:03:33 -080061 """Raised if a service appears unstable after restart."""
Don Garrett8db752c2014-10-17 16:56:55 -070062
63
Don Garrett35711212014-12-18 14:33:41 -080064def strip_terminal_codes(text):
65 """This function removes all terminal formatting codes from a string.
66
67 @param text: String of text to cleanup.
68 @returns String with format codes removed.
69 """
70 ESC = '\x1b'
71 return re.sub(ESC+r'\[[^m]*m', '', text)
72
73
Don Garrett8db752c2014-10-17 16:56:55 -070074def verify_repo_clean():
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070075 """This function cleans the current repo then verifies that it is valid.
Don Garrett8db752c2014-10-17 16:56:55 -070076
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070077 @raises DirtyTreeException if the repo is still not clean.
Don Garrett8db752c2014-10-17 16:56:55 -070078 @raises subprocess.CalledProcessError on a repo command failure.
79 """
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070080 subprocess.check_output(['git', 'reset', '--hard'])
Don Garrett8db752c2014-10-17 16:56:55 -070081 out = subprocess.check_output(['repo', 'status'], stderr=subprocess.STDOUT)
Don Garrett35711212014-12-18 14:33:41 -080082 out = strip_terminal_codes(out).strip()
Don Garrett699b4b32014-12-11 13:10:15 -080083
Shuqian Zhao8754a1a2016-08-24 12:54:11 -070084 if not 'working directory clean' in out:
Dan Shicf278042016-04-06 21:16:34 -070085 raise DirtyTreeException(out)
Don Garrett8db752c2014-10-17 16:56:55 -070086
Don Garrett8db752c2014-10-17 16:56:55 -070087
88def repo_versions():
89 """This function collects the versions of all git repos in the general repo.
90
Don Garrettfa2c1c42014-12-11 12:11:49 -080091 @returns A dictionary mapping project names to git hashes for HEAD.
Don Garrett8db752c2014-10-17 16:56:55 -070092 @raises subprocess.CalledProcessError on a repo command failure.
93 """
Don Garrettfa2c1c42014-12-11 12:11:49 -080094 cmd = ['repo', 'forall', '-p', '-c', 'pwd && git log -1 --format=%h']
Don Garrett35711212014-12-18 14:33:41 -080095 output = strip_terminal_codes(subprocess.check_output(cmd))
Don Garrettfa2c1c42014-12-11 12:11:49 -080096
97 # The expected output format is:
98
99 # project chrome_build/
100 # /dir/holding/chrome_build
101 # 73dee9d
102 #
103 # project chrome_release/
104 # /dir/holding/chrome_release
105 # 9f3a5d8
106
107 lines = output.splitlines()
108
109 PROJECT_PREFIX = 'project '
110
111 project_heads = {}
112 for n in range(0, len(lines), 4):
113 project_line = lines[n]
114 project_dir = lines[n+1]
115 project_hash = lines[n+2]
116 # lines[n+3] is a blank line, but doesn't exist for the final block.
117
118 # Convert 'project chrome_build/' -> 'chrome_build'
119 assert project_line.startswith(PROJECT_PREFIX)
120 name = project_line[len(PROJECT_PREFIX):].rstrip('/')
121
122 project_heads[name] = (project_dir, project_hash)
123
124 return project_heads
Don Garrett8db752c2014-10-17 16:56:55 -0700125
126
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700127def repo_versions_to_decide_whether_run_cmd_update():
128 """Collect versions of repos/files defined in COMMANDS_TO_REPOS_DICT.
129
130 For the update_commands defined in config files, no need to run the command
131 every time. Only run it when the repos/files related to the commands have
132 been changed.
133
134 @returns A set of tuples: {(cmd, repo_version), ()...}
135 """
136 results = set()
137 for cmd, repo in COMMANDS_TO_REPOS_DICT.iteritems():
138 version = subprocess.check_output(
139 ['git', 'log', '-1', '--pretty=tformat:%h',
140 '%s/%s' % (common.autotest_dir, repo)])
141 results.add((cmd, version.strip()))
142 return results
143
144
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700145def repo_sync(update_push_servers=False):
Don Garrett8db752c2014-10-17 16:56:55 -0700146 """Perform a repo sync.
147
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700148 @param update_push_servers: If True, then update test_push servers to ToT.
149 Otherwise, update server to prod branch.
Don Garrett8db752c2014-10-17 16:56:55 -0700150 @raises subprocess.CalledProcessError on a repo command failure.
151 """
Don Garrettd0321722014-11-18 16:03:33 -0800152 subprocess.check_output(['repo', 'sync'])
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700153 if update_push_servers:
154 print('Updating push servers, checkout cros/master')
155 subprocess.check_output(['git', 'checkout', 'cros/master'])
156 else:
157 print('Updating server to prod branch')
158 subprocess.check_output(['git', 'checkout', 'cros/prod'])
Don Garrett8db752c2014-10-17 16:56:55 -0700159
160
Don Garrettd0321722014-11-18 16:03:33 -0800161def discover_update_commands():
162 """Lookup the commands to run on this server.
Don Garrett8db752c2014-10-17 16:56:55 -0700163
Don Garrettd0321722014-11-18 16:03:33 -0800164 These commonly come from shadow_config.ini, since they vary by server type.
Don Garrett8db752c2014-10-17 16:56:55 -0700165
Don Garrettd0321722014-11-18 16:03:33 -0800166 @returns List of command names in string format.
Don Garrett8db752c2014-10-17 16:56:55 -0700167 """
Don Garrett8db752c2014-10-17 16:56:55 -0700168 try:
Don Garrettd0321722014-11-18 16:03:33 -0800169 return global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700170 'UPDATE', 'commands', type=list)
171
172 except (ConfigParser.NoSectionError, global_config.ConfigError):
Don Garrettd0321722014-11-18 16:03:33 -0800173 return []
Don Garrett8db752c2014-10-17 16:56:55 -0700174
Don Garrettd0321722014-11-18 16:03:33 -0800175
176def discover_restart_services():
177 """Find the services that need restarting on the current server.
178
179 These commonly come from shadow_config.ini, since they vary by server type.
180
181 @returns List of service names in string format.
182 """
Allen Li43b275a2016-10-04 15:14:11 -0700183 services = list(UNIVERSAL_SERVICES)
Don Garrett8db752c2014-10-17 16:56:55 -0700184 try:
Allen Li43b275a2016-10-04 15:14:11 -0700185 # Look up services from shadow_config.ini.
186 extra_services = global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700187 'UPDATE', 'services', type=list)
Allen Li43b275a2016-10-04 15:14:11 -0700188 services.extend(extra_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700189 except (ConfigParser.NoSectionError, global_config.ConfigError):
Allen Li43b275a2016-10-04 15:14:11 -0700190 pass
191 return services
Don Garrett8db752c2014-10-17 16:56:55 -0700192
Don Garrett8db752c2014-10-17 16:56:55 -0700193
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700194def update_command(cmd_tag, dryrun=False, use_chromite_master=False):
Don Garrettd0321722014-11-18 16:03:33 -0800195 """Restart a command.
Don Garrett8db752c2014-10-17 16:56:55 -0700196
Don Garrettd0321722014-11-18 16:03:33 -0800197 The command name is looked up in global_config.ini to find the full command
198 to run, then it's executed.
Don Garrett8db752c2014-10-17 16:56:55 -0700199
Don Garrettd0321722014-11-18 16:03:33 -0800200 @param cmd_tag: Which command to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800201 @param dryrun: If true print the command that would have been run.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700202 @param use_chromite_master: True if updating chromite to master, rather
203 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700204
Don Garrettd0321722014-11-18 16:03:33 -0800205 @raises UnknownCommandException If cmd_tag can't be looked up.
206 @raises subprocess.CalledProcessError on a command failure.
207 """
208 # Lookup the list of commands to consider. They are intended to be
209 # in global_config.ini so that they can be shared everywhere.
210 cmds = dict(global_config.global_config.config.items(
211 'UPDATE_COMMANDS'))
Don Garrett8db752c2014-10-17 16:56:55 -0700212
Don Garrettd0321722014-11-18 16:03:33 -0800213 if cmd_tag not in cmds:
214 raise UnknownCommandException(cmd_tag, cmds)
Don Garrett8db752c2014-10-17 16:56:55 -0700215
Don Garrettd0321722014-11-18 16:03:33 -0800216 expanded_command = cmds[cmd_tag].replace('AUTOTEST_REPO',
217 common.autotest_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700218 # When updating push servers, pass an arg to build_externals to update
219 # chromite to master branch for testing
220 if use_chromite_master and cmd_tag == BUILD_EXTERNALS_COMMAND:
221 expanded_command += ' --use_chromite_master'
Don Garrett8db752c2014-10-17 16:56:55 -0700222
Don Garrett699b4b32014-12-11 13:10:15 -0800223 print('Running: %s: %s' % (cmd_tag, expanded_command))
Don Garrett03432d62014-11-19 18:18:35 -0800224 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800225 print('Skip: %s' % expanded_command)
Don Garrett03432d62014-11-19 18:18:35 -0800226 else:
Don Garrett4769c902015-01-05 15:58:56 -0800227 try:
228 subprocess.check_output(expanded_command, shell=True,
229 stderr=subprocess.STDOUT)
230 except subprocess.CalledProcessError as e:
231 print('FAILED:')
232 print(e.output)
233 raise
Don Garrett8db752c2014-10-17 16:56:55 -0700234
Don Garrett8db752c2014-10-17 16:56:55 -0700235
Don Garrett03432d62014-11-19 18:18:35 -0800236def restart_service(service_name, dryrun=False):
Don Garrettd0321722014-11-18 16:03:33 -0800237 """Restart a service.
238
239 Restarts the standard service with "service <name> restart".
240
241 @param service_name: The name of the service to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800242 @param dryrun: Don't really run anything, just print out the command.
Don Garrettd0321722014-11-18 16:03:33 -0800243
244 @raises subprocess.CalledProcessError on a command failure.
245 """
Don Garrett03432d62014-11-19 18:18:35 -0800246 cmd = ['sudo', 'service', service_name, 'restart']
Don Garrett699b4b32014-12-11 13:10:15 -0800247 print('Restarting: %s' % service_name)
Don Garrett03432d62014-11-19 18:18:35 -0800248 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800249 print('Skip: %s' % ' '.join(cmd))
Don Garrett03432d62014-11-19 18:18:35 -0800250 else:
Don Garrett03432d62014-11-19 18:18:35 -0800251 subprocess.check_call(cmd)
Don Garrettd0321722014-11-18 16:03:33 -0800252
253
254def service_status(service_name):
255 """Return the results "status <name>" for a given service.
256
257 This string is expected to contain the pid, and so to change is the service
258 is shutdown or restarted for any reason.
259
260 @param service_name: The name of the service to check on.
Don Garrett03432d62014-11-19 18:18:35 -0800261
Don Garrettd0321722014-11-18 16:03:33 -0800262 @returns The output of the external command.
263 Ex: autofs start/running, process 1931
264
265 @raises subprocess.CalledProcessError on a command failure.
266 """
267 return subprocess.check_output(['sudo', 'status', service_name])
268
269
Dan Shi57d4c732015-01-22 18:38:50 -0800270def restart_services(service_names, dryrun=False, skip_service_status=False):
Don Garrettd0321722014-11-18 16:03:33 -0800271 """Restart services as needed for the current server type.
272
273 Restart the listed set of services, and watch to see if they are stable for
274 at least SERVICE_STABILITY_TIMER. It restarts all services quickly,
275 waits for that delay, then verifies the status of all of them.
276
277 @param service_names: The list of service to restart and monitor.
Don Garrett03432d62014-11-19 18:18:35 -0800278 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800279 @param skip_service_status: Set to True to skip service status check.
280 Default is False.
Don Garrettd0321722014-11-18 16:03:33 -0800281
282 @raises subprocess.CalledProcessError on a command failure.
Don Garrett03432d62014-11-19 18:18:35 -0800283 @raises UnstableServices if any services are unstable after restart.
Don Garrettd0321722014-11-18 16:03:33 -0800284 """
285 service_statuses = {}
286
Don Garrett03432d62014-11-19 18:18:35 -0800287 if dryrun:
288 for name in service_names:
289 restart_service(name, dryrun=True)
290 return
291
Don Garrettd0321722014-11-18 16:03:33 -0800292 # Restart each, and record the status (including pid).
293 for name in service_names:
294 restart_service(name)
295 service_statuses[name] = service_status(name)
296
Dan Shi57d4c732015-01-22 18:38:50 -0800297 # Skip service status check if --skip-service-status is specified. Used for
298 # servers in backup status.
299 if skip_service_status:
300 print('--skip-service-status is specified, skip checking services.')
301 return
302
Don Garrettd0321722014-11-18 16:03:33 -0800303 # Wait for a while to let the services settle.
304 time.sleep(SERVICE_STABILITY_TIMER)
305
306 # Look for any services that changed status.
307 unstable_services = [n for n in service_names
308 if service_status(n) != service_statuses[n]]
309
310 # Report any services having issues.
311 if unstable_services:
312 raise UnstableServices(unstable_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700313
314
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700315def run_deploy_actions(cmds_skip=set(), dryrun=False,
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700316 skip_service_status=False, use_chromite_master=False):
Don Garrettfa2c1c42014-12-11 12:11:49 -0800317 """Run arbitrary update commands specified in global.ini.
Don Garrett8db752c2014-10-17 16:56:55 -0700318
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700319 @param cmds_skip: cmds no need to run since the corresponding repo/file
320 does not change.
Don Garrett03432d62014-11-19 18:18:35 -0800321 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800322 @param skip_service_status: Set to True to skip service status check.
323 Default is False.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700324 @param use_chromite_master: True if updating chromite to master, rather
325 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700326
Don Garrett03432d62014-11-19 18:18:35 -0800327 @raises subprocess.CalledProcessError on a command failure.
328 @raises UnstableServices if any services are unstable after restart.
329 """
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700330 defined_cmds = set(discover_update_commands())
331 cmds = defined_cmds - cmds_skip
Don Garrettd0321722014-11-18 16:03:33 -0800332 if cmds:
333 print('Running update commands:', ', '.join(cmds))
334 for cmd in cmds:
Dan Shicf278042016-04-06 21:16:34 -0700335 if (cmd in PRIMARY_ONLY_COMMANDS and
336 not AFE.run('get_servers', hostname=socket.getfqdn(),
337 status='primary')):
338 print('Command %s is only applicable to primary servers.' % cmd)
339 continue
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700340 update_command(cmd, dryrun=dryrun,
341 use_chromite_master=use_chromite_master)
Don Garrettd0321722014-11-18 16:03:33 -0800342
343 services = discover_restart_services()
344 if services:
Don Garrett03432d62014-11-19 18:18:35 -0800345 print('Restarting Services:', ', '.join(services))
Dan Shi57d4c732015-01-22 18:38:50 -0800346 restart_services(services, dryrun=dryrun,
347 skip_service_status=skip_service_status)
Don Garrett03432d62014-11-19 18:18:35 -0800348
349
Don Garrettfa2c1c42014-12-11 12:11:49 -0800350def report_changes(versions_before, versions_after):
351 """Produce a report describing what changed in all repos.
352
353 @param versions_before: Results of repo_versions() from before the update.
354 @param versions_after: Results of repo_versions() from after the update.
355
356 @returns string containing a human friendly changes report.
357 """
358 result = []
359
Don Garrett35711212014-12-18 14:33:41 -0800360 if versions_after:
361 for project in sorted(set(versions_before.keys() + versions_after.keys())):
362 result.append('%s:' % project)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800363
Don Garrett35711212014-12-18 14:33:41 -0800364 _, before_hash = versions_before.get(project, (None, None))
365 after_dir, after_hash = versions_after.get(project, (None, None))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800366
Don Garrett35711212014-12-18 14:33:41 -0800367 if project not in versions_before:
368 result.append('Added.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800369
Don Garrett35711212014-12-18 14:33:41 -0800370 elif project not in versions_after:
371 result.append('Removed.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800372
Don Garrett35711212014-12-18 14:33:41 -0800373 elif before_hash == after_hash:
374 result.append('No Change.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800375
Don Garrett35711212014-12-18 14:33:41 -0800376 else:
377 hashes = '%s..%s' % (before_hash, after_hash)
378 cmd = ['git', 'log', hashes, '--oneline']
379 out = subprocess.check_output(cmd, cwd=after_dir,
380 stderr=subprocess.STDOUT)
381 result.append(out.strip())
Don Garrettfa2c1c42014-12-11 12:11:49 -0800382
Don Garrett35711212014-12-18 14:33:41 -0800383 result.append('')
384 else:
385 for project in sorted(versions_before.keys()):
386 _, before_hash = versions_before[project]
387 result.append('%s: %s' % (project, before_hash))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800388 result.append('')
389
390 return '\n'.join(result)
391
392
Don Garrett03432d62014-11-19 18:18:35 -0800393def parse_arguments(args):
394 """Parse command line arguments.
395
396 @param args: The command line arguments to parse. (ususally sys.argsv[1:])
397
Don Garrett40036362014-12-08 15:52:44 -0800398 @returns An argparse.Namespace populated with argument values.
Don Garrett03432d62014-11-19 18:18:35 -0800399 """
400 parser = argparse.ArgumentParser(
401 description='Command to update an autotest server.')
402 parser.add_argument('--skip-verify', action='store_false',
403 dest='verify', default=True,
404 help='Disable verification of a clean repository.')
405 parser.add_argument('--skip-update', action='store_false',
406 dest='update', default=True,
407 help='Skip the repository source code update.')
408 parser.add_argument('--skip-actions', action='store_false',
409 dest='actions', default=True,
410 help='Skip the post update actions.')
411 parser.add_argument('--skip-report', action='store_false',
412 dest='report', default=True,
413 help='Skip the git version report.')
Don Garrette3718912014-12-05 13:11:44 -0800414 parser.add_argument('--actions-only', action='store_true',
415 help='Run the post update actions (restart services).')
Don Garrett03432d62014-11-19 18:18:35 -0800416 parser.add_argument('--dryrun', action='store_true',
417 help='Don\'t actually run any commands, just log.')
Dan Shi57d4c732015-01-22 18:38:50 -0800418 parser.add_argument('--skip-service-status', action='store_true',
419 help='Skip checking the service status.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700420 parser.add_argument('--update_push_servers', action='store_true',
421 help='Indicate to update test_push server. If not '
422 'specify, then update server to production.')
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700423 parser.add_argument('--force_update', action='store_true',
424 help='Force to run the update commands for afe, tko '
425 'and build_externals')
Don Garrett03432d62014-11-19 18:18:35 -0800426
427 results = parser.parse_args(args)
428
Don Garrette3718912014-12-05 13:11:44 -0800429 if results.actions_only:
430 results.verify = False
431 results.update = False
432 results.report = False
433
Don Garrett03432d62014-11-19 18:18:35 -0800434 # TODO(dgarrett): Make these behaviors support dryrun.
435 if results.dryrun:
436 results.verify = False
437 results.update = False
438
439 return results
440
441
Allen Lie8c4ea42016-10-05 18:08:29 -0700442class ChangeDir(object):
443
444 """Context manager for changing to a directory temporarily."""
445
446 def __init__(self, dir):
447 self.new_dir = dir
448 self.old_dir = None
449
450 def __enter__(self):
451 self.old_dir = os.getcwd()
452 os.chdir(self.new_dir)
453
454 def __exit__(self, exc_type, exc_val, exc_tb):
455 os.chdir(self.old_dir)
456
457
Allen Li1f5a1682016-10-06 15:23:06 -0700458def update_chromeos():
459 """Update /usr/local/google/chromeos repo."""
460 print('Updating /usr/local/google/chromeos')
461 with ChangeDir('/usr/local/google/chromeos'):
462 ret = subprocess.call(['repo', 'sync'])
Allen Lie8c4ea42016-10-05 18:08:29 -0700463 if ret != 0:
464 print('Update failed, exited with status: %d' % ret)
465
466
Don Garrett03432d62014-11-19 18:18:35 -0800467def main(args):
468 """Main method."""
469 os.chdir(common.autotest_dir)
470 global_config.global_config.parse_config_file()
471
472 behaviors = parse_arguments(args)
473
474 if behaviors.verify:
Don Garrettd0321722014-11-18 16:03:33 -0800475 try:
Don Garrett03432d62014-11-19 18:18:35 -0800476 print('Checking tree status:')
477 verify_repo_clean()
478 print('Clean.')
479 except DirtyTreeException as e:
480 print('Local tree is dirty, can\'t perform update safely.')
481 print()
482 print('repo status:')
Don Garrettd0321722014-11-18 16:03:33 -0800483 print(e.args[0])
484 return 1
Don Garrett8db752c2014-10-17 16:56:55 -0700485
Don Garrett35711212014-12-18 14:33:41 -0800486 versions_before = repo_versions()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700487 versions_after = set()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700488 cmd_versions_before = repo_versions_to_decide_whether_run_cmd_update()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700489 cmd_versions_after = set()
Don Garrettfa2c1c42014-12-11 12:11:49 -0800490
Don Garrett03432d62014-11-19 18:18:35 -0800491 if behaviors.update:
Don Garrett03432d62014-11-19 18:18:35 -0800492 print('Updating Repo.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700493 repo_sync(behaviors.update_push_servers)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800494 versions_after = repo_versions()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700495 cmd_versions_after = repo_versions_to_decide_whether_run_cmd_update()
Don Garrett03432d62014-11-19 18:18:35 -0800496
Allen Li1f5a1682016-10-06 15:23:06 -0700497 update_chromeos()
Allen Lie8c4ea42016-10-05 18:08:29 -0700498
Don Garrett03432d62014-11-19 18:18:35 -0800499 if behaviors.actions:
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700500 # If the corresponding repo/file not change, no need to run the cmd.
501 cmds_skip = (set() if behaviors.force_update else
502 {t[0] for t in cmd_versions_before & cmd_versions_after})
Don Garrett03432d62014-11-19 18:18:35 -0800503 try:
Dan Shi57d4c732015-01-22 18:38:50 -0800504 run_deploy_actions(
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700505 cmds_skip, behaviors.dryrun, behaviors.skip_service_status,
506 use_chromite_master=behaviors.update_push_servers)
Don Garrett03432d62014-11-19 18:18:35 -0800507 except UnstableServices as e:
508 print('The following services were not stable after '
509 'the update:')
510 print(e.args[0])
511 return 1
512
Don Garrett35711212014-12-18 14:33:41 -0800513 if behaviors.report:
Don Garrettfa2c1c42014-12-11 12:11:49 -0800514 print('Changes:')
515 print(report_changes(versions_before, versions_after))
Don Garrett8db752c2014-10-17 16:56:55 -0700516
517
518if __name__ == '__main__':
Don Garrett03432d62014-11-19 18:18:35 -0800519 sys.exit(main(sys.argv[1:]))