TargetExternalView implementation

To make router quickly update, we will have a target external view 
implementation that make router listen on newly calculated change of master 
instead of waiting for M->S and S->M complete.


Project: http://git-wip-us.apache.org/repos/asf/helix/repo
Commit: http://git-wip-us.apache.org/repos/asf/helix/commit/d89fbb93
Tree: http://git-wip-us.apache.org/repos/asf/helix/tree/d89fbb93
Diff: http://git-wip-us.apache.org/repos/asf/helix/diff/d89fbb93

Branch: refs/heads/master
Commit: d89fbb93de273f1101e94d2c4c06bd6ad1256f23
Parents: a83e8d6
Author: Junkai Xue <j...@linkedin.com>
Authored: Tue Sep 26 11:07:48 2017 -0700
Committer: Junkai Xue <j...@linkedin.com>
Committed: Mon Nov 6 17:08:59 2017 -0800

----------------------------------------------------------------------
 .../java/org/apache/helix/HelixConstants.java   |   1 +
 .../java/org/apache/helix/HelixManager.java     |   6 +-
 .../main/java/org/apache/helix/PropertyKey.java |  17 +++
 .../org/apache/helix/PropertyPathBuilder.java   |  10 ++
 .../java/org/apache/helix/PropertyType.java     |   1 +
 .../controller/GenericHelixController.java      |   2 +
 .../controller/stages/ClusterDataCache.java     |  10 ++
 .../stages/TargetExteralViewCalcStage.java      | 116 +++++++++++++++++++
 .../helix/manager/zk/CallbackHandler.java       |   6 +-
 .../apache/helix/manager/zk/ZKHelixManager.java |   6 +
 .../org/apache/helix/model/ClusterConfig.java   |  18 ++-
 .../controller/stages/DummyClusterManager.java  |   5 +
 .../controller/TestTargetExternalView.java      |  85 ++++++++++++++
 .../java/org/apache/helix/mock/MockManager.java |   5 +
 .../helix/participant/MockZKHelixManager.java   |   5 +
 15 files changed, 289 insertions(+), 4 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/HelixConstants.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/HelixConstants.java 
