blob: 285d944cf0b46d1910b7b416a19439f6cb0f5737 [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')
Shuqian Zhao9febd452017-01-31 15:36:40 -0800155 subprocess.check_output(['git', 'checkout', 'cros/master'],
156 stderr=subprocess.STDOUT)
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700157 else:
158 print('Updating server to prod branch')
Shuqian Zhao9febd452017-01-31 15:36:40 -0800159 subprocess.check_output(['git', 'checkout', 'cros/prod'],
160 stderr=subprocess.STDOUT)
Shuqian Zhaoec26bd72017-01-31 10:17:25 -0800161 # Remove .pyc files via pyclean, which is a package on all ubuntu server.
162 print('Removing .pyc files')
163 subprocess.check_output(['pyclean', '.', '-q'])
Don Garrett8db752c2014-10-17 16:56:55 -0700164
165
Don Garrettd0321722014-11-18 16:03:33 -0800166def discover_update_commands():
167 """Lookup the commands to run on this server.
Don Garrett8db752c2014-10-17 16:56:55 -0700168
Don Garrettd0321722014-11-18 16:03:33 -0800169 These commonly come from shadow_config.ini, since they vary by server type.
Don Garrett8db752c2014-10-17 16:56:55 -0700170
Don Garrettd0321722014-11-18 16:03:33 -0800171 @returns List of command names in string format.
Don Garrett8db752c2014-10-17 16:56:55 -0700172 """
Don Garrett8db752c2014-10-17 16:56:55 -0700173 try:
Don Garrettd0321722014-11-18 16:03:33 -0800174 return global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700175 'UPDATE', 'commands', type=list)
176
177 except (ConfigParser.NoSectionError, global_config.ConfigError):
Don Garrettd0321722014-11-18 16:03:33 -0800178 return []
Don Garrett8db752c2014-10-17 16:56:55 -0700179
Don Garrettd0321722014-11-18 16:03:33 -0800180
181def discover_restart_services():
182 """Find the services that need restarting on the current server.
183
184 These commonly come from shadow_config.ini, since they vary by server type.
185
186 @returns List of service names in string format.
187 """
Allen Li43b275a2016-10-04 15:14:11 -0700188 services = list(UNIVERSAL_SERVICES)
Don Garrett8db752c2014-10-17 16:56:55 -0700189 try:
Allen Li43b275a2016-10-04 15:14:11 -0700190 # Look up services from shadow_config.ini.
191 extra_services = global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700192 'UPDATE', 'services', type=list)
Allen Li43b275a2016-10-04 15:14:11 -0700193 services.extend(extra_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700194 except (ConfigParser.NoSectionError, global_config.ConfigError):
Allen Li43b275a2016-10-04 15:14:11 -0700195 pass
196 return services
Don Garrett8db752c2014-10-17 16:56:55 -0700197
Don Garrett8db752c2014-10-17 16:56:55 -0700198
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700199def update_command(cmd_tag, dryrun=False, use_chromite_master=False):
Don Garrettd0321722014-11-18 16:03:33 -0800200 """Restart a command.
Don Garrett8db752c2014-10-17 16:56:55 -0700201
Don Garrettd0321722014-11-18 16:03:33 -0800202 The command name is looked up in global_config.ini to find the full command
203 to run, then it's executed.
Don Garrett8db752c2014-10-17 16:56:55 -0700204
Don Garrettd0321722014-11-18 16:03:33 -0800205 @param cmd_tag: Which command to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800206 @param dryrun: If true print the command that would have been run.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700207 @param use_chromite_master: True if updating chromite to master, rather
208 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700209
Don Garrettd0321722014-11-18 16:03:33 -0800210 @raises UnknownCommandException If cmd_tag can't be looked up.
211 @raises subprocess.CalledProcessError on a command failure.
212 """
213 # Lookup the list of commands to consider. They are intended to be
214 # in global_config.ini so that they can be shared everywhere.
215 cmds = dict(global_config.global_config.config.items(
216 'UPDATE_COMMANDS'))
Don Garrett8db752c2014-10-17 16:56:55 -0700217
Don Garrettd0321722014-11-18 16:03:33 -0800218 if cmd_tag not in cmds:
219 raise UnknownCommandException(cmd_tag, cmds)
Don Garrett8db752c2014-10-17 16:56:55 -0700220
Don Garrettd0321722014-11-18 16:03:33 -0800221 expanded_command = cmds[cmd_tag].replace('AUTOTEST_REPO',
222 common.autotest_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700223 # When updating push servers, pass an arg to build_externals to update
224 # chromite to master branch for testing
225 if use_chromite_master and cmd_tag == BUILD_EXTERNALS_COMMAND:
226 expanded_command += ' --use_chromite_master'
Don Garrett8db752c2014-10-17 16:56:55 -0700227
Don Garrett699b4b32014-12-11 13:10:15 -0800228 print('Running: %s: %s' % (cmd_tag, expanded_command))
Don Garrett03432d62014-11-19 18:18:35 -0800229 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800230 print('Skip: %s' % expanded_command)
Don Garrett03432d62014-11-19 18:18:35 -0800231 else:
Don Garrett4769c902015-01-05 15:58:56 -0800232 try:
233 subprocess.check_output(expanded_command, shell=True,
234 stderr=subprocess.STDOUT)
235 except subprocess.CalledProcessError as e:
236 print('FAILED:')
237 print(e.output)
238 raise
Don Garrett8db752c2014-10-17 16:56:55 -0700239
Don Garrett8db752c2014-10-17 16:56:55 -0700240
Don Garrett03432d62014-11-19 18:18:35 -0800241def restart_service(service_name, dryrun=False):
Don Garrettd0321722014-11-18 16:03:33 -0800242 """Restart a service.
243
244 Restarts the standard service with "service <name> restart".
245
246 @param service_name: The name of the service to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800247 @param dryrun: Don't really run anything, just print out the command.
Don Garrettd0321722014-11-18 16:03:33 -0800248
249 @raises subprocess.CalledProcessError on a command failure.
250 """
Don Garrett03432d62014-11-19 18:18:35 -0800251 cmd = ['sudo', 'service', service_name, 'restart']
Don Garrett699b4b32014-12-11 13:10:15 -0800252 print('Restarting: %s' % service_name)
Don Garrett03432d62014-11-19 18:18:35 -0800253 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800254 print('Skip: %s' % ' '.join(cmd))
Don Garrett03432d62014-11-19 18:18:35 -0800255 else:
Shuqian Zhao9febd452017-01-31 15:36:40 -0800256 subprocess.check_call(cmd, stderr=subprocess.STDOUT)
Don Garrettd0321722014-11-18 16:03:33 -0800257
258
259def service_status(service_name):
260 """Return the results "status <name>" for a given service.
261
262 This string is expected to contain the pid, and so to change is the service
263 is shutdown or restarted for any reason.
264
265 @param service_name: The name of the service to check on.
Don Garrett03432d62014-11-19 18:18:35 -0800266
Don Garrettd0321722014-11-18 16:03:33 -0800267 @returns The output of the external command.
268 Ex: autofs start/running, process 1931
269
270 @raises subprocess.CalledProcessError on a command failure.
271 """
272 return subprocess.check_output(['sudo', 'status', service_name])
273
274
Dan Shi57d4c732015-01-22 18:38:50 -0800275def restart_services(service_names, dryrun=False, skip_service_status=False):
Don Garrettd0321722014-11-18 16:03:33 -0800276 """Restart services as needed for the current server type.
277
278 Restart the listed set of services, and watch to see if they are stable for
279 at least SERVICE_STABILITY_TIMER. It restarts all services quickly,
280 waits for that delay, then verifies the status of all of them.
281
282 @param service_names: The list of service to restart and monitor.
Don Garrett03432d62014-11-19 18:18:35 -0800283 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800284 @param skip_service_status: Set to True to skip service status check.
285 Default is False.
Don Garrettd0321722014-11-18 16:03:33 -0800286
287 @raises subprocess.CalledProcessError on a command failure.
Don Garrett03432d62014-11-19 18:18:35 -0800288 @raises UnstableServices if any services are unstable after restart.
Don Garrettd0321722014-11-18 16:03:33 -0800289 """
290 service_statuses = {}
291
Don Garrett03432d62014-11-19 18:18:35 -0800292 if dryrun:
293 for name in service_names:
294 restart_service(name, dryrun=True)
295 return
296
Don Garrettd0321722014-11-18 16:03:33 -0800297 # Restart each, and record the status (including pid).
298 for name in service_names:
299 restart_service(name)
300 service_statuses[name] = service_status(name)
301
Dan Shi57d4c732015-01-22 18:38:50 -0800302 # Skip service status check if --skip-service-status is specified. Used for
303 # servers in backup status.
304 if skip_service_status:
305 print('--skip-service-status is specified, skip checking services.')
306 return
307
Don Garrettd0321722014-11-18 16:03:33 -0800308 # Wait for a while to let the services settle.
309 time.sleep(SERVICE_STABILITY_TIMER)
310
311 # Look for any services that changed status.
312 unstable_services = [n for n in service_names
313 if service_status(n) != service_statuses[n]]
314
315 # Report any services having issues.
316 if unstable_services:
317 raise UnstableServices(unstable_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700318
319
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700320def run_deploy_actions(cmds_skip=set(), dryrun=False,
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700321 skip_service_status=False, use_chromite_master=False):
Don Garrettfa2c1c42014-12-11 12:11:49 -0800322 """Run arbitrary update commands specified in global.ini.
Don Garrett8db752c2014-10-17 16:56:55 -0700323
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700324 @param cmds_skip: cmds no need to run since the corresponding repo/file
325 does not change.
Don Garrett03432d62014-11-19 18:18:35 -0800326 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800327 @param skip_service_status: Set to True to skip service status check.
328 Default is False.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700329 @param use_chromite_master: True if updating chromite to master, rather
330 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700331
Don Garrett03432d62014-11-19 18:18:35 -0800332 @raises subprocess.CalledProcessError on a command failure.
333 @raises UnstableServices if any services are unstable after restart.
334 """
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700335 defined_cmds = set(discover_update_commands())
336 cmds = defined_cmds - cmds_skip
Don Garrettd0321722014-11-18 16:03:33 -0800337 if cmds:
338 print('Running update commands:', ', '.join(cmds))
339 for cmd in cmds:
Dan Shicf278042016-04-06 21:16:34 -0700340 if (cmd in PRIMARY_ONLY_COMMANDS and
341 not AFE.run('get_servers', hostname=socket.getfqdn(),
342 status='primary')):
343 print('Command %s is only applicable to primary servers.' % cmd)
344 continue
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700345 update_command(cmd, dryrun=dryrun,
346 use_chromite_master=use_chromite_master)
Don Garrettd0321722014-11-18 16:03:33 -0800347
348 services = discover_restart_services()
349 if services:
Don Garrett03432d62014-11-19 18:18:35 -0800350 print('Restarting Services:', ', '.join(services))
Dan Shi57d4c732015-01-22 18:38:50 -0800351 restart_services(services, dryrun=dryrun,
352 skip_service_status=skip_service_status)
Don Garrett03432d62014-11-19 18:18:35 -0800353
354
Don Garrettfa2c1c42014-12-11 12:11:49 -0800355def report_changes(versions_before, versions_after):
356 """Produce a report describing what changed in all repos.
357
358 @param versions_before: Results of repo_versions() from before the update.
359 @param versions_after: Results of repo_versions() from after the update.
360
361 @returns string containing a human friendly changes report.
362 """
363 result = []
364
Don Garrett35711212014-12-18 14:33:41 -0800365 if versions_after:
366 for project in sorted(set(versions_before.keys() + versions_after.keys())):
367 result.append('%s:' % project)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800368
Don Garrett35711212014-12-18 14:33:41 -0800369 _, before_hash = versions_before.get(project, (None, None))
370 after_dir, after_hash = versions_after.get(project, (None, None))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800371
Don Garrett35711212014-12-18 14:33:41 -0800372 if project not in versions_before:
373 result.append('Added.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800374
Don Garrett35711212014-12-18 14:33:41 -0800375 elif project not in versions_after:
376 result.append('Removed.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800377
Don Garrett35711212014-12-18 14:33:41 -0800378 elif before_hash == after_hash:
379 result.append('No Change.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800380
Don Garrett35711212014-12-18 14:33:41 -0800381 else:
382 hashes = '%s..%s' % (before_hash, after_hash)
383 cmd = ['git', 'log', hashes, '--oneline']
384 out = subprocess.check_output(cmd, cwd=after_dir,
385 stderr=subprocess.STDOUT)
386 result.append(out.strip())
Don Garrettfa2c1c42014-12-11 12:11:49 -0800387
Don Garrett35711212014-12-18 14:33:41 -0800388 result.append('')
389 else:
390 for project in sorted(versions_before.keys()):
391 _, before_hash = versions_before[project]
392 result.append('%s: %s' % (project, before_hash))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800393 result.append('')
394
395 return '\n'.join(result)
396
397
Don Garrett03432d62014-11-19 18:18:35 -0800398def parse_arguments(args):
399 """Parse command line arguments.
400
401 @param args: The command line arguments to parse. (ususally sys.argsv[1:])
402
Don Garrett40036362014-12-08 15:52:44 -0800403 @returns An argparse.Namespace populated with argument values.
Don Garrett03432d62014-11-19 18:18:35 -0800404 """
405 parser = argparse.ArgumentParser(
406 description='Command to update an autotest server.')
407 parser.add_argument('--skip-verify', action='store_false',
408 dest='verify', default=True,
409 help='Disable verification of a clean repository.')
410 parser.add_argument('--skip-update', action='store_false',
411 dest='update', default=True,
412 help='Skip the repository source code update.')
413 parser.add_argument('--skip-actions', action='store_false',
414 dest='actions', default=True,
415 help='Skip the post update actions.')
416 parser.add_argument('--skip-report', action='store_false',
417 dest='report', default=True,
418 help='Skip the git version report.')
Don Garrette3718912014-12-05 13:11:44 -0800419 parser.add_argument('--actions-only', action='store_true',
420 help='Run the post update actions (restart services).')
Don Garrett03432d62014-11-19 18:18:35 -0800421 parser.add_argument('--dryrun', action='store_true',
422 help='Don\'t actually run any commands, just log.')
Dan Shi57d4c732015-01-22 18:38:50 -0800423 parser.add_argument('--skip-service-status', action='store_true',
424 help='Skip checking the service status.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700425 parser.add_argument('--update_push_servers', action='store_true',
426 help='Indicate to update test_push server. If not '
427 'specify, then update server to production.')
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700428 parser.add_argument('--force_update', action='store_true',
429 help='Force to run the update commands for afe, tko '
430 'and build_externals')
Don Garrett03432d62014-11-19 18:18:35 -0800431
432 results = parser.parse_args(args)
433
Don Garrette3718912014-12-05 13:11:44 -0800434 if results.actions_only:
435 results.verify = False
436 results.update = False
437 results.report = False
438
Don Garrett03432d62014-11-19 18:18:35 -0800439 # TODO(dgarrett): Make these behaviors support dryrun.
440 if results.dryrun:
441 results.verify = False
442 results.update = False
443
444 return results
445
446
Allen Lie8c4ea42016-10-05 18:08:29 -0700447class ChangeDir(object):
448
449 """Context manager for changing to a directory temporarily."""
450
451 def __init__(self, dir):
452 self.new_dir = dir
453 self.old_dir = None
454
455 def __enter__(self):
456 self.old_dir = os.getcwd()
457 os.chdir(self.new_dir)
458
459 def __exit__(self, exc_type, exc_val, exc_tb):
460 os.chdir(self.old_dir)
461
462
Allen Li8802a5e2017-01-17 15:04:15 -0800463def _sync_chromiumos_repo():
464 """Update ~chromeos-test/chromiumos repo."""
465 print('Updating ~chromeos-test/chromiumos')
Allen Li19c48eb2017-01-25 15:03:10 -0800466 with ChangeDir(os.path.expanduser('~chromeos-test/chromiumos')):
Shuqian Zhao9febd452017-01-31 15:36:40 -0800467 ret = subprocess.call(['repo', 'sync'], stderr=subprocess.STDOUT)
Shuqian Zhaoec26bd72017-01-31 10:17:25 -0800468 # Remove .pyc files via pyclean, which is a package on all ubuntu server
469 print('Removing .pyc files')
470 subprocess.check_output(['pyclean', '.', '-q'])
Allen Lie8c4ea42016-10-05 18:08:29 -0700471 if ret != 0:
472 print('Update failed, exited with status: %d' % ret)
473
474
Don Garrett03432d62014-11-19 18:18:35 -0800475def main(args):
476 """Main method."""
477 os.chdir(common.autotest_dir)
478 global_config.global_config.parse_config_file()
479
480 behaviors = parse_arguments(args)
481
482 if behaviors.verify:
Don Garrettd0321722014-11-18 16:03:33 -0800483 try:
Don Garrett03432d62014-11-19 18:18:35 -0800484 print('Checking tree status:')
485 verify_repo_clean()
486 print('Clean.')
487 except DirtyTreeException as e:
488 print('Local tree is dirty, can\'t perform update safely.')
489 print()
490 print('repo status:')
Don Garrettd0321722014-11-18 16:03:33 -0800491 print(e.args[0])
492 return 1
Don Garrett8db752c2014-10-17 16:56:55 -0700493
Don Garrett35711212014-12-18 14:33:41 -0800494 versions_before = repo_versions()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700495 versions_after = set()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700496 cmd_versions_before = repo_versions_to_decide_whether_run_cmd_update()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700497 cmd_versions_after = set()
Don Garrettfa2c1c42014-12-11 12:11:49 -0800498
Don Garrett03432d62014-11-19 18:18:35 -0800499 if behaviors.update:
Don Garrett03432d62014-11-19 18:18:35 -0800500 print('Updating Repo.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700501 repo_sync(behaviors.update_push_servers)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800502 versions_after = repo_versions()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700503 cmd_versions_after = repo_versions_to_decide_whether_run_cmd_update()
Don Garrett03432d62014-11-19 18:18:35 -0800504
Allen Li8802a5e2017-01-17 15:04:15 -0800505 _sync_chromiumos_repo()
Allen Lie8c4ea42016-10-05 18:08:29 -0700506
Don Garrett03432d62014-11-19 18:18:35 -0800507 if behaviors.actions:
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700508 # If the corresponding repo/file not change, no need to run the cmd.
509 cmds_skip = (set() if behaviors.force_update else
510 {t[0] for t in cmd_versions_before & cmd_versions_after})
Don Garrett03432d62014-11-19 18:18:35 -0800511 try:
Dan Shi57d4c732015-01-22 18:38:50 -0800512 run_deploy_actions(
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700513 cmds_skip, behaviors.dryrun, behaviors.skip_service_status,
514 use_chromite_master=behaviors.update_push_servers)
Don Garrett03432d62014-11-19 18:18:35 -0800515 except UnstableServices as e:
516 print('The following services were not stable after '
517 'the update:')
518 print(e.args[0])
519 return 1
520
Don Garrett35711212014-12-18 14:33:41 -0800521 if behaviors.report:
Don Garrettfa2c1c42014-12-11 12:11:49 -0800522 print('Changes:')
523 print(report_changes(versions_before, versions_after))
Don Garrett8db752c2014-10-17 16:56:55 -0700524
525
526if __name__ == '__main__':
Don Garrett03432d62014-11-19 18:18:35 -0800527 sys.exit(main(sys.argv[1:]))