blob: 5df87620c55cf834a350f47a7a483ae3c1b69dc1 [file] [log] [blame]
#!/usr/bin/python
# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Runs on autotest servers from a cron job to self update them.
This script is designed to run on all autotest servers to allow them to
automatically self-update based on the manifests used to create their (existing)
repos.
"""
from __future__ import print_function
import ConfigParser
import os
import subprocess
import sys
import time
import common
from autotest_lib.client.common_lib import global_config
class DirtyTreeException(Exception):
"""Raised when the tree has been modified in an unexpected way."""
class UnknownCommandException(Exception):
"""Raised when we try to run a command name with no associated command."""
class UnstableServices(Exception):
"""Raised if a service appears unstable after restart."""
def verify_repo_clean():
"""This function verifies that the current repo is valid, and clean.
@raises DirtyTreeException if the repo is not clean.
@raises subprocess.CalledProcessError on a repo command failure.
"""
CLEAN_STATUS_OUTPUT = 'nothing to commit (working directory clean)'
print('Checking tree status:')
out = subprocess.check_output(['repo', 'status'], stderr=subprocess.STDOUT)
if out.strip() != CLEAN_STATUS_OUTPUT:
print('Dirty.')
raise DirtyTreeException(out)
print('Clean.')
def repo_versions():
"""This function collects the versions of all git repos in the general repo.
@returns A string the describes HEAD of all git repos.
@raises subprocess.CalledProcessError on a repo command failure.
"""
print('Checking repository versions.')
cmd = ['repo', 'forall', '-p', '-c', 'git', 'log', '-1', '--oneline']
return subprocess.check_output(cmd)
def repo_sync():
"""Perform a repo sync.
@raises subprocess.CalledProcessError on a repo command failure.
"""
print('Updating Repo.')
subprocess.check_output(['repo', 'sync'], stderr=subprocess.STDOUT)
def restart_services():
"""Restart services as needed for the current server type.
This checks the shadow_config.ini to see what should be restarted.
@raises UnknownCommandException If shadow_config uses an unknown command.
@raises subprocess.CalledProcessError on a command failure.
"""
global_config.global_config.parse_config_file()
cmd_names = ''
service_names = ''
try:
# Lookup the list of commands to consider. They are intended to be
# in global_config.ini so that they can be shared everywhere.
cmds = dict(global_config.global_config.config.items(
'UPDATE_COMMANDS'))
# Lookup which commands to run. These commonly come from
# shadow_config.ini, since they vary by server type.
cmd_names = global_config.global_config.get_config_value(
'UPDATE', 'commands', type=list)
except (ConfigParser.NoSectionError, global_config.ConfigError):
pass
try:
# From shadow_config.ini, lookup which services to restart.
service_names = global_config.global_config.get_config_value(
'UPDATE', 'services', type=list)
except (ConfigParser.NoSectionError, global_config.ConfigError):
pass
if cmd_names:
print('Running update commands....')
for name in cmd_names:
if name not in cmds:
raise UnknownCommandException(name, cmds)
expanded_command = cmds[name]
expanded_command.replace('AUTOTEST_REPO', common.autotest_dir)
print('Updating %s with: %s' % (name, expanded_command))
subprocess.check_call(expanded_command, shell=True)
if service_names:
service_status = {}
print('Restarting Services....')
for name in service_names:
print('Updating %s with: %s' % (name, expanded_command))
subprocess.check_call(['sudo', 'service', name, 'restart'])
def status(name):
return subprocess.check_output(['sudo', 'status', name])
# Record the status of each service (including pid).
service_status = {n: status(n) for n in service_names}
time.sleep(60)
# Look for any services that changed status.
unstable_services = [n for n in service_names
if status(n) != service_status[n]]
# Report any services having issues.
if unstable_names:
raise UnstableServices(unstable_services)
def main():
"""Main method."""
os.chdir(common.autotest_dir)
try:
verify_repo_clean()
except DirtyTreeException as e:
print('Local tree is dirty, can\'t perform update safely.')
print()
print('repo status:')
print(e.args[0])
sys.exit(1)
versions_before = repo_versions()
repo_sync()
versions_after = repo_versions()
if versions_before == versions_after:
print('No change found.')
return
try:
restart_services()
except UnstableServices as e:
print('The following services were not stable after the update:')
print(e.args[0])
sys.exit(1)
print('Current production versions:')
print(versions_after)
if __name__ == '__main__':
main()