b/helix-core/src/main/java/org/apache/helix/HelixConstants.java
index 119ad5a..6935a23 100644
--- a/helix-core/src/main/java/org/apache/helix/HelixConstants.java
+++ b/helix-core/src/main/java/org/apache/helix/HelixConstants.java
@@ -35,6 +35,7 @@ public interface HelixConstants {
     CURRENT_STATE,
     MESSAGE,
     EXTERNAL_VIEW,
+    TARGET_EXTERNAL_VIEW,
     CONTROLLER,
     MESSAGES_CONTROLLER,
     HEALTH

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/HelixManager.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/HelixManager.java 
b/helix-core/src/main/java/org/apache/helix/HelixManager.java
index bc67a1a..ff61a04 100644
--- a/helix-core/src/main/java/org/apache/helix/HelixManager.java
+++ b/helix-core/src/main/java/org/apache/helix/HelixManager.java
@@ -215,7 +215,11 @@ public interface HelixManager {
    */
   void addExternalViewChangeListener(ExternalViewChangeListener listener) 
throws Exception;
 
-
+  /**
+   * @see ExternalViewChangeListener#onExternalViewChange(List, 
NotificationContext)
+   * @param listener
+   */
+  void addTargetExternalViewChangeListener(ExternalViewChangeListener 
listener) throws Exception;
   /**
    * @see ExternalViewChangeListener#onExternalViewChange(List, 
NotificationContext)
    * @param listener

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/PropertyKey.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/PropertyKey.java 
b/helix-core/src/main/java/org/apache/helix/PropertyKey.java
index b198ac2..20cb833 100644
--- a/helix-core/src/main/java/org/apache/helix/PropertyKey.java
+++ b/helix-core/src/main/java/org/apache/helix/PropertyKey.java
@@ -554,6 +554,23 @@ public class PropertyKey {
     }
 
     /**
+     * Get a property key associated with all target external view
+     * @return {@link PropertyKey}
+     */
+    public PropertyKey targetExternalViews() {
+      return new PropertyKey(TARGETEXTERNALVIEW, ExternalView.class, 
_clusterName);
+    }
+
+    /**
+     * Get a property key associated with an target external view of a resource
+     * @param resourceName
+     * @return {@link PropertyKey}
+     */
+    public PropertyKey targetExternalView(String resourceName) {
+      return new PropertyKey(TARGETEXTERNALVIEW, ExternalView.class, 
_clusterName, resourceName);
+    }
+
+    /**
      * Get a property key associated with a controller
      * @return {@link PropertyKey}
      */

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/PropertyPathBuilder.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/PropertyPathBuilder.java 
b/helix-core/src/main/java/org/apache/helix/PropertyPathBuilder.java
index 756b716..61aeebb 100644
--- a/helix-core/src/main/java/org/apache/helix/PropertyPathBuilder.java
+++ b/helix-core/src/main/java/org/apache/helix/PropertyPathBuilder.java
@@ -83,6 +83,8 @@ public class PropertyPathBuilder {
     addEntry(PropertyType.IDEALSTATES, 2, 
"/{clusterName}/IDEALSTATES/{resourceName}");
     addEntry(PropertyType.EXTERNALVIEW, 1, "/{clusterName}/EXTERNALVIEW");
     addEntry(PropertyType.EXTERNALVIEW, 2, 
"/{clusterName}/EXTERNALVIEW/{resourceName}");
+    addEntry(PropertyType.TARGETEXTERNALVIEW, 1, 
"/{clusterName}/TARGETEXTERNALVIEW");
+    addEntry(PropertyType.TARGETEXTERNALVIEW, 2, 
"/{clusterName}/TARGETEXTERNALVIEW/{resourceName}");
     addEntry(PropertyType.STATEMODELDEFS, 1, "/{clusterName}/STATEMODELDEFS");
     addEntry(PropertyType.STATEMODELDEFS, 2, 
"/{clusterName}/STATEMODELDEFS/{stateModelName}");
     addEntry(PropertyType.CONTROLLER, 1, "/{clusterName}/CONTROLLER");
@@ -228,6 +230,14 @@ public class PropertyPathBuilder {
     return String.format("/%s/EXTERNALVIEW/%s", clusterName, resourceName);
   }
 
+  public static String targetExternalView(String clusterName) {
+    return String.format("/%s/TARGETEXTERNALVIEW", clusterName);
+  }
+
+  public static String targetExternalView(String clusterName, String 
resourceName) {
+    return String.format("/%s/TARGETEXTERNALVIEW/%s", clusterName, 
resourceName);
+  }
+
   public static String liveInstance(String clusterName) {
     return String.format("/%s/LIVEINSTANCES", clusterName);
   }

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/PropertyType.java
----------------------------------------------------------------------
diff --git a/helix-core/src/main/java/org/apache/helix/PropertyType.java 
b/helix-core/src/main/java/org/apache/helix/PropertyType.java
index 75bdc22..b522014 100644
--- a/helix-core/src/main/java/org/apache/helix/PropertyType.java
+++ b/helix-core/src/main/java/org/apache/helix/PropertyType.java
@@ -41,6 +41,7 @@ public enum PropertyType {
   INSTANCES(Type.CLUSTER, true, false),
   IDEALSTATES(Type.CLUSTER, true, false, false, false, true),
   EXTERNALVIEW(Type.CLUSTER, true, false),
+  TARGETEXTERNALVIEW(Type.CLUSTER, true, false),
   STATEMODELDEFS(Type.CLUSTER, true, false, false, false, true),
   CONTROLLER(Type.CLUSTER, true, false),
   PROPERTYSTORE(Type.CLUSTER, true, false),

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java
 
b/helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java
index 9496aec..b101049 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/GenericHelixController.java
@@ -65,6 +65,7 @@ import 
org.apache.helix.controller.stages.PersistAssignmentStage;
 import org.apache.helix.controller.stages.ReadClusterDataStage;
 import org.apache.helix.controller.stages.ResourceComputationStage;
 import org.apache.helix.controller.stages.ResourceValidationStage;
+import org.apache.helix.controller.stages.TargetExteralViewCalcStage;
 import org.apache.helix.controller.stages.TaskAssignmentStage;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.CurrentState;
@@ -248,6 +249,7 @@ public class GenericHelixController implements 
IdealStateChangeListener,
       rebalancePipeline.addStage(new MessageThrottleStage());
       rebalancePipeline.addStage(new TaskAssignmentStage());
       rebalancePipeline.addStage(new PersistAssignmentStage());
+      rebalancePipeline.addStage(new TargetExteralViewCalcStage());
 
       // external view generation
       Pipeline externalViewPipeline = new Pipeline(pipelineName);

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/controller/stages/ClusterDataCache.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/stages/ClusterDataCache.java
 
b/helix-core/src/main/java/org/apache/helix/controller/stages/ClusterDataCache.java
index 8ce614c..f9fc514 100644
--- 
a/helix-core/src/main/java/org/apache/helix/controller/stages/ClusterDataCache.java
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/stages/ClusterDataCache.java
@@ -41,6 +41,7 @@ import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ClusterConstraints;
 import org.apache.helix.model.ClusterConstraints.ConstraintType;
 import org.apache.helix.model.CurrentState;
+import org.apache.helix.model.ExternalView;
 import org.apache.helix.model.IdealState;
 import org.apache.helix.model.InstanceConfig;
 import org.apache.helix.model.LiveInstance;
@@ -91,6 +92,7 @@ public class ClusterDataCache {
   private Map<String, JobConfig> _jobConfigMap = new HashMap<>();
   private Map<String, WorkflowConfig> _workflowConfigMap = new HashMap<>();
   private Map<String, ZNRecord> _contextMap = new HashMap<>();
+  private Map<String, ExternalView> _targetExternalViewMap = Maps.newHashMap();
 
   // maintain a cache of participant messages across pipeline runs
   private Map<String, Map<String, Message>> _messageCache = Maps.newHashMap();
@@ -859,6 +861,14 @@ public class ClusterDataCache {
     return _contextMap;
   }
 
+  public ExternalView getTargetExternalView(String resourceName) {
+    return _targetExternalViewMap.get(resourceName);
+  }
+
+  public void updateTargetExternalView(String resourceName, ExternalView 
targetExternalView) {
+    _targetExternalViewMap.put(resourceName, targetExternalView);
+  }
+
   /**
    * Indicate that a full read should be done on the next refresh
    */

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/controller/stages/TargetExteralViewCalcStage.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/main/java/org/apache/helix/controller/stages/TargetExteralViewCalcStage.java
 
b/helix-core/src/main/java/org/apache/helix/controller/stages/TargetExteralViewCalcStage.java
new file mode 100644
index 0000000..46fc64e
--- /dev/null
+++ 
b/helix-core/src/main/java/org/apache/helix/controller/stages/TargetExteralViewCalcStage.java
@@ -0,0 +1,116 @@
+package org.apache.helix.controller.stages;
+
+/*
+ * 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 org.apache.helix.AccessOption;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.HelixManager;
+import org.apache.helix.HelixProperty;
+import org.apache.helix.PropertyKey;
+import org.apache.helix.controller.common.PartitionStateMap;
+import org.apache.helix.controller.pipeline.AbstractBaseStage;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.IdealState;
+import org.apache.helix.model.Partition;
+import org.apache.helix.model.Resource;
+import org.apache.log4j.Logger;
+
+import com.google.common.collect.Maps;
+
+public class TargetExteralViewCalcStage extends AbstractBaseStage {
+  private static final Logger LOG = 
Logger.getLogger(TargetExteralViewCalcStage.class);
+
+  @Override
+  public void process(ClusterEvent event) throws Exception {
+    LOG.info("START TargetExteralViewCalcStage.process()");
+    long startTime = System.currentTimeMillis();
+    ClusterDataCache cache = 
event.getAttribute(AttributeName.ClusterDataCache.name());
+    ClusterConfig clusterConfig = cache.getClusterConfig();
+
+    if (cache.isTaskCache() || !clusterConfig.isTargetExternalViewEnabled()) {
+      return;
+    }
+
+    HelixManager helixManager = 
event.getAttribute(AttributeName.helixmanager.name());
+    HelixDataAccessor accessor = helixManager.getHelixDataAccessor();
+
+    if (!accessor.getBaseDataAccessor()
+        .exists(accessor.keyBuilder().targetExternalViews().getPath(), 
AccessOption.PERSISTENT)) {
+      accessor.getBaseDataAccessor()
+          .create(accessor.keyBuilder().targetExternalViews().getPath(), null,
+              AccessOption.PERSISTENT);
+    }
+
+    IntermediateStateOutput intermediateAssignment =
+        event.getAttribute(AttributeName.INTERMEDIATE_STATE.name());
+    Map<String, Resource> resourceMap = 
event.getAttribute(AttributeName.RESOURCES.name());
+
+    List<PropertyKey> keys = new ArrayList<>();
+    List<HelixProperty> targetExternalViews = new ArrayList<>();
+
+    for (String resourceName : intermediateAssignment.resourceSet()) {
+      if (cache.getIdealState(resourceName) == null || 
cache.getIdealState(resourceName).isExternalViewDisabled()) {
+        continue;
+      }
+      Resource resource = resourceMap.get(resourceName);
+      if (resource != null) {
+        PartitionStateMap partitionStateMap =
+            intermediateAssignment.getPartitionStateMap(resourceName);
+        Map<String, Map<String, String>> assignmentToPersist = 
convertToMapFields(partitionStateMap.getStateMap());
+
+        ExternalView targetExternalView = 
cache.getTargetExternalView(resourceName);
+        if (targetExternalView == null) {
+          targetExternalView = new ExternalView(resourceName);
+          targetExternalView.getRecord()
+              .getSimpleFields()
+              
.putAll(cache.getIdealState(resourceName).getRecord().getSimpleFields());
+        }
+        if (assignmentToPersist != null && 
!targetExternalView.getRecord().getMapFields()
+            .equals(assignmentToPersist)) {
+          targetExternalView.getRecord().setMapFields(assignmentToPersist);
+          keys.add(accessor.keyBuilder().targetExternalView(resourceName));
+          targetExternalViews.add(targetExternalView);
+          cache.updateTargetExternalView(resourceName, targetExternalView);
+        }
+      }
+    }
+    accessor.setChildren(keys, targetExternalViews);
+
+    long endTime = System.currentTimeMillis();
+    LOG.info(
+        "END TargetExteralViewCalcStage.process() for cluster " + 
cache.getClusterName() + " took " + (
+            endTime - startTime) + " ms");
+  }
+
+  private Map<String, Map<String, String>> convertToMapFields(
+      Map<Partition, Map<String, String>> partitionMapMap) {
+    Map<String, Map<String, String>> mapFields = Maps.newHashMap();
+    for (Partition p : partitionMapMap.keySet()) {
+      mapFields.put(p.getPartitionName(), new 
HashMap<>(partitionMapMap.get(p)));
+    }
+    return mapFields;
+  }
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java 
b/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
index 41fae3e..aa0cbbc 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/CallbackHandler.java
@@ -191,6 +191,7 @@ public class CallbackHandler implements IZkChildListener, 
IZkDataListener {
         listenerClass = MessageListener.class;
         break;
       case EXTERNAL_VIEW:
+      case TARGET_EXTERNAL_VIEW:
         listenerClass = ExternalViewChangeListener.class;
         break;
       case CONTROLLER:
@@ -362,7 +363,7 @@ public class CallbackHandler implements IZkChildListener, 
IZkDataListener {
         List<Message> messages = preFetch(_propertyKey);
         messageListener.onMessage(_manager.getInstanceName(), messages, 
changeContext);
 
-      } else if (_changeType == EXTERNAL_VIEW) {
+      } else if (_changeType == EXTERNAL_VIEW || _changeType == 
TARGET_EXTERNAL_VIEW) {
         ExternalViewChangeListener externalViewListener = 
(ExternalViewChangeListener) _listener;
         subscribeForChanges(changeContext, _path, true);
         List<ExternalView> externalViewList = preFetch(_propertyKey);
@@ -447,7 +448,8 @@ public class CallbackHandler implements IZkChildListener, 
IZkDataListener {
           switch (_changeType) {
           case CURRENT_STATE:
           case IDEAL_STATE:
-          case EXTERNAL_VIEW: {
+          case EXTERNAL_VIEW:
+            case TARGET_EXTERNAL_VIEW:{
             // check if bucketized
             BaseDataAccessor<ZNRecord> baseAccessor = new 
ZkBaseDataAccessor<ZNRecord>(_zkClient);
             List<ZNRecord> records = baseAccessor.getChildren(path, null, 0);

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java 
b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
index db660bd..c32ce64 100644
--- a/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
+++ b/helix-core/src/main/java/org/apache/helix/manager/zk/ZKHelixManager.java
@@ -539,6 +539,12 @@ public class ZKHelixManager implements HelixManager, 
IZkStateListener {
         new EventType[] { EventType.NodeChildrenChanged });
   }
 
+  @Override
+  public void addTargetExternalViewChangeListener(ExternalViewChangeListener 
listener) throws Exception {
+    addListener(listener, new Builder(_clusterName).externalViews(), 
ChangeType.TARGET_EXTERNAL_VIEW,
+        new EventType[] { EventType.NodeChildrenChanged });
+  }
+
   @Deprecated
   @Override
   public void 
addExternalViewChangeListener(org.apache.helix.ExternalViewChangeListener 
listener) throws Exception {

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/main/java/org/apache/helix/model/ClusterConfig.java
----------------------------------------------------------------------
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 542ac65..9ca106f 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
@@ -58,7 +58,8 @@ public class ClusterConfig extends HelixProperty {
     REBALANCE_TIMER_PERIOD,
     MAX_CONCURRENT_TASK_PER_INSTANCE,
     MAX_PARTITIONS_PER_INSTANCE,
-    MAX_OFFLINE_INSTANCES_ALLOWED
+    MAX_OFFLINE_INSTANCES_ALLOWED,
+    TARGET_EXTERNALVIEW_ENABLED
   }
   private final static int DEFAULT_MAX_CONCURRENT_TASK_PER_INSTANCE = 40;
   private static final String IDEAL_STATE_RULE_PREFIX = "IdealStateRule!";
@@ -418,6 +419,21 @@ public class ClusterConfig extends HelixProperty {
   }
 
   /**
+   * Enable/disable target externalview persist
+   * @param enabled
+   */
+  public void enableTargetExternalView(boolean enabled) {
+    
_record.setBooleanField(ClusterConfigProperty.TARGET_EXTERNALVIEW_ENABLED.name(),
 enabled);
+  }
+
+  /**
+   * Determine whether target externalview is enabled or disabled
+   * @return
+   */
+  public boolean isTargetExternalViewEnabled() {
+    return 
_record.getBooleanField(ClusterConfigProperty.TARGET_EXTERNALVIEW_ENABLED.name(),
 false);
+  }
+  /**
    * Get maximum allowed running task count on all instances in this cluster.
    * Instance level configuration will override cluster configuration.
    * @return the maximum task count

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/test/java/org/apache/helix/controller/stages/DummyClusterManager.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/test/java/org/apache/helix/controller/stages/DummyClusterManager.java
 
b/helix-core/src/test/java/org/apache/helix/controller/stages/DummyClusterManager.java
index 2055c61..87ea7f3 100644
--- 
a/helix-core/src/test/java/org/apache/helix/controller/stages/DummyClusterManager.java
+++ 
b/helix-core/src/test/java/org/apache/helix/controller/stages/DummyClusterManager.java
@@ -135,6 +135,11 @@ public class DummyClusterManager implements HelixManager {
   }
 
   @Override
+  public void addTargetExternalViewChangeListener(ExternalViewChangeListener 
listener) throws Exception {
+    // TODO Auto-generated method stub
+  }
+
+  @Override
   public void 
addExternalViewChangeListener(org.apache.helix.ExternalViewChangeListener 
listener) throws Exception {
 
   }

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/test/java/org/apache/helix/integration/controller/TestTargetExternalView.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/test/java/org/apache/helix/integration/controller/TestTargetExternalView.java
 
b/helix-core/src/test/java/org/apache/helix/integration/controller/TestTargetExternalView.java
new file mode 100644
index 0000000..d6fdb10
--- /dev/null
+++ 
b/helix-core/src/test/java/org/apache/helix/integration/controller/TestTargetExternalView.java
@@ -0,0 +1,85 @@
+package org.apache.helix.integration.controller;
+
+/*
+ * 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.List;
+
+import org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixDataAccessor;
+import org.apache.helix.integration.task.TaskTestBase;
+import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.ExternalView;
+import org.apache.helix.model.IdealState;
+import org.testng.Assert;
+import org.testng.annotations.BeforeClass;
+import org.testng.annotations.Test;
+
+public class TestTargetExternalView extends TaskTestBase{
+
+  private ConfigAccessor _configAccessor;
+  private HelixDataAccessor _accessor;
+  @BeforeClass
+  public void beforeClass() throws Exception {
+    _numDbs = 3;
+    _numParitions = 8;
+    _numNodes = 4;
+    _numReplicas = 2;
+    super.beforeClass();
+    _configAccessor = new ConfigAccessor(_gZkClient);
+    _accessor = _manager.getHelixDataAccessor();
+  }
+
+  @Test
+  public void testTargetExternalViewEnable() throws InterruptedException {
+    // Before enable target external view
+    Assert
+        
.assertFalse(_gZkClient.exists(_accessor.keyBuilder().targetExternalViews().getPath()));
+
+    ClusterConfig clusterConfig = 
_configAccessor.getClusterConfig(CLUSTER_NAME);
+    clusterConfig.enableTargetExternalView(true);
+    clusterConfig.setPersistIntermediateAssignment(true);
+    _configAccessor.setClusterConfig(CLUSTER_NAME, clusterConfig);
+    Thread.sleep(2000L);
+
+    Assert
+        
.assertEquals(_accessor.getChildNames(_accessor.keyBuilder().targetExternalViews()).size(),
+            3);
+
+    List<ExternalView> targetExternalViews =
+        _accessor.getChildValues(_accessor.keyBuilder().externalViews());
+    List<IdealState> idealStates = 
_accessor.getChildValues(_accessor.keyBuilder().idealStates());
+
+    for (int i = 0; i < idealStates.size(); i++) {
+      Assert.assertTrue(targetExternalViews.get(i).getRecord().getMapFields()
+          .equals(idealStates.get(i).getRecord().getMapFields()));
+    }
+
+    // Disable one instance to see whether the target external views changes.
+    _gSetupTool.getClusterManagementTool().enableInstance(CLUSTER_NAME, 
_participants[0].getInstanceName(), false);
+
+    targetExternalViews = 
_accessor.getChildValues(_accessor.keyBuilder().externalViews());
+    idealStates = 
_accessor.getChildValues(_accessor.keyBuilder().idealStates());
+
+    for (int i = 0; i < idealStates.size(); i++) {
+      Assert.assertTrue(
+          
targetExternalViews.get(i).getRecord().getMapFields().equals(idealStates.get(i).getRecord().getMapFields()));
+    }
+  }
+}

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/test/java/org/apache/helix/mock/MockManager.java
----------------------------------------------------------------------
diff --git a/helix-core/src/test/java/org/apache/helix/mock/MockManager.java 
b/helix-core/src/test/java/org/apache/helix/mock/MockManager.java
index 778a4f5..109b016 100644
--- a/helix-core/src/test/java/org/apache/helix/mock/MockManager.java
+++ b/helix-core/src/test/java/org/apache/helix/mock/MockManager.java
@@ -136,6 +136,11 @@ public class MockManager implements HelixManager {
   }
 
   @Override
+  public void addTargetExternalViewChangeListener(ExternalViewChangeListener 
listener) throws Exception {
+    // TODO Auto-generated method stub
+  }
+
+  @Override
   public void 
addExternalViewChangeListener(org.apache.helix.ExternalViewChangeListener 
listener) throws Exception {
 
   }

http://git-wip-us.apache.org/repos/asf/helix/blob/d89fbb93/helix-core/src/test/java/org/apache/helix/participant/MockZKHelixManager.java
----------------------------------------------------------------------
diff --git 
a/helix-core/src/test/java/org/apache/helix/participant/MockZKHelixManager.java 
b/helix-core/src/test/java/org/apache/helix/participant/MockZKHelixManager.java
index 6d768a7..049d4c6 100644
--- 
a/helix-core/src/test/java/org/apache/helix/participant/MockZKHelixManager.java
+++ 
b/helix-core/src/test/java/org/apache/helix/participant/MockZKHelixManager.java
@@ -157,6 +157,11 @@ public class MockZKHelixManager implements HelixManager {
   }
 
   @Override
+  public void addTargetExternalViewChangeListener(ExternalViewChangeListener 
listener) throws Exception {
+    // TODO Auto-generated method stub
+  }
+
+  @Override
   public void 
addExternalViewChangeListener(org.apache.helix.ExternalViewChangeListener 
listener) throws Exception {
 
   }

Reply via email to