autotest: Make 'get_hosts' respect static attributes & labels.

BUG=chromium:792309
TEST=Ran unittest. Call RPC 'get_hosts' locally.

Change-Id: I1fd5cbe59781fd3c17816c5e386cffc05635c0ff
Reviewed-on: https://chromium-review.googlesource.com/929609
Commit-Ready: Xixuan Wu <[email protected]>
Tested-by: Xixuan Wu <[email protected]>
Reviewed-by: Shuqian Zhao <[email protected]>
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index d086d5d..a8b4302 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -89,6 +89,9 @@
 RESPECT_STATIC_LABELS = global_config.global_config.get_config_value(
         'SKYLAB', 'respect_static_labels', type=bool, default=False)
 
+RESPECT_STATIC_ATTRIBUTES = global_config.global_config.get_config_value(
+        'SKYLAB', 'respect_static_attributes', type=bool, default=False)
+
 # Relevant CrosDynamicSuiteExceptions are defined in client/common_lib/error.py.
 
 # labels
@@ -637,14 +640,40 @@
                                                'acl_list')
     models.Host.objects.populate_relationships(hosts, models.HostAttribute,
                                                'attribute_list')
+    models.Host.objects.populate_relationships(hosts,
+                                               models.StaticHostAttribute,
+                                               'staticattribute_list')
     host_dicts = []
     for host_obj in hosts:
         host_dict = host_obj.get_object_dict()
-        host_dict['labels'] = [label.name for label in host_obj.label_list]
-        host_dict['platform'] = rpc_utils.find_platform(host_obj)
         host_dict['acls'] = [acl.name for acl in host_obj.acl_list]
         host_dict['attributes'] = dict((attribute.attribute, attribute.value)
                                        for attribute in host_obj.attribute_list)
+        if RESPECT_STATIC_LABELS:
+            label_list = []
+            # Only keep static labels which has a corresponding entries in
+            # afe_labels.
+            for label in host_obj.label_list:
+                if label.is_replaced_by_static():
+                    static_label = models.StaticLabel.smart_get(label.name)
+                    label_list.append(static_label)
+                else:
+                    label_list.append(label)
+
+            host_dict['labels'] = [label.name for label in label_list]
+            host_dict['platform'] = rpc_utils.find_platform(
+                    host_obj.hostname, label_list)
+        else:
+            host_dict['labels'] = [label.name for label in host_obj.label_list]
+            host_dict['platform'] = rpc_utils.find_platform(
+                    host_obj.hostname, host_obj.label_list)
+
+        if RESPECT_STATIC_ATTRIBUTES:
+            # Overwrite attribute with values in afe_static_host_attributes.
+            for attr in host_obj.staticattribute_list:
+                if attr.attribute in host_dict['attributes']:
+                    host_dict['attributes'][attr.attribute] = attr.value
+
         if include_current_job:
             host_dict['current_job'] = None
             host_dict['current_special_task'] = None
@@ -660,6 +689,7 @@
                         '%d-%s' % (tasks[0].get_object_dict()['id'],
                                    tasks[0].get_object_dict()['task'].lower()))
         host_dicts.append(host_dict)
+
     return rpc_utils.prepare_for_serialization(host_dicts)
 
 
diff --git a/frontend/afe/rpc_interface_unittest.py b/frontend/afe/rpc_interface_unittest.py
index f134d97..017b122 100755
--- a/frontend/afe/rpc_interface_unittest.py
+++ b/frontend/afe/rpc_interface_unittest.py
@@ -246,6 +246,68 @@
         self._do_heartbeat_and_assert_response(known_hosts=[host1])
 
 
