You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ambari.apache.org by js...@apache.org on 2015/03/02 21:31:52 UTC
ambari git commit: AMBARI-9870. Provide ability to test KDC
connection over UDP
Repository: ambari
Updated Branches:
refs/heads/trunk 121924874 -> 86d658938
AMBARI-9870. Provide ability to test KDC connection over UDP
Project: http://git-wip-us.apache.org/repos/asf/ambari/repo
Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/86d65893
Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/86d65893
Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/86d65893
Branch: refs/heads/trunk
Commit: 86d6589382ac0467640c2f286c950877387fd41a
Parents: 1219248
Author: John Speidel <js...@hortonworks.com>
Authored: Mon Mar 2 11:23:50 2015 -0500
Committer: John Speidel <js...@hortonworks.com>
Committed: Mon Mar 2 15:26:44 2015 -0500
----------------------------------------------------------------------
ambari-project/pom.xml | 5 +
ambari-server/pom.xml | 4 +
.../server/KdcServerConnectionVerification.java | 128 ++++++++++++-
.../KdcServerConnectionVerificationTest.java | 190 +++++++++++++++++++
4 files changed, 321 insertions(+), 6 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/ambari/blob/86d65893/ambari-project/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-project/pom.xml b/ambari-project/pom.xml
index 0577bee..48c7214 100644
--- a/ambari-project/pom.xml
+++ b/ambari-project/pom.xml
@@ -247,6 +247,11 @@ instead of a SNAPSHOT. -->
</dependency>
<dependency>
<groupId>org.apache.directory.server</groupId>
+ <artifactId>kerberos-client</artifactId>
+ <version>2.0.0-M19</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.directory.server</groupId>
<artifactId>apacheds-protocol-ldap</artifactId>
<version>2.0.0-M19</version>
<exclusions>
http://git-wip-us.apache.org/repos/asf/ambari/blob/86d65893/ambari-server/pom.xml
----------------------------------------------------------------------
diff --git a/ambari-server/pom.xml b/ambari-server/pom.xml
index c57a2d0..4c56d78 100644
--- a/ambari-server/pom.xml
+++ b/ambari-server/pom.xml
@@ -1515,6 +1515,10 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.apache.directory.server</groupId>
+ <artifactId>kerberos-client</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.apache.directory.shared</groupId>
<artifactId>shared-ldap</artifactId>
<scope>test</scope>
http://git-wip-us.apache.org/repos/asf/ambari/blob/86d65893/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java b/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java
index 8bfbc5f..b7bfef9 100644
--- a/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java
+++ b/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java
@@ -22,9 +22,18 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.FutureTask;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.commons.lang.StringUtils;
+import org.apache.directory.kerberos.client.KdcConfig;
+import org.apache.directory.kerberos.client.KdcConnection;
+import org.apache.directory.shared.kerberos.exceptions.ErrorType;
+import org.apache.directory.shared.kerberos.exceptions.KerberosException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -53,6 +62,11 @@ public class KdcServerConnectionVerification {
private static Logger LOG = LoggerFactory.getLogger(KdcServerConnectionVerification.class);
private Configuration config;
+
+ /**
+ * UDP connection timeout in seconds.
+ */
+ private int udpTimeout = 10;
@Inject
public KdcServerConnectionVerification(Configuration config) {
@@ -84,15 +98,30 @@ public class KdcServerConnectionVerification {
return false;
}
}
+
/**
- * Given server IP or hostname, checks if server is reachable i.e.
- * we can make a socket connection to it.
- *
+ * Given a host and port, checks if server is reachable meaning that we
+ * can communicate with it. First we attempt to connect via TCP and if
+ * that is unsuccessful, attempt via UDP. It is important to understand that
+ * we are not validating credentials, only attempting to communicate with server
+ * process for the give host and port.
+ *
* @param server KDC server IP or hostname
* @param port KDC port
* @return true, if server is accepting connection given port; false otherwise.
*/
- public boolean isKdcReachable(String server, Integer port) {
+ public boolean isKdcReachable(String server, int port) {
+ return isKdcReachableViaTCP(server, port) || isKdcReachableViaUDP(server, port);
+ }
+
+ /**
+ * Attempt to connect to KDC server over TCP.
+ *
+ * @param server KDC server IP or hostname
+ * @param port KDC server port
+ * @return true, if server is accepting connection given port; false otherwise.
+ */
+ public boolean isKdcReachableViaTCP(String server, int port) {
Socket socket = null;
try {
socket = new Socket();
@@ -117,17 +146,104 @@ public class KdcServerConnectionVerification {
}
/**
+ * Attempt to communicate with KDC server over UDP.
+ * @param server KDC hostname or IP address
+ * @param port KDC server port
+ * @return true if communication is successful; false otherwise
+ */
+ public boolean isKdcReachableViaUDP(final String server, final int port) {
+ int timeoutMillis = udpTimeout * 1000;
+ final KdcConfig config = KdcConfig.getDefaultConfig();
+ config.setHostName(server);
+ config.setKdcPort(port);
+ config.setUseUdp(true);
+ config.setTimeout(timeoutMillis);
+
+ final KdcConnection connection = getKdcUdpConnection(config);
+ FutureTask<Boolean> future = new FutureTask<Boolean>(new Callable<Boolean>() {
+ @Override
+ public Boolean call() {
+ try {
+ // we are only testing whether we can communicate with server and not
+ // validating credentials
+ connection.getTgt("noUser@noRealm", "noPassword");
+ } catch (KerberosException e) {
+ // unfortunately, need to look at msg as error 60 is a generic error code
+ return ! (e.getErrorCode() == ErrorType.KRB_ERR_GENERIC.getValue() &&
+ e.getMessage().contains("TimeOut"));
+ //todo: evaluate other error codes to provide better information
+ //todo: as there may be other error codes where we should return false
+ } catch (Exception e) {
+ // some bad unexpected thing occurred
+ throw new RuntimeException(e);
+ }
+ return true;
+ }
+ });
+
+ new Thread(future).start();
+ Boolean result;
+ try {
+ // timeout after specified timeout
+ result = future.get(timeoutMillis, TimeUnit.MILLISECONDS);
+ } catch (InterruptedException e) {
+ LOG.error("Interrupted while trying to communicate with KDC server over UDP");
+ result = false;
+ future.cancel(true);
+ } catch (ExecutionException e) {
+ LOG.error("An unexpected exception occurred while attempting to communicate with the KDC server over UDP", e);
+ result = false;
+ } catch (TimeoutException e) {
+ LOG.error("Timeout occurred while attempting to to communicate with KDC server over UDP");
+ result = false;
+ future.cancel(true);
+ }
+
+ return result;
+ }
+
+ /**
+ * Get a KDC UDP connection for the given configuration.
+ * This has been extracted into it's own method primarily
+ * for unit testing purposes.
+ *
+ * @param config KDC connection configuration
+ * @return new KDC connection
+ */
+ protected KdcConnection getKdcUdpConnection(KdcConfig config) {
+ return new KdcConnection(config);
+ }
+
+ /**
+ * Set the UDP connection timeout.
+ * This is the amount of time that we will attempt to read data from UDP connection.
+ *
+ * @param timeoutSeconds timeout in seconds
+ */
+ public void setUdpTimeout(int timeoutSeconds) {
+ udpTimeout = (timeoutSeconds < 1) ? 1 : timeoutSeconds;
+ }
+
+ /**
+ * Get the UDP timeout value.
+ *
+ * @return the UDP connection timeout value in seconds
+ */
+ public int getUdpTimeout() {
+ return udpTimeout;
+ }
+
+ /**
* Parses port number from given string.
* @param port port number string
* @throws NumberFormatException if given string cannot be parsed
* @throws IllegalArgumentException if given string is null or empty
* @return parsed port number
*/
- private final int parsePort(String port) {
+ private int parsePort(String port) {
if (StringUtils.isEmpty(port)) {
throw new IllegalArgumentException("Port number must be non-empty, non-null positive integer");
}
return Integer.parseInt(port);
}
-
}
http://git-wip-us.apache.org/repos/asf/ambari/blob/86d65893/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java
----------------------------------------------------------------------
diff --git a/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java b/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java
index f8ec650..da47eb2 100644
--- a/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java
+++ b/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java
@@ -17,6 +17,11 @@
*/
package org.apache.ambari.server.api.rest;
+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;
@@ -29,12 +34,18 @@ import org.apache.ambari.server.KdcServerConnectionVerification;
import org.apache.ambari.server.configuration.Configuration;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
+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.exceptions.ErrorType;
+import org.apache.directory.shared.kerberos.exceptions.KerberosException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.springframework.test.annotation.ExpectedException;
+
/**
* Test for {@link KdcServerConnectionVerification}
*/
@@ -100,6 +111,141 @@ public class KdcServerConnectionVerificationTest {
assertFalse(kdcConnectionVerifier.isKdcReachable("localhost:abc"));
}
+ @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.isKdcReachableViaUDP("test-host", 11111);
+ 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 {
+ KdcConnection connection = createStrictMock(KdcConnection.class);
+
+ expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
+ new KerberosException(ErrorType.KDC_ERR_C_PRINCIPAL_UNKNOWN));
+ replay(connection);
+
+ TestKdcServerConnectionVerification kdcConnVerifier =
+ new TestKdcServerConnectionVerification(configuration, connection);
+
+ boolean result = kdcConnVerifier.isKdcReachableViaUDP("test-host", 11111);
+ 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__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.isKdcReachableViaUDP("test-host", 11111);
+ 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.setUdpTimeout(timeout);
+
+ boolean result = kdcConnVerifier.isKdcReachableViaUDP("test-host", 11111);
+ 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.isKdcReachableViaUDP("test-host", 11111);
+ 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 {
+ KdcConnection connection = createStrictMock(KdcConnection.class);
+
+ expect(connection.getTgt("noUser@noRealm", "noPassword")).andThrow(
+ new KerberosException(ErrorType.KRB_ERR_GENERIC, "foo"));
+ replay(connection);
+
+ TestKdcServerConnectionVerification kdcConnVerifier =
+ new TestKdcServerConnectionVerification(configuration, connection);
+
+ boolean result = kdcConnVerifier.isKdcReachableViaUDP("test-host", 11111);
+ 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);
+ }
+
+
/**
* Socket server for test
* We need a separate thread as accept() is a blocking call
@@ -132,4 +278,48 @@ public class KdcServerConnectionVerificationTest {
LOG.debug("IOException during tearDown. Can be safely ignored");
}
}
+
+ // 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 getKdcUdpConnection(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;
+ }
+ }
}