content: /*
   * 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.server.view;
  
  import java.io.IOException;
  import java.io.InputStream;
  import java.net.MalformedURLException;
  import java.net.URL;
  import java.util.ArrayList;
  import java.util.HashMap;
  import java.util.HashSet;
  import java.util.List;
  import java.util.Map;
  import java.util.Set;
  import java.util.concurrent.ExecutionException;
  import java.util.concurrent.TimeUnit;
  
  import org.apache.ambari.server.configuration.Configuration;
  import org.apache.ambari.server.orm.entities.RemoteAmbariClusterEntity;
  import org.apache.ambari.view.AmbariHttpException;
  import org.apache.ambari.view.AmbariStreamProvider;
  import org.apache.ambari.view.cluster.Cluster;
  import org.apache.commons.io.IOUtils;
  
  import com.google.common.cache.CacheBuilder;
  import com.google.common.cache.CacheLoader;
  import com.google.common.cache.LoadingCache;
  import com.google.gson.Gson;
  import com.google.gson.JsonElement;
  import com.google.gson.JsonParser;
  import com.google.gson.reflect.TypeToken;
  
  /**
   * View associated  Remote cluster implementation.
   */
  public class RemoteAmbariCluster implements Cluster {
  
    public static final String AMBARI_OR_CLUSTER_ADMIN = "/api/v1/users/%s?privileges/PrivilegeInfo/permission_name=AMBARI.ADMINISTRATOR|" +
      "(privileges/PrivilegeInfo/permission_name=CLUSTER.ADMINISTRATOR&privileges/PrivilegeInfo/cluster_name=%s)";
  
    /**
     * Name of the remote Ambari Cluster
     */
    private String name;
  
    /**
     * StreamProvider for the remote cluster
     * Base path will be http://host:port
     */
    private AmbariStreamProvider streamProvider;
  
    /**
     * Path for the cluster.
     * Value will be like : /api/v1/clusters/clusterName
     */
    private String clusterPath;
  
    /**
     * User for the cluster
     */
    private String username;
  
    private final LoadingCache<String, JsonElement> configurationCache = CacheBuilder.newBuilder()
      .expireAfterWrite(10, TimeUnit.SECONDS)
      .build(new CacheLoader<String, JsonElement>() {
        @Override
        public JsonElement load(String url) throws Exception {
          return readFromUrlJSON(url);
        }
      });
  
  
    /**
     * Constructor for Remote Ambari Cluster
     *
     * @param remoteAmbariClusterEntity
     */
    public RemoteAmbariCluster(RemoteAmbariClusterEntity remoteAmbariClusterEntity, Configuration config) throws MalformedURLException {
  
      this.name = getClusterName(remoteAmbariClusterEntity);
      this.username = remoteAmbariClusterEntity.getUsername();
  
      URL url = new URL(remoteAmbariClusterEntity.getUrl());
  
      String portString = url.getPort() == -1 ? "" : ":" + url.getPort();
      String baseUrl = url.getProtocol() + "://" + url.getHost() + portString;
  
      this.clusterPath = url.getPath();
  
      this.streamProvider = new RemoteAmbariStreamProvider(
        baseUrl, remoteAmbariClusterEntity.getUsername(),
        remoteAmbariClusterEntity.getPassword(), config.getRequestConnectTimeout(), config.getRequestReadTimeout());
    }
  
    private String getClusterName(RemoteAmbariClusterEntity remoteAmbariClusterEntity) {
      String[] urlSplit = remoteAmbariClusterEntity.getUrl().split("/");
  
      // remoteAmbariClusterEntity.getName() is not the actual name of Remote Cluster
      // We need to extract the name from cluster url which is like. http://host:port/api/vi/clusters/${clusterName}
      return urlSplit[urlSplit.length - 1];
    }
  
    /**
     * Constructor for Remote Ambari Cluster
     *
     * @param name
     * @param streamProvider
     */
    public RemoteAmbariCluster(String name, String clusterPath, AmbariStreamProvider streamProvider) {
      this.name = name;
      this.clusterPath = clusterPath;
      this.streamProvider = streamProvider;
    }
  
    @Override
    public String getName() {
      return this.name;
    }
  
    @Override
    public String getConfigurationValue(String type, String key) {
      JsonElement config = null;
      try {
        String desiredTag = getDesiredConfig(type);
        if (desiredTag != null) {
          config = configurationCache.get(String.format("%s/configurations?(type=%s&tag=%s)",this.clusterPath, type, desiredTag));
        }
      } catch (ExecutionException e) {
        throw new RemoteAmbariConfigurationReadException("Can't retrieve configuration from Remote Ambari", e);
      }
  
      if (config == null || !config.isJsonObject()) return null;
      JsonElement items = config.getAsJsonObject().get("items");
  
      if (items == null || !items.isJsonArray()) return null;
      JsonElement item = items.getAsJsonArray().get(0);
  
      if (item == null || !item.isJsonObject()) return null;
      JsonElement properties = item.getAsJsonObject().get("properties");
  
      if (properties == null || !properties.isJsonObject()) return null;
      JsonElement property = properties.getAsJsonObject().get(key);
  
      if (property == null || !property.isJsonPrimitive()) return null;
  
      return property.getAsJsonPrimitive().getAsString();
    }
  
    @Override
    public Map<String, String> getConfigByType(String type) {
      JsonElement config = null;
      try {
        String desiredTag = getDesiredConfig(type);
        if (desiredTag != null) {
          config = configurationCache.get(String.format("%s/configurations?(type=%s&tag=%s)",this.clusterPath, type, desiredTag));
        }
      } catch (ExecutionException e) {
        throw new RemoteAmbariConfigurationReadException("Can't retrieve configuration from Remote Ambari", e);
      }
      if (config == null || !config.isJsonObject()) return null;
      JsonElement items = config.getAsJsonObject().get("items");
  
      if (items == null || !items.isJsonArray()) return null;
      JsonElement item = items.getAsJsonArray().get(0);
  
      if (item == null || !item.isJsonObject()) return null;
      JsonElement properties = item.getAsJsonObject().get("properties");
  
      if (properties == null || !properties.isJsonObject()) return null;
  
      Map<String, String> retMap = new Gson().fromJson(properties, new TypeToken<HashMap<String, String>>() {}.getType());
      return retMap;
    }
  
    @Override
    public List<String> getHostsForServiceComponent(String serviceName, String componentName) {
      String url = String.format("%s/services/%s/components/%s?" +
        "fields=host_components/HostRoles/host_name", this.clusterPath, serviceName, componentName);
  
      List<String> hosts = new ArrayList<>();
  
      try {
        JsonElement response = configurationCache.get(url);
  
        if (response == null || !response.isJsonObject()) return hosts;
  
        JsonElement hostComponents = response.getAsJsonObject().get("host_components");
  
        if (hostComponents == null || !hostComponents.isJsonArray()) return hosts;
  
        for (JsonElement element : hostComponents.getAsJsonArray()) {
          JsonElement hostRoles = element.getAsJsonObject().get("HostRoles");
          String hostName = hostRoles.getAsJsonObject().get("host_name").getAsString();
          hosts.add(hostName);
        }
  
      } catch (ExecutionException e) {
        throw new RemoteAmbariConfigurationReadException("Can't retrieve host information from Remote Ambari", e);
      }
  
      return hosts;
    }
  
    /**
     * Get list of services installed on the remote cluster
     *
     * @return list of services Available on cluster
     */
    public Set<String> getServices() throws IOException, AmbariHttpException {
      Set<String> services = new HashSet<>();
      String path = this.clusterPath + "?fields=services/ServiceInfo/service_name";
      JsonElement config = configurationCache.getUnchecked(path);
  
      if (config != null && config.isJsonObject()) {
        JsonElement items = config.getAsJsonObject().get("services");
        if (items != null && items.isJsonArray()) {
          for (JsonElement item : items.getAsJsonArray()) {
            JsonElement serviceInfo = item.getAsJsonObject().get("ServiceInfo");
            if (serviceInfo != null && serviceInfo.isJsonObject()) {
              String serviceName = serviceInfo.getAsJsonObject().get("service_name").getAsString();
              services.add(serviceName);
            }
          }
        }
      }
  
      return services;
    }
  
    public boolean isAmbariOrClusterAdmin() throws AmbariHttpException {
  
      if (username == null) return false;
  
      String url = String.format(AMBARI_OR_CLUSTER_ADMIN, username, name);
      JsonElement response = configurationCache.getUnchecked(url);
  
      if (response != null && response.isJsonObject()) {
        JsonElement privileges = response.getAsJsonObject().get("privileges");
        if (privileges != null && privileges.isJsonArray()) {
          if (privileges.getAsJsonArray().size() > 0) return true;
        }
      }
      return false;
    }
  
    /**
     * Get the current tag for the config type
     *
     * @param type
     * @return
     * @throws ExecutionException
     */
    private String getDesiredConfig(String type) throws ExecutionException {
      JsonElement desiredConfigResponse = configurationCache.get(this.clusterPath +"?fields=services/ServiceInfo,hosts,Clusters");
  
      if (desiredConfigResponse == null || !desiredConfigResponse.isJsonObject()) return null;
      JsonElement clusters = desiredConfigResponse.getAsJsonObject().get("Clusters");
  
      if (clusters == null || !clusters.isJsonObject()) return null;
      JsonElement desiredConfig = clusters.getAsJsonObject().get("desired_configs");
  
      if (desiredConfig == null || !desiredConfig.isJsonObject()) return null;
      JsonElement desiredConfigForType = desiredConfig.getAsJsonObject().get(type);
  
      if (desiredConfigForType == null || !desiredConfigForType.isJsonObject()) return null;
      JsonElement typeJson = desiredConfigForType.getAsJsonObject().get("tag");
  
      if (typeJson == null || !(typeJson.isJsonPrimitive())) return null;
  
      return typeJson.getAsJsonPrimitive().getAsString();
    }
  
    /**
     * Read the content of the url from remote cluster
     *
     * @param url
     * @return
     * @throws IOException
     * @throws AmbariHttpException
     */
    private JsonElement readFromUrlJSON(String url) throws IOException, AmbariHttpException {
      InputStream inputStream = streamProvider.readFrom(url, "GET", (String) null, null);
      String response = IOUtils.toString(inputStream);
      return new JsonParser().parse(response);
    }
  
  }
  