[autotest] Ensure one host only has one board label

Ensure one host only has one board label with it, otherwise shard-client
will crash

BUG=chromium:654100
TEST=Test in local autotest

Change-Id: I4e9035d331d03554612ba46040d118a6807d950c
Reviewed-on: https://chromium-review.googlesource.com/396530
Commit-Ready: Shuqian Zhao <shuqianz@chromium.org>
Tested-by: Shuqian Zhao <shuqianz@chromium.org>
Reviewed-by: Dan Shi <dshi@google.com>
diff --git a/frontend/afe/models.py b/frontend/afe/models.py
index 8be942a..b0ec4cf 100644
--- a/frontend/afe/models.py
+++ b/frontend/afe/models.py
@@ -681,6 +681,29 @@
             raise model_logic.ValidationError({'labels': '; '.join(errors)})
 
 
+    @classmethod
+    def check_no_board(cls, hosts):
+        """Verify the specified hosts have no board label.
+
+        @param cls: Implicit class object.
+        @param hosts: The hosts to verify.
+        @raises model_logic.ValidationError if any hosts already have a board
+            label.
+        """
+        Host.objects.populate_relationships(hosts, Label, 'label_list')
+        errors = []
+        for host in hosts:
+            boards = [label.name for label in host.label_list
+                      if label.name.startswith('board:')]
+            if boards:
+                # do a join, just in case this host has multiple boards,
+                # we'll be able to see it
+                errors.append('Host %s already has a board label: %s' % (
+                              host.hostname, ', '.join(boards)))
+        if errors:
+            raise model_logic.ValidationError({'labels': '; '.join(errors)})
+
+
     def is_dead(self):
         """Returns whether the host is dead (has status repair failed)."""
         return self.status == Host.Status.REPAIR_FAILED
diff --git a/frontend/afe/models_test.py b/frontend/afe/models_test.py
index 253e9c9..c404fde 100755
--- a/frontend/afe/models_test.py
+++ b/frontend/afe/models_test.py
@@ -67,6 +67,18 @@
         self.assertEquals(host2.status, models.Host.Status.RUNNING)
 
 
+    def test_check_no_board(self):
+        host = models.Host.create_one_time_host('othost')
+        # First check with host with no board label.
+        self.assertEqual(host.check_no_board([host]), None)
+
+        # Second check with host with board label
+        label = models.Label.add_object(name='board:test')
+        label.host_set.add(host)
+        self.assertRaises(model_logic.ValidationError,
+                          host.check_no_board, [host])
+
+
 class SpecialTaskUnittest(unittest.TestCase,
                           frontend_test_utils.FrontendTestMixin):
     def setUp(self):
diff --git a/frontend/afe/rpc_interface.py b/frontend/afe/rpc_interface.py
index d7c433c..6c9c131 100644
--- a/frontend/afe/rpc_interface.py
+++ b/frontend/afe/rpc_interface.py
@@ -143,6 +143,9 @@
     host_objs = models.Host.smart_get_bulk(hosts)
     if label.platform:
         models.Host.check_no_platform(host_objs)
+    # Ensure a host has no more than one board label with it.
+    if label.name.startswith('board:'):
+        models.Host.check_no_board(host_objs)
     label.host_set.add(*host_objs)
 
 
@@ -430,7 +433,7 @@
     @param id: id or hostname for a host.
     @param labels: ids or names for labels.
 
-    @raises ValidationError: If adding more than one platform label.
+    @raises ValidationError: If adding more than one platform/board label.
     """
     # Create the labels on the master/shards.
     for label in labels:
@@ -438,14 +441,18 @@
 
     label_objs = models.Label.smart_get_bulk(labels)
     platforms = [label.name for label in label_objs if label.platform]
-    if len(platforms) > 1:
+    boards = [label.name for label in label_objs
+              if label.name.startswith('board:')]
+    if len(platforms) > 1 or len(boards) > 1:
         raise model_logic.ValidationError(
-            {'labels': 'Adding more than one platform label: %s' %
-                       ', '.join(platforms)})
+            {'labels': 'Adding more than one platform/board label: %s %s' %
+                       (', '.join(platforms), ', '.join(boards))})
 
     host_obj = models.Host.smart_get(id)
     if len(platforms) == 1:
         models.Host.check_no_platform([host_obj])
+    if len(boards) == 1:
+        models.Host.check_no_board([host_obj])
     add_labels_to_host(id, labels)
 
     rpc_utils.fanout_rpc([host_obj], 'add_labels_to_host', False,