+class RpcInterfaceTestWithStaticAttribute(
+        mox.MoxTestBase, unittest.TestCase,
+        frontend_test_utils.FrontendTestMixin):
+
+    def setUp(self):
+        super(RpcInterfaceTestWithStaticAttribute, self).setUp()
+        self._frontend_common_setup()
+        self.god = mock.mock_god()
+        self.old_respect_static_config = rpc_interface.RESPECT_STATIC_ATTRIBUTES
+        rpc_interface.RESPECT_STATIC_ATTRIBUTES = True
+        models.RESPECT_STATIC_ATTRIBUTES = True
+
+
+    def tearDown(self):
+        self.god.unstub_all()
+        self._frontend_common_teardown()
+        global_config.global_config.reset_config_values()
+        rpc_interface.RESPECT_STATIC_ATTRIBUTES = self.old_respect_static_config
+        models.RESPECT_STATIC_ATTRIBUTES = self.old_respect_static_config
+
+
+    def _set_static_attribute(self, host, attribute, value):
+        """Set static attribute for a host.
+
+        It ensures that all static attributes have a corresponding
+        entry in afe_host_attributes.
+        """
+        # Get or create the reference object in afe_host_attributes.
+        model, args = host._get_attribute_model_and_args(attribute)
+        model.objects.get_or_create(**args)
+
+        attribute_model, get_args = host._get_static_attribute_model_and_args(
+            attribute)
+        attribute_object, _ = attribute_model.objects.get_or_create(**get_args)
+        attribute_object.value = value
+        attribute_object.save()
+
+
+    def _fake_host_with_static_attributes(self):
+        host1 = models.Host.objects.create(hostname='test_host')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        host1.set_attribute('test_attribute2', 'test_value2')
+        self._set_static_attribute(host1, 'test_attribute1', 'static_value1')
+        self._set_static_attribute(host1, 'static_attribute1', 'static_value2')
+        host1.save()
+        return host1
+
+
+    def test_get_hosts(self):
+        host1 = self._fake_host_with_static_attributes()
+        hosts = rpc_interface.get_hosts(hostname=host1.hostname)
+        host = hosts[0]
+
+        self.assertEquals(host['hostname'], 'test_host')
+        self.assertEquals(host['acls'], ['Everyone'])
+        # Respect the value of static attributes.
+        self.assertEquals(host['attributes'],
+                          {'test_attribute1': 'static_value1',
+                           'test_attribute2': 'test_value2',
+                           'static_attribute1': 'static_value2'})
+
+
 class RpcInterfaceTestWithStaticLabel(ShardHeartbeatTest,
                                       frontend_test_utils.FrontendTestMixin):
 
@@ -268,6 +330,44 @@
         models.RESPECT_STATIC_LABELS = self.old_respect_static_config
 
 
+    def _fake_host_with_static_labels(self):
+        host1 = models.Host.objects.create(hostname='test_host')
+        label1 = models.Label.objects.create(
+                name='non_static_label1', platform=False)
+        non_static_platform = models.Label.objects.create(
+                name='static_platform', platform=False)
+        static_platform = models.StaticLabel.objects.create(
+                name='static_platform', platform=True)
+        models.ReplacedLabel.objects.create(label_id=non_static_platform.id)
+        host1.static_labels.add(static_platform)
+        host1.labels.add(non_static_platform)
+        host1.labels.add(label1)
+        host1.save()
+        return host1
+
+
+    def test_get_hosts(self):
+        host1 = self._fake_host_with_static_labels()
+        hosts = rpc_interface.get_hosts(hostname=host1.hostname)
+        host = hosts[0]
+
+        self.assertEquals(host['hostname'], 'test_host')
+        self.assertEquals(host['acls'], ['Everyone'])
+        # Respect all labels in afe_hosts_labels.
+        self.assertEquals(host['labels'],
+                          ['non_static_label1', 'static_platform'])
+        # Respect static labels.
+        self.assertEquals(host['platform'], 'static_platform')
+
+
+    def test_get_hosts_multiple_labels(self):
+        self._fake_host_with_static_labels()
+        hosts = rpc_interface.get_hosts(
+                multiple_labels=['non_static_label1', 'static_platform'])
+        host = hosts[0]
+        self.assertEquals(host['hostname'], 'test_host')
+
+
     def test_delete_static_label(self):
         label1 = models.Label.smart_get('static')
 
diff --git a/frontend/afe/rpc_utils.py b/frontend/afe/rpc_utils.py
index 5741d2e..c347ffb 100644
--- a/frontend/afe/rpc_utils.py
+++ b/frontend/afe/rpc_utils.py
@@ -557,21 +557,26 @@
     return False
 
 
-def find_platform(host):
+def find_platform(hostname, label_list):
     """
     Figure out the platform name for the given host
     object.  If none, the return value for either will be None.
 
+    @param hostname: The hostname to find platform.
+    @param label_list: The label list to find platform.
+
     @returns platform name for the given host.
     """
-    platforms = [label.name for label in host.label_list if label.platform]
+    platforms = [label.name for label in label_list if label.platform]
     if not platforms:
         platform = None
     else:
         platform = platforms[0]
+
     if len(platforms) > 1:
         raise ValueError('Host %s has more than one platform: %s' %
-                         (host.hostname, ', '.join(platforms)))
+                         (hostname, ', '.join(platforms)))
+
     return platform