Add GetNodesWithTag

This makes tags much easier to work with.  It is common to want to find
all the matching nodes for a tag/function.

Change-Id: Ibe2876fc825dea1ffee5686f7e40dce85ebc8793
diff --git a/aos/configuration.cc b/aos/configuration.cc
index 1d39f27..67d2b96 100644
--- a/aos/configuration.cc
+++ b/aos/configuration.cc
@@ -956,6 +956,31 @@
   return nodes;
 }
 
+std::vector<const Node *> GetNodesWithTag(const Configuration *config,
+                                          std::string_view tag) {
+  std::vector<const Node *> nodes;
+  if (!MultiNode(config)) {
+    nodes.emplace_back(nullptr);
+  } else {
+    for (const Node *node : *config->nodes()) {
+      if (!node->has_tags()) {
+        continue;
+      }
+      bool did_found_tag = false;
+      for (const flatbuffers::String *found_tag : *node->tags()) {
+        if (found_tag->string_view() == tag) {
+          did_found_tag = true;
+          break;
+        }
+      }
+      if (did_found_tag) {
+        nodes.emplace_back(node);
+      }
+    }
+  }
+  return nodes;
+}
+
 bool MultiNode(const Configuration *config) { return config->has_nodes(); }
 
 bool ChannelIsSendableOnNode(const Channel *channel, const Node *node) {
diff --git a/aos/configuration.h b/aos/configuration.h
index 5579334..7383a1a 100644
--- a/aos/configuration.h
+++ b/aos/configuration.h
@@ -96,6 +96,11 @@
 // in a single node world.)
 std::vector<const Node *> GetNodes(const Configuration *config);
 
+// Returns a vector of the nodes in the config with the provided tag.  If this
+// is a single-node world, we assume that all tags match.
+std::vector<const Node *> GetNodesWithTag(const Configuration *config,
+                                          std::string_view tag);
+
 // Returns the node index for a node.  Note: will be faster if node is a pointer
 // to a node in config, but is not required.
 int GetNodeIndex(const Configuration *config, const Node *node);
diff --git a/aos/configuration_test.cc b/aos/configuration_test.cc
index c800017..a5b3b59 100644
--- a/aos/configuration_test.cc
+++ b/aos/configuration_test.cc
@@ -700,6 +700,30 @@
   }
 }
 
+// Tests that we can pull out all the nodes with a tag.
+TEST_F(ConfigurationTest, GetNodesWithTag) {
+  {
+    FlatbufferDetachedBuffer<Configuration> config =
+        ReadConfig(kConfigPrefix + "good_multinode.json");
+    const Node *pi1 = GetNode(&config.message(), "pi1");
+    const Node *pi2 = GetNode(&config.message(), "pi2");
+
+    EXPECT_THAT(GetNodesWithTag(&config.message(), "a"),
+                ::testing::ElementsAre(pi1));
+    EXPECT_THAT(GetNodesWithTag(&config.message(), "b"),
+                ::testing::ElementsAre(pi2));
+    EXPECT_THAT(GetNodesWithTag(&config.message(), "c"),
+                ::testing::ElementsAre(pi1, pi2));
+  }
+
+  {
+    FlatbufferDetachedBuffer<Configuration> config =
+        ReadConfig(kConfigPrefix + "config1.json");
+    EXPECT_THAT(GetNodesWithTag(&config.message(), "arglfish"),
+                ::testing::ElementsAre(nullptr));
+  }
+}
+
 // Tests that we can extract a node index from a config.
 TEST_F(ConfigurationTest, GetNodeIndex) {
   FlatbufferDetachedBuffer<Configuration> config =
diff --git a/aos/testdata/good_multinode.json b/aos/testdata/good_multinode.json
index 52a53b0..f344691 100644
--- a/aos/testdata/good_multinode.json
+++ b/aos/testdata/good_multinode.json
@@ -70,11 +70,13 @@
   "nodes": [
     {
       "name": "pi1",
-      "hostname": "raspberrypi"
+      "hostname": "raspberrypi",
+      "tags": ["a", "c"]
     },
     {
       "name": "pi2",
-      "hostname": "raspberrypi2"
+      "hostname": "raspberrypi2",
+      "tags": ["b", "c"]
     }
   ]
 }