xref: /aosp_15_r20/external/autotest/server/site_host_attributes.py (revision 9c5db1993ded3edbeafc8092d69fe5de2ee02df7)
1# Lint as: python2, python3
2# Copyright (c) 2011 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"""Host attributes define properties on individual hosts.
7
8Host attributes are specified a strings with the format:
9    <key>{,<value>}?
10
11A machine may have a list of strings for attributes like:
12
13    ['has_80211n,True',
14     'has_ssd,False',
15     'drive_kind,string,ssd,1']
16
17A legal attribute has the pattern:
18    <name>,<kind>(,<extra>)?
19
20Name can be any legal python identifier.  Kind may be any of 'string', 'True',
21or 'False'.  Only if kind is string can there be extra data.
22
23Strings which are not legal attributes are ignored.
24
25Given the above list of attributes, you can use the syntax:
26    host_attributes.drive_kind => 'ssd,1'
27    host_attributes.has_80211n => True
28    host_attributes.has_ssd => False
29    host_attributes.unknown_attribute => raise KeyError
30
31Machine attributes can be specified in two ways.
32
33If you create private_host_attributes_config.py and private_host_attributes
34there, we will use it when possible instead of using the server front-end.
35
36Example configuration:
37    private_host_attributes = {
38        "mydevice": ["has_80211n,True",
39                     "has_resume_bug,False"]
40    }
41
42We also consult the AFE database for its labels which are all treated as host
43attribute strings defined above.  Illegal strings are ignored.
44"""
45
46
47import hashlib, logging, os
48
49from autotest_lib.client.common_lib import utils
50
51private_host_attributes = utils.import_site_symbol(
52        __file__,
53        'autotest_lib.server.private_host_attributes_config',
54        'private_host_attributes',
55        placeholder={})
56
57try:
58    settings = 'autotest_lib.frontend.settings'
59    os.environ['DJANGO_SETTINGS_MODULE'] = settings
60    has_models = True
61except Exception:
62    has_models = False
63
64
65_DEFAULT_ATTRIBUTES = [
66    'has_80211n,True',
67    'has_bluetooth,False',
68    'has_chromeos_firmware,True',
69    'has_resume_bug,False',
70    'has_ssd,True'
71    ]
72
73
74class HostAttributes(object):
75    """Host attribute class for site specific attributes."""
76
77    def __init__(self, host):
78        """Create an instance of HostAttribute for the given hostname.
79
80        We look up the host in both the hardcoded configuration and the AFE
81        models if they can be found.
82
83        Args:
84            host: Host name to find attributes for.
85        """
86        self._add_attributes(_DEFAULT_ATTRIBUTES)
87        if host in private_host_attributes:
88            logging.info('Including private_host_attributes file for %s', host)
89            self._add_attributes(private_host_attributes[host])
90        if has_models:
91            logging.info("Including labels for %s from database", host)
92            host_obj = models.Host.valid_objects.get(hostname=host)
93            self._add_attributes([label.name for label in
94                                  host_obj.labels.all()])
95        for key, value in self.__dict__.items():
96            logging.info('Host attribute: %s => %s', key, value)
97
98    def _add_attributes(self, attributes):
99        for attribute in attributes:
100            splitnames = attribute.split(',')
101            if len(splitnames) == 1:
102                if 'netbook_' in attribute:
103                    # Hash board names to prevent any accidental leaks.
104                    splitnames = ['netbook_' + hashlib.sha256(
105                        attribute.split('netbook_')[1]
106                        .encode('utf-8')).hexdigest()[:8], 'True']
107                else:
108                    splitnames = attribute.split(':')
109                    if len(splitnames) == 2:
110                        setattr(self, splitnames[0], splitnames[1])
111                    continue
112            value = ','.join(splitnames[1:])
113            if value == 'True':
114                value = True
115            elif value == 'False':
116                value = False
117            elif splitnames[1] == 'string' and len(splitnames) > 2:
118                value = ','.join(splitnames[2:])
119            else:
120                logging.info('Non-attribute string "%s" is ignored', attribute)
121                continue
122            setattr(self, splitnames[0], value)
123
124    def get_attributes(self):
125        """Return a list of non-False attributes for this host."""
126        return [key for key, value in self.__dict__.items() if value]
127