AMBARI-24725 - Infra Solr: manage autoscaling properties in Ambari (#2)

diff --git a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudCLI.java b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudCLI.java
index b0c7781..cc69985 100644
--- a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudCLI.java
+++ b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudCLI.java
@@ -56,6 +56,10 @@
   private static final String TRANSFER_ZNODE_COMMAND = "transfer-znode";
   private static final String DELETE_ZNODE_COMMAND = "delete-znode";
   private static final String DUMP_COLLECTIONS_DATA_COMMAND = "dump-collections";
+  private static final String SET_AUTO_SCALING_COMMAND = "set-autoscaling";
+  private static final String SET_AUTO_SCALING_COMMAND_SHORT = "as";
+  private static final String AUTO_SCALING_JSON_LOCATION = "autoscaling-json-location";
+  private static final String AUTO_SCALING_JSON_LOCATION_SHORT = "ajl";
   private static final String CMD_LINE_SYNTAX =
     "\n./solrCloudCli.sh --create-collection -z host1:2181,host2:2181/ambari-solr -c collection -cs conf_set"
       + "\n./solrCloudCli.sh --upload-config -z host1:2181,host2:2181/ambari-solr -d /tmp/myconfig_dir -cs config_set"
@@ -72,7 +76,8 @@
       + "\n./solrCloudCli.sh --secure-znode -z host1:2181,host2:2181 -zn /ambari-solr -su logsearch,atlas,ranger --jaas-file /etc/myconf/jaas_file"
       + "\n./solrCloudCli.sh --unsecure-znode -z host1:2181,host2:2181 -zn /ambari-solr --jaas-file /etc/myconf/jaas_file"
       + "\n./solrCloudCli.sh --secure-solr-znode -z host1:2181,host2:2181 -zn /ambari-solr -su logsearch,atlas,ranger --jaas-file /etc/myconf/jaas_file"
-      + "\n./solrCloudCli.sh --setup-kerberos-plugin -z host1:2181,host2:2181 -zn /ambari-solr --security-json-location /etc/infra-solr/conf/security.json\n ";
+      + "\n./solrCloudCli.sh --setup-kerberos-plugin -z host1:2181,host2:2181 -zn /ambari-solr --security-json-location /etc/infra-solr/conf/security.json"
+      + "\n./solrCloudCli.sh --" + SET_AUTO_SCALING_COMMAND + " -z host1:2181,host2:2181 -zn /ambari-solr [--" + AUTO_SCALING_JSON_LOCATION + "|--" + AUTO_SCALING_JSON_LOCATION_SHORT + "] /etc/infra-solr/conf/autoscaling.json\n ";
 
   public static void main(String[] args) {
     Options options = new Options();
@@ -379,6 +384,18 @@
       .desc("Include the number of docs as well in collection dump")
       .build();
 
+    final Option setAutoScaling = Option.builder(SET_AUTO_SCALING_COMMAND_SHORT)
+            .longOpt(SET_AUTO_SCALING_COMMAND)
+            .desc("Upload and set the specified autoscaling.json to znode")
+            .build();
+
+    final Option autoScalingJsonLocationOption = Option.builder(AUTO_SCALING_JSON_LOCATION_SHORT)
+            .longOpt(AUTO_SCALING_JSON_LOCATION)
+            .desc("Local autoscaling.json path")
+            .numberOfArgs(1)
+            .argName("autoscaling.json location")
+            .build();
+
     options.addOption(helpOption);
     options.addOption(retryOption);
     options.addOption(removeAdminHandlerOption);
@@ -427,6 +444,8 @@
     options.addOption(securityJsonLocationOption);
     options.addOption(outputOption);
     options.addOption(includeDocNumberOption);
+    options.addOption(setAutoScaling);
+    options.addOption(autoScalingJsonLocationOption);
 
     AmbariSolrCloudClient solrCloudClient = null;
 
@@ -487,11 +506,14 @@
       } else if (cli.hasOption("dcd")) {
         command = DUMP_COLLECTIONS_DATA_COMMAND;
         validateRequiredOptions(cli, command, zkConnectStringOption, outputOption);
+      } else if (cli.hasOption(SET_AUTO_SCALING_COMMAND_SHORT)) {
+        command = SET_AUTO_SCALING_COMMAND;
+        validateRequiredOptions(cli, command, zkConnectStringOption, znodeOption, autoScalingJsonLocationOption);
       } else {
         List<String> commands = Arrays.asList(CREATE_COLLECTION_COMMAND, CREATE_SHARD_COMMAND, UPLOAD_CONFIG_COMMAND,
           DOWNLOAD_CONFIG_COMMAND, CONFIG_CHECK_COMMAND, SET_CLUSTER_PROP, CREATE_ZNODE, SECURE_ZNODE_COMMAND, UNSECURE_ZNODE_COMMAND,
           SECURE_SOLR_ZNODE_COMMAND, CHECK_ZNODE, SETUP_KERBEROS_PLUGIN, REMOVE_ADMIN_HANDLERS, TRANSFER_ZNODE_COMMAND, DELETE_ZNODE_COMMAND,
-          DUMP_COLLECTIONS_DATA_COMMAND);
+          DUMP_COLLECTIONS_DATA_COMMAND, SET_AUTO_SCALING_COMMAND);
         helpFormatter.printHelp(CMD_LINE_SYNTAX, options);
         exit(1, String.format("One of the supported commands is required (%s)", StringUtils.join(commands, "|")));
       }
