Return-Path: X-Original-To: apmail-ambari-commits-archive@www.apache.org Delivered-To: apmail-ambari-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id C673517C8B for ; Wed, 14 Jan 2015 20:36:01 +0000 (UTC) Received: (qmail 80150 invoked by uid 500); 14 Jan 2015 20:36:03 -0000 Delivered-To: apmail-ambari-commits-archive@ambari.apache.org Received: (qmail 80119 invoked by uid 500); 14 Jan 2015 20:36:03 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 80109 invoked by uid 99); 14 Jan 2015 20:36:03 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 14 Jan 2015 20:36:03 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 29002A42B15; Wed, 14 Jan 2015 20:36:03 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: jaimin@apache.org To: commits@ambari.apache.org Message-Id: <1eaed6e4a7a649e08e868dc09ab8d476@git.apache.org> X-Mailer: ASF-Git Admin Mailer Subject: ambari git commit: AMBARI-8116. Implement custom command for checking connectivity to KDC, via REST API. (Rishi Pidva via jaimin) Date: Wed, 14 Jan 2015 20:36:03 +0000 (UTC) Repository: ambari Updated Branches: refs/heads/trunk daea86e2e -> e56c82998 AMBARI-8116. Implement custom command for checking connectivity to KDC, via REST API. (Rishi Pidva via jaimin) Project: http://git-wip-us.apache.org/repos/asf/ambari/repo Commit: http://git-wip-us.apache.org/repos/asf/ambari/commit/e56c8299 Tree: http://git-wip-us.apache.org/repos/asf/ambari/tree/e56c8299 Diff: http://git-wip-us.apache.org/repos/asf/ambari/diff/e56c8299 Branch: refs/heads/trunk Commit: e56c82998d5cc537a3360a03309a1122ff119ea6 Parents: daea86e Author: Jaimin Jetly Authored: Wed Jan 14 12:35:46 2015 -0800 Committer: Jaimin Jetly Committed: Wed Jan 14 12:35:46 2015 -0800 ---------------------------------------------------------------------- .../server/KdcServerConnectionVerification.java | 133 ++++++++++++++++++ .../api/rest/KdcServerReachabilityCheck.java | 107 +++++++++++++++ .../server/configuration/Configuration.java | 32 +++++ .../KdcServerConnectionVerificationTest.java | 135 +++++++++++++++++++ 4 files changed, 407 insertions(+) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/ambari/blob/e56c8299/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 new file mode 100644 index 0000000..8bfbc5f --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/KdcServerConnectionVerification.java @@ -0,0 +1,133 @@ +/** + * 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; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.UnknownHostException; + +import org.apache.ambari.server.configuration.Configuration; +import org.apache.commons.lang.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.inject.Inject; +import com.google.inject.Singleton; + +/** + * Utility class which checks connection to Kerberos Server. + *

+ * It has two potential clients. + *

    + *
  • Ambari Agent: + * Uses it to make sure host can talk to specified KDC Server + *
  • + * + *
  • Ambari Server: + * Uses it for connection check, like agent, and also validates + * the credentials provided on Server side. + *
  • + *
+ *

