001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, software 013 * distributed under the License is distributed on an "AS IS" BASIS, 014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 015 * See the License for the specific language governing permissions and 016 * limitations under the License. 017 */ 018package org.apache.hadoop.hbase; 019 020import java.io.IOException; 021import java.lang.management.ManagementFactory; 022import java.rmi.registry.LocateRegistry; 023import java.rmi.registry.Registry; 024import java.rmi.server.RMIClientSocketFactory; 025import java.rmi.server.RMIServerSocketFactory; 026import java.rmi.server.UnicastRemoteObject; 027import java.util.HashMap; 028import javax.management.MBeanServer; 029import javax.management.remote.JMXConnectorServer; 030import javax.management.remote.JMXConnectorServerFactory; 031import javax.management.remote.JMXServiceURL; 032import javax.management.remote.rmi.RMIConnectorServer; 033import org.apache.hadoop.conf.Configuration; 034import org.apache.hadoop.hbase.coprocessor.MasterCoprocessor; 035import org.apache.hadoop.hbase.coprocessor.MasterCoprocessorEnvironment; 036import org.apache.hadoop.hbase.coprocessor.RegionCoprocessorEnvironment; 037import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessor; 038import org.apache.hadoop.hbase.coprocessor.RegionServerCoprocessorEnvironment; 039import org.apache.yetus.audience.InterfaceAudience; 040import org.slf4j.Logger; 041import org.slf4j.LoggerFactory; 042 043/** 044 * Pluggable JMX Agent for HBase(to fix the 2 random TCP ports issue of the out-of-the-box JMX 045 * Agent): 1)connector port can share with the registry port if SSL is OFF 2)support password 046 * authentication 3)support subset of SSL (with default configuration) 047 */ 048@InterfaceAudience.Private 049public class JMXListener implements MasterCoprocessor, RegionServerCoprocessor { 050 private static final Logger LOG = LoggerFactory.getLogger(JMXListener.class); 051 public static final String RMI_REGISTRY_PORT_CONF_KEY = ".rmi.registry.port"; 052 public static final String RMI_CONNECTOR_PORT_CONF_KEY = ".rmi.connector.port"; 053 public static final int defMasterRMIRegistryPort = 10101; 054 public static final int defRegionserverRMIRegistryPort = 10102; 055 056 /** 057 * workaround for HBASE-11146 master and regionserver are in 1 JVM in standalone mode only 1 JMX 058 * instance is allowed, otherwise there is port conflict even if we only load regionserver 059 * coprocessor on master 060 */ 061 private static JMXConnectorServer JMX_CS = null; 062 private Registry rmiRegistry = null; 063 064 public static JMXServiceURL buildJMXServiceURL(int rmiRegistryPort, int rmiConnectorPort) 065 throws IOException { 066 // Build jmxURL 067 StringBuilder url = new StringBuilder(); 068 url.append("service:jmx:rmi://localhost:"); 069 url.append(rmiConnectorPort); 070 url.append("/jndi/rmi://localhost:"); 071 url.append(rmiRegistryPort); 072 url.append("/jmxrmi"); 073 074 return new JMXServiceURL(url.toString()); 075 076 } 077 078 public void startConnectorServer(int rmiRegistryPort, int rmiConnectorPort) throws IOException { 079 boolean rmiSSL = false; 080 boolean authenticate = true; 081 String passwordFile = null; 082 String accessFile = null; 083 084 System.setProperty("java.rmi.server.randomIDs", "true"); 085 086 String rmiSSLValue = System.getProperty("com.sun.management.jmxremote.ssl", "false"); 087 rmiSSL = Boolean.parseBoolean(rmiSSLValue); 088 089 String authenticateValue = 090 System.getProperty("com.sun.management.jmxremote.authenticate", "false"); 091 authenticate = Boolean.parseBoolean(authenticateValue); 092 093 passwordFile = System.getProperty("com.sun.management.jmxremote.password.file"); 094 accessFile = System.getProperty("com.sun.management.jmxremote.access.file"); 095 096 LOG.info("rmiSSL:" + rmiSSLValue + ",authenticate:" + authenticateValue + ",passwordFile:" 097 + passwordFile + ",accessFile:" + accessFile); 098 099 // Environment map 100 HashMap<String, Object> jmxEnv = new HashMap<>(); 101 102 RMIClientSocketFactory csf = null; 103 RMIServerSocketFactory ssf = null; 104 105 if (rmiSSL) { 106 if (rmiRegistryPort == rmiConnectorPort) { 107 throw new IOException( 108 "SSL is enabled. " + "rmiConnectorPort cannot share with the rmiRegistryPort!"); 109 } 110 csf = new SslRMIClientSocketFactorySecure(); 111 ssf = new SslRMIServerSocketFactorySecure(); 112 } 113 114 if (csf != null) { 115 jmxEnv.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf); 116 } 117 if (ssf != null) { 118 jmxEnv.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf); 119 } 120 121 // Configure authentication 122 if (authenticate) { 123 jmxEnv.put("jmx.remote.x.password.file", passwordFile); 124 jmxEnv.put("jmx.remote.x.access.file", accessFile); 125 } 126 127 // Create the RMI registry 128 rmiRegistry = LocateRegistry.createRegistry(rmiRegistryPort); 129 // Retrieve the PlatformMBeanServer. 130 MBeanServer mbs = ManagementFactory.getPlatformMBeanServer(); 131 132 // Build jmxURL 133 JMXServiceURL serviceUrl = buildJMXServiceURL(rmiRegistryPort, rmiConnectorPort); 134 135 try { 136 // Start the JMXListener with the connection string 137 synchronized (JMXListener.class) { 138 if (JMX_CS != null) { 139 throw new RuntimeException("Started by another thread?"); 140 } 141 JMX_CS = JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl, jmxEnv, mbs); 142 JMX_CS.start(); 143 } 144 LOG.info("JMXConnectorServer started!"); 145 } catch (IOException e) { 146 LOG.error("Failed start of JMXConnectorServer!", e); 147 // deregister the RMI registry 148 if (rmiRegistry != null) { 149 UnicastRemoteObject.unexportObject(rmiRegistry, true); 150 } 151 } 152 153 } 154 155 public void stopConnectorServer() throws IOException { 156 synchronized (JMXListener.class) { 157 if (JMX_CS != null) { 158 JMX_CS.stop(); 159 LOG.info("ConnectorServer stopped!"); 160 JMX_CS = null; 161 } 162 // deregister the RMI registry 163 if (rmiRegistry != null) { 164 UnicastRemoteObject.unexportObject(rmiRegistry, true); 165 } 166 } 167 } 168 169 @Override 170 public void start(CoprocessorEnvironment env) throws IOException { 171 int rmiRegistryPort = -1; 172 int rmiConnectorPort = -1; 173 Configuration conf = env.getConfiguration(); 174 175 if (env instanceof MasterCoprocessorEnvironment) { 176 // running on Master 177 rmiRegistryPort = 178 conf.getInt("master" + RMI_REGISTRY_PORT_CONF_KEY, defMasterRMIRegistryPort); 179 rmiConnectorPort = conf.getInt("master" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort); 180 LOG.info("Master rmiRegistryPort:" + rmiRegistryPort + ",Master rmiConnectorPort:" 181 + rmiConnectorPort); 182 } else if (env instanceof RegionServerCoprocessorEnvironment) { 183 // running on RegionServer 184 rmiRegistryPort = 185 conf.getInt("regionserver" + RMI_REGISTRY_PORT_CONF_KEY, defRegionserverRMIRegistryPort); 186 rmiConnectorPort = conf.getInt("regionserver" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort); 187 LOG.info("RegionServer rmiRegistryPort:" + rmiRegistryPort + ",RegionServer rmiConnectorPort:" 188 + rmiConnectorPort); 189 190 } else if (env instanceof RegionCoprocessorEnvironment) { 191 LOG.error("JMXListener should not be loaded in Region Environment!"); 192 return; 193 } 194 195 synchronized (JMXListener.class) { 196 if (JMX_CS != null) { 197 LOG.info("JMXListener has been started at Registry port " + rmiRegistryPort); 198 } else { 199 startConnectorServer(rmiRegistryPort, rmiConnectorPort); 200 } 201 } 202 } 203 204 @Override 205 public void stop(CoprocessorEnvironment env) throws IOException { 206 stopConnectorServer(); 207 } 208 209}