@@ -527,6 +549,7 @@
       String transferMode = cli.hasOption("tm") ? cli.getOptionValue("tm") : "NONE";
       String output = cli.hasOption("o") ? cli.getOptionValue("o") : null;
       boolean includeDocNumber = cli.hasOption("idn");
+      String autoScalingJsonLocation = cli.hasOption(AUTO_SCALING_JSON_LOCATION_SHORT) ? cli.getOptionValue(AUTO_SCALING_JSON_LOCATION_SHORT) : "";
 
       AmbariSolrCloudClientBuilder clientBuilder = new AmbariSolrCloudClientBuilder()
         .withZkConnectString(zkConnectString)
@@ -558,7 +581,8 @@
         .withSecurityJsonLocation(securityJsonLocation)
         .withZnode(znode)
         .withSecure(isSecure)
-        .withSaslUsers(saslUsers);
+        .withSaslUsers(saslUsers)
+        .withAutoScalingJsonLocation(autoScalingJsonLocation);
 
       switch (command) {
         case CREATE_COLLECTION_COMMAND:
@@ -640,6 +664,10 @@
             .withSolrCloudClient().build();
           solrCloudClient.outputCollectionData();
           break;
+        case SET_AUTO_SCALING_COMMAND:
+          solrCloudClient = clientBuilder.withSolrCloudClient().build();
+          solrCloudClient.setAutoScaling();
+          break;
         default:
           throw new AmbariSolrCloudClientException(String.format("Not found command: '%s'", command));
       }
diff --git a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClient.java b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClient.java
index 7571c99..4ef629d 100644
--- a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClient.java
+++ b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClient.java
@@ -18,7 +18,11 @@
  */
 package org.apache.ambari.infra.solr;
 
+import java.util.Collection;
+import java.util.List;
+
 import org.apache.ambari.infra.solr.commands.CheckConfigZkCommand;
+import org.apache.ambari.infra.solr.commands.CheckZnodeZkCommand;
 import org.apache.ambari.infra.solr.commands.CreateCollectionCommand;
 import org.apache.ambari.infra.solr.commands.CreateShardCommand;
 import org.apache.ambari.infra.solr.commands.CreateSolrZnodeZkCommand;
@@ -32,11 +36,11 @@
 import org.apache.ambari.infra.solr.commands.RemoveAdminHandlersCommand;
 import org.apache.ambari.infra.solr.commands.SecureSolrZNodeZkCommand;
 import org.apache.ambari.infra.solr.commands.SecureZNodeZkCommand;
+import org.apache.ambari.infra.solr.commands.SetAutoScalingZkCommand;
 import org.apache.ambari.infra.solr.commands.SetClusterPropertyZkCommand;
 import org.apache.ambari.infra.solr.commands.TransferZnodeZkCommand;
 import org.apache.ambari.infra.solr.commands.UnsecureZNodeZkCommand;
 import org.apache.ambari.infra.solr.commands.UploadConfigZkCommand;
-import org.apache.ambari.infra.solr.commands.CheckZnodeZkCommand;
 import org.apache.ambari.infra.solr.util.ShardUtils;
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.common.cloud.Slice;
@@ -44,9 +48,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collection;
-import java.util.List;
-
 /**
  * Client for communicate with Solr (and Zookeeper)
  */
@@ -80,6 +81,7 @@
   private final String copyDest;
   private final String output;
   private final boolean includeDocNumber;
