autotest: Make RPC 'get_hosts_by_attribute' respect static attributes. am: 91ecef5829 am: 8a15d7af76 am: 5d84275966
am: 53bcc0545e

Change-Id: Ia665f7ff8cc57038ca1e27b2997e04d5f68d0380
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index a8b4302..b4e1733 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -1773,9 +1773,43 @@
     @returns List of hostnames that all have the same host attribute and
              value.
     """
-    hosts = models.HostAttribute.query_objects({'attribute': attribute,
-                                                'value': value})
-    return [row.host.hostname for row in hosts if row.host.invalid == 0]
+    rows = models.HostAttribute.query_objects({'attribute': attribute,
+                                               'value': value})
+    if RESPECT_STATIC_ATTRIBUTES:
+        returned_hosts = set()
+        # Add hosts:
+        #     * Non-valid
+        #     * Exist in afe_host_attribute with given attribute.
+        #     * Don't exist in afe_static_host_attribute OR exist in
+        #       afe_static_host_attribute with the same given value.
+        for row in rows:
+            if row.host.invalid != 0:
+                continue
+
+            static_hosts = models.StaticHostAttribute.query_objects(
+                {'host_id': row.host.id, 'attribute': attribute})
+            values = [static_host.value for static_host in static_hosts]
+            if len(values) == 0 or values[0] == value:
+                returned_hosts.add(row.host.hostname)
+
+        # Add hosts:
+        #     * Non-valid
+        #     * Exist in afe_static_host_attribute with given attribute
+        #       and value
+        #     * No need to check whether each static attribute has its
+        #       corresponding entry in afe_host_attribute since it is ensured
+        #       in inventory sync.
+        static_rows = models.StaticHostAttribute.query_objects(
+                {'attribute': attribute, 'value': value})
+        for row in static_rows:
+            if row.host.invalid != 0:
+                continue
+
+            returned_hosts.add(row.host.hostname)
+
+        return list(returned_hosts)
+    else:
+        return [row.host.hostname for row in rows if row.host.invalid == 0]
 
 
 def canonicalize_suite_name(suite_name):
diff --git a/frontend/afe/rpc_interface_unittest.py b/frontend/afe/rpc_interface_unittest.py
index 017b122..bf31ccb 100755
--- a/frontend/afe/rpc_interface_unittest.py
+++ b/frontend/afe/rpc_interface_unittest.py
@@ -307,6 +307,48 @@
                            'test_attribute2': 'test_value2',
                            'static_attribute1': 'static_value2'})
 
+    def test_get_hosts_by_attribute_without_static(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host2']))
+
+
+    def test_get_hosts_by_attribute_with_static(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        self._set_static_attribute(host1, 'test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+        self._set_static_attribute(host2, 'test_attribute1', 'static_value1')
+        host3 = models.Host.objects.create(hostname='test_host3')
+        self._set_static_attribute(host3, 'test_attribute1', 'test_value1')
+        host4 = models.Host.objects.create(hostname='test_host4')
+        host4.set_attribute('test_attribute1', 'test_value1')
+        host5 = models.Host.objects.create(hostname='test_host5')
+        host5.set_attribute('test_attribute1', 'temp_value1')
+        self._set_static_attribute(host5, 'test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        # host1: matched, it has the same value for test_attribute1.
+        # host2: not matched, it has a new value in
+        #        afe_static_host_attributes for test_attribute1.
+        # host3: matched, it has a corresponding entry in
+        #        afe_host_attributes for test_attribute1.
+        # host4: matched, test_attribute1 is not replaced by static
+        #        attribute.
+        # host5: matched, it has an updated & matched value for
+        #        test_attribute1 in afe_static_host_attributes.
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host3',
+                               'test_host4', 'test_host5']))
+
 
 class RpcInterfaceTestWithStaticLabel(ShardHeartbeatTest,
                                       frontend_test_utils.FrontendTestMixin):
@@ -690,6 +732,18 @@
         self.assertEquals(rpc_interface.ping_db(), [True])
 
 
+    def test_get_hosts_by_attribute(self):
+        host1 = models.Host.objects.create(hostname='test_host1')
+        host1.set_attribute('test_attribute1', 'test_value1')
+        host2 = models.Host.objects.create(hostname='test_host2')
+        host2.set_attribute('test_attribute1', 'test_value1')
+
+        hosts = rpc_interface.get_hosts_by_attribute(
+                'test_attribute1', 'test_value1')
+        self.assertEquals(set(hosts),
+                          set(['test_host1', 'test_host2']))
+
+
     def test_get_hosts(self):
         hosts = rpc_interface.get_hosts()
         self._check_hostnames(hosts, [host.hostname for host in self.hosts])