You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@helix.apache.org by jx...@apache.org on 2020/01/03 18:34:40 UTC

[helix] 01/02: Add CloudConfig code

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

jxue pushed a commit to branch helix-cloud
in repository https://gitbox.apache.org/repos/asf/helix.git

commit 1cf8295972542d835bf75162a15a1b66ff96e890
Author: Ali Reza Zamani Zadeh Najari <an...@linkedin.com>
AuthorDate: Mon Nov 11 11:28:32 2019 -0800

    Add CloudConfig code
    
    In order to move toward supporting cloud environments and autoregisterations,
    we need to add CloudConfig to Zookeeper.
    The code regarding CloudConfig is added.
    A new test has been added to check the correctness of the code.
---
 .../main/java/org/apache/helix/ConfigAccessor.java |  23 ++
 .../main/java/org/apache/helix/PropertyKey.java    |  11 +
 .../helix/cloud/constants/CloudProvider.java       |  25 ++
 .../java/org/apache/helix/model/CloudConfig.java   | 290 +++++++++++++++++++++
 .../org/apache/helix/model/HelixConfigScope.java   |   6 +-
 .../model/builder/HelixConfigScopeBuilder.java     |   3 +
 .../apache/helix/model/cloud/TestCloudConfig.java  | 204 +++++++++++++++
 7 files changed, 561 insertions(+), 1 deletion(-)

diff --git a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
index 018743d..264fd3f 100644
--- a/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
+++ b/helix-core/src/main/java/org/apache/helix/ConfigAccessor.java
@@ -32,6 +32,7 @@ import org.apache.helix.manager.zk.ZNRecordSerializer;
 import org.apache.helix.manager.zk.client.HelixZkClient;
 import org.apache.helix.manager.zk.client.SharedZkClientFactory;
 import org.apache.helix.model.ClusterConfig;
+import org.apache.helix.model.CloudConfig;
 import org.apache.helix.model.ConfigScope;
 import org.apache.helix.model.HelixConfigScope;
 import org.apache.helix.model.HelixConfigScope.ConfigScopeProperty;
