Moblab: Servo Support via Host Attributes.
Moblab devices will look for servo_args inside a host's host
attributes.
Host attributes can be set via the CLI or the Web Frontend.
CLI to see attributes:
atest host stat <host>
CLI to set attribute:
atest host mod --attribute <attribute> --value <value> <host>
Updated the afe's management system so that it properly configures
the admin interface to allow editing of host attributes.
BUG=chromium:394544
TEST=local moblab setup. Tested both CLI and AFE host attribute
manipulation. Launched Servod, and ensure servo host is only
created when the attribute is applied to the host.
Change-Id: Ie3cccab31aa7518435ef0abc6ce206363406c272
Reviewed-on: https://chromium-review.googlesource.com/255550
Reviewed-by: Simran Basi <sbasi@chromium.org>
Commit-Queue: Simran Basi <sbasi@chromium.org>
Tested-by: Simran Basi <sbasi@chromium.org>
diff --git a/cli/host.py b/cli/host.py
index be1f318..53d572d 100644
--- a/cli/host.py
+++ b/cli/host.py
@@ -20,6 +20,7 @@
See topic_common.py for a High Level Design and Algorithm.
"""
+import re
from autotest_lib.cli import action_common, topic_common
from autotest_lib.client.common_lib import host_protections
@@ -229,12 +230,12 @@
acls = self.execute_rpc('get_acl_groups', hosts__hostname=host)
labels = self.execute_rpc('get_labels', host__hostname=host)
- results.append ([[stat], acls, labels])
+ results.append([[stat], acls, labels, stat['attributes']])
return results
def output(self, results):
- for stats, acls, labels in results:
+ for stats, acls, labels, attributes in results:
print '-'*5
self.print_fields(stats,
keys=['hostname', 'platform',
@@ -243,6 +244,7 @@
self.print_by_ids(acls, 'ACLs', line_before=True)
labels = self._cleanup_labels(labels)
self.print_by_ids(labels, 'Labels', line_before=True)
+ self.print_dict(attributes, 'Host Attributes', line_before=True)
class host_jobs(host):
@@ -308,11 +310,14 @@
"""atest host mod --lock|--unlock|--protection
--mlist <file>|<hosts>"""
usage_action = 'mod'
+ attribute_regex = r'^(?P<attribute>\w+)=(?P<value>.+)?'
def __init__(self):
"""Add the options specific to the mod action"""
self.data = {}
self.messages = []
+ self.attribute = None
+ self.value = None
super(host_mod, self).__init__()
self.parser.add_option('-l', '--lock',
help='Lock hosts',
@@ -326,6 +331,10 @@
', '.join('"%s"' % p
for p in self.protections)),
choices=self.protections)
+ self.parser.add_option('--attribute', '-a', default='',
+ help=('Host attribute to add or change. Format '
+ 'is <attribute>=<value>. Value can be '
+ 'blank to delete attribute.'))
def parse(self):
@@ -338,8 +347,18 @@
self.data['protection'] = options.protection
self.messages.append('Protection set to "%s"' % options.protection)
- if len(self.data) == 0:
+ if len(self.data) == 0 and not options.attribute:
self.invalid_syntax('No modification requested')
+
+ if options.attribute:
+ match = re.match(self.attribute_regex, options.attribute)
+ if not match:
+ self.invalid_syntax('Attributes must be in <attribute>=<value>'
+ ' syntax!')
+
+ self.attribute = match.group('attribute')
+ self.value = match.group('value')
+
return (options, leftover)
@@ -349,6 +368,10 @@
try:
res = self.execute_rpc('modify_host', item=host,
id=host, **self.data)
+ if self.attribute:
+ self.execute_rpc('set_host_attribute',
+ attribute=self.attribute,
+ value=self.value, hostname=host)
# TODO: Make the AFE return True or False,
# especially for lock
successes.append(host)
diff --git a/cli/host_unittest.py b/cli/host_unittest.py
index 33a2103..a9bdd90 100755
--- a/cli/host_unittest.py
+++ b/cli/host_unittest.py
@@ -622,7 +622,8 @@
u'synch_id': None,
u'shard': None,
u'platform': u'plat1',
- u'id': 3}]),
+ u'id': 3,
+ u'attributes': {}}]),
('get_hosts', {'hostname': 'host0'},
True,
[{u'status': u'Ready',
@@ -636,7 +637,8 @@
u'shard': None,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 2}]),
+ u'id': 2,
+ u'attributes': {}}]),
('get_acl_groups', {'hosts__hostname': 'host1'},
True,
[{u'description': u'',
@@ -697,7 +699,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 2}]),
+ u'id': 2,
+ u'attributes': {}}]),
('get_acl_groups', {'hosts__hostname': 'host0'},
True,
[{u'description': u'',
@@ -747,7 +750,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 2}]),
+ u'id': 2,
+ u'attributes': {}}]),
('get_acl_groups', {'hosts__hostname': 'host0'},
True,
[{u'description': u'',
@@ -795,7 +799,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat1',
- u'id': 3},
+ u'id': 3,
+ u'attributes': {}},
{u'status': u'Ready',
u'hostname': u'host0',
u'locked': False,
@@ -806,7 +811,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 2}]),
+ u'id': 2,
+ u'attributes': {}}]),
('get_acl_groups', {'hosts__hostname': 'host1'},
True,
[{u'description': u'',
@@ -865,7 +871,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 5}]),
+ u'id': 5,
+ u'attributes': {}}]),
('get_hosts', {'hostname__startswith': 'ho'},
True,
[{u'status': u'Ready',
@@ -878,7 +885,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat1',
- u'id': 3},
+ u'id': 3,
+ u'attributes': {}},
{u'status': u'Ready',
u'hostname': u'host0',
u'locked': False,
@@ -889,7 +897,8 @@
u'invalid': False,
u'synch_id': None,
u'platform': u'plat0',
- u'id': 2}]),
+ u'id': 2,
+ u'attributes': {}}]),
('get_acl_groups', {'hosts__hostname': 'newhost0'},
True,
[{u'description': u'',
diff --git a/cli/topic_common.py b/cli/topic_common.py
index 2ee42b8..b191e6c 100644
--- a/cli/topic_common.py
+++ b/cli/topic_common.py
@@ -666,6 +666,23 @@
return ' '.join(["%%-%ds" % lens[key] for key in keys])
+ def print_dict(self, items, title=None, line_before=False):
+ """Print a dictionary.
+
+ @param items: Dictionary to print.
+ @param title: Title of the output, default to None.
+ @param line_before: True to print an empty line before the output,
+ default to False.
+ """
+ if not items:
+ return
+ if line_before:
+ print
+ print title
+ for key, value in items.items():
+ print '%s : %s' % (key, value)
+
+
def print_table_std(self, items, keys_header, sublist_keys=()):
"""Print a mix of header and lists in a user readable format.
diff --git a/frontend/afe/management.py b/frontend/afe/management.py
index 70fd775..3e063ad 100644
--- a/frontend/afe/management.py
+++ b/frontend/afe/management.py
@@ -25,7 +25,7 @@
PermissionModel = auth.models.Permission
have_permissions = list(admin_group.permissions.all())
for model_name in ('host', 'label', 'test', 'aclgroup', 'profiler',
- 'atomicgroup'):
+ 'atomicgroup', 'hostattribute'):
for permission_type in ('add', 'change', 'delete'):
codename = permission_type + '_' + model_name
permissions = list(PermissionModel.objects.filter(
diff --git a/server/hosts/servo_host.py b/server/hosts/servo_host.py
index c0f2a9b..cbe6099 100644
--- a/server/hosts/servo_host.py
+++ b/server/hosts/servo_host.py
@@ -25,10 +25,17 @@
from autotest_lib.client.common_lib.cros.network import ping_runner
from autotest_lib.server import site_utils as server_site_utils
from autotest_lib.server.cros.servo import servo
+from autotest_lib.server.cros.dynamic_suite import frontend_wrappers
from autotest_lib.server.hosts import ssh_host
from autotest_lib.site_utils.rpm_control_system import rpm_client
+# Names of the host attributes in the database that represent the values for
+# the servo_host and servo_port for a servo connected to the DUT.
+SERVO_HOST_ATTR = 'servo_host'
+SERVO_PORT_ATTR = 'servo_port'
+
+
class ServoHostException(error.AutoservError):
"""This is the base class for exceptions raised by ServoHost."""
pass
@@ -325,6 +332,8 @@
@raises ServoHostVerifyFailure if /var/lib/servod/config does not exist.
"""
+ if self._is_localhost:
+ return
try:
self.run('test -f /var/lib/servod/config')
except (error.AutoservRunError, error.AutoservSSHTimeout) as e:
@@ -644,8 +653,19 @@
@returns: A ServoHost object or None. See comments above.
"""
- lab_servo_hostname = make_servo_hostname(dut)
- is_in_lab = utils.host_is_in_lab_zone(lab_servo_hostname)
+ if not utils.is_moblab():
+ lab_servo_hostname = make_servo_hostname(dut)
+ is_in_lab = utils.host_is_in_lab_zone(lab_servo_hostname)
+ else:
+ # Servos on Moblab are not in the actual lab.
+ is_in_lab = False
+ afe = frontend_wrappers.RetryingAFE(timeout_min=5, delay_sec=10)
+ hosts = afe.get_hosts(hostname=dut)
+ if hosts and SERVO_HOST_ATTR in hosts[0].attributes:
+ servo_args = {}
+ servo_args[SERVO_HOST_ATTR] = hosts[0].attributes[SERVO_HOST_ATTR]
+ servo_args[SERVO_PORT_ATTR] = hosts[0].attributes.get(
+ SERVO_PORT_ATTR, 9999)
if not is_in_lab:
if servo_args is None: