You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by hu...@apache.org on 2019/05/25 01:19:37 UTC
[helix] 03/44: Single stoppable API impl
This is an automated email from the ASF dual-hosted git repository.
hulee pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/helix.git
commit 7d7001b54ee3a7bc2954878c34447ba8a338c0c1
Author: Yi Wang <yw...@linkedin.com>
AuthorDate: Wed Mar 20 16:46:22 2019 -0700
Single stoppable API impl
RB=1603158
G=helix-reviewers
A=jxue,hulee
Signed-off-by: Hunter Lee <hu...@linkedin.com>
---
.../helix/manager/zk/ZKHelixDataAccessor.java | 8 +++
.../java/org/apache/helix/model/ClusterConfig.java | 11 ---
.../org/apache/helix/model/TestClusterConfig.java | 18 -----
.../apache/helix/rest/client/CustomRestClient.java | 43 ++++++++++++
.../helix/rest/client/CustomRestClientFactory.java | 35 ++++++++++
.../helix/rest/client/CustomRestClientImpl.java | 38 ++++++++++
...rWrapper.java => HelixDataAccessorWrapper.java} | 10 +--
.../rest/server/json/instance/StoppableCheck.java | 69 +++++++++++++++++++
.../server/resources/helix/InstanceAccessor.java | 35 ++++++++++
.../helix/rest/server/service/InstanceService.java | 34 +++++++++
.../rest/server/service/InstanceServiceImpl.java | 80 ++++++++++++++++++++++
.../helix/rest/server/TestInstanceAccessor.java | 14 ++++
.../server/json/instance/TestStoppableCheck.java | 56 +++++++++++++++
.../rest/server/util/JerseyUriRequestBuilder.java | 3 +-
14 files changed, 417 insertions(+), 37 deletions(-)
diff --git a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixDataAccessor.java b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixDataAccessor.java
index c871573..51f16ac 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixDataAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixDataAccessor.java
@@ -72,6 +72,14 @@ public class ZKHelixDataAccessor implements HelixDataAccessor {
_propertyKeyBuilder = new PropertyKey.Builder(_clusterName);
}
+ /* Copy constructor */
+ public ZKHelixDataAccessor(ZKHelixDataAccessor dataAccessor) {
+ _clusterName = dataAccessor._clusterName;
+ _instanceType = dataAccessor._instanceType;
+ _baseDataAccessor = dataAccessor._baseDataAccessor;
+ _propertyKeyBuilder = new PropertyKey.Builder(_clusterName);
+ }
+
@Override
public boolean createStateModelDef(StateModelDefinition stateModelDef) {
String path = PropertyPathBuilder.stateModelDef(_clusterName, stateModelDef.getId());
diff --git a/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java b/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
index ec9b1d3..bbbaa72 100644
--- a/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
+++ b/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
@@ -338,17 +338,6 @@ public class ClusterConfig extends HelixProperty {
}
/**
- * Get cluster topology by level.
- * E.g, {zone, rack, host, instance}
- * @return
- */
- public String[] getTopologyLevel() {
- String topology = getTopology();
- String[] parts = topology.split(TOPOLOGY_SPLITTER);
- return Arrays.copyOfRange(parts, 1, parts.length);
- }
-
- /**
* Set cluster fault zone type, this should be set combined with {@link #setTopology(String)}.
* @param faultZoneType
*/
diff --git a/helix-core/src/test/java/org/apache/helix/model/TestClusterConfig.java b/helix-core/src/test/java/org/apache/helix/model/TestClusterConfig.java
deleted file mode 100644
index 0b4fb0a..0000000
--- a/helix-core/src/test/java/org/apache/helix/model/TestClusterConfig.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package org.apache.helix.model;
-
-import org.testng.Assert;
-import org.testng.annotations.Test;
-
-
-public class TestClusterConfig {
-
- @Test
- public void testGetZoneId() {
- ClusterConfig clusterConfig = new ClusterConfig("test");
- clusterConfig.setTopology("/zone/rack/host/instance");
-
- String[] levels = clusterConfig.getTopologyLevel();
-
- Assert.assertEquals(levels.length, 4);
- }
-}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClient.java b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClient.java
new file mode 100644
index 0000000..f8ce0e5
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClient.java
@@ -0,0 +1,43 @@
+package org.apache.helix.rest.client;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+
+/**
+ * Interface for interacting with client side rest endpoints
+ */
+public interface CustomRestClient {
+ /**
+ * Get stoppable check result on instance
+ *
+ * @param customPayloads generic payloads required from client side and helix only works as proxy
+ * @return a map where key is custom stoppable check name and boolean value indicates if the check succeeds
+ */
+ Map<String, Boolean> getInstanceStoppableCheck(Map<String, String> customPayloads);
+ /**
+ * Get stoppable check result on partition
+ *
+ * @param customPayloads generic payloads required from client side and helix only works as proxy
+ * @return a map where key is custom stoppable check name and boolean value indicates if the check succeeds
+ */
+ Map<String, Boolean> getPartitionStoppableCheck(Map<String, String> customPayloads);
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientFactory.java b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientFactory.java
new file mode 100644
index 0000000..acbfef7
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientFactory.java
@@ -0,0 +1,35 @@
+package org.apache.helix.rest.client;
+
+/*
+ * 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.
+ */
+
+/**
+ * The memory efficient factory to create instances for {@link CustomRestClient}
+ */
+public class CustomRestClientFactory {
+ private static final String INSTANCE_HEALTH_STATUS = "/instanceHealthStatus";
+ private static final String PARTITION_HEALTH_STATUS = "/partitionHealthStatus";
+
+ private CustomRestClientFactory() {}
+
+ public static CustomRestClient get(String jsonContent) {
+ //TODO: add implementation
+ return new CustomRestClientImpl();
+ }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientImpl.java b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientImpl.java
new file mode 100644
index 0000000..133a338
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/client/CustomRestClientImpl.java
@@ -0,0 +1,38 @@
+package org.apache.helix.rest.client;
+
+/*
+ * 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.
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+
+//TODO: add implementation details
+class CustomRestClientImpl implements CustomRestClient {
+
+ @Override
+ public Map<String, Boolean> getInstanceStoppableCheck(Map<String, String> customPayloads) {
+ return new HashMap<>();
+ }
+
+ @Override
+ public Map<String, Boolean> getPartitionStoppableCheck(Map<String, String> customPayloads) {
+ return new HashMap<>();
+ }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/common/ZKReadAccessorWrapper.java b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixDataAccessorWrapper.java
similarity index 76%
rename from helix-rest/src/main/java/org/apache/helix/rest/common/ZKReadAccessorWrapper.java
rename to helix-rest/src/main/java/org/apache/helix/rest/common/HelixDataAccessorWrapper.java
index f8a4f4f..1a26831 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/common/ZKReadAccessorWrapper.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/common/HelixDataAccessorWrapper.java
@@ -4,11 +4,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import org.apache.helix.BaseDataAccessor;
import org.apache.helix.HelixProperty;
-import org.apache.helix.InstanceType;
import org.apache.helix.PropertyKey;
-import org.apache.helix.ZNRecord;
import org.apache.helix.manager.zk.ZKHelixDataAccessor;
@@ -17,13 +14,12 @@ import org.apache.helix.manager.zk.ZKHelixDataAccessor;
* The caches is of the value from get methods and short lived for the lifecycle of one rest request
* TODO: add more cached read method based on needs
*/
-public class ZKReadAccessorWrapper extends ZKHelixDataAccessor {
+public final class HelixDataAccessorWrapper extends ZKHelixDataAccessor {
private final Map<PropertyKey, HelixProperty> _propertyCache = new HashMap<>();
private final Map<PropertyKey, List<String>> _batchNameCache = new HashMap<>();
- public ZKReadAccessorWrapper(String clusterName, InstanceType instanceType,
- BaseDataAccessor<ZNRecord> baseDataAccessor) {
- super(clusterName, instanceType, baseDataAccessor);
+ public HelixDataAccessorWrapper(ZKHelixDataAccessor dataAccessor) {
+ super(dataAccessor);
}
@Override
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/json/instance/StoppableCheck.java b/helix-rest/src/main/java/org/apache/helix/rest/server/json/instance/StoppableCheck.java
new file mode 100644
index 0000000..8dd1d31
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/json/instance/StoppableCheck.java
@@ -0,0 +1,69 @@
+package org.apache.helix.rest.server.json.instance;
+
+/*
+ * 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.
+ */
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.google.common.collect.ImmutableMap;
+
+
+public class StoppableCheck {
+ private static final String HELIX_CHECK_PREFIX = "Helix:";
+ private static final String CUSTOM_CHECK_PREFIX = "Custom:";
+
+ @JsonProperty("stoppable")
+ private boolean isStoppable;
+ @JsonProperty("failedChecks")
+ private List<String> failedChecks;
+
+ public StoppableCheck(boolean isStoppable, List<String> failedChecks) {
+ this.isStoppable = isStoppable;
+ this.failedChecks = failedChecks;
+ }
+
+ public static StoppableCheck mergeStoppableChecks(Map<String, Boolean> helixChecks, Map<String, Boolean> customChecks) {
+ Map<String, Boolean> mergedResult = ImmutableMap.<String, Boolean>builder()
+ .putAll(appendPrefix(helixChecks, HELIX_CHECK_PREFIX))
+ .putAll(appendPrefix(customChecks, CUSTOM_CHECK_PREFIX))
+ .build();
+
+ List<String> failedChecks = new ArrayList<>();
+ for (Map.Entry<String, Boolean> entry : mergedResult.entrySet()) {
+ if (!entry.getValue()) {
+ failedChecks.add(entry.getKey());
+ }
+ }
+
+ return new StoppableCheck(failedChecks.isEmpty(), failedChecks);
+ }
+
+ private static Map<String, Boolean> appendPrefix(Map<String, Boolean> checks, String prefix) {
+ Map<String, Boolean> result = new HashMap<>();
+ for (Map.Entry<String, Boolean> entry : checks.entrySet()) {
+ result.put(prefix + entry.getKey(), entry.getValue());
+ }
+
+ return result;
+ }
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstanceAccessor.java b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstanceAccessor.java
index cfed0e2..db44ff9 100644
--- a/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstanceAccessor.java
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/resources/helix/InstanceAccessor.java
@@ -20,10 +20,12 @@ package org.apache.helix.rest.server.resources.helix;
*/
import java.io.IOException;
+import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
@@ -31,6 +33,7 @@ import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.QueryParam;
+import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.apache.helix.ConfigAccessor;
@@ -38,6 +41,7 @@ import org.apache.helix.HelixAdmin;
import org.apache.helix.HelixDataAccessor;
import org.apache.helix.HelixException;
import org.apache.helix.ZNRecord;
+import org.apache.helix.manager.zk.ZKHelixDataAccessor;
import org.apache.helix.model.ClusterConfig;
import org.apache.helix.model.CurrentState;
import org.apache.helix.model.Error;
@@ -48,6 +52,12 @@ import org.apache.helix.model.LiveInstance;
import org.apache.helix.model.Message;
import org.apache.helix.model.ParticipantHistory;
import org.apache.helix.model.builder.HelixConfigScopeBuilder;
+import org.apache.helix.rest.client.CustomRestClient;
+import org.apache.helix.rest.client.CustomRestClientFactory;
+import org.apache.helix.rest.common.HelixDataAccessorWrapper;
+import org.apache.helix.rest.server.json.instance.StoppableCheck;
+import org.apache.helix.rest.server.service.InstanceService;
+import org.apache.helix.rest.server.service.InstanceServiceImpl;
import org.codehaus.jackson.JsonNode;
import org.codehaus.jackson.node.ArrayNode;
import org.codehaus.jackson.node.JsonNodeFactory;
@@ -56,6 +66,8 @@ import org.eclipse.jetty.util.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.fasterxml.jackson.databind.ObjectMapper;
+
@Path("/clusters/{clusterId}/instances")
public class InstanceAccessor extends AbstractHelixResource {
private final static Logger _logger = LoggerFactory.getLogger(InstanceAccessor.class);
@@ -185,6 +197,29 @@ public class InstanceAccessor extends AbstractHelixResource {
return JSONRepresentation(instanceMap);
}
+ @POST
+ @Path("{instanceName}/stoppable")
+ @Consumes(MediaType.APPLICATION_JSON)
+ public Response isInstanceStoppable(String jsonContent,
+ @PathParam("clusterId") String clusterId, @PathParam("instanceName") String instanceName) throws IOException {
+ ObjectMapper objectMapper = new ObjectMapper();
+ HelixDataAccessor dataAccessor = getDataAccssor(clusterId);
+ // TODO reduce GC by dependency injection
+ InstanceService instanceService = new InstanceServiceImpl(
+ new HelixDataAccessorWrapper((ZKHelixDataAccessor) dataAccessor), getConfigAccessor());
+
+ Map<String, Boolean> helixStoppableCheck =
+ instanceService.getInstanceStoppableCheck(clusterId, instanceName);
+ CustomRestClient customClient = CustomRestClientFactory.get(jsonContent);
+ // TODO add the json content parse logic
+ Map<String, Boolean> customStoppableCheck =
+ customClient.getInstanceStoppableCheck(Collections.<String, String> emptyMap());
+ StoppableCheck stoppableCheck =
+ StoppableCheck.mergeStoppableChecks(helixStoppableCheck, customStoppableCheck);
+
+ return OK(objectMapper.writeValueAsString(stoppableCheck));
+ }
+
@PUT
@Path("{instanceName}")
public Response addInstance(@PathParam("clusterId") String clusterId,
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceService.java b/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceService.java
new file mode 100644
index 0000000..664d9c0
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceService.java
@@ -0,0 +1,34 @@
+package org.apache.helix.rest.server.service;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+
+public interface InstanceService {
+ /**
+ * Get the current instance stoppable checks based on Helix own business logic
+ *
+ * @param clusterId
+ * @param instanceName
+ * @return a map where key is stoppable check name and boolean value represents whether the check succeeds
+ */
+ Map<String, Boolean> getInstanceStoppableCheck(String clusterId, String instanceName);
+}
diff --git a/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceServiceImpl.java b/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceServiceImpl.java
new file mode 100644
index 0000000..d179c27
--- /dev/null
+++ b/helix-rest/src/main/java/org/apache/helix/rest/server/service/InstanceServiceImpl.java
@@ -0,0 +1,80 @@
+package org.apache.helix.rest.server.service;
+
+/*
+ * 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.
+ */
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixException;
+import org.apache.helix.util.InstanceValidationUtil;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class InstanceServiceImpl implements InstanceService {
+ private static final Logger _logger = LoggerFactory.getLogger(InstanceServiceImpl.class);
+
+ private final HelixDataAccessor _dataAccessor;
+ private final ConfigAccessor _configAccessor;
+
+ public InstanceServiceImpl(HelixDataAccessor dataAccessor, ConfigAccessor configAccessor) {
+ _dataAccessor = dataAccessor;
+ _configAccessor = configAccessor;
+ }
+
+ @Override
+ public Map<String, Boolean> getInstanceStoppableCheck(String clusterId, String instanceName) {
+ Map<String, Boolean> healthStatus = new HashMap<>();
+ healthStatus.put(HealthStatus.HAS_VALID_CONFIG.name(), InstanceValidationUtil.hasValidConfig(_dataAccessor, clusterId, instanceName));
+ if (!healthStatus.get(HealthStatus.HAS_VALID_CONFIG.name())) {
+ _logger.error("The instance {} doesn't have valid configuration", instanceName);
+ return healthStatus;
+ }
+
+ // Any exceptions occurred below due to invalid instance config shouldn't happen
+ healthStatus.put(HealthStatus.IS_ENABLED.name(), InstanceValidationUtil.isEnabled(_dataAccessor, _configAccessor, clusterId, instanceName));
+ healthStatus.put(HealthStatus.IS_ALIVE.name(), InstanceValidationUtil.isAlive(_dataAccessor, clusterId, instanceName));
+ healthStatus.put(HealthStatus.HAS_RESOURCE_ASSIGNED.name(), InstanceValidationUtil.hasResourceAssigned(_dataAccessor, clusterId, instanceName));
+ healthStatus.put(HealthStatus.HAS_DISABLED_PARTITIONS.name(), InstanceValidationUtil.hasDisabledPartitions(_dataAccessor, clusterId, instanceName));
+ healthStatus.put(HealthStatus.HAS_ERROR_PARTITIONS.name(), InstanceValidationUtil.hasErrorPartitions(_dataAccessor, clusterId, instanceName));
+
+ try {
+ boolean isStable = InstanceValidationUtil.isInstanceStable(_dataAccessor, instanceName);
+ healthStatus.put(HealthStatus.IS_STABLE.name(), isStable);
+ } catch (HelixException e) {
+ _logger.error("Failed to check instance is stable, message: {}", e.getMessage());
+ // TODO action on the stable check exception
+ }
+
+ return healthStatus;
+ }
+
+ private enum HealthStatus {
+ IS_ALIVE,
+ IS_ENABLED,
+ HAS_RESOURCE_ASSIGNED,
+ HAS_DISABLED_PARTITIONS,
+ HAS_VALID_CONFIG,
+ HAS_ERROR_PARTITIONS,
+ IS_STABLE
+ }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstanceAccessor.java b/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstanceAccessor.java
index be47cab..54f85d6 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstanceAccessor.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/TestInstanceAccessor.java
@@ -25,6 +25,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import javax.ws.rs.client.Entity;
@@ -54,6 +55,19 @@ public class TestInstanceAccessor extends AbstractTestClass {
private final static String INSTANCE_NAME = CLUSTER_NAME + "localhost_12918";
@Test
+ public void testIsInstanceStoppable() throws IOException {
+ System.out.println("Start test :" + TestHelper.getTestMethodName());
+ Map<String, String> params = ImmutableMap.of("client", "espresso");
+ Entity entity =
+ Entity.entity(OBJECT_MAPPER.writeValueAsString(params), MediaType.APPLICATION_JSON_TYPE);
+ Response response = new JerseyUriRequestBuilder("clusters/{}/instances/{}/stoppable")
+ .format(CLUSTER_NAME, INSTANCE_NAME).post(this, entity);
+ String checkResult = response.readEntity(String.class);
+ Assert.assertEquals(checkResult,
+ "{\"stoppable\":false,\"failedChecks\":[\"Helix:HAS_DISABLED_PARTITIONS\",\"Helix:HAS_RESOURCE_ASSIGNED\",\"Helix:HAS_ERROR_PARTITIONS\",\"Helix:IS_ALIVE\"]}");
+ }
+
+ @Test (dependsOnMethods = "testIsInstanceStoppable")
public void testGetAllMessages() throws IOException {
System.out.println("Start test :" + TestHelper.getTestMethodName());
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/json/instance/TestStoppableCheck.java b/helix-rest/src/test/java/org/apache/helix/rest/server/json/instance/TestStoppableCheck.java
new file mode 100644
index 0000000..25702cf
--- /dev/null
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/json/instance/TestStoppableCheck.java
@@ -0,0 +1,56 @@
+package org.apache.helix.rest.server.json.instance;
+
+/*
+ * 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.
+ */
+
+import java.util.Map;
+
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+
+public class TestStoppableCheck {
+
+ @Test
+ public void whenSerializingStoppableCheck() throws JsonProcessingException {
+ StoppableCheck stoppableCheck = new StoppableCheck(false, ImmutableList.of("failedCheck"));
+
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(stoppableCheck);
+
+ Assert.assertEquals(result, "{\"stoppable\":false,\"failedChecks\":[\"failedCheck\"]}");
+ }
+
+ @Test
+ public void testMergeStoppableChecks() throws JsonProcessingException {
+ Map<String, Boolean> helixCheck = ImmutableMap.of("check0", false, "check1", false);
+ Map<String, Boolean> customCheck = ImmutableMap.of("check1", true, "check2", true);
+
+ StoppableCheck stoppableCheck = StoppableCheck.mergeStoppableChecks(helixCheck, customCheck);
+ ObjectMapper mapper = new ObjectMapper();
+ String result = mapper.writeValueAsString(stoppableCheck);
+
+ Assert.assertEquals(result, "{\"stoppable\":false,\"failedChecks\":[\"Helix:check1\",\"Helix:check0\"]}");
+ }
+}
diff --git a/helix-rest/src/test/java/org/apache/helix/rest/server/util/JerseyUriRequestBuilder.java b/helix-rest/src/test/java/org/apache/helix/rest/server/util/JerseyUriRequestBuilder.java
index a8fde73..e0642b3 100644
--- a/helix-rest/src/test/java/org/apache/helix/rest/server/util/JerseyUriRequestBuilder.java
+++ b/helix-rest/src/test/java/org/apache/helix/rest/server/util/JerseyUriRequestBuilder.java
@@ -107,9 +107,10 @@ public class JerseyUriRequestBuilder {
* @param container
* @param entity
*/
- public void post(JerseyTestNg.ContainerPerClassTest container, Entity entity) {
+ public Response post(JerseyTestNg.ContainerPerClassTest container, Entity entity) {
final Response response = buildWebTarget(container).request().post(entity);
Assert.assertEquals(response.getStatus(), _expectedStatusCode);
+ return response;
}
/**