Add cloud set/remove to cluster setup (#1783)

In this commit, the CloudConfig set and remove APIs have been added
to the cluster setup.
diff --git a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
index 667cab7..ab236e0 100644
--- a/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
+++ b/helix-core/src/main/java/org/apache/helix/tools/ClusterSetup.java
@@ -27,6 +27,8 @@
 import java.util.List;
 import java.util.Map;
 
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
 import org.apache.commons.cli.CommandLine;
 import org.apache.commons.cli.CommandLineParser;
 import org.apache.commons.cli.GnuParser;
@@ -72,6 +74,7 @@
 import org.apache.helix.zookeeper.datamodel.serializer.ZNRecordSerializer;
 import org.apache.helix.zookeeper.impl.client.FederatedZkClient;
 import org.apache.helix.zookeeper.impl.factory.SharedZkClientFactory;
+import org.apache.helix.zookeeper.introspect.CodehausJacksonIntrospector;
 import org.apache.helix.zookeeper.zkclient.DataUpdater;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -138,6 +141,11 @@
   public static final String setConfig = "setConfig";
   public static final String removeConfig = "removeConfig";
 
+  // set/remove cloud configs
+  public static final String setCloudConfig = "setCloudConfig";
+  public static final String removeCloudConfig = "removeCloudConfig";
+
+
   // get/set/remove constraints
   public static final String getConstraints = "getConstraints";
   public static final String setConstraint = "setConstraint";
@@ -151,6 +159,10 @@
   private final boolean _usesExternalZkClient;
   private final HelixAdmin _admin;
 
+  protected static ObjectReader ZNRECORD_READER = new ObjectMapper()
+      .setAnnotationIntrospector(new CodehausJacksonIntrospector())
+      .readerFor(ZNRecord.class);
+
   @Deprecated
   public ClusterSetup(String zkServerAddress) {
     // If the multi ZK config is enabled, use FederatedZkClient on multi-realm mode
@@ -624,6 +636,33 @@
   }
 
   /**
+   * set cloud configs
+   * @param clusterName
+   * @param cloudConfigManifest
+   */
+  public void setCloudConfig(String clusterName, String cloudConfigManifest) {
+    ZNRecord record;
+    try {
+      record = ZNRECORD_READER.readValue(cloudConfigManifest);
+    } catch (IOException e) {
+      _logger
+          .error("Failed to deserialize user's input " + cloudConfigManifest + ", Exception: " + e);
+      throw new IllegalArgumentException("Failed to deserialize user's input ");
+    }
+
+    CloudConfig cloudConfig = new CloudConfig.Builder(record).build();
+    _admin.addCloudConfig(clusterName, cloudConfig);
+  }
+
+  /**
+   * remove cloud configs
+   * @param clusterName
+   */
+  public void removeCloudConfig(String clusterName) {
+    _admin.removeCloudConfig(clusterName);
+  }
+
+  /**
    * get configs
    * @param type config-scope-type, e.g. CLUSTER, RESOURCE, etc.
    * @param scopeArgsCsv csv-formatted scope-args, e.g myCluster,testDB
@@ -1058,6 +1097,19 @@
             .withLongOpt(removeConstraint)
             .withDescription("Remove a constraint associated with given id").create();
 
+    Option setCloudConfigOption = OptionBuilder.withLongOpt(setCloudConfig).withDescription(
+        "Set the Cloud Configuration of the cluster. Example:\n sh helix-admin.sh --zkSvr ZookeeperServerAddress --setCloudConfig ClusterName '{\"simpleFields\" : {\"CLOUD_ENABLED\" : \"true\",\"CLOUD_PROVIDER\": \"AZURE\"}}'")
+        .create();
+    setCloudConfigOption.setArgs(2);
+    setCloudConfigOption.setRequired(false);
+    setCloudConfigOption.setArgName("clusterName CloudConfigurationManifest");
+
+    Option removeCloudConfigOption = OptionBuilder.withLongOpt(removeCloudConfig)
+        .withDescription("Remove the Cloud Configuration of the cluster").create();
+    removeCloudConfigOption.setArgs(1);
+    removeCloudConfigOption.setRequired(false);
+    removeCloudConfigOption.setArgName("clusterName");
+
     OptionGroup group = new OptionGroup();
     group.setRequired(true);
     group.addOption(rebalanceOption);
@@ -1108,6 +1160,10 @@
     group.addOption(getConstraintsOption);
     group.addOption(removeConstraintOption);
 
+    // set/remove cloud configs
+    group.addOption(setCloudConfigOption);
+    group.addOption(removeCloudConfigOption);
+
     group.addOption(addInstanceTagOption);
     group.addOption(removeInstanceTagOption);
     group.addOption(instanceGroupTagOption);
@@ -1571,6 +1627,15 @@
 
       setupTool.removeResourceProperty(clusterName, resourceName, propertyKey);
       return 0;
+    } else if (cmd.hasOption(setCloudConfig)) {
+      String clusterName = cmd.getOptionValues(setCloudConfig)[0];
+      String cloudConfigManifest = cmd.getOptionValues(setCloudConfig)[1];
+      setupTool.setCloudConfig(clusterName, cloudConfigManifest);
+      return 0;
+    } else if (cmd.hasOption(removeCloudConfig)) {
+      String clusterName = cmd.getOptionValues(removeCloudConfig)[0];
+      setupTool.removeCloudConfig(clusterName);
+      return 0;
     }
     return 0;
   }
diff --git a/helix-core/src/test/java/org/apache/helix/tools/TestClusterSetup.java b/helix-core/src/test/java/org/apache/helix/tools/TestClusterSetup.java
index 749ca2a..44cacc8 100644
--- a/helix-core/src/test/java/org/apache/helix/tools/TestClusterSetup.java
+++ b/helix-core/src/test/java/org/apache/helix/tools/TestClusterSetup.java
@@ -552,4 +552,34 @@
     Assert.assertNull(cloudConfigFromZk.getCloudInfoProcessorName());
     Assert.assertEquals(cloudConfigFromZk.getCloudProvider(), CloudProvider.AZURE.name());
   }
+
+  @Test(dependsOnMethods = "testAddClusterAzureProvider")
+  public void testSetRemoveCloudConfig() throws Exception {
+    String className = TestHelper.getTestClassName();
+    String methodName = TestHelper.getTestMethodName();
+    String clusterName = className + "_" + methodName;
+
+    // Create Cluster without cloud config
+    _clusterSetup.addCluster(clusterName, false);
+
+    // Read CloudConfig from Zookeeper and check the content
+    ConfigAccessor _configAccessor = new ConfigAccessor(ZK_ADDR);
+    CloudConfig cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertNull(cloudConfigFromZk);
+
+    String cloudConfigManifest =
+        "{\"simpleFields\" : {\"CLOUD_ENABLED\" : \"true\",\"CLOUD_PROVIDER\": \"AZURE\"}}\"";
+    _clusterSetup.setCloudConfig(clusterName, cloudConfigManifest);
+
+    // Read cloud config from ZK and make sure the fields are accurate
+    cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertNotNull(cloudConfigFromZk);
+    Assert.assertEquals(CloudProvider.AZURE.name(), cloudConfigFromZk.getCloudProvider());
+    Assert.assertTrue(cloudConfigFromZk.isCloudEnabled());
+
+    // Remove cloud config and make sure it has been removed
+    _clusterSetup.removeCloudConfig(clusterName);
+    cloudConfigFromZk = _configAccessor.getCloudConfig(clusterName);
+    Assert.assertNull(cloudConfigFromZk);
+  }
 }