View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase;
20  
21  import org.apache.commons.logging.Log;
22  import org.apache.commons.logging.LogFactory;
23  import org.apache.hadoop.conf.Configuration;
24  import org.apache.hadoop.hbase.CoprocessorEnvironment;
25  import org.apache.hadoop.hbase.coprocessor.*;
26  
27  import java.io.IOException;
28  import java.lang.management.ManagementFactory;
29  import java.rmi.registry.LocateRegistry;
30  import java.rmi.server.RMIClientSocketFactory;
31  import java.rmi.server.RMIServerSocketFactory;
32  import java.util.HashMap;
33  
34  import javax.management.MBeanServer;
35  import javax.management.remote.JMXConnectorServer;
36  import javax.management.remote.JMXConnectorServerFactory;
37  import javax.management.remote.JMXServiceURL;
38  import javax.management.remote.rmi.RMIConnectorServer;
39  import javax.rmi.ssl.SslRMIClientSocketFactory;
40  import javax.rmi.ssl.SslRMIServerSocketFactory;
41  
42  /**
43   * Pluggable JMX Agent for HBase(to fix the 2 random TCP ports issue
44   * of the out-of-the-box JMX Agent):
45   * 1)connector port can share with the registry port if SSL is OFF
46   * 2)support password authentication
47   * 3)support subset of SSL (with default configuration)
48   */
49  public class JMXListener implements Coprocessor {
50  
51    public static final Log LOG = LogFactory.getLog(JMXListener.class);
52    public static final String RMI_REGISTRY_PORT_CONF_KEY = ".rmi.registry.port";
53    public static final String RMI_CONNECTOR_PORT_CONF_KEY = ".rmi.connector.port";
54    public static final int defMasterRMIRegistryPort = 10101;
55    public static final int defRegionserverRMIRegistryPort = 10102;
56  
57    /**
58     * workaround for HBASE-11146
59     * master and regionserver are in 1 JVM in standalone mode
60     * only 1 JMX instance is allowed, otherwise there is port conflict even if
61     * we only load regionserver coprocessor on master
62     */
63    private static JMXConnectorServer jmxCS = null;
64  
65    public static JMXServiceURL buildJMXServiceURL(int rmiRegistryPort,
66        int rmiConnectorPort) throws IOException {
67      // Build jmxURL
68      StringBuilder url = new StringBuilder();
69      url.append("service:jmx:rmi://localhost:");
70      url.append(rmiConnectorPort);
71      url.append("/jndi/rmi://localhost:");
72      url.append(rmiRegistryPort);
73      url.append("/jmxrmi");
74  
75      return new JMXServiceURL(url.toString());
76  
77    }
78  
79    public void startConnectorServer(int rmiRegistryPort, int rmiConnectorPort)
80                throws IOException {
81      boolean rmiSSL = false;
82      boolean authenticate = true;
83      String passwordFile = null;
84      String accessFile = null;
85  
86      System.setProperty("java.rmi.server.randomIDs", "true");
87  
88      String rmiSSLValue = System.getProperty("com.sun.management.jmxremote.ssl",
89                                              "false");
90      rmiSSL = Boolean.parseBoolean(rmiSSLValue);
91  
92      String authenticateValue =
93          System.getProperty("com.sun.management.jmxremote.authenticate", "false");
94      authenticate = Boolean.parseBoolean(authenticateValue);
95  
96      passwordFile = System.getProperty("com.sun.management.jmxremote.password.file");
97      accessFile = System.getProperty("com.sun.management.jmxremote.access.file");
98  
99      LOG.info("rmiSSL:" + rmiSSLValue + ",authenticate:" + authenticateValue
100               + ",passwordFile:" + passwordFile + ",accessFile:" + accessFile);
101 
102     // Environment map
103     HashMap<String, Object> jmxEnv = new HashMap<String, Object>();
104 
105     RMIClientSocketFactory csf = null;
106     RMIServerSocketFactory ssf = null;
107 
108     if (rmiSSL) {
109       if (rmiRegistryPort == rmiConnectorPort) {
110         throw new IOException("SSL is enabled. " +
111             "rmiConnectorPort cannot share with the rmiRegistryPort!");
112       }
113       csf = new SslRMIClientSocketFactorySecure();
114       ssf = new SslRMIServerSocketFactorySecure();
115     }
116 
117     if (csf != null) {
118       jmxEnv.put(RMIConnectorServer.RMI_CLIENT_SOCKET_FACTORY_ATTRIBUTE, csf);
119     }
120     if (ssf != null) {
121       jmxEnv.put(RMIConnectorServer.RMI_SERVER_SOCKET_FACTORY_ATTRIBUTE, ssf);
122     }
123 
124     // Configure authentication
125     if (authenticate) {
126       jmxEnv.put("jmx.remote.x.password.file", passwordFile);
127       jmxEnv.put("jmx.remote.x.access.file", accessFile);
128     }
129 
130     // Create the RMI registry
131     LocateRegistry.createRegistry(rmiRegistryPort);
132     // Retrieve the PlatformMBeanServer.
133     MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();
134 
135     // Build jmxURL
136     JMXServiceURL serviceUrl = buildJMXServiceURL(rmiRegistryPort, rmiConnectorPort);
137 
138     try {
139       // Start the JMXListener with the connection string
140       jmxCS = JMXConnectorServerFactory.newJMXConnectorServer(serviceUrl, jmxEnv, mbs);
141       jmxCS.start();
142       LOG.info("ConnectorServer started!");
143     } catch (IOException e) {
144       LOG.error("fail to start connector server!", e);
145     }
146 
147   }
148 
149   public void stopConnectorServer() throws IOException {
150     synchronized(JMXListener.class) {
151       if (jmxCS != null) {
152         jmxCS.stop();
153         LOG.info("ConnectorServer stopped!");
154         jmxCS = null;
155       }
156     }
157   }
158 
159 
160   @Override
161   public void start(CoprocessorEnvironment env) throws IOException {
162     int rmiRegistryPort = -1;
163     int rmiConnectorPort = -1;
164     Configuration conf = env.getConfiguration();
165 
166     if (env instanceof MasterCoprocessorEnvironment) {
167       // running on Master
168       rmiRegistryPort =
169           conf.getInt("master" + RMI_REGISTRY_PORT_CONF_KEY, defMasterRMIRegistryPort);
170       rmiConnectorPort = conf.getInt("master" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort);
171       LOG.info("Master rmiRegistryPort:" + rmiRegistryPort + ",Master rmiConnectorPort:"
172           + rmiConnectorPort);
173     } else if (env instanceof RegionServerCoprocessorEnvironment) {
174       // running on RegionServer
175       rmiRegistryPort =
176         conf.getInt("regionserver" + RMI_REGISTRY_PORT_CONF_KEY,
177         defRegionserverRMIRegistryPort);
178       rmiConnectorPort =
179         conf.getInt("regionserver" + RMI_CONNECTOR_PORT_CONF_KEY, rmiRegistryPort);
180       LOG.info("RegionServer rmiRegistryPort:" + rmiRegistryPort
181         + ",RegionServer rmiConnectorPort:" + rmiConnectorPort);
182 
183     } else if (env instanceof RegionCoprocessorEnvironment) {
184       LOG.error("JMXListener should not be loaded in Region Environment!");
185       return;
186     }
187 
188     synchronized(JMXListener.class) {
189       if (jmxCS != null) {
190         LOG.info("JMXListener has been started at Registry port " + rmiRegistryPort);
191       }
192       else {
193         startConnectorServer(rmiRegistryPort, rmiConnectorPort);
194       }
195     }
196   }
197 
198   @Override
199   public void stop(CoprocessorEnvironment env) throws IOException {
200     stopConnectorServer();
201   }
202 
203 }