You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@pinot.apache.org by sn...@apache.org on 2023/07/06 06:21:27 UTC

[pinot] branch master updated: Instance drop operation validation check API (#10969)

This is an automated email from the ASF dual-hosted git repository.

snlee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/pinot.git


The following commit(s) were added to refs/heads/master by this push:
     new 878f61357b Instance drop operation validation check API (#10969)
878f61357b is described below

commit 878f61357b6868d98a3eb8c84ec1dfb414c44b6f
Author: Shounak kulkarni <sh...@gmail.com>
AuthorDate: Thu Jul 6 11:51:21 2023 +0530

    Instance drop operation validation check API (#10969)
    
    * extract instance drop validity check code to a separate method
    
    * Add javadoc
    
    * API endpoint to expose the safety validation on instance drop operation
    
    * lint fix
    
    * add license header to new class
    
    * update the endpoint path and method
    
    * pass error list as code, message tuple
---
 .../api/resources/OperationValidationResponse.java | 93 ++++++++++++++++++++++
 .../resources/PinotInstanceRestletResource.java    | 26 ++++++
 .../helix/core/PinotHelixResourceManager.java      | 43 ++++++----
 3 files changed, 148 insertions(+), 14 deletions(-)

diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java
new file mode 100644
index 0000000000..43897ccdc0
--- /dev/null
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/OperationValidationResponse.java
@@ -0,0 +1,93 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.pinot.controller.api.resources;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import java.util.ArrayList;
+import java.util.List;
+
+
+public class OperationValidationResponse {
+  private String _instanceName;
+  private boolean _safe;
+  private final List<ErrorWrapper> _issues = new ArrayList<>();
+
+  @JsonProperty("instanceName")
+  public String getInstanceName() {
+    return _instanceName;
+  }
+
+  public OperationValidationResponse setInstanceName(String instanceName) {
+    _instanceName = instanceName;
+    return this;
+  }
+
+  @JsonProperty("isSafe")
+  public boolean isSafe() {
+    return _safe;
+  }
+
+  public OperationValidationResponse setSafe(boolean safe) {
+    _safe = safe;
+    return this;
+  }
+
+  @JsonProperty("issues")
+  public List<ErrorWrapper> getIssues() {
+    return _issues;
+  }
+
+  public OperationValidationResponse putIssue(ErrorCode code, String... args) {
+    _issues.add(new ErrorWrapper(code, args));
+    return this;
+  }
+
+  public String getIssueMessage(int index) {
+    return _issues.get(index).getMessage();
+  }
+
+  public static class ErrorWrapper {
+    ErrorCode _code;
+    String _message;
+
+    public ErrorWrapper(ErrorCode code, String... args) {
+      _code = code;
+      _message = String.format(code._description, args);
+    }
+
+    public ErrorCode getCode() {
+      return _code;
+    }
+
+    public String getMessage() {
+      return _message;
+    }
+  }
+
+  public enum ErrorCode {
+    IS_ALIVE("Instance %s is still live"),
+    CONTAINS_RESOURCE("Instance %s exists in ideal state for %s");
+
+    public final String _description;
+
+    ErrorCode(String description) {
+      _description = description;
+    }
+  }
+}
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java
index cff4a470a4..5752599d0a 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/api/resources/PinotInstanceRestletResource.java
@@ -31,6 +31,7 @@ import io.swagger.annotations.SecurityDefinition;
 import io.swagger.annotations.SwaggerDefinition;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 import javax.inject.Inject;
 import javax.ws.rs.ClientErrorException;
 import javax.ws.rs.Consumes;
@@ -390,4 +391,29 @@ public class PinotInstanceRestletResource {
           Response.Status.INTERNAL_SERVER_ERROR, e);
     }
   }
+
+  @GET
+  @Path("/instances/dropInstance/validate")
+  @Produces(MediaType.APPLICATION_JSON)
+  @ApiOperation(value = "Check if it's safe to drop the given instances. If not list all the reasons why its not safe.")
+  @ApiResponses(value = {
+      @ApiResponse(code = 200, message = "Success"),
+      @ApiResponse(code = 500, message = "Internal error")
+  })
+  public List<OperationValidationResponse> instanceDropSafetyCheck(
+      @ApiParam(value = "Instance names", required = true,
+          example = "Broker_my.broker.com_30000")
+      @QueryParam("instanceNames") List<String> instanceNames) {
+    LOGGER.info("Performing safety check on drop operation request received for instances: {}", instanceNames);
+    try {
+      return instanceNames.stream()
+              .map(instance -> _pinotHelixResourceManager.instanceDropSafetyCheck(instance))
+              .collect(Collectors.toList());
+    } catch (ClientErrorException e) {
+      throw new ControllerApplicationException(LOGGER, e.getMessage(), e.getResponse().getStatus());
+    } catch (Exception e) {
+      throw new ControllerApplicationException(LOGGER, "Failed to check the safety for instance drop operation.",
+          Response.Status.INTERNAL_SERVER_ERROR, e);
+    }
+  }
 }
