#!/usr/bin/python
# Copyright (c) 2013 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.
"""A small wrapper script, iterates through
the known hosts and tries to call get_labels()
to discover host functionality, and adds these
detected labels to host.
Limitations:
- Does not keep a count of how many labels were
actually added.
- If a label is added by this script because it
is detected as supported by get_labels, but later becomes
unsupported, this script has no way to know that it
should be removed, so it will remain attached to the host.
See crosbug.com/38569
"""
from multiprocessing import pool
import logging
import socket
import argparse
import sys
import common
from autotest_lib.server import hosts
from autotest_lib.server import frontend
from autotest_lib.client.common_lib import error
# A list of label prefix that each dut should only have one of such label with
# the given prefix, e.g., a dut can't have both labels of power:battery and
# power:AC_only.
SINGLETON_LABEL_PREFIX = ['power:']
def add_missing_labels(afe, hostname):
"""
Queries the detectable labels supported by the given host,
and adds those labels to the host.
@param afe: A frontend.AFE() instance.
@param hostname: The host to query and update.
@return: True on success.
False on failure to fetch labels or to add any individual label.
"""
host = None
try:
host = hosts.create_host(hostname)
labels = host.get_labels()
except socket.gaierror:
logging.warning('Unable to establish ssh connection to hostname '
'%s. Skipping.', hostname)
return False
except error.AutoservError:
logging.warning('Unable to query labels on hostname %s. Skipping.',
hostname)
return False
finally:
if host:
host.close()
afe_host = afe.get_hosts(hostname=hostname)[0]
label_matches = afe.get_labels(name__in=labels)
for label in label_matches:
singleton_prefixes = [p for p in SINGLETON_LABEL_PREFIX
if label.name.startswith(p)]
if len(singleton_prefixes) == 1:
singleton_prefix = singleton_prefixes[0]
# Delete existing label with `singleton_prefix`
labels_to_delete = [l for l in afe_host.labels
if l.startswith(singleton_prefix) and
not l in labels]
if labels_to_delete:
logging.warning('Removing label %s', labels_to_delete)
afe_labels_to_delete = afe.get_labels(name__in=labels_to_delete)
for afe_label in afe_labels_to_delete:
afe_label.remove_hosts(hosts=[hostname])
label.add_hosts(hosts=[hostname])
missing_labels = set(labels) - set([l.name for l in label_matches])
if missing_labels:
for label in missing_labels:
logging.warning('Unable to add label %s to host %s. '
'Skipping unknown label.', label, hostname)
return False
return True
def main():
""""
Entry point for add_detected_host_labels script.
"""
parser = argparse.ArgumentParser()
parser.add_argument('-s', '--silent', dest='silent', action='store_true',
help='Suppress all but critical logging messages.')
parser.add_argument('-i', '--info', dest='info_only', action='store_true',
help='Suppress logging messages below INFO priority.')
parser.add_argument('-m', '--machines', dest='machines',
help='Comma separated list of machines to check.')
options = parser.parse_args()
if options.silent and options.info_only:
print 'The -i and -s flags cannot be used together.'
parser.print_help()
return 0
if options.silent:
logging.disable(logging.CRITICAL)
if options.info_only:
logging.disable(logging.DEBUG)
threadpool = pool.ThreadPool()
afe = frontend.AFE()
if options.machines:
hostnames = [m.strip() for m in options.machines.split(',')]
else:
hostnames = afe.get_hostnames()
successes = sum(threadpool.imap_unordered(
lambda x: add_missing_labels(afe, x),
hostnames))
attempts = len(hostnames)
logging.info('Label updating finished. Failed update on %d out of %d '
'hosts.', attempts-successes, attempts)
return 0
if __name__ == '__main__':
sys.exit(main())