@@ -585,6 +586,28 @@ public class ConfigAccessor {
   }
 
   /**
+   * Get CloudConfig of the given cluster.
+   * @param clusterName
+   * @return The instance of {@link CloudConfig}
+   */
+  public CloudConfig getCloudConfig(String clusterName) {
+    if (!ZKUtil.isClusterSetup(clusterName, zkClient)) {
+      throw new HelixException(
+          String.format("Failed to get config. cluster: %s is not setup.", clusterName));
+    }
+    HelixConfigScope scope =
+        new HelixConfigScopeBuilder(ConfigScopeProperty.CLOUD).forCluster(clusterName).build();
+    ZNRecord record = getConfigZnRecord(scope);
+
+    if (record == null) {
+      LOG.warn("No cloud config found at {}.", scope.getZkPath());
+      return null;
+    }
+
+    return new CloudConfig(record);
+  }
+
+  /**
    * Get RestConfig of the given cluster.
    *
    * @param clusterName The cluster
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 30305b8..73cc3f0 100644
--- a/helix-core/src/main/java/org/apache/helix/PropertyKey.java
+++ b/helix-core/src/main/java/org/apache/helix/PropertyKey.java
@@ -22,6 +22,7 @@ package org.apache.helix;
 import java.util.Arrays;
 import java.util.Objects;
 
+import org.apache.helix.model.CloudConfig;
 import org.apache.helix.model.ClusterConfig;
 import org.apache.helix.model.ClusterConstraints;
 import org.apache.helix.model.ControllerHistory;
@@ -233,6 +234,16 @@ public class PropertyKey {
           _clusterName, ConfigScopeProperty.CLUSTER.toString(), _clusterName);
     }
 
+
+    /**
+     * Get a property key associated with this Cloud configuration
+     * @return {@link PropertyKey}
+     */
+    public PropertyKey cloudConfig() {
+      return new PropertyKey(CONFIGS, ConfigScopeProperty.CLOUD, CloudConfig.class,
+          _clusterName, ConfigScopeProperty.CLOUD.name(), _clusterName);
+    }
+
     /**
      * Get a property key associated with {@link InstanceConfig}
      * @return {@link PropertyKey}
diff --git a/helix-core/src/main/java/org/apache/helix/cloud/constants/CloudProvider.java b/helix-core/src/main/java/org/apache/helix/cloud/constants/CloudProvider.java
new file mode 100644
index 0000000..1cb836f
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/cloud/constants/CloudProvider.java
@@ -0,0 +1,25 @@
+package org.apache.helix.cloud.constants;
+
+/*
+ * 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.
+ */
+
+public enum CloudProvider {
+  AZURE,
+  CUSTOMIZED
+}
diff --git a/helix-core/src/main/java/org/apache/helix/model/CloudConfig.java b/helix-core/src/main/java/org/apache/helix/model/CloudConfig.java
new file mode 100644
index 0000000..c8ab6eb
--- /dev/null
+++ b/helix-core/src/main/java/org/apache/helix/model/CloudConfig.java
@@ -0,0 +1,290 @@
+package org.apache.helix.model;
+
+/*
+ * 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.List;
+import org.apache.helix.HelixException;
+import org.apache.helix.HelixProperty;
+import org.apache.helix.ZNRecord;
+import org.apache.helix.cloud.constants.CloudProvider;
+
+/**
+ * Cloud configurations
+ */
+public class CloudConfig extends HelixProperty {
+  /**
+   * Configurable characteristics of a cloud.
+   * NOTE: Do NOT use this field name directly, use its corresponding getter/setter in the
+   * CloudConfig.
+   */
+  public enum CloudConfigProperty {
+    CLOUD_ENABLED, // determine whether the cluster is inside cloud environment.
+    CLOUD_PROVIDER, // the environment the cluster is in, e.g. Azure, AWS, or Customized
+    CLOUD_ID, // the cloud Id that belongs to this cluster.
+
+    // If user uses Helix supported default provider, the below entries will not be shown in
+    // CloudConfig.
+    CLOUD_INFO_SOURCE, // the source for retrieving the cloud information.
+    CLOUD_INFO_PROCESSOR_NAME // the name of the function that processes the fetching and parsing of
+                              // cloud information.
+  }
+
+  /* Default values */
+  private static final boolean DEFAULT_CLOUD_ENABLED = false;
+
+  /**
+   * Instantiate the CloudConfig for the cloud
+   * @param cluster
+   */
+  public CloudConfig(String cluster) {
+    super(cluster);
+  }
+
+  /**
+   * Instantiate with a pre-populated record
+   * @param record a ZNRecord corresponding to a cloud configuration
+   */
+  public CloudConfig(ZNRecord record) {
+    super(record);
+  }
+
+  /**
+   * Instantiate the config using each field individually.
+   * Users should use CloudConfig.Builder to create CloudConfig.
+   * @param cluster
+   * @param enabled
+   * @param cloudID
+   */
+  public CloudConfig(String cluster, boolean enabled, CloudProvider cloudProvider, String cloudID,
+      List<String> cloudInfoSource, String cloudProcessorName) {
+    super(cluster);
+    _record.setBooleanField(CloudConfigProperty.CLOUD_ENABLED.name(), enabled);
+    _record.setSimpleField(CloudConfigProperty.CLOUD_PROVIDER.name(), cloudProvider.name());
+    _record.setSimpleField(CloudConfigProperty.CLOUD_ID.name(), cloudID);
+    if (cloudProvider.equals(CloudProvider.CUSTOMIZED)) {
+      _record
+          .setSimpleField(CloudConfigProperty.CLOUD_INFO_PROCESSOR_NAME.name(), cloudProcessorName);
+      _record.setListField(CloudConfigProperty.CLOUD_INFO_SOURCE.name(), cloudInfoSource);
+    }
+  }
+
+  /**
+   * Enable/Disable the CLOUD_ENABLED field.
+   * @param enabled
+   */
+  public void setCloudEnabled(boolean enabled) {
+    _record.setBooleanField(CloudConfigProperty.CLOUD_ENABLED.name(), enabled);
+  }
+
+  /**
+   * Whether CLOUD_ENABLED field is enabled or not.
+   * @return
+   */
+  public boolean isCloudEnabled() {
+    return _record.getBooleanField(CloudConfigProperty.CLOUD_ENABLED.name(), false);
+  }
+
+  /**
+   * Set the cloudID field.
+   * @param cloudID
+   */
+  public void setCloudID(String cloudID) {
+    _record.setSimpleField(CloudConfigProperty.CLOUD_ID.name(), cloudID);
+  }
+
+  /**
+   * Get the CloudID field.
+   * @return CloudID
+   */
+  public String getCloudID() {
+    return _record.getSimpleField(CloudConfigProperty.CLOUD_ID.name());
+  }
+
+  /**
+   * Set the CLOUD_INFO_SOURCE field.
+   * @param cloudInfoSources
+   */
+  public void setCloudInfoSource(List<String> cloudInfoSources) {
+    _record.setListField(CloudConfigProperty.CLOUD_INFO_SOURCE.name(), cloudInfoSources);
+  }
+
+  /**
+   * Get the CLOUD_INFO_SOURCE field.
+   * @return CLOUD_INFO_SOURCE field.
+   */
+  public List<String> getCloudInfoSources() {
+    return _record.getListField(CloudConfigProperty.CLOUD_INFO_SOURCE.name());
+  }
+
+  /**
+   * Set the CLOUD_INFO_PROCESSOR_NAME field.
+   * @param cloudInfoProcessorName
+   */
+  public void setCloudInfoFProcessorName(String cloudInfoProcessorName) {
+    _record.setSimpleField(CloudConfigProperty.CLOUD_INFO_PROCESSOR_NAME.name(),
+        cloudInfoProcessorName);
+  }
+
+  /**
+   * Get the CLOUD_INFO_PROCESSOR_NAME field.
+   * @return CLOUD_INFO_PROCESSOR_NAME field.
+   */
+  public String getCloudInfoProcessorName() {
+    return _record.getSimpleField(CloudConfigProperty.CLOUD_INFO_PROCESSOR_NAME.name());
+  }
+
+  /**
+   * Set the CLOUD_PROVIDER field.
+   * @param cloudProvider
+   */
+  public void setCloudProvider(CloudProvider cloudProvider) {
+    _record.setSimpleField(CloudConfigProperty.CLOUD_PROVIDER.name(), cloudProvider.name());
+  }
+
+  /**
+   * Get the CLOUD_PROVIDER field.
+   * @return CLOUD_PROVIDER field.
+   */
+  public String getCloudProvider() {
+    return _record.getSimpleField(CloudConfigProperty.CLOUD_PROVIDER.name());
+  }
+
+  public static class Builder {
+    private String _clusterName = null;
+    private CloudProvider _cloudProvider;
+    private boolean _cloudEnabled = DEFAULT_CLOUD_ENABLED;
+    private String _cloudID;
+    private List<String> _cloudInfoSources;
+    private String _cloudInfoProcessorName;
+
+    public CloudConfig build() {
+      validate();
+      return new CloudConfig(_clusterName, _cloudEnabled, _cloudProvider, _cloudID,
+          _cloudInfoSources, _cloudInfoProcessorName);
+    }
+
+    /**
+     * Default constructor
+     */
+    public Builder() {
+    }
+
+    /**
+     * Constructor with Cluster Name as input
+     * @param clusterName
+     */
+    public Builder(String clusterName) {
+      _clusterName = clusterName;
+    }
+
+    /**
+     * Constructor with CloudConfig as input
+     * @param cloudConfig
+     */
+    public Builder(CloudConfig cloudConfig) {
+      _cloudEnabled = cloudConfig.isCloudEnabled();
+      _cloudProvider = CloudProvider.valueOf(cloudConfig.getCloudProvider());
+      _cloudID = cloudConfig.getCloudID();
+      _cloudInfoSources = cloudConfig.getCloudInfoSources();
+      _cloudInfoProcessorName = cloudConfig.getCloudInfoProcessorName();
+    }
+
+    public Builder setClusterName(String v) {
+      _clusterName = v;
+      return this;
+    }
+
+    public Builder setCloudEnabled(boolean isEnabled) {
+      _cloudEnabled = isEnabled;
+      return this;
+    }
+
+    public Builder setCloudProvider(CloudProvider cloudProvider) {
+      _cloudProvider = cloudProvider;
+      return this;
+    }
+
+    public Builder setCloudID(String v) {
+      _cloudID = v;
+      return this;
+    }
+
+    public Builder setCloudInfoSources(List<String> v) {
+      _cloudInfoSources = v;
+      return this;
+    }
+
+    public Builder addCloudInfoSource(String v) {
+      if (_cloudInfoSources == null) {
+        _cloudInfoSources = new ArrayList<String>();
+      }
+      _cloudInfoSources.add(v);
+      return this;
+    }
+
+    public Builder setCloudInfoProcessorName(String v) {
+      _cloudInfoProcessorName = v;
+      return this;
+    }
+
+    public String getClusterName() {
+      return _clusterName;
+    }
+
+    public CloudProvider getCloudProvider() {
+      return _cloudProvider;
+    }
+
+    public boolean getCloudEnabled() {
+      return _cloudEnabled;
+    }
+
+    public String getCloudID() {
+      return _cloudID;
+    }
+
+    public List<String> getCloudInfoSources() {
+      return _cloudInfoSources;
+    }
+
+    public String getCloudInfoProcessorName() {
+      return _cloudInfoProcessorName;
+    }
+
+    private void validate() {
+      if (_cloudEnabled) {
+        if (_cloudID == null) {
+          throw new HelixException(
+              "This Cloud Configuration is Invalid. The CloudID is missing from the config.");
+        }
+        if (_cloudProvider == null) {
+          throw new HelixException(
+              "This Cloud Configuration is Invalid. The Cloud Provider is missing from the config.");
+        } else if (_cloudProvider == CloudProvider.CUSTOMIZED) {
+          if (_cloudInfoProcessorName == null || _cloudInfoSources == null || _cloudInfoSources.size() == 0) {
+            throw new HelixException(
+                "This Cloud Configuration is Invalid. CUSTOMIZED provider has been chosen without defining CloudInfoProcessorName or CloudInfoSources");
+          }
+        }
+      }
+    }
+  }
+}
diff --git a/helix-core/src/main/java/org/apache/helix/model/HelixConfigScope.java b/helix-core/src/main/java/org/apache/helix/model/HelixConfigScope.java
index 7c5c91d..8d814c5 100644
--- a/helix-core/src/main/java/org/apache/helix/model/HelixConfigScope.java
+++ b/helix-core/src/main/java/org/apache/helix/model/HelixConfigScope.java
@@ -36,7 +36,8 @@ public class HelixConfigScope {
     RESOURCE(2, 0),
     PARTITION(2, 1),
     CONSTRAINT(2, 0),
-    REST(2, 0);
+    REST(2, 0),
+    CLOUD(2, 0);
 
     final int _zkPathArgNum;
     final int _mapKeyArgNum;
@@ -78,12 +79,15 @@ public class HelixConfigScope {
         "/{clusterName}/CONFIGS/RESOURCE/{resourceName}");
     template.addEntry(ConfigScopeProperty.PARTITION, 2,
         "/{clusterName}/CONFIGS/RESOURCE/{resourceName}");
+    template.addEntry(ConfigScopeProperty.CLOUD, 2,
+        "/{clusterName}/CONFIGS/CLOUD/{clusterName}");
 
     // get children
     template.addEntry(ConfigScopeProperty.CLUSTER, 1, "/{clusterName}/CONFIGS/CLUSTER");
     template.addEntry(ConfigScopeProperty.PARTICIPANT, 1, "/{clusterName}/CONFIGS/PARTICIPANT");
     template.addEntry(ConfigScopeProperty.RESOURCE, 1, "/{clusterName}/CONFIGS/RESOURCE");
     template.addEntry(ConfigScopeProperty.REST, 2, "/{clusterName}/CONFIGS/REST/{clusterName}");
+    template.addEntry(ConfigScopeProperty.CLOUD, 1, "/{clusterName}/CONFIGS/CLOUD");
   }
 
   final ConfigScopeProperty _type;