diff --git a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
index e18eeff927..fdbca20433 100644
--- a/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
+++ b/pinot-controller/src/main/java/org/apache/pinot/controller/helix/core/PinotHelixResourceManager.java
@@ -134,6 +134,7 @@ import org.apache.pinot.controller.api.exception.InvalidTableConfigException;
 import org.apache.pinot.controller.api.exception.TableAlreadyExistsException;
 import org.apache.pinot.controller.api.exception.UserAlreadyExistsException;
 import org.apache.pinot.controller.api.resources.InstanceInfo;
+import org.apache.pinot.controller.api.resources.OperationValidationResponse;
 import org.apache.pinot.controller.api.resources.PeriodicTaskInvocationResponse;
 import org.apache.pinot.controller.api.resources.StateType;
 import org.apache.pinot.controller.helix.core.assignment.instance.InstanceAssignmentDriver;
@@ -3122,20 +3123,9 @@ public class PinotHelixResourceManager {
    * </ul>
    */
   public PinotResourceManagerResponse dropInstance(String instanceName) {
-    // Check if the instance is live
-    if (_helixDataAccessor.getProperty(_keyBuilder.liveInstance(instanceName)) != null) {
-      return PinotResourceManagerResponse.failure("Instance " + instanceName + " is still live");
-    }
-
-    // Check if any ideal state includes the instance
-    for (String resource : getAllResources()) {
-      IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, resource);
-      for (String partition : idealState.getPartitionSet()) {
-        if (idealState.getInstanceSet(partition).contains(instanceName)) {
-          return PinotResourceManagerResponse.failure(
-              "Instance " + instanceName + " exists in ideal state for " + resource);
-        }
-      }
+    OperationValidationResponse check = instanceDropSafetyCheck(instanceName);
+    if (!check.isSafe()) {
+      return PinotResourceManagerResponse.failure(check.getIssueMessage(0));
     }
 
     // Remove '/INSTANCES/<instanceName>'
@@ -3157,6 +3147,31 @@ public class PinotHelixResourceManager {
     return PinotResourceManagerResponse.success("Instance " + instanceName + " dropped");
   }
 
+  /**
+   * Utility to perform a safety check of the operation to drop an instance.
+   * If the resource is not safe to drop the utility lists all the possible reasons.
+   * @param instanceName Pinot instance name
+   * @return {@link OperationValidationResponse}
+   */
+  public OperationValidationResponse instanceDropSafetyCheck(String instanceName) {
+    OperationValidationResponse response = new OperationValidationResponse().setInstanceName(instanceName);
+    // Check if the instance is live
+    if (_helixDataAccessor.getProperty(_keyBuilder.liveInstance(instanceName)) != null) {
+      response.putIssue(OperationValidationResponse.ErrorCode.IS_ALIVE, instanceName);
+    }
+    // Check if any ideal state includes the instance
+    getAllResources().forEach(resource -> {
+      IdealState idealState = _helixAdmin.getResourceIdealState(_helixClusterName, resource);
+      for (String partition : idealState.getPartitionSet()) {
+        if (idealState.getInstanceSet(partition).contains(instanceName)) {
+          response.putIssue(OperationValidationResponse.ErrorCode.CONTAINS_RESOURCE, instanceName, resource);
+          break;
+        }
+      }
+    });
+    return response.setSafe(response.getIssues().isEmpty());
+  }
+
   /**
    * Toggle the status of an Instance between OFFLINE and ONLINE.
    * Keeps checking until ideal-state is successfully updated or times out.


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@pinot.apache.org
For additional commands, e-mail: commits-help@pinot.apache.org