/**
 * 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
 * <p>
 * http://www.apache.org/licenses/LICENSE-2.0
 * <p>
 * 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.view.utils.ambari;


import org.apache.ambari.view.URLStreamProvider;
import org.apache.ambari.view.ViewContext;
import org.apache.ambari.view.cluster.Cluster;
import org.apache.commons.io.IOUtils;
import org.easymock.EasyMock;
import org.easymock.EasyMockSupport;
import org.junit.Test;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;

import static org.easymock.EasyMock.*;
import static org.junit.Assert.assertEquals;

public class ServicesTest extends EasyMockSupport {

  private static final String HTTP_RM_URL1 = "http://c1.ambari.apache.org:8088";
  private static final String HTTP_RM_URL2 = "http://c2.ambari.apache.org:8088";
  private static final String HTTPS_RM_URL1 = "https://c1.ambari.apache.org:8088";
  private static final String HTTPS_RM_URL2 = "https://c2.ambari.apache.org:8088";
  private static final String RM_URL1_HOST_PORT = "c1.ambari.apache.org:8088";
  private static final String RM_URL2_HOST_PORT = "c2.ambari.apache.org:8088";
  private static final String RM_INFO_API_ENDPOINT = Services.RM_INFO_API_ENDPOINT;

  @Test(expected = AmbariApiException.class)
  public void shouldCheckForEmptyATSUrlInCustomConfig() {
    ViewContext viewContext = getViewContext(new HashMap<String, String>());
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    services.getTimelineServerUrl();

  }

  @Test
  public void shouldReturnATSUrlConfiguredInCustomMode() throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.timeline-server.url", HTTP_RM_URL1);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL1, services.getTimelineServerUrl());
  }

  @Test(expected = AmbariApiException.class)
  public void shouldThrowExceptionIfNoProtocolInCustomMode() {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.timeline-server.url", RM_URL1_HOST_PORT);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    services.getTimelineServerUrl();
  }


  @Test
  public void shouldReturnATSUrlFromYarnSiteInClusteredMode() throws Exception {
    ViewContext viewContext = getViewContext(new HashMap<String, String>());
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    Cluster cluster = createNiceMock(Cluster.class);
    Services services = new Services(ambariApi, viewContext);

    expect(ambariApi.isClusterAssociated()).andReturn(true).anyTimes();
    setClusterExpectation(cluster, "HTTP_ONLY");
    expect(ambariApi.getCluster()).andReturn(cluster).anyTimes();
    replayAll();

    assertEquals(HTTP_RM_URL1, services.getTimelineServerUrl());

    reset(cluster);
    setClusterExpectation(cluster, "HTTPS_ONLY");
    replay(cluster);

    assertEquals(HTTPS_RM_URL2, services.getTimelineServerUrl());

  }

  @Test(expected = AmbariApiException.class)
  public void shouldCheckForEmptyYarnRMUrlInCustomConfig() {
    ViewContext viewContext = getViewContext(new HashMap<String, String>());
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    services.getRMUrl();


  }

  @Test(expected = AmbariApiException.class)
  public void shouldCheckIfAllRMUrlsHaveProtocolInCustomConfig() {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1 + "," + RM_URL2_HOST_PORT);
    ViewContext viewContext = getViewContext(map);
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    services.getRMUrl();
  }

  @Test
  public void shouldReturnUrlIfSingleIsConfiguredInCustomConfig() {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1);
    ViewContext viewContext = getViewContext(map);
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    expect(ambariApi.isClusterAssociated()).andReturn(false);

    replay(viewContext);

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL1, services.getRMUrl());
  }

  @Test
  public void shouldConnectToFirstUrlWhenMultipleRMUrlIsConfiguredInCustomConfig() throws IOException {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1 + ", " + HTTP_RM_URL2);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    URLStreamProvider urlStreamProvider = createNiceMock(URLStreamProvider.class);

    InputStream inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");

    expect(ambariApi.isClusterAssociated()).andReturn(false);
    expect(viewContext.getURLStreamProvider()).andReturn(urlStreamProvider);
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);

    replayAll();

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL1, services.getRMUrl());

  }

  @Test
  public void shouldConnectToSecondUrlWhenTheFirstURLTimesOut() throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1 + ", " + HTTP_RM_URL2);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    URLStreamProvider urlStreamProvider = createNiceMock(URLStreamProvider.class);

    InputStream inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");

    expect(ambariApi.isClusterAssociated()).andReturn(false);
    expect(viewContext.getURLStreamProvider()).andReturn(urlStreamProvider).anyTimes();
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andThrow(new IOException());
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL2 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);

    replayAll();

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL2, services.getRMUrl());

  }

  @Test(expected = AmbariApiException.class)
  public void shouldThrowExceptionWhenAllUrlCannotBeReached() throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1 + ", " + HTTP_RM_URL2);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    URLStreamProvider urlStreamProvider = createNiceMock(URLStreamProvider.class);

    expect(ambariApi.isClusterAssociated()).andReturn(false);
    expect(viewContext.getURLStreamProvider()).andReturn(urlStreamProvider).anyTimes();
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andThrow(new IOException());
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL2 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andThrow(new IOException());

    replayAll();

    Services services = new Services(ambariApi, viewContext);
    services.getRMUrl();
  }

  @Test
  public void shouldReturnActiveRMUrlWhenConnectingToStandby() throws Exception {
    Map<String, String> map = new HashMap<>();
    map.put("yarn.resourcemanager.url", HTTP_RM_URL1 + ", " + HTTP_RM_URL2);
    ViewContext viewContext = getViewContext(map);

    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    URLStreamProvider urlStreamProvider = createNiceMock(URLStreamProvider.class);

    InputStream inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"STANDBY\"}}");

    expect(ambariApi.isClusterAssociated()).andReturn(false);
    expect(viewContext.getURLStreamProvider()).andReturn(urlStreamProvider).anyTimes();
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);

    InputStream inputStreamActive = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL2 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStreamActive);

    replayAll();

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL2, services.getRMUrl());

    verify(urlStreamProvider);

  }

  @Test
  public void shouldConnectToRMConfiguredInClusterMode() throws Exception {
    ViewContext viewContext = getViewContext(new HashMap<String, String>());
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    Cluster cluster = createNiceMock(Cluster.class);

    expect(ambariApi.isClusterAssociated()).andReturn(true).anyTimes();
    setClusterExpectation(cluster, "HTTP_ONLY");
    expect(ambariApi.getCluster()).andReturn(cluster).anyTimes();

    replayAll();

    Services services = new Services(ambariApi, viewContext);
    assertEquals(HTTP_RM_URL1, services.getRMUrl());

    reset(cluster);
    setClusterExpectation(cluster, "HTTPS_ONLY");
    replay(cluster);

    assertEquals(HTTPS_RM_URL2, services.getRMUrl());

    reset(cluster);
    setClusterExpectation(cluster, "HTTPS_ONLY_XYZ");
    replay(cluster);

    assertEquals(HTTP_RM_URL1, services.getRMUrl());
  }

  @Test
  public void shouldFetchRMUrlsWhileHAEnabledInClusterMode() throws Exception {
    ViewContext viewContext = getViewContext(new HashMap<String, String>());
    AmbariApi ambariApi = createNiceMock(AmbariApi.class);
    Cluster cluster = createNiceMock(Cluster.class);
    URLStreamProvider urlStreamProvider = createNiceMock(URLStreamProvider.class);
    Services services = new Services(ambariApi, viewContext);

    InputStream inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");



    expect(ambariApi.isClusterAssociated()).andReturn(true).anyTimes();
    setClusterExpectationInHA(cluster, "HTTP_ONLY");
    expect(ambariApi.getCluster()).andReturn(cluster).anyTimes();
    expect(viewContext.getURLStreamProvider()).andReturn(urlStreamProvider).anyTimes();
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);

    replayAll();

    assertEquals(HTTP_RM_URL1, services.getRMUrl());

    reset(cluster, urlStreamProvider);
    setClusterExpectationInHA(cluster, "HTTP_ONLY");
    inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andThrow(new IOException());
    expect(urlStreamProvider.readFrom(eq(HTTP_RM_URL2 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);
    replay(cluster, urlStreamProvider);

    assertEquals(HTTP_RM_URL2, services.getRMUrl());

    reset(cluster, urlStreamProvider);
    setClusterExpectationInHA(cluster, "HTTPS_ONLY");
    inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");
    expect(urlStreamProvider.readFrom(eq(HTTPS_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);
    replay(cluster, urlStreamProvider);

    assertEquals(HTTPS_RM_URL1, services.getRMUrl());

    reset(cluster, urlStreamProvider);
    setClusterExpectationInHA(cluster, "HTTPS_ONLY");
    inputStream = IOUtils.toInputStream("{\"clusterInfo\": {\"haState\": \"ACTIVE\"}}");
    expect(urlStreamProvider.readFrom(eq(HTTPS_RM_URL1 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andThrow(new IOException());
    expect(urlStreamProvider.readFrom(eq(HTTPS_RM_URL2 + RM_INFO_API_ENDPOINT), eq("GET"), anyString(), EasyMock.<Map<String, String>>anyObject())).andReturn(inputStream);
    replay(cluster, urlStreamProvider);

    assertEquals(HTTPS_RM_URL2, services.getRMUrl());
  }

  private void setClusterExpectation(Cluster cluster, String httpPolicy) {
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.ha.enabled")).andReturn("false");
    expect(cluster.getConfigurationValue("yarn-site", "yarn.http.policy")).andReturn(httpPolicy);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.address")).andReturn(RM_URL1_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.https.address")).andReturn(RM_URL2_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.timeline-service.webapp.address")).andReturn(RM_URL1_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.timeline-service.webapp.https.address")).andReturn(RM_URL2_HOST_PORT);
  }

  private void setClusterExpectationInHA(Cluster cluster, String httpPolicy) {
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.ha.enabled")).andReturn("true");
    expect(cluster.getConfigurationValue("yarn-site", "yarn.http.policy")).andReturn(httpPolicy);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.ha.rm-ids")).andReturn("rm1,rm2");
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.address.rm1")).andReturn(RM_URL1_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.address.rm2")).andReturn(RM_URL2_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.https.address.rm1")).andReturn(RM_URL1_HOST_PORT);
    expect(cluster.getConfigurationValue("yarn-site", "yarn.resourcemanager.webapp.https.address.rm2")).andReturn(RM_URL2_HOST_PORT);
  }

  private ViewContext getViewContext(Map<String, String> map) {
    ViewContext viewContextMock = createNiceMock(ViewContext.class);
    expect(viewContextMock.getProperties()).andReturn(map);
    return viewContextMock;
  }
}