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.api.rest;
  
  import static org.apache.ambari.server.KdcServerConnectionVerification.ConnectionProtocol.TCP;
  import static org.apache.ambari.server.KdcServerConnectionVerification.ConnectionProtocol.UDP;
  import static org.easymock.EasyMock.createNiceMock;
  import static org.easymock.EasyMock.createStrictMock;
  import static org.easymock.EasyMock.expect;
  import static org.easymock.EasyMock.replay;
  import static org.easymock.EasyMock.verify;
  import static org.junit.Assert.assertEquals;
  import static org.junit.Assert.assertFalse;
  import static org.junit.Assert.assertTrue;
  
  import java.util.Properties;
  
  import org.apache.ambari.server.KdcServerConnectionVerification;
  import org.apache.ambari.server.configuration.Configuration;
  import org.apache.directory.kerberos.client.KdcConfig;
  import org.apache.directory.kerberos.client.KdcConnection;
  import org.apache.directory.kerberos.client.TgTicket;
  import org.apache.directory.shared.kerberos.KerberosMessageType;
  import org.apache.directory.shared.kerberos.exceptions.ErrorType;
  import org.apache.directory.shared.kerberos.exceptions.KerberosException;
  import org.apache.directory.shared.kerberos.messages.KrbError;
  import org.junit.Before;
  import org.junit.Ignore;
  import org.junit.Test;
  
  
  /**
   * Test for {@link KdcServerConnectionVerification}
   */
  public class KdcServerConnectionVerificationTest  {
  
    private Configuration configuration;
  
    private static final int KDC_TEST_PORT = 8090;
  
    @Before
    public void before() throws Exception {
      Properties configProps = new Properties();
      configProps.setProperty(Configuration.KDC_PORT.getKey(), Integer.toString(KDC_TEST_PORT));
      configuration = new Configuration(configProps);
    }
  
    @Test
    public void testValidate__Fail_InvalidPort() throws Exception {
      assertFalse(new KdcServerConnectionVerification(configuration).isKdcReachable("test-host:abcd"));
    }
  
    @Test
    public void testValidate__Success() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andReturn(null).once();
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host:11111");
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateTCP__Successful() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andReturn(null).once();
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateTCP__Successful2() throws Exception {
      KrbError error = createNiceMock(KrbError.class);
      expect(error.getErrorCode()).andReturn(ErrorType.KDC_ERR_C_PRINCIPAL_UNKNOWN).once();
      expect(error.getMessageType()).andReturn(KerberosMessageType.KRB_ERROR).once();
  
      KerberosException exception = createNiceMock(KerberosException.class);
      expect(exception.getError()).andReturn(error).once();
  
      KdcConnection connection = createStrictMock(KdcConnection.class);
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(exception);
  
      replay(connection, exception, error);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection, exception);
    }
  
    @Test
    public void testValidateTCP__Fail_UnknownException() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
          new RuntimeException("This is a really bad exception"));
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateTCP__Fail_Timeout() throws Exception {
      int timeout = 1;
      KdcConnection connection = new BlockingKdcConnection(null);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      kdcConnVerifier.setConnectionTimeout(timeout);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(timeout * 1000, kdcConfig.getTimeout());
    }
  
    @Test
    public void testValidateTCP__Fail_TimeoutErrorCode() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
          new KerberosException(ErrorType.KRB_ERR_GENERIC, "TimeOut occurred"));
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateTCP__Fail_GeneralErrorCode_NotTimeout() throws Exception {
      KrbError error = createNiceMock(KrbError.class);
      expect(error.getErrorCode()).andReturn(ErrorType.KRB_ERR_GENERIC).once();
      expect(error.getMessageType()).andReturn(KerberosMessageType.KRB_ERROR).once();
  
      KerberosException exception = createNiceMock(KerberosException.class);
      expect(exception.getError()).andReturn(error).once();
  
      KdcConnection connection = createStrictMock(KdcConnection.class);
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(exception);
  
      replay(connection, exception, error);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, TCP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertFalse(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection, exception);
    }
  
    @Test
    public void testValidateUDP__Successful() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andReturn(null).once();
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateUDP__Successful2() throws Exception {
      KrbError error = createNiceMock(KrbError.class);
      expect(error.getErrorCode()).andReturn(ErrorType.KDC_ERR_C_PRINCIPAL_UNKNOWN).once();
      expect(error.getMessageType()).andReturn(KerberosMessageType.KRB_ERROR).once();
  
      KerberosException exception = createNiceMock(KerberosException.class);
      expect(exception.getError()).andReturn(error).once();
  
      KdcConnection connection = createStrictMock(KdcConnection.class);
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(exception);
  
      replay(connection, exception, error);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection, exception);
    }
  
    @Test
    public void testValidateUDP__Fail_UnknownException() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
          new RuntimeException("This is a really bad exception"));
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateUDP__Fail_Timeout() throws Exception {
      int timeout = 1;
      KdcConnection connection = new BlockingKdcConnection(null);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      kdcConnVerifier.setConnectionTimeout(timeout);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(timeout * 1000, kdcConfig.getTimeout());
    }
  
    @Test
    public void testValidateUDP__Fail_TimeoutErrorCode() throws Exception {
      KdcConnection connection = createStrictMock(KdcConnection.class);
  
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
          new KerberosException(ErrorType.KRB_ERR_GENERIC, "TimeOut occurred"));
      replay(connection);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertFalse(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection);
    }
  
    @Test
    public void testValidateUDP__Fail_GeneralErrorCode_NotTimeout() throws Exception {
      KrbError error = createNiceMock(KrbError.class);
      expect(error.getErrorCode()).andReturn(ErrorType.KRB_ERR_GENERIC).once();
      expect(error.getMessageType()).andReturn(KerberosMessageType.KRB_ERROR).once();
  
      KerberosException exception = createNiceMock(KerberosException.class);
      expect(exception.getError()).andReturn(error).once();
  
      KdcConnection connection = createStrictMock(KdcConnection.class);
      expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(exception);
  
      replay(connection, exception, error);
  
      TestKdcServerConnectionVerification kdcConnVerifier =
          new TestKdcServerConnectionVerification(configuration, connection);
  
      boolean result = kdcConnVerifier.isKdcReachable("test-host", 11111, UDP);
      assertTrue(result);
  
      KdcConfig kdcConfig = kdcConnVerifier.getConfigUsedInConnectionCreation();
      assertTrue(kdcConfig.isUseUdp());
      assertEquals("test-host", kdcConfig.getHostName());
      assertEquals(11111, kdcConfig.getKdcPort());
      assertEquals(10 * 1000, kdcConfig.getTimeout());
  
      verify(connection, exception);
    }
  
    @Test
    @Ignore
    public void testValidate__Live() throws Exception {
      KdcServerConnectionVerification kdcConnVerifier = new KdcServerConnectionVerification(configuration);
      boolean result = kdcConnVerifier.isKdcReachable("c6501:88");
      assertTrue(result);
    }
  
  
    // Test implementation which allows a mock KDC connection to be used.
    private static class TestKdcServerConnectionVerification extends KdcServerConnectionVerification {
      private KdcConnection connection;
      private KdcConfig kdcConfig = null;
  
      public TestKdcServerConnectionVerification(Configuration config, KdcConnection connectionMock) {
        super(config);
        connection = connectionMock;
      }
  
      @Override
      protected KdcConnection getKdcConnection(KdcConfig config) {
        kdcConfig = config;
        return connection;
      }
  
      public KdcConfig getConfigUsedInConnectionCreation() {
        return kdcConfig;
      }
    }
  
    /**
     * Test implementation which blocks on getTgt() for 60 seconds to facilitate timeout testing.
     */
    private static class BlockingKdcConnection extends KdcConnection {
  
      public BlockingKdcConnection(KdcConfig config) {
        super(config);
      }
  
      @Override
      public TgTicket getTgt(String principal, String password) throws Exception {
        // although it is generally a bad idea to use sleep in a unit test for a
        // timing mechanism, this is being used to simulate a timeout and should be
        // generally safe as we are not relying on this for timing other than expecting
        // that this will block longer than the timeout set on the connection validator
        // which should be set to 1 second when using this implementation.
        // We will only block the full 60 seconds in the case of a specific test failure
        // where the callable doesn't properly set the timeout on the get.
        Thread.sleep(60000);
        return null;
      }
    }
  }
  