+ */ +@Singleton +public class KdcServerConnectionVerification { + + private static Logger LOG = LoggerFactory.getLogger(KdcServerConnectionVerification.class); + + private Configuration config; + + @Inject + public KdcServerConnectionVerification(Configuration config) { + this.config = config; + } + + + /** + * Given server IP or hostname, checks if server is reachable i.e. + * we can make a socket connection to it. Hostname may contain port + * number separated by a colon. + * + * @param kdcHost KDC server IP or hostname (with optional port number) + * @return true, if server is accepting connection given port; false otherwise. + */ + public boolean isKdcReachable(String kdcHost) { + try { + if (kdcHost == null || kdcHost.isEmpty()) { + throw new IllegalArgumentException("Invalid hostname for KDC server"); + } + String[] kdcDetails = kdcHost.split(":"); + if (kdcDetails.length == 1) { + return isKdcReachable(kdcDetails[0], parsePort(config.getDefaultKdcPort())); + } else { + return isKdcReachable(kdcDetails[0], parsePort(kdcDetails[1])); + } + } catch (Exception e) { + LOG.error("Exception while checking KDC reachability: " + e); + return false; + } + } + /** + * Given server IP or hostname, checks if server is reachable i.e. + * we can make a socket connection to it. + * + * @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) { + Socket socket = null; + try { + socket = new Socket(); + socket.connect(new InetSocketAddress(server, port), config.getKdcConnectionCheckTimeout()); + } catch (UnknownHostException e) { + LOG.error("Unable to resolve Kerberos Server hostname"); + return false; + } catch (IOException e) { + LOG.error("Unable to connect to Kerberos Server"); + return false; + } finally { + if (socket != null) { + try { + socket.close(); + } catch (IOException e) { + LOG.debug("Error while closing socket connection to Kerberos Server. Can be ignored."); + } + } + } + + return true; + } + + /** + * 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) { + 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/e56c8299/ambari-server/src/main/java/org/apache/ambari/server/api/rest/KdcServerReachabilityCheck.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/api/rest/KdcServerReachabilityCheck.java b/ambari-server/src/main/java/org/apache/ambari/server/api/rest/KdcServerReachabilityCheck.java new file mode 100644 index 0000000..827b187 --- /dev/null +++ b/ambari-server/src/main/java/org/apache/ambari/server/api/rest/KdcServerReachabilityCheck.java @@ -0,0 +1,107 @@ +/** + * 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 javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.UriInfo; + +import org.apache.ambari.server.KdcServerConnectionVerification; +import org.apache.ambari.server.StaticallyInject; + +import com.google.inject.Inject; + +/** + * Service responsible for kerberos related resource requests. + */ +@StaticallyInject +@Path("/kdc_check/") +public class KdcServerReachabilityCheck { + private static final String REACHABLE = "REACHABLE"; + private static final String UNREACHABLE = "UNREACHABLE"; + + @Inject + private static KdcServerConnectionVerification kdcConnectionChecker; + + + + /** + * Handles: GET /kdc_check/{hostname} + * Checks the reachability of given KDC server + * + * @param headers http headers + * @param ui uri info + * @param kdcServerHostName HostName of KDC server. May contain port separate by a colon (:) + * + * @return status whether KDC server is reachable or not + */ + @GET + @Path("{hostname}") + @Produces(MediaType.TEXT_PLAIN) + public String plainTextCheck(@Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("hostname") String kdcServerHostName) { + String status = UNREACHABLE; + if (kdcConnectionChecker.isKdcReachable(kdcServerHostName)) { + status = REACHABLE; + } + return status; + } + + + // This method is called if XML is request + @GET + @Path("{hostname}") + @Produces(MediaType.TEXT_XML) + public String xmlCheck(@Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("hostname") String kdcServerHostName) { + String status = UNREACHABLE; + if (kdcConnectionChecker.isKdcReachable(kdcServerHostName)) { + status = REACHABLE; + } + return new StringBuilder() + .append("") + .append("").append(status).append("") + .toString(); + } + + // This method is called if HTML is request + @GET + @Path("{hostname}") + @Produces(MediaType.TEXT_HTML) + public String htmlCheck(@Context HttpHeaders headers, @Context UriInfo ui, + @PathParam("hostname") String kdcServerHostName) { + String status = UNREACHABLE; + if (kdcConnectionChecker.isKdcReachable(kdcServerHostName)) { + status = REACHABLE; + } + return new StringBuilder() + .append("\n") + .append("").append("Status").append("\n") + .append("

").append(status).append("

\n") + .append(" ") + .toString(); + } + + +} http://git-wip-us.apache.org/repos/asf/ambari/blob/e56c8299/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java ---------------------------------------------------------------------- diff --git a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java index e5b4cc4..d8b8854 100644 --- a/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java +++ b/ambari-server/src/main/java/org/apache/ambari/server/configuration/Configuration.java @@ -254,6 +254,13 @@ public class Configuration { public static final String DEF_ARCHIVE_CONTENT_TYPE; /** + * Kerberos related configuration options + */ + public static final String KDC_PORT_KEY = "default.kdcserver.port"; + public static final String KDC_PORT_KEY_DEFAULT = "88"; + public static final String KDC_CONNECTION_CHECK_TIMEOUT_KEY = "kdcserver.connection.check.timeout"; + public static final String KDC_CONNECTION_CHECK_TIMEOUT_DEFAULT = "10000"; + /** * This key defines whether stages of parallel requests are executed in * parallel or sequentally. Only stages from different requests * running on not interfering host sets may be executed in parallel. @@ -432,6 +439,10 @@ public class Configuration { configsMap.put(SHARED_RESOURCES_DIR_KEY, properties.getProperty( SHARED_RESOURCES_DIR_KEY, SHARED_RESOURCES_DIR_DEFAULT)); + + configsMap.put(KDC_PORT_KEY, properties.getProperty( + KDC_PORT_KEY, KDC_PORT_KEY_DEFAULT)); + File passFile = new File(configsMap.get(SRVR_KSTR_DIR_KEY) + File.separator + configsMap.get(SRVR_CRT_PASS_FILE_KEY)); @@ -1253,4 +1264,25 @@ public class Configuration { public String getAlertTemplateFile() { return properties.getProperty(ALERT_TEMPLATE_FILE); } + + /** + * Gets the default KDC port to use when no port is specified in KDC hostname + * + * @return the default KDC port to use. + */ + public String getDefaultKdcPort() { + return properties.getProperty(KDC_PORT_KEY, KDC_PORT_KEY_DEFAULT); + } + + /** + * Gets the inactivity timeout value, in milliseconds, for socket connection + * made to KDC Server for its reachability verification. + * + * @return the timeout value as configured in {@code ambari.properties} + * or {@code 10000 ms} for default. + */ + public int getKdcConnectionCheckTimeout() { + return Integer.parseInt(properties.getProperty( + KDC_CONNECTION_CHECK_TIMEOUT_KEY, KDC_CONNECTION_CHECK_TIMEOUT_DEFAULT)); + } } http://git-wip-us.apache.org/repos/asf/ambari/blob/e56c8299/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 new file mode 100644 index 0000000..f8ec650 --- /dev/null +++ b/ambari-server/src/test/java/org/apache/ambari/server/api/rest/KdcServerConnectionVerificationTest.java @@ -0,0 +1,135 @@ +/* + * 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.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.IOException; +import java.net.ServerSocket; +import java.net.SocketException; +import java.util.Properties; + +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.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; +import org.springframework.test.annotation.ExpectedException; + +/** + * Test for {@link KdcServerConnectionVerification} + */ +public class KdcServerConnectionVerificationTest { + + private static Log LOG = LogFactory.getLog(KdcServerConnectionVerificationTest.class); + + private KdcServerConnectionVerification kdcConnectionVerifier; + private Properties configProps; + private Configuration configuration; + + private static ServerSocket serverSocket = null; + private static boolean serverStop = false; + + private static final int KDC_TEST_PORT = 8090; + // Some dummy port to test a non-listening KDC server + private static final int DUMMY_KDC_PORT = 11234; + + @BeforeClass + public static void beforeClass() throws Exception { + createSocketServer(KDC_TEST_PORT); + } + + @AfterClass + public static void afterClass() throws Exception { + closeServerSocket(); + } + + @Before + public void before() throws Exception { + configProps = new Properties(); + configProps.setProperty(Configuration.KDC_PORT_KEY, Integer.toString(KDC_TEST_PORT)); + configuration = new Configuration(configProps); + kdcConnectionVerifier = new KdcServerConnectionVerification(configuration); + } + + @Test + public void testWithPortSuccess() throws Exception { + assertTrue(kdcConnectionVerifier.isKdcReachable(String.format("localhost:%d", KDC_TEST_PORT))); + } + + @Test + public void testWithoutPortSuccess() throws Exception { + assertTrue(kdcConnectionVerifier.isKdcReachable("localhost")); + } + + @Test + public void testWithoutPortFailure() throws Exception { + // Assumption: test machine has no KDC so nothing listening on port DUMMY_KDC_PORT + configProps.setProperty(Configuration.KDC_PORT_KEY, Integer.toString(DUMMY_KDC_PORT)); + assertFalse(kdcConnectionVerifier.isKdcReachable("localhost")); + } + + @Test + public void testWithPortFailure() throws Exception { + assertFalse(kdcConnectionVerifier.isKdcReachable("localhost:8091")); + } + + + @Test + @ExpectedException(NumberFormatException.class) + public void testPortParsingFailure() throws Exception { + assertFalse(kdcConnectionVerifier.isKdcReachable("localhost:abc")); + } + + /** + * Socket server for test + * We need a separate thread as accept() is a blocking call + */ + private static class SocketThread extends Thread { + public void run() { + while (serverSocket != null && !serverStop) { + try { + serverSocket.accept(); + } catch (SocketException se) { + LOG.debug("SocketException during tearDown. Can be safely ignored"); + } catch (IOException e) { + LOG.error("Unexpected exception while accepting connection request"); + } + } + + } + } + + private static void createSocketServer(int port) throws Exception { + serverSocket = new ServerSocket(port); + new SocketThread().start(); + } + + private static void closeServerSocket() throws Exception { + serverStop = true; + try{ + serverSocket.close(); + } catch (IOException ioe) { + LOG.debug("IOException during tearDown. Can be safely ignored"); + } + } +}