You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
323 lines
11 KiB
323 lines
11 KiB
# Copyright 2016 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.
|
|
|
|
"""This class defines the Base Label classes."""
|
|
|
|
|
|
import logging
|
|
|
|
import common
|
|
from autotest_lib.server.hosts import afe_store
|
|
from autotest_lib.server.hosts import host_info
|
|
from autotest_lib.server.hosts import shadowing_store
|
|
|
|
|
|
def forever_exists_decorate(exists):
|
|
"""
|
|
Decorator for labels that should exist forever once applied.
|
|
|
|
We'll check if the label already exists on the host and return True if so.
|
|
Otherwise we'll check if the label should exist on the host.
|
|
|
|
@param exists: The exists method on the label class.
|
|
"""
|
|
def exists_wrapper(self, host):
|
|
"""
|
|
Wrapper around the label exists method.
|
|
|
|
@param self: The label object.
|
|
@param host: The host object to run methods on.
|
|
|
|
@returns True if the label already exists on the host, otherwise run
|
|
the exists method.
|
|
"""
|
|
info = host.host_info_store.get()
|
|
return (self._NAME in info.labels) or exists(self, host)
|
|
return exists_wrapper
|
|
|
|
|
|
class BaseLabel(object):
|
|
"""
|
|
This class contains the scaffolding for the host-specific labels.
|
|
|
|
@property _NAME String that is either the label returned or a prefix of a
|
|
generated label.
|
|
"""
|
|
|
|
_NAME = None
|
|
|
|
def generate_labels(self, host):
|
|
"""
|
|
Return the list of labels generated for the host.
|
|
|
|
@param host: The host object to check on. Not needed here for base case
|
|
but could be needed for subclasses.
|
|
|
|
@return a list of labels applicable to the host.
|
|
"""
|
|
return [self._NAME]
|
|
|
|
|
|
def exists(self, host):
|
|
"""
|
|
Checks the host if the label is applicable or not.
|
|
|
|
This method is geared for the type of labels that indicate if the host
|
|
has a feature (bluetooth, touchscreen, etc) and as such require
|
|
detection logic to determine if the label should be applicable to the
|
|
host or not.
|
|
|
|
@param host: The host object to check on.
|
|
"""
|
|
raise NotImplementedError('exists not implemented')
|
|
|
|
|
|
def get(self, host):
|
|
"""
|
|
Return the list of labels.
|
|
|
|
@param host: The host object to check on.
|
|
"""
|
|
if self.exists(host):
|
|
return self.generate_labels(host)
|
|
else:
|
|
return []
|
|
|
|
|
|
def get_all_labels(self):
|
|
"""
|
|
Return all possible labels generated by this label class.
|
|
|
|
@returns a tuple of sets, the first set is for labels that are prefixes
|
|
like 'os:android'. The second set is for labels that are full
|
|
labels by themselves like 'bluetooth'.
|
|
"""
|
|
# Another subclass takes care of prefixed labels so this is empty.
|
|
prefix_labels = set()
|
|
full_labels_list = (self._NAME if isinstance(self._NAME, list) else
|
|
[self._NAME])
|
|
full_labels = set(full_labels_list)
|
|
|
|
return prefix_labels, full_labels
|
|
|
|
|
|
def update_for_task(self, task_name):
|
|
"""
|
|
This method helps to check which labels need to be updated.
|
|
State config labels are updated only for repair task.
|
|
Lab config labels are updated only for deploy task.
|
|
All labels are updated for any task.
|
|
|
|
It is the responsibility of the subclass to override this method
|
|
to differentiate itself as a state config label or a lab config label
|
|
and return the appropriate boolean value.
|
|
|
|
If the subclass doesn't override this method then that label will
|
|
always be updated for any type of task.
|
|
|
|
@returns True if labels should be updated for the task with given name
|
|
"""
|
|
return True
|
|
|
|
|
|
class StringLabel(BaseLabel):
|
|
"""
|
|
This class represents a string label that is dynamically generated.
|
|
|
|
This label class is used for the types of label that are always
|
|
present and will return at least one label out of a list of possible labels
|
|
(listed in _NAME). It is required that the subclasses implement
|
|
generate_labels() since the label class will need to figure out which labels
|
|
to return.
|
|
|
|
_NAME must always be overridden by the subclass with all the possible
|
|
labels that this label detection class can return in order to allow for
|
|
accurate label updating.
|
|
"""
|
|
|
|
def generate_labels(self, host):
|
|
raise NotImplementedError('generate_labels not implemented')
|
|
|
|
|
|
def exists(self, host):
|
|
"""Set to true since it is assumed the label is always applicable."""
|
|
return True
|
|
|
|
|
|
class StringPrefixLabel(StringLabel):
|
|
"""
|
|
This class represents a string label that is dynamically generated.
|
|
|
|
This label class is used for the types of label that usually are always
|
|
present and indicate the os/board/etc type of the host. The _NAME property
|
|
will be prepended with a colon to the generated labels like so:
|
|
|
|
_NAME = 'os'
|
|
generate_label() returns ['android']
|
|
|
|
The labels returned by this label class will be ['os:android'].
|
|
It is important that the _NAME attribute be overridden by the
|
|
subclass; otherwise, all labels returned will be prefixed with 'None:'.
|
|
"""
|
|
|
|
def get(self, host):
|
|
"""Return the list of labels with _NAME prefixed with a colon.
|
|
|
|
@param host: The host object to check on.
|
|
"""
|
|
if self.exists(host):
|
|
return ['%s:%s' % (self._NAME, label)
|
|
for label in self.generate_labels(host)]
|
|
else:
|
|
return []
|
|
|
|
|
|
def get_all_labels(self):
|
|
"""
|
|
Return all possible labels generated by this label class.
|
|
|
|
@returns a tuple of sets, the first set is for labels that are prefixes
|
|
like 'os:android'. The second set is for labels that are full
|
|
labels by themselves like 'bluetooth'.
|
|
"""
|
|
# Since this is a prefix label class, we only care about
|
|
# prefixed_labels. We'll need to append the ':' to the label name to
|
|
# make sure we only match on prefix labels.
|
|
full_labels = set()
|
|
prefix_labels = set(['%s:' % self._NAME])
|
|
|
|
return prefix_labels, full_labels
|
|
|
|
|
|
class LabelRetriever(object):
|
|
"""This class will assist in retrieving/updating the host labels."""
|
|
|
|
def _populate_known_labels(self, label_list, task_name):
|
|
"""Create a list of known labels that is created through this class."""
|
|
for label_instance in label_list:
|
|
# populate only the labels that need to be updated for this task.
|
|
if label_instance.update_for_task(task_name):
|
|
prefixed_labels, full_labels = label_instance.get_all_labels()
|
|
self.label_prefix_names.update(prefixed_labels)
|
|
self.label_full_names.update(full_labels)
|
|
|
|
|
|
def __init__(self, label_list):
|
|
self._labels = label_list
|
|
# These two sets will contain the list of labels we can safely remove
|
|
# during the update_labels call.
|
|
self.label_full_names = set()
|
|
self.label_prefix_names = set()
|
|
|
|
|
|
def get_labels(self, host):
|
|
"""
|
|
Retrieve the labels for the host.
|
|
|
|
@param host: The host to get the labels for.
|
|
"""
|
|
labels = []
|
|
for label in self._labels:
|
|
logging.info('checking label %s', label.__class__.__name__)
|
|
try:
|
|
labels.extend(label.get(host))
|
|
except Exception:
|
|
logging.exception('error getting label %s.',
|
|
label.__class__.__name__)
|
|
return labels
|
|
|
|
|
|
def get_labels_for_update(self, host, task_name):
|
|
"""
|
|
Retrieve the labels for the host which needs to be updated.
|
|
|
|
@param host: The host to get the labels for updating.
|
|
@param task_name: task name(repair/deploy) for the operation.
|
|
|
|
@returns labels to be updated
|
|
"""
|
|
labels = []
|
|
for label in self._labels:
|
|
try:
|
|
# get only the labels which need to be updated for this task.
|
|
if label.update_for_task(task_name):
|
|
logging.info('checking label update %s',
|
|
label.__class__.__name__)
|
|
labels.extend(label.get(host))
|
|
except Exception:
|
|
logging.exception('error getting label %s.',
|
|
label.__class__.__name__)
|
|
return labels
|
|
|
|
|
|
def _is_known_label(self, label):
|
|
"""
|
|
Checks if the label is a label known to the label detection framework.
|
|
|
|
@param label: The label to check if we want to skip or not.
|
|
|
|
@returns True to skip (which means to keep this label, False to remove.
|
|
"""
|
|
return (label in self.label_full_names or
|
|
any([label.startswith(p) for p in self.label_prefix_names]))
|
|
|
|
|
|
def _carry_over_unknown_labels(self, old_labels, new_labels):
|
|
"""Update new_labels by adding back old unknown labels.
|
|
|
|
We only delete labels that we might have created earlier. There are
|
|
some labels we should not be removing (e.g. pool:bvt) that we
|
|
want to keep but won't be part of the new labels detected on the host.
|
|
To do that we compare the passed in label to our list of known labels
|
|
and if we get a match, we feel safe knowing we can remove the label.
|
|
Otherwise we leave that label alone since it was generated elsewhere.
|
|
|
|
@param old_labels: List of labels already on the host.
|
|
@param new_labels: List of newly detected labels. This list will be
|
|
updated to add back labels that are not tracked by the detection
|
|
framework.
|
|
"""
|
|
missing_labels = set(old_labels) - set(new_labels)
|
|
for label in missing_labels:
|
|
if not self._is_known_label(label):
|
|
new_labels.append(label)
|
|
|
|
|
|
def _commit_info(self, host, new_info, keep_pool):
|
|
if keep_pool and isinstance(host.host_info_store,
|
|
shadowing_store.ShadowingStore):
|
|
primary_store = afe_store.AfeStoreKeepPool(host.hostname)
|
|
host.host_info_store.commit_with_substitute(
|
|
new_info,
|
|
primary_store=primary_store,
|
|
shadow_store=None)
|
|
return
|
|
|
|
host.host_info_store.commit(new_info)
|
|
|
|
|
|
def update_labels(self, host, task_name='', keep_pool=False):
|
|
"""
|
|
Retrieve the labels from the host and update if needed.
|
|
|
|
@param host: The host to update the labels for.
|
|
"""
|
|
# If we haven't yet grabbed our list of known labels, do so now.
|
|
if not self.label_full_names and not self.label_prefix_names:
|
|
self._populate_known_labels(self._labels, task_name)
|
|
|
|
# Label detection hits the DUT so it can be slow. Do it before reading
|
|
# old labels from HostInfoStore to minimize the time between read and
|
|
# commit of the HostInfo.
|
|
new_labels = self.get_labels_for_update(host, task_name)
|
|
old_info = host.host_info_store.get()
|
|
self._carry_over_unknown_labels(old_info.labels, new_labels)
|
|
new_info = host_info.HostInfo(
|
|
labels=new_labels,
|
|
attributes=old_info.attributes,
|
|
stable_versions=old_info.stable_versions,
|
|
)
|
|
if old_info != new_info:
|
|
self._commit_info(host, new_info, keep_pool)
|