blob: c23ad1b2602b391ae3873399b9b7be41cff8f789 [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')
Shuqian Zhaoff5c8352017-02-08 17:39:57 -0800163 try:
164 subprocess.check_output(['pyclean', '.', '-q'])
165 except Exception as e:
166 print('Warning: fail to remove .pyc! %s' % e)
Don Garrett8db752c2014-10-17 16:56:55 -0700167
Don Garrettd0321722014-11-18 16:03:33 -0800168def discover_update_commands():
169 """Lookup the commands to run on this server.
Don Garrett8db752c2014-10-17 16:56:55 -0700170
Don Garrettd0321722014-11-18 16:03:33 -0800171 These commonly come from shadow_config.ini, since they vary by server type.
Don Garrett8db752c2014-10-17 16:56:55 -0700172
Don Garrettd0321722014-11-18 16:03:33 -0800173 @returns List of command names in string format.
Don Garrett8db752c2014-10-17 16:56:55 -0700174 """
Don Garrett8db752c2014-10-17 16:56:55 -0700175 try:
Don Garrettd0321722014-11-18 16:03:33 -0800176 return global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700177 'UPDATE', 'commands', type=list)
178
179 except (ConfigParser.NoSectionError, global_config.ConfigError):
Don Garrettd0321722014-11-18 16:03:33 -0800180 return []
Don Garrett8db752c2014-10-17 16:56:55 -0700181
Don Garrettd0321722014-11-18 16:03:33 -0800182
183def discover_restart_services():
184 """Find the services that need restarting on the current server.
185
186 These commonly come from shadow_config.ini, since they vary by server type.
187
188 @returns List of service names in string format.
189 """
Allen Li43b275a2016-10-04 15:14:11 -0700190 services = list(UNIVERSAL_SERVICES)
Don Garrett8db752c2014-10-17 16:56:55 -0700191 try:
Allen Li43b275a2016-10-04 15:14:11 -0700192 # Look up services from shadow_config.ini.
193 extra_services = global_config.global_config.get_config_value(
Don Garrett8db752c2014-10-17 16:56:55 -0700194 'UPDATE', 'services', type=list)
Allen Li43b275a2016-10-04 15:14:11 -0700195 services.extend(extra_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700196 except (ConfigParser.NoSectionError, global_config.ConfigError):
Allen Li43b275a2016-10-04 15:14:11 -0700197 pass
198 return services
Don Garrett8db752c2014-10-17 16:56:55 -0700199
Don Garrett8db752c2014-10-17 16:56:55 -0700200
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700201def update_command(cmd_tag, dryrun=False, use_chromite_master=False):
Don Garrettd0321722014-11-18 16:03:33 -0800202 """Restart a command.
Don Garrett8db752c2014-10-17 16:56:55 -0700203
Don Garrettd0321722014-11-18 16:03:33 -0800204 The command name is looked up in global_config.ini to find the full command
205 to run, then it's executed.
Don Garrett8db752c2014-10-17 16:56:55 -0700206
Don Garrettd0321722014-11-18 16:03:33 -0800207 @param cmd_tag: Which command to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800208 @param dryrun: If true print the command that would have been run.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700209 @param use_chromite_master: True if updating chromite to master, rather
210 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700211
Don Garrettd0321722014-11-18 16:03:33 -0800212 @raises UnknownCommandException If cmd_tag can't be looked up.
213 @raises subprocess.CalledProcessError on a command failure.
214 """
215 # Lookup the list of commands to consider. They are intended to be
216 # in global_config.ini so that they can be shared everywhere.
217 cmds = dict(global_config.global_config.config.items(
218 'UPDATE_COMMANDS'))
Don Garrett8db752c2014-10-17 16:56:55 -0700219
Don Garrettd0321722014-11-18 16:03:33 -0800220 if cmd_tag not in cmds:
221 raise UnknownCommandException(cmd_tag, cmds)
Don Garrett8db752c2014-10-17 16:56:55 -0700222
Don Garrettd0321722014-11-18 16:03:33 -0800223 expanded_command = cmds[cmd_tag].replace('AUTOTEST_REPO',
224 common.autotest_dir)
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700225 # When updating push servers, pass an arg to build_externals to update
226 # chromite to master branch for testing
227 if use_chromite_master and cmd_tag == BUILD_EXTERNALS_COMMAND:
228 expanded_command += ' --use_chromite_master'
Don Garrett8db752c2014-10-17 16:56:55 -0700229
Don Garrett699b4b32014-12-11 13:10:15 -0800230 print('Running: %s: %s' % (cmd_tag, expanded_command))
Don Garrett03432d62014-11-19 18:18:35 -0800231 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800232 print('Skip: %s' % expanded_command)
Don Garrett03432d62014-11-19 18:18:35 -0800233 else:
Don Garrett4769c902015-01-05 15:58:56 -0800234 try:
235 subprocess.check_output(expanded_command, shell=True,
236 stderr=subprocess.STDOUT)
237 except subprocess.CalledProcessError as e:
238 print('FAILED:')
239 print(e.output)
240 raise
Don Garrett8db752c2014-10-17 16:56:55 -0700241
Don Garrett8db752c2014-10-17 16:56:55 -0700242
Don Garrett03432d62014-11-19 18:18:35 -0800243def restart_service(service_name, dryrun=False):
Don Garrettd0321722014-11-18 16:03:33 -0800244 """Restart a service.
245
246 Restarts the standard service with "service <name> restart".
247
248 @param service_name: The name of the service to restart.
Don Garrett03432d62014-11-19 18:18:35 -0800249 @param dryrun: Don't really run anything, just print out the command.
Don Garrettd0321722014-11-18 16:03:33 -0800250
251 @raises subprocess.CalledProcessError on a command failure.
252 """
Don Garrett03432d62014-11-19 18:18:35 -0800253 cmd = ['sudo', 'service', service_name, 'restart']
Don Garrett699b4b32014-12-11 13:10:15 -0800254 print('Restarting: %s' % service_name)
Don Garrett03432d62014-11-19 18:18:35 -0800255 if dryrun:
Don Garrett699b4b32014-12-11 13:10:15 -0800256 print('Skip: %s' % ' '.join(cmd))
Don Garrett03432d62014-11-19 18:18:35 -0800257 else:
Shuqian Zhao9febd452017-01-31 15:36:40 -0800258 subprocess.check_call(cmd, stderr=subprocess.STDOUT)
Don Garrettd0321722014-11-18 16:03:33 -0800259
260
261def service_status(service_name):
262 """Return the results "status <name>" for a given service.
263
264 This string is expected to contain the pid, and so to change is the service
265 is shutdown or restarted for any reason.
266
267 @param service_name: The name of the service to check on.
Don Garrett03432d62014-11-19 18:18:35 -0800268
Don Garrettd0321722014-11-18 16:03:33 -0800269 @returns The output of the external command.
270 Ex: autofs start/running, process 1931
271
272 @raises subprocess.CalledProcessError on a command failure.
273 """
274 return subprocess.check_output(['sudo', 'status', service_name])
275
276
Dan Shi57d4c732015-01-22 18:38:50 -0800277def restart_services(service_names, dryrun=False, skip_service_status=False):
Don Garrettd0321722014-11-18 16:03:33 -0800278 """Restart services as needed for the current server type.
279
280 Restart the listed set of services, and watch to see if they are stable for
281 at least SERVICE_STABILITY_TIMER. It restarts all services quickly,
282 waits for that delay, then verifies the status of all of them.
283
284 @param service_names: The list of service to restart and monitor.
Don Garrett03432d62014-11-19 18:18:35 -0800285 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800286 @param skip_service_status: Set to True to skip service status check.
287 Default is False.
Don Garrettd0321722014-11-18 16:03:33 -0800288
289 @raises subprocess.CalledProcessError on a command failure.
Don Garrett03432d62014-11-19 18:18:35 -0800290 @raises UnstableServices if any services are unstable after restart.
Don Garrettd0321722014-11-18 16:03:33 -0800291 """
292 service_statuses = {}
293
Don Garrett03432d62014-11-19 18:18:35 -0800294 if dryrun:
295 for name in service_names:
296 restart_service(name, dryrun=True)
297 return
298
Don Garrettd0321722014-11-18 16:03:33 -0800299 # Restart each, and record the status (including pid).
300 for name in service_names:
301 restart_service(name)
302 service_statuses[name] = service_status(name)
303
Dan Shi57d4c732015-01-22 18:38:50 -0800304 # Skip service status check if --skip-service-status is specified. Used for
305 # servers in backup status.
306 if skip_service_status:
307 print('--skip-service-status is specified, skip checking services.')
308 return
309
Don Garrettd0321722014-11-18 16:03:33 -0800310 # Wait for a while to let the services settle.
311 time.sleep(SERVICE_STABILITY_TIMER)
312
313 # Look for any services that changed status.
314 unstable_services = [n for n in service_names
315 if service_status(n) != service_statuses[n]]
316
317 # Report any services having issues.
318 if unstable_services:
319 raise UnstableServices(unstable_services)
Don Garrett8db752c2014-10-17 16:56:55 -0700320
321
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700322def run_deploy_actions(cmds_skip=set(), dryrun=False,
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700323 skip_service_status=False, use_chromite_master=False):
Don Garrettfa2c1c42014-12-11 12:11:49 -0800324 """Run arbitrary update commands specified in global.ini.
Don Garrett8db752c2014-10-17 16:56:55 -0700325
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700326 @param cmds_skip: cmds no need to run since the corresponding repo/file
327 does not change.
Don Garrett03432d62014-11-19 18:18:35 -0800328 @param dryrun: Don't really restart the service, just print out the command.
Dan Shi57d4c732015-01-22 18:38:50 -0800329 @param skip_service_status: Set to True to skip service status check.
330 Default is False.
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700331 @param use_chromite_master: True if updating chromite to master, rather
332 than prod.
Don Garrett8db752c2014-10-17 16:56:55 -0700333
Don Garrett03432d62014-11-19 18:18:35 -0800334 @raises subprocess.CalledProcessError on a command failure.
335 @raises UnstableServices if any services are unstable after restart.
336 """
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700337 defined_cmds = set(discover_update_commands())
338 cmds = defined_cmds - cmds_skip
Don Garrettd0321722014-11-18 16:03:33 -0800339 if cmds:
340 print('Running update commands:', ', '.join(cmds))
341 for cmd in cmds:
Dan Shicf278042016-04-06 21:16:34 -0700342 if (cmd in PRIMARY_ONLY_COMMANDS and
343 not AFE.run('get_servers', hostname=socket.getfqdn(),
344 status='primary')):
345 print('Command %s is only applicable to primary servers.' % cmd)
346 continue
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700347 update_command(cmd, dryrun=dryrun,
348 use_chromite_master=use_chromite_master)
Don Garrettd0321722014-11-18 16:03:33 -0800349
350 services = discover_restart_services()
351 if services:
Don Garrett03432d62014-11-19 18:18:35 -0800352 print('Restarting Services:', ', '.join(services))
Dan Shi57d4c732015-01-22 18:38:50 -0800353 restart_services(services, dryrun=dryrun,
354 skip_service_status=skip_service_status)
Don Garrett03432d62014-11-19 18:18:35 -0800355
356
Don Garrettfa2c1c42014-12-11 12:11:49 -0800357def report_changes(versions_before, versions_after):
358 """Produce a report describing what changed in all repos.
359
360 @param versions_before: Results of repo_versions() from before the update.
361 @param versions_after: Results of repo_versions() from after the update.
362
363 @returns string containing a human friendly changes report.
364 """
365 result = []
366
Don Garrett35711212014-12-18 14:33:41 -0800367 if versions_after:
368 for project in sorted(set(versions_before.keys() + versions_after.keys())):
369 result.append('%s:' % project)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800370
Don Garrett35711212014-12-18 14:33:41 -0800371 _, before_hash = versions_before.get(project, (None, None))
372 after_dir, after_hash = versions_after.get(project, (None, None))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800373
Don Garrett35711212014-12-18 14:33:41 -0800374 if project not in versions_before:
375 result.append('Added.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800376
Don Garrett35711212014-12-18 14:33:41 -0800377 elif project not in versions_after:
378 result.append('Removed.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800379
Don Garrett35711212014-12-18 14:33:41 -0800380 elif before_hash == after_hash:
381 result.append('No Change.')
Don Garrettfa2c1c42014-12-11 12:11:49 -0800382
Don Garrett35711212014-12-18 14:33:41 -0800383 else:
384 hashes = '%s..%s' % (before_hash, after_hash)
385 cmd = ['git', 'log', hashes, '--oneline']
386 out = subprocess.check_output(cmd, cwd=after_dir,
387 stderr=subprocess.STDOUT)
388 result.append(out.strip())
Don Garrettfa2c1c42014-12-11 12:11:49 -0800389
Don Garrett35711212014-12-18 14:33:41 -0800390 result.append('')
391 else:
392 for project in sorted(versions_before.keys()):
393 _, before_hash = versions_before[project]
394 result.append('%s: %s' % (project, before_hash))
Don Garrettfa2c1c42014-12-11 12:11:49 -0800395 result.append('')
396
397 return '\n'.join(result)
398
399
Don Garrett03432d62014-11-19 18:18:35 -0800400def parse_arguments(args):
401 """Parse command line arguments.
402
403 @param args: The command line arguments to parse. (ususally sys.argsv[1:])
404
Don Garrett40036362014-12-08 15:52:44 -0800405 @returns An argparse.Namespace populated with argument values.
Don Garrett03432d62014-11-19 18:18:35 -0800406 """
407 parser = argparse.ArgumentParser(
408 description='Command to update an autotest server.')
409 parser.add_argument('--skip-verify', action='store_false',
410 dest='verify', default=True,
411 help='Disable verification of a clean repository.')
412 parser.add_argument('--skip-update', action='store_false',
413 dest='update', default=True,
414 help='Skip the repository source code update.')
415 parser.add_argument('--skip-actions', action='store_false',
416 dest='actions', default=True,
417 help='Skip the post update actions.')
418 parser.add_argument('--skip-report', action='store_false',
419 dest='report', default=True,
420 help='Skip the git version report.')
Don Garrette3718912014-12-05 13:11:44 -0800421 parser.add_argument('--actions-only', action='store_true',
422 help='Run the post update actions (restart services).')
Don Garrett03432d62014-11-19 18:18:35 -0800423 parser.add_argument('--dryrun', action='store_true',
424 help='Don\'t actually run any commands, just log.')
Dan Shi57d4c732015-01-22 18:38:50 -0800425 parser.add_argument('--skip-service-status', action='store_true',
426 help='Skip checking the service status.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700427 parser.add_argument('--update_push_servers', action='store_true',
428 help='Indicate to update test_push server. If not '
429 'specify, then update server to production.')
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700430 parser.add_argument('--force_update', action='store_true',
431 help='Force to run the update commands for afe, tko '
432 'and build_externals')
Don Garrett03432d62014-11-19 18:18:35 -0800433
434 results = parser.parse_args(args)
435
Don Garrette3718912014-12-05 13:11:44 -0800436 if results.actions_only:
437 results.verify = False
438 results.update = False
439 results.report = False
440
Don Garrett03432d62014-11-19 18:18:35 -0800441 # TODO(dgarrett): Make these behaviors support dryrun.
442 if results.dryrun:
443 results.verify = False
444 results.update = False
445
446 return results
447
448
Allen Lie8c4ea42016-10-05 18:08:29 -0700449class ChangeDir(object):
450
451 """Context manager for changing to a directory temporarily."""
452
453 def __init__(self, dir):
454 self.new_dir = dir
455 self.old_dir = None
456
457 def __enter__(self):
458 self.old_dir = os.getcwd()
459 os.chdir(self.new_dir)
460
461 def __exit__(self, exc_type, exc_val, exc_tb):
462 os.chdir(self.old_dir)
463
464
Allen Li8802a5e2017-01-17 15:04:15 -0800465def _sync_chromiumos_repo():
466 """Update ~chromeos-test/chromiumos repo."""
467 print('Updating ~chromeos-test/chromiumos')
Allen Li19c48eb2017-01-25 15:03:10 -0800468 with ChangeDir(os.path.expanduser('~chromeos-test/chromiumos')):
Shuqian Zhao9febd452017-01-31 15:36:40 -0800469 ret = subprocess.call(['repo', 'sync'], stderr=subprocess.STDOUT)
Shuqian Zhaoec26bd72017-01-31 10:17:25 -0800470 # Remove .pyc files via pyclean, which is a package on all ubuntu server
471 print('Removing .pyc files')
Shuqian Zhaoff5c8352017-02-08 17:39:57 -0800472 try:
473 subprocess.check_output(['pyclean', '.', '-q'])
474 except Exception as e:
475 print('Warning: fail to remove .pyc! %s' % e)
Allen Lie8c4ea42016-10-05 18:08:29 -0700476 if ret != 0:
477 print('Update failed, exited with status: %d' % ret)
478
479
Don Garrett03432d62014-11-19 18:18:35 -0800480def main(args):
481 """Main method."""
482 os.chdir(common.autotest_dir)
483 global_config.global_config.parse_config_file()
484
485 behaviors = parse_arguments(args)
486
487 if behaviors.verify:
Don Garrettd0321722014-11-18 16:03:33 -0800488 try:
Don Garrett03432d62014-11-19 18:18:35 -0800489 print('Checking tree status:')
490 verify_repo_clean()
491 print('Clean.')
492 except DirtyTreeException as e:
493 print('Local tree is dirty, can\'t perform update safely.')
494 print()
495 print('repo status:')
Don Garrettd0321722014-11-18 16:03:33 -0800496 print(e.args[0])
497 return 1
Don Garrett8db752c2014-10-17 16:56:55 -0700498
Don Garrett35711212014-12-18 14:33:41 -0800499 versions_before = repo_versions()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700500 versions_after = set()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700501 cmd_versions_before = repo_versions_to_decide_whether_run_cmd_update()
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700502 cmd_versions_after = set()
Don Garrettfa2c1c42014-12-11 12:11:49 -0800503
Don Garrett03432d62014-11-19 18:18:35 -0800504 if behaviors.update:
Don Garrett03432d62014-11-19 18:18:35 -0800505 print('Updating Repo.')
Shuqian Zhao8754a1a2016-08-24 12:54:11 -0700506 repo_sync(behaviors.update_push_servers)
Don Garrettfa2c1c42014-12-11 12:11:49 -0800507 versions_after = repo_versions()
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700508 cmd_versions_after = repo_versions_to_decide_whether_run_cmd_update()
Don Garrett03432d62014-11-19 18:18:35 -0800509
Allen Li8802a5e2017-01-17 15:04:15 -0800510 _sync_chromiumos_repo()
Allen Lie8c4ea42016-10-05 18:08:29 -0700511
Don Garrett03432d62014-11-19 18:18:35 -0800512 if behaviors.actions:
Shuqian Zhaoa3438a52016-09-20 15:11:02 -0700513 # If the corresponding repo/file not change, no need to run the cmd.
514 cmds_skip = (set() if behaviors.force_update else
515 {t[0] for t in cmd_versions_before & cmd_versions_after})
Don Garrett03432d62014-11-19 18:18:35 -0800516 try:
Dan Shi57d4c732015-01-22 18:38:50 -0800517 run_deploy_actions(
Shuqian Zhao697f7ed2016-10-21 14:58:28 -0700518 cmds_skip, behaviors.dryrun, behaviors.skip_service_status,
519 use_chromite_master=behaviors.update_push_servers)
Don Garrett03432d62014-11-19 18:18:35 -0800520 except UnstableServices as e:
521 print('The following services were not stable after '
522 'the update:')
523 print(e.args[0])
524 return 1
525
Don Garrett35711212014-12-18 14:33:41 -0800526 if behaviors.report:
Don Garrettfa2c1c42014-12-11 12:11:49 -0800527 print('Changes:')
528 print(report_changes(versions_before, versions_after))
Don Garrett8db752c2014-10-17 16:56:55 -0700529
530
531if __name__ == '__main__':
Don Garrett03432d62014-11-19 18:18:35 -0800532 sys.exit(main(sys.argv[1:]))