Repository: hbase Updated Branches: refs/heads/0.98 0cce7d16a -> 8ac95e73a
HBASE-10289 Avoid random port usage by default JMX Server. Create Custom JMX server (Qiang Tian) Project: http://git-wip-us.apache.org/repos/asf/hbase/repo Commit: http://git-wip-us.apache.org/repos/asf/hbase/commit/8ac95e73 Tree: http://git-wip-us.apache.org/repos/asf/hbase/tree/8ac95e73 Diff: http://git-wip-us.apache.org/repos/asf/hbase/diff/8ac95e73 Branch: refs/heads/0.98 Commit: 8ac95e73aeb76bf6bc0679122c772ce093be2955 Parents: 0cce7d1 Author: Andrew Purtell <apurt...@apache.org> Authored: Thu Jun 19 17:21:06 2014 -0700 Committer: Andrew Purtell <apurt...@apache.org> Committed: Thu Jun 19 17:21:06 2014 -0700 ---------------------------------------------------------------------- conf/hbase-env.sh | 4 +- .../org/apache/hadoop/hbase/JMXListener.java | 190 +++++++++++++++++++ .../apache/hadoop/hbase/TestJMXListener.java | 95 ++++++++++ 3 files changed, 288 insertions(+), 1 deletion(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/hbase/blob/8ac95e73/conf/hbase-env.sh ---------------------------------------------------------------------- diff --git a/conf/hbase-env.sh b/conf/hbase-env.sh index 91aca76..1885fa9 100644 --- a/conf/hbase-env.sh +++ b/conf/hbase-env.sh @@ -74,7 +74,9 @@ export HBASE_OPTS="-XX:+UseConcMarkSweepGC" # Uncomment and adjust to enable JMX exporting # See jmxremote.password and jmxremote.access in $JRE_HOME/lib/management to configure remote password access. # More details at: http://java.sun.com/javase/6/docs/technotes/guides/management/agent.html -# +# NOTE: HBase provides an alternative JMX implementation to fix the random ports issue, please see JMX +# section in HBase Reference Guide for instructions. + # export HBASE_JMX_BASE="-Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false" # export HBASE_MASTER_OPTS="$HBASE_MASTER_OPTS $HBASE_JMX_BASE -Dcom.sun.management.jmxremote.port=10101" # export HBASE_REGIONSERVER_OPTS="$HBASE_REGIONSERVER_OPTS $HBASE_JMX_BASE -Dcom.sun.management.jmxremote.port=10102" http://git-wip-us.apache.org/repos/asf/hbase/blob/8ac95e73/hbase-server/src/main/java/org/apache/hadoop/hbase/JMXListener.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/main/java/org/apache/hadoop/hbase/JMXListener.java b/hbase-server/src/main/java/org/apache/hadoop/hbase/JMXListener.java new file mode 100644 index 0000000..f292d4c --- /dev/null +++ b/hbase-server/src/main/java/org/apache/hadoop/hbase/JMXListener.java @@ -0,0 +1,190 @@ +/** + * + * 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.hadoop.hbase; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.CoprocessorEnvironment; +import org.apache.hadoop.hbase.coprocessor.*; + +import java.io.IOException; +import java.lang.management.ManagementFactory; +import java.rmi.registry.LocateRegistry; +import java.rmi.server.RMIClientSocketFactory; +import java.rmi.server.RMIServerSocketFactory; +import java.util.HashMap; + +import javax.management.MBeanServer; +import javax.management.remote.JMXConnectorServer; +import javax.management.remote.JMXConnectorServerFactory; +import javax.management.remote.JMXServiceURL; +import javax.management.remote.rmi.RMIConnectorServer; +import javax.rmi.ssl.SslRMIClientSocketFactory; +import javax.rmi.ssl.SslRMIServerSocketFactory; + +/** + * Pluggable JMX Agent for HBase(to fix the 2 random TCP ports issue + * of the out-of-the-box JMX Agent): + * 1)connector port can share with the registry port if SSL is OFF + * 2)support password authentication + * 3)support subset of SSL (with default configuration) + */ +public class JMXListener implements Coprocessor { + + public static final Log LOG = LogFactory.getLog(JMXListener.class); + public static final String RMI_REGISTRY_PORT_CONF_KEY = ".rmi.registry.port"; + public static final String RMI_CONNECTOR_PORT_CONF_KEY = ".rmi.connector.port"; + public static int defMasterRMIRegistryPort = 10101; + public static int defRegionserverRMIRegistryPort = 10102; + + private JMXConnectorServer jmxCS = null; + + public static JMXServiceURL buildJMXServiceURL(int rmiRegistryPort, + int rmiConnectorPort) throws IOException { + // Build jmxURL + StringBuilder url = new StringBuilder(); + url.append("service:jmx:rmi://localhost:"); + url.append(rmiConnectorPort); + url.append("/jndi/rmi://localhost:"); + url.append(rmiRegistryPort); + url.append("/jmxrmi"); + + return new JMXServiceURL(url.toString()); + + } + + public void startConnectorServer(int rmiRegistryPort, int rmiConnectorPort) + throws IOException { + boolean rmiSSL = false; + boolean authenticate = true; + String passwordFile = null; + String accessFile = null; + + System.setProperty("java.rmi.server.randomIDs", "true"); + + String rmiSSLValue = System.getProperty("com.sun.management.jmxremote.ssl", + "false"); + rmiSSL = Boolean.parseBoolean(rmiSSLValue); + + String authenticateValue = + System.getProperty("com.sun.management.jmxremote.authenticate", "false"); + authenticate = Boolean.parseBoolean(authenticateValue); + + passwordFile = System.getProperty("com.sun.management.jmxremote.password.file"); + accessFile = System.getProperty("com.sun.management.jmxremote.access.file"); + + LOG.info("rmiSSL:" + rmiSSLValue + ",authenticate:" + authenticateValue + + ",passwordFile:" + passwordFile + ",accessFile:" + accessFile); + + // Environment map + HashMap<String, Object> jmxEnv = new HashMap<String, Object>(); + + RMIClientSocketFactory csf = null; + RMIServerSocketFactory ssf = null; + + if (rmiSSL) { + if (rmiRegistryPort == rmiConnectorPort) { + throw new IOException("SSL is enabled. " + + "rmiConnectorPort cannot share with the rmiRegistryPort!"); + } + csf = new SslRMIClientSocketFactory(); + ssf = new SslRMIServerSocketFactory(); + } + + if (csf != null) { + jmxEnv.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); + } + if (ssf != null) { + jmxEnv.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); + } + + // Configure authentication + if (authenticate) { + jmxEnv.put("jmx.remote.x.password.file", passwordFile); + jmxEnv.put("jmx.remote.x.access.file", accessFile); + } + + // Create the RMI registry + LocateRegistry.createRegistry(rmiRegistryPort); + // Retrieve the PlatformMBeanServer. + MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); + + // Build jmxURL + JMXServiceURL serviceUrl = buildJMXServiceURL(rmiRegistryPort, rmiConnectorPort); + + try { + // Start the JMXListener with the connection string + jmxCS = JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl, jmxEnv, mbs); + jmxCS.start(); + LOG.info("ConnectorServer started!"); + } catch (IOException e) { + LOG.error("fail to start connector server!", e); + } + + } + + public void stopConnectorServer() throws IOException { + if (jmxCS != null) { + jmxCS.stop(); + LOG.info("ConnectorServer stopped!"); + jmxCS = null; + } + } + + + @Override + public void start(CoprocessorEnvironment env) throws IOException { + int rmiRegistryPort = -1; + int rmiConnectorPort = -1; + Configuration conf = env.getConfiguration(); + + if (env instanceof MasterCoprocessorEnvironment) { + // running on Master + rmiRegistryPort = + conf.getInt("master" + RMI_REGISTRY_PORT_CONF_KEY, + defMasterRMIRegistryPort); + rmiConnectorPort = + conf.getInt("master" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort); + LOG.info("Master rmiRegistryPort:" + rmiRegistryPort + + ",Master rmiConnectorPort:" + rmiConnectorPort); + + } else if (env instanceof RegionServerCoprocessorEnvironment) { + // running on RegionServer + rmiRegistryPort = + conf.getInt("regionserver" + RMI_REGISTRY_PORT_CONF_KEY, + defRegionserverRMIRegistryPort); + rmiConnectorPort = + conf.getInt("regionserver" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort); + LOG.info("RegionServer rmiRegistryPort:" + rmiRegistryPort + + ",RegionServer rmiConnectorPort:" + rmiConnectorPort); + + } else if (env instanceof RegionCoprocessorEnvironment) { + LOG.error("JMXListener should not be loaded in Region Environment!"); + } + + startConnectorServer(rmiRegistryPort, rmiConnectorPort); + } + + @Override + public void stop(CoprocessorEnvironment env) throws IOException { + stopConnectorServer(); + } + +} http://git-wip-us.apache.org/repos/asf/hbase/blob/8ac95e73/hbase-server/src/test/java/org/apache/hadoop/hbase/TestJMXListener.java ---------------------------------------------------------------------- diff --git a/hbase-server/src/test/java/org/apache/hadoop/hbase/TestJMXListener.java b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestJMXListener.java new file mode 100644 index 0000000..719e04f --- /dev/null +++ b/hbase-server/src/test/java/org/apache/hadoop/hbase/TestJMXListener.java @@ -0,0 +1,95 @@ +/** + * + * 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.hadoop.hbase; + +import java.io.IOException; + +import javax.management.MBeanServerConnection; +import javax.management.remote.JMXConnector; +import javax.management.remote.JMXConnectorFactory; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.hbase.coprocessor.CoprocessorHost; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.experimental.categories.Category; +import org.junit.rules.ExpectedException; + + + +@Category(MediumTests.class) +public class TestJMXListener { + private static final Log LOG = LogFactory.getLog(TestJMXListener.class); + private static HBaseTestingUtility UTIL = new HBaseTestingUtility(); + private static int connectorPort = 61120; + + @BeforeClass + public static void setupBeforeClass() throws Exception { + Configuration conf = UTIL.getConfiguration(); + + conf.set(CoprocessorHost.REGIONSERVER_COPROCESSOR_CONF_KEY, + JMXListener.class.getName()); + conf.setInt("regionserver.rmi.registry.port", connectorPort); + + UTIL.startMiniCluster(); + } + + @AfterClass + public static void tearDownAfterClass() throws Exception { + UTIL.shutdownMiniCluster(); + } + + @Test + public void testStart() throws Exception { + JMXConnector connector = JMXConnectorFactory.connect( + JMXListener.buildJMXServiceURL(connectorPort,connectorPort)); + + MBeanServerConnection mb = connector.getMBeanServerConnection(); + String domain = mb.getDefaultDomain(); + Assert.assertTrue("default domain is not correct", + !domain.isEmpty()); + connector.close(); + + } + + //shutdown hbase only. then try connect, IOException expected + @Rule + public ExpectedException expectedEx = ExpectedException.none(); + @Test + public void testStop() throws Exception { + MiniHBaseCluster cluster = UTIL.getHBaseCluster(); + LOG.info("shutdown hbase cluster..."); + cluster.shutdown(); + LOG.info("wait for the hbase cluster shutdown..."); + cluster.waitUntilShutDown(); + + JMXConnector connector = JMXConnectorFactory.newJMXConnector( + JMXListener.buildJMXServiceURL(connectorPort,connectorPort), null); + expectedEx.expect(IOException.class); + connector.connect(); + + } + + +} \ No newline at end of file