+  private final String autoScalingJsonLocation;
 
   public AmbariSolrCloudClient(AmbariSolrCloudClientBuilder builder) {
     this.zkConnectString = builder.zkConnectString;
@@ -108,6 +110,7 @@
     this.copyDest = builder.copyDest;
     this.output = builder.output;
     this.includeDocNumber = builder.includeDocNumber;
+    this.autoScalingJsonLocation = builder.autoScalingJsonLocation;
   }
 
   /**
@@ -299,6 +302,10 @@
     return new DeleteZnodeZkCommand(getRetryTimes(), getInterval()).run(this);
   }
 
+  public void setAutoScaling() throws Exception {
+    new SetAutoScalingZkCommand(getRetryTimes(), getInterval(), autoScalingJsonLocation).run(this);
+  }
+
   public String getZkConnectString() {
     return zkConnectString;
   }
diff --git a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientBuilder.java b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientBuilder.java
index db4396b..834b87a 100644
--- a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientBuilder.java
+++ b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/AmbariSolrCloudClientBuilder.java
@@ -19,6 +19,10 @@
 
 package org.apache.ambari.infra.solr;
 
+import static java.util.Collections.singletonList;
+
+import java.util.Optional;
+
 import org.apache.solr.client.solrj.impl.CloudSolrClient;
 import org.apache.solr.client.solrj.impl.Krb5HttpClientBuilder;
 import org.apache.solr.common.cloud.SolrZkClient;
@@ -58,7 +62,8 @@
   String copySrc;
   String copyDest;
   String output;
-  public boolean includeDocNumber;
+  boolean includeDocNumber;
+  String autoScalingJsonLocation;
 
   public AmbariSolrCloudClient build() {
     return new AmbariSolrCloudClient(this);
@@ -131,7 +136,7 @@
   }
 
   public AmbariSolrCloudClientBuilder withSolrCloudClient() {
-    this.solrCloudClient = new CloudSolrClient.Builder().withZkHost(this.zkConnectString).build();
+    this.solrCloudClient = new CloudSolrClient.Builder(singletonList(this.zkConnectString), Optional.empty()).build();
     return this;
   }
 
@@ -243,4 +248,9 @@
       System.setProperty(SOLR_HTTPCLIENT_BUILDER_FACTORY, Krb5HttpClientBuilder.class.getCanonicalName());
     }
   }
+
+  public AmbariSolrCloudClientBuilder withAutoScalingJsonLocation(String autoScalingJsonLocation) {
+    this.autoScalingJsonLocation = autoScalingJsonLocation;
+    return this;
+  }
 }
diff --git a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetAutoScalingZkCommand.java b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetAutoScalingZkCommand.java
new file mode 100644
index 0000000..ccf89fe
--- /dev/null
+++ b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/commands/SetAutoScalingZkCommand.java
@@ -0,0 +1,64 @@
+/*
+ * 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.ambari.infra.solr.commands;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.commons.lang.StringUtils.isBlank;
+
+import java.io.File;
+import java.util.Optional;
+
+import org.apache.ambari.infra.solr.AmbariSolrCloudClient;
+import org.apache.ambari.infra.solr.domain.ZookeeperClient;
+import org.apache.commons.io.FileUtils;
+import org.apache.solr.common.cloud.SolrZkClient;
+import org.apache.solr.common.cloud.SolrZooKeeper;
+
+public class SetAutoScalingZkCommand extends AbstractZookeeperRetryCommand<String> {
+  private static final String AUTO_SCALING_JSON = "/autoscaling.json";
+
+  private final String autoScalingJsonLocation;
+
+  public SetAutoScalingZkCommand(int maxRetries, int interval, String autoScalingJsonLocation) {
+    super(maxRetries, interval);
+    this.autoScalingJsonLocation = autoScalingJsonLocation;
+  }
+
+  @Override
+  protected String executeZkCommand(AmbariSolrCloudClient client, SolrZkClient zkClient, SolrZooKeeper solrZooKeeper) throws Exception {
+    if (isBlank(autoScalingJsonLocation))
+      return "";
+
+    File fileToUpload = new File(autoScalingJsonLocation);
+    if (!fileToUpload.exists())
+      return "";
+
+    String contentToUpload = FileUtils.readFileToString(fileToUpload, UTF_8);
+    if (isBlank(contentToUpload))
+      return "";
+
+    String zFilePath = client.getZnode() + AUTO_SCALING_JSON;
+    ZookeeperClient zookeeperClient = new ZookeeperClient(zkClient);
+    Optional<String> fileContent = zookeeperClient.getFileContent(zFilePath);
+    if (!fileContent.isPresent() || !contentToUpload.equals(fileContent.get()))
+      zookeeperClient.putFileContent(zFilePath, contentToUpload);
+
+    return contentToUpload;
+  }
+}
diff --git a/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/ZookeeperClient.java b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/ZookeeperClient.java
new file mode 100644
index 0000000..97be0a1
--- /dev/null
+++ b/ambari-infra-solr-client/src/main/java/org/apache/ambari/infra/solr/domain/ZookeeperClient.java
@@ -0,0 +1,50 @@
+/*
+ * 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.ambari.infra.solr.domain;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.apache.zookeeper.CreateMode.PERSISTENT;
+
+import java.util.Optional;
+
+import org.apache.solr.common.cloud.SolrZkClient;
+
+public class ZookeeperClient {
+  private final SolrZkClient zkClient;
+
+  public ZookeeperClient(SolrZkClient zkClient) {
+    this.zkClient = zkClient;
+  }
+
+  public void putFileContent(String fileName, String content) throws Exception {
+    if (zkClient.exists(fileName, true)) {
+      zkClient.setData(fileName, content.getBytes(UTF_8), true);
+    } else {
+      zkClient.create(fileName, content.getBytes(UTF_8), PERSISTENT, true);
+    }
+  }
+
+  public Optional<String> getFileContent(String fileName) throws Exception {
+    if (!zkClient.exists(fileName, true))
+      return Optional.empty();
+
+    byte[] data = zkClient.getData(fileName, null, null, true);
+    return Optional.of(new String(data, UTF_8));
+  }
+}