diff --git a/helix-core/src/main/java/org/apache/helix/model/builder/HelixConfigScopeBuilder.java b/helix-core/src/main/java/org/apache/helix/model/builder/HelixConfigScopeBuilder.java
index b1a7cf6..78ed074 100644
--- a/helix-core/src/main/java/org/apache/helix/model/builder/HelixConfigScopeBuilder.java
+++ b/helix-core/src/main/java/org/apache/helix/model/builder/HelixConfigScopeBuilder.java
@@ -126,6 +126,9 @@ public class HelixConfigScopeBuilder {
     case REST:
       scope = new HelixConfigScope(_type, Arrays.asList(_clusterName, _clusterName), null);
       break;
+    case CLOUD:
+      scope = new HelixConfigScope(_type, Arrays.asList(_clusterName, _clusterName), null);
+      break;
     default:
       break;
     }
diff --git a/helix-core/src/test/java/org/apache/helix/model/cloud/TestCloudConfig.java b/helix-core/src/test/java/org/apache/helix/model/cloud/TestCloudConfig.java
new file mode 100644
index 0000000..ee83011
--- /dev/null
+++ b/helix-core/src/test/java/org/apache/helix/model/cloud/TestCloudConfig.java
@@ -0,0 +1,204 @@
+package org.apache.helix.model.cloud;
+
+/*
+ * 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 org.apache.helix.ConfigAccessor;
+import org.apache.helix.HelixException;
+import org.apache.helix.PropertyKey.Builder;
+import org.apache.helix.TestHelper;
+import org.apache.helix.ZkUnitTestBase;
+import org.apache.helix.manager.zk.ZKHelixDataAccessor;
+import org.apache.helix.manager.zk.ZkBaseDataAccessor;
+import org.apache.helix.model.CloudConfig;
+import org.apache.helix.cloud.constants.CloudProvider;
+import java.util.List;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class TestCloudConfig extends ZkUnitTestBase {
+
+  @Test(expectedExceptions = HelixException.class)
+  public void testCloudConfigNonExistentCluster() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    // Read CloudConfig from Zookeeper and get exception since cluster in not setup yet
+    ConfigAccessor _configAccessor = new ConfigAccessor(_gZkClient);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+  }
+
+  @Test(dependsOnMethods = "testCloudConfigNonExistentCluster")
+  public void testCloudConfigNull() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    TestHelper.setupEmptyCluster(_gZkClient, clusterName);
+    // Read CloudConfig from Zookeeper
+    ConfigAccessor _configAccessor = new ConfigAccessor(_gZkClient);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    // since CloudConfig is not written to ZooKeeper, the output should be null
+    Assert.assertNull(cloudConfigFromZk);
+  }
+
+  @Test(dependsOnMethods = "testCloudConfigNull")
+  public void testCloudConfig() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    TestHelper.setupEmptyCluster(_gZkClient, clusterName);
+
+    // Create dummy CloudConfig object
+    CloudConfig cloudConfig = new CloudConfig(clusterName);
+    cloudConfig.setCloudEnabled(true);
+    cloudConfig.setCloudProvider(CloudProvider.AZURE);
+    cloudConfig.setCloudID("TestID");
+    List<String> infoURL = new ArrayList<String>();
+    infoURL.add("TestURL");
+    cloudConfig.setCloudInfoSource(infoURL);
+    cloudConfig.setCloudInfoFProcessorName("TestProcessor");
+
+    // Write the CloudConfig to Zookeeper
+    ZKHelixDataAccessor accessor =
+        new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor(_gZkClient));
+    Builder keyBuilder = accessor.keyBuilder();
+    accessor.setProperty(keyBuilder.cloudConfig(), cloudConfig);
+
+    // Read CloudConfig from Zookeeper and check the content
+    ConfigAccessor _configAccessor = new ConfigAccessor(_gZkClient);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertTrue(cloudConfigFromZk.isCloudEnabled());
+    Assert.assertEquals(cloudConfigFromZk.getCloudProvider(), CloudProvider.AZURE.name());
+    Assert.assertEquals(cloudConfigFromZk.getCloudID(), "TestID");
+    Assert.assertEquals(cloudConfigFromZk.getCloudInfoSources().size(), 1);
+    Assert.assertEquals(cloudConfigFromZk.getCloudInfoSources().get(0), "TestURL");
+    Assert.assertEquals(cloudConfigFromZk.getCloudInfoProcessorName(), "TestProcessor");
+  }
+
+  @Test(expectedExceptions = HelixException.class)
+  public void testUnverifiedCloudConfigBuilder() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    CloudConfig.Builder builder = new CloudConfig.Builder(clusterName);
+    builder.setCloudEnabled(true);
+    // Verify will fail because cloudID has net been defined.
+    CloudConfig cloudConfig = builder.build();
+  }
+
+  @Test(expectedExceptions = HelixException.class)
+  public void testUnverifiedCloudConfigBuilderEmptySources() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    CloudConfig.Builder builder = new CloudConfig.Builder(clusterName);
+    builder.setCloudEnabled(true);
+    builder.setCloudProvider(CloudProvider.CUSTOMIZED);
+    builder.setCloudID("TestID");
+    List<String> emptyList = new ArrayList<String>();
+    builder.setCloudInfoSources(emptyList);
+    builder.setCloudInfoProcessorName("TestProcessor");
+    CloudConfig cloudConfig = builder.build();
+  }
+
+  @Test(expectedExceptions = HelixException.class)
+  public void testUnverifiedCloudConfigBuilderWithoutProcessor() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    CloudConfig.Builder builder = new CloudConfig.Builder(clusterName);
+    builder.setCloudEnabled(true);
+    builder.setCloudProvider(CloudProvider.CUSTOMIZED);
+    builder.setCloudID("TestID");
+    List<String> testList = new ArrayList<String>();
+    builder.setCloudInfoSources(testList);
+    builder.addCloudInfoSource("TestURL");
+    CloudConfig cloudConfig = builder.build();
+  }
+
+  @Test(dependsOnMethods = "testCloudConfig")
+  public void testCloudConfigBuilder() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    TestHelper.setupEmptyCluster(_gZkClient, clusterName);
+    CloudConfig.Builder builder = new CloudConfig.Builder(clusterName);
+    builder.setCloudEnabled(true);
+    builder.setCloudProvider(CloudProvider.CUSTOMIZED);
+    builder.setCloudID("TestID");
+    builder.addCloudInfoSource("TestURL0");
+    builder.addCloudInfoSource("TestURL1");
+    builder.setCloudInfoProcessorName("TestProcessor");
+
+    // Check builder getter methods
+    Assert.assertTrue(builder.getCloudEnabled());
+    Assert.assertEquals(builder.getCloudProvider(), CloudProvider.CUSTOMIZED);
+    Assert.assertEquals(builder.getClusterName(), clusterName);
+    Assert.assertEquals(builder.getCloudID(), "TestID");
+    List<String> listUrlFromBuilder = builder.getCloudInfoSources();
+    Assert.assertEquals(listUrlFromBuilder.size(), 2);
+    Assert.assertEquals(listUrlFromBuilder.get(0), "TestURL0");
+    Assert.assertEquals(listUrlFromBuilder.get(1), "TestURL1");
+    Assert.assertEquals(builder.getCloudInfoProcessorName(), "TestProcessor");
+
+    CloudConfig cloudConfig = builder.build();
+
+    ZKHelixDataAccessor accessor =
+        new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor(_gZkClient));
+    Builder keyBuilder = accessor.keyBuilder();
+    accessor.setProperty(keyBuilder.cloudConfig(), cloudConfig);
+
+    // Read CloudConfig from Zookeeper and check the content
+    ConfigAccessor _configAccessor = new ConfigAccessor(_gZkClient);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertTrue(cloudConfigFromZk.isCloudEnabled());
+    Assert.assertEquals(cloudConfigFromZk.getCloudProvider(), CloudProvider.CUSTOMIZED.name());
+    Assert.assertEquals(cloudConfigFromZk.getCloudID(), "TestID");
+    List<String> listUrlFromZk = cloudConfigFromZk.getCloudInfoSources();
+    Assert.assertEquals(listUrlFromZk.get(0), "TestURL0");
+    Assert.assertEquals(listUrlFromZk.get(1), "TestURL1");
+    Assert.assertEquals(cloudConfigFromZk.getCloudInfoProcessorName(), "TestProcessor");
+  }
+
+  @Test(dependsOnMethods = "testCloudConfigBuilder")
+  public void testCloudConfigBuilderAzureProvider() {
+    String className = getShortClassName();
+    String clusterName = "CLUSTER_" + className;
+    TestHelper.setupEmptyCluster(_gZkClient, clusterName);
+    CloudConfig.Builder builder = new CloudConfig.Builder(clusterName);
+    builder.setCloudEnabled(true);
+    builder.setCloudProvider(CloudProvider.AZURE);
+    builder.setCloudID("TestID");
+    builder.setCloudInfoProcessorName("TestProcessor");
+
+    // Check builder getter methods
+    Assert.assertTrue(builder.getCloudEnabled());
+    Assert.assertEquals(builder.getCloudProvider(), CloudProvider.AZURE);
+
+    CloudConfig cloudConfig = builder.build();
+
+    ZKHelixDataAccessor accessor =
+        new ZKHelixDataAccessor(clusterName, new ZkBaseDataAccessor(_gZkClient));
+    Builder keyBuilder = accessor.keyBuilder();
+    accessor.setProperty(keyBuilder.cloudConfig(), cloudConfig);
+
+    // Read CloudConfig from Zookeeper and check the content
+    ConfigAccessor _configAccessor = new ConfigAccessor(_gZkClient);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertTrue(cloudConfigFromZk.isCloudEnabled());
+    Assert.assertEquals(cloudConfigFromZk.getCloudProvider(), CloudProvider.AZURE.name());
+
+    // Since CloudProvider is not CUSTOMIZED, CloudInfoProcessor will be null.
+    Assert.assertNull(cloudConfigFromZk.getCloudInfoProcessorName());
+  }
+}