/* * Licensed to ElasticSearch and Shay Banon under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. ElasticSearch 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.elasticsearch.jmx; import org.elasticsearch.common.logging.ESLogger; import org.elasticsearch.common.network.NetworkService; import org.elasticsearch.common.settings.Settings; import org.elasticsearch.common.transport.PortsRange; import javax.management.InstanceAlreadyExistsException; import javax.management.MBeanServer; import javax.management.ObjectName; import javax.management.remote.JMXConnectorServer; import javax.management.remote.JMXConnectorServerFactory; import javax.management.remote.JMXServiceURL; import java.io.IOException; import java.lang.management.ManagementFactory; import java.rmi.registry.LocateRegistry; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicReference; import org.elasticsearch.jmx.JmxConnectorCreationException; import org.elasticsearch.jmx.JmxRegistrationException; import org.elasticsearch.jmx.ResourceDMBean; /** * */ public class JmxService { public static class SettingsConstants { public static final String EXPORT = "jmx.export"; public static final String CREATE_CONNECTOR = "jmx.create_connector"; } public static boolean shouldExport(Settings settings) { return settings.getAsBoolean(SettingsConstants.CREATE_CONNECTOR, false) || settings.getAsBoolean(SettingsConstants.EXPORT, false); } // we use {jmx.port} without prefix of $ since we don't want it to be resolved as a setting property public static final String JMXRMI_URI_PATTERN = "service:jmx:rmi:///jndi/rmi://:{jmx.port}/jmxrmi"; public static final String JMXRMI_PUBLISH_URI_PATTERN = "service:jmx:rmi:///jndi/rmi://{jmx.host}:{jmx.port}/jmxrmi"; private final ESLogger logger; private final Settings settings; private final String jmxDomain; private String serviceUrl; private String publishUrl; private final MBeanServer mBeanServer; private JMXConnectorServer connectorServer; private final CopyOnWriteArrayList<ResourceDMBean> constructionMBeans = new CopyOnWriteArrayList<ResourceDMBean>(); private final CopyOnWriteArrayList<ResourceDMBean> registeredMBeans = new CopyOnWriteArrayList<ResourceDMBean>(); private String nodeDescription; private volatile boolean started = false; public JmxService(ESLogger logger, final Settings settings) { this.logger = logger; this.settings = settings; this.jmxDomain = settings.get("jmx.domain", "org.elasticsearch"); this.mBeanServer = ManagementFactory.getPlatformMBeanServer(); } public String serviceUrl() { return this.serviceUrl; } public String publishUrl() { return this.publishUrl; } public void connectAndRegister(String nodeDescription, final NetworkService networkService) { if (started) { return; } started = true; this.nodeDescription = nodeDescription; if (settings.getAsBoolean(SettingsConstants.CREATE_CONNECTOR, false)) { // we are going to create the connector, set the GC interval to a large value try { if (System.getProperty("sun.rmi.dgc.client.gcInterval") == null) System.setProperty("sun.rmi.dgc.client.gcInterval", "36000000"); if (System.getProperty("sun.rmi.dgc.server.gcInterval") == null) System.setProperty("sun.rmi.dgc.server.gcInterval", "36000000"); } catch (Exception secExc) { logger.warn("Failed to set sun.rmi.dgc.xxx system properties", secExc); } final String port = settings.get("jmx.port", "9400-9500"); PortsRange portsRange = new PortsRange(port); final AtomicReference<Exception> lastException = new AtomicReference<Exception>(); boolean success = portsRange.iterate(new PortsRange.PortCallback() { @Override public boolean onPortNumber(int portNumber) { try { LocateRegistry.createRegistry(portNumber); serviceUrl = settings.get("jmx.service_url", JMXRMI_URI_PATTERN).replace("{jmx.port}", Integer.toString(portNumber)); // Create the JMX service URL. JMXServiceURL url = new JMXServiceURL(serviceUrl); // Create the connector server now. connectorServer = JMXConnectorServerFactory.newJMXConnectorServer(url, settings.getAsMap(), mBeanServer); connectorServer.start(); // create the publish url String publishHost = networkService.resolvePublishHostAddress(settings.get("jmx.publish_host")).getHostAddress(); publishUrl = settings.get("jmx.publish_url", JMXRMI_PUBLISH_URI_PATTERN).replace("{jmx.port}", Integer.toString(portNumber)).replace("{jmx.host}", publishHost); } catch (Exception e) { lastException.set(e); return false; } return true; } }); if (!success) { throw new JmxConnectorCreationException("Failed to bind to [" + port + "]", lastException.get()); } logger.info("bound_address {{}}, publish_address {{}}", serviceUrl, publishUrl); } for (ResourceDMBean resource : constructionMBeans) { register(resource); } } public void registerMBean(Object instance) { ResourceDMBean resourceDMBean = new ResourceDMBean(instance, logger); if (!resourceDMBean.isManagedResource()) { return; } if (!started) { constructionMBeans.add(resourceDMBean); return; } register(resourceDMBean); } public void unregisterGroup(String groupName) { for (ResourceDMBean resource : registeredMBeans) { if (!groupName.equals(resource.getGroupName())) { continue; } registeredMBeans.remove(resource); String resourceName = resource.getFullObjectName(); try { ObjectName objectName = new ObjectName(getObjectName(resourceName)); if (mBeanServer.isRegistered(objectName)) { mBeanServer.unregisterMBean(objectName); if (logger.isTraceEnabled()) { logger.trace("Unregistered " + objectName); } } } catch (Exception e) { logger.warn("Failed to unregister " + resource.getFullObjectName()); } } } public void close() { if (!started) { return; } started = false; // unregister mbeans for (ResourceDMBean resource : registeredMBeans) { String resourceName = resource.getFullObjectName(); try { ObjectName objectName = new ObjectName(getObjectName(resourceName)); if (mBeanServer.isRegistered(objectName)) { mBeanServer.unregisterMBean(objectName); if (logger.isTraceEnabled()) { logger.trace("Unregistered " + objectName); } } } catch (Exception e) { logger.warn("Failed to unregister " + resource.getFullObjectName()); } } if (connectorServer != null) { try { connectorServer.stop(); } catch (IOException e) { logger.debug("Failed to close connector", e); } } } private void register(ResourceDMBean resourceDMBean) throws JmxRegistrationException { try { String resourceName = resourceDMBean.getFullObjectName(); ObjectName objectName = new ObjectName(getObjectName(resourceName)); if (!mBeanServer.isRegistered(objectName)) { try { mBeanServer.registerMBean(resourceDMBean, objectName); registeredMBeans.add(resourceDMBean); if (logger.isTraceEnabled()) { logger.trace("Registered " + resourceDMBean + " under " + objectName); } } catch (InstanceAlreadyExistsException e) { //this might happen if multiple instances are trying to concurrently register same objectName logger.debug("Could not register object with name:" + objectName + "(" + e.getMessage() + ")"); } } else { logger.debug("Could not register object with name: " + objectName + ", already registered"); } } catch (Exception e) { logger.warn("Could not register object with name: " + resourceDMBean.getFullObjectName() + "(" + e.getMessage() + ")"); } } private String getObjectName(String resourceName) { return getObjectName(jmxDomain, resourceName); } private String getObjectName(String jmxDomain, String resourceName) { return jmxDomain + ":" + resourceName; } }