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