Copilot commented on code in PR #17375:
URL: https://github.com/apache/pinot/pull/17375#discussion_r2622896777


##########
pinot-controller/src/test/java/org/apache/pinot/controller/api/PinotInstanceRestletResourceTest.java:
##########
@@ -296,6 +297,184 @@ private List<String> getInstanceTags(String instance)
     return tags;
   }
 
+  @Test
+  public void testDrainMinionInstance()
+      throws Exception {
+    // Create a minion instance with minion_untagged tag
+    String createInstanceUrl = _urlBuilder.forInstanceCreate();
+    Instance minionInstance =
+        new Instance("minion1.test.com", 9514, InstanceType.MINION,
+            Collections.singletonList(Helix.UNTAGGED_MINION_INSTANCE),
+            null, 0, 0, 0, 0, false);
+    sendPostRequest(createInstanceUrl, minionInstance.toJsonString());
+    String minionInstanceId = "Minion_minion1.test.com_9514";
+
+    // Verify the minion was created with minion_untagged tag
+    checkInstanceInfo(minionInstanceId, "minion1.test.com", 9514, new 
String[]{Helix.UNTAGGED_MINION_INSTANCE},
+        null, -1, -1, -1, -1, false);
+
+    // Drain the minion instance
+    String drainUrl = _urlBuilder.forInstanceState(minionInstanceId);
+    sendPutRequest(drainUrl + "?state=DRAIN", "");
+
+    // Verify the minion now has minion_drained tag instead of minion_untagged
+    JsonNode response = 
JsonUtils.stringToJsonNode(sendGetRequest(_urlBuilder.forInstance(minionInstanceId)));
+    assertEquals(response.get("instanceName").asText(), minionInstanceId);
+    JsonNode tags = response.get("tags");
+    assertEquals(tags.size(), 1);
+    assertEquals(tags.get(0).asText(), Helix.DRAINED_MINION_INSTANCE);
+
+    // Cleanup - delete the minion instance
+    sendDeleteRequest(_urlBuilder.forInstance(minionInstanceId));
+  }
+
+  @Test
+  public void testDrainMinionInstanceWithMultipleTags()
+      throws Exception {
+    // Create a minion instance with multiple tags including minion_untagged
+    String createInstanceUrl = _urlBuilder.forInstanceCreate();
+    List<String> tags = Arrays.asList(Helix.UNTAGGED_MINION_INSTANCE, 
"custom_tag1", "custom_tag2");
+    Instance minionInstance =
+        new Instance("minion2.test.com", 9515, InstanceType.MINION, tags, 
null, 0, 0, 0, 0, false);
+    sendPostRequest(createInstanceUrl, minionInstance.toJsonString());
+    String minionInstanceId = "Minion_minion2.test.com_9515";
+
+    // Drain the minion instance
+    String drainUrl = _urlBuilder.forInstanceState(minionInstanceId);
+    sendPutRequest(drainUrl + "?state=DRAIN", "");
+
+    // Verify minion_untagged was replaced with minion_drained, other tags 
remain
+    JsonNode response = 
JsonUtils.stringToJsonNode(sendGetRequest(_urlBuilder.forInstance(minionInstanceId)));
+    JsonNode responseTags = response.get("tags");
+    assertEquals(responseTags.size(), 3);
+    List<String> actualTags = new ArrayList<>();
+    for (JsonNode tag : responseTags) {
+      actualTags.add(tag.asText());
+    }
+    assertTrue(actualTags.contains(Helix.DRAINED_MINION_INSTANCE));
+    assertTrue(actualTags.contains("custom_tag1"));
+    assertTrue(actualTags.contains("custom_tag2"));
+    assertFalse(actualTags.contains(Helix.UNTAGGED_MINION_INSTANCE));
+
+    // Cleanup
+    sendDeleteRequest(_urlBuilder.forInstance(minionInstanceId));
+  }
+
+  @Test
+  public void testDrainMinionInstanceWithoutUntaggedTag()
+      throws Exception {
+    // Create a minion instance without minion_untagged tag
+    String createInstanceUrl = _urlBuilder.forInstanceCreate();
+    List<String> tags = List.of("custom_tag");

Review Comment:
   Using List.of() creates an immutable list. While this works for the test, 
consider using Arrays.asList() or Collections.singletonList() for consistency 
with other test methods in this file (see lines 336, 398).
   ```suggestion
       List<String> tags = Arrays.asList("custom_tag");
   ```



##########
pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java:
##########
@@ -3624,6 +3624,50 @@ public PinotResourceManagerResponse dropInstance(String 
instanceName) {
     return PinotResourceManagerResponse.success("Instance " + instanceName + " 
dropped");
   }
 
+  /**
+   * Drains a minion instance by preventing new task assignments while 
allowing existing tasks to complete.
+   * This is achieved by changing the instance tag from minion_untagged to 
minion_drained, which prevents
+   * Helix from assigning new tasks to this instance.
+   *
+   * @param instanceName Name of the minion instance to drain
+   * @return Response indicating success or failure
+   */
+  public synchronized PinotResourceManagerResponse drainMinionInstance(String 
instanceName) {
+    InstanceConfig instanceConfig = getHelixInstanceConfig(instanceName);
+    if (instanceConfig == null) {
+      return PinotResourceManagerResponse.failure("Instance " + instanceName + 
" not found");
+    }
+
+    // Replace minion_untagged with minion_drained tag
+    List<String> updatedTags = replaceMinionTag(instanceConfig.getTags());
+    instanceConfig.getRecord().setListField(
+        InstanceConfig.InstanceConfigProperty.TAG_LIST.name(), updatedTags);
+
+    // Save to Helix
+    if 
(!_helixDataAccessor.setProperty(_keyBuilder.instanceConfig(instanceName), 
instanceConfig)) {
+      return PinotResourceManagerResponse.failure("Failed to set instance 
config for instance: " + instanceName);
+    }
+
+    LOGGER.info("Successfully drained minion instance: {}", instanceName);
+    return PinotResourceManagerResponse.success(
+        "Successfully drained minion instance: " + instanceName);
+  }
+
+  /**
+   * Replaces minion_untagged tag with minion_drained tag in the given tag 
list.
+   *
+   * @param currentTags Current list of instance tags
+   * @return Updated list with a minion_drained tag instead of minion_untagged
+   */
+  private List<String> replaceMinionTag(List<String> currentTags) {

Review Comment:
   The method doesn't handle null currentTags. If currentTags is null, this 
will throw a NullPointerException when creating the ArrayList. Add a null check 
at the beginning of the method and return a list containing only 
DRAINED_MINION_INSTANCE if currentTags is null.
   ```suggestion
     private List<String> replaceMinionTag(List<String> currentTags) {
       if (currentTags == null) {
         // If currentTags is null, return a list containing only the drained 
minion tag
         return Collections.singletonList(Helix.DRAINED_MINION_INSTANCE);
       }
   ```



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to