blob: 80e1ba20ade2f1190f16a141a9d543ce710df861 [file] [log] [blame]
/*
* 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.geode.internal.process;
import static org.apache.commons.lang3.Validate.isTrue;
import static org.apache.commons.lang3.Validate.notNull;
import java.io.IOException;
import java.util.Properties;
import java.util.Set;
import javax.management.InstanceNotFoundException;
import javax.management.MBeanException;
import javax.management.MBeanServerConnection;
import javax.management.ObjectName;
import javax.management.Query;
import javax.management.QueryExp;
import javax.management.ReflectionException;
import javax.management.remote.JMXConnector;
import javax.management.remote.JMXConnectorFactory;
import javax.management.remote.JMXServiceURL;
import com.sun.tools.attach.AttachNotSupportedException;
import com.sun.tools.attach.VirtualMachine;
/**
* Controls a {@link ControllableProcess} using the Attach API to manipulate MBeans.
*
* @since GemFire 8.0
*/
class MBeanProcessController implements ProcessController {
/** Property name for the JMX local connector address (from sun.management.Agent) */
private static final String PROPERTY_LOCAL_CONNECTOR_ADDRESS =
"com.sun.management.jmxremote.localConnectorAddress";
private static final Object[] PARAMS = {};
private static final String[] SIGNATURE = {};
private final MBeanControllerParameters arguments;
private final int pid;
private JMXConnector jmxc;
private MBeanServerConnection server;
/**
* Constructs an instance for controlling a local process.
*
* @param pid process id identifying the process to attach to
*
* @throws IllegalArgumentException if pid is not a positive integer
*/
MBeanProcessController(final MBeanControllerParameters arguments, final int pid) {
notNull(arguments, "Invalid arguments '" + arguments + "' specified");
isTrue(pid > 0, "Invalid pid '" + pid + "' specified");
this.pid = pid;
this.arguments = arguments;
}
@Override
public int getProcessId() {
return pid;
}
@Override
public String status() throws UnableToControlProcessException, ConnectionFailedException,
IOException, MBeanInvocationFailedException {
return status(arguments.getNamePattern(), arguments.getPidAttribute(),
arguments.getStatusMethod(), arguments.getAttributes(), arguments.getValues());
}
@Override
public void stop() throws UnableToControlProcessException, ConnectionFailedException, IOException,
MBeanInvocationFailedException {
stop(arguments.getNamePattern(), arguments.getPidAttribute(), arguments.getStopMethod(),
arguments.getAttributes(), arguments.getValues());
}
@Override
public void checkPidSupport() {
// nothing
}
/**
* Connects to the process and tells it to shut down.
*
* @param namePattern the name pattern of the MBean to use for stopping
* @param pidAttribute the name of the MBean attribute with the process id to compare against
* @param stopMethod the name of the MBean operation to invoke
* @param attributes the names of the MBean attributes to compare with expected values
* @param values the expected values of the specified MBean attributes
*
* @throws ConnectionFailedException if there was a failure to connect to the local JMX connector
* in the process
* @throws IOException if a communication problem occurred when talking to the MBean server
* @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason
*/
private void stop(final ObjectName namePattern, final String pidAttribute,
final String stopMethod, final String[] attributes, final Object[] values)
throws ConnectionFailedException, IOException, MBeanInvocationFailedException {
invokeOperationOnTargetMBean(namePattern, pidAttribute, stopMethod, attributes, values);
}
/**
* Connects to the process and acquires its status.
*
* @param namePattern the name pattern of the MBean to use for stopping
* @param pidAttribute the name of the MBean attribute with the process id to compare against
* @param statusMethod the name of the MBean operation to invoke
* @param attributes the names of the MBean attributes to compare with expected values
* @param values the expected values of the specified MBean attributes
*
* @return string describing the status of the process
*
* @throws ConnectionFailedException if there was a failure to connect to the local JMX connector
* in the process
* @throws IOException if a communication problem occurred when talking to the MBean server
* @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason
*/
private String status(final ObjectName namePattern, final String pidAttribute,
final String statusMethod, final String[] attributes, final Object[] values)
throws ConnectionFailedException, IOException, MBeanInvocationFailedException {
return invokeOperationOnTargetMBean(namePattern, pidAttribute, statusMethod, attributes, values)
.toString();
}
/**
* Connects to the process and use its MBean to stop it.
*
* @param namePattern the name pattern of the MBean to use for stopping
* @param pidAttribute the name of the MBean attribute with the process id to compare against
* @param methodName the name of the MBean operation to invoke
* @param attributes the names of the MBean attributes to compare with expected values
* @param values the expected values of the specified MBean attributes
*
* @throws ConnectionFailedException if there was a failure to connect to the local JMX connector
* in the process
* @throws IOException if a communication problem occurred when talking to the MBean server
* @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason
*/
private Object invokeOperationOnTargetMBean(final ObjectName namePattern,
final String pidAttribute, final String methodName, final String[] attributes,
final Object[] values)
throws ConnectionFailedException, IOException, MBeanInvocationFailedException {
ObjectName objectName = namePattern;
connect();
try {
QueryExp constraint = buildQueryExp(pidAttribute, attributes, values);
Set<ObjectName> mbeanNames = server.queryNames(namePattern, constraint);
if (mbeanNames.isEmpty()) {
throw new MBeanInvocationFailedException("Failed to find mbean matching '" + namePattern
+ "' with attribute '" + pidAttribute + "' of value '" + pid + "'");
}
if (mbeanNames.size() > 1) {
throw new MBeanInvocationFailedException("Found more than one mbean matching '"
+ namePattern + "' with attribute '" + pidAttribute + "' of value '" + pid + "'");
}
objectName = mbeanNames.iterator().next();
return invoke(objectName, methodName);
} catch (InstanceNotFoundException | MBeanException | ReflectionException e) {
throw new MBeanInvocationFailedException(
"Failed to invoke " + methodName + " on " + objectName, e);
} finally {
disconnect();
}
}
/**
* Connects to the JMX agent in the local process.
*
* @throws ConnectionFailedException if there was a failure to connect to the local JMX connector
* in the process
* @throws IOException if the JDK management agent cannot be found and loaded
*/
private void connect() throws ConnectionFailedException, IOException {
try {
JMXServiceURL jmxUrl = getJMXServiceURL();
jmxc = JMXConnectorFactory.connect(jmxUrl);
server = jmxc.getMBeanServerConnection();
} catch (AttachNotSupportedException e) {
throw new ConnectionFailedException("Failed to connect to process '" + pid + "'", e);
}
}
/**
* Disconnects from the JMX agent in the local process.
*/
private void disconnect() {
server = null;
if (jmxc != null) {
try {
jmxc.close();
} catch (IOException ignored) {
// ignore
}
}
jmxc = null;
}
/**
* Uses the Attach API to connect to the local process and ensures that it has loaded the JMX
* management agent. The JMXServiceURL identifying the local connector address for the JMX agent
* in the process is returned.
*
* @return the address of the JMX API connector server for connecting to the local process
*
* @throws AttachNotSupportedException if unable to use the Attach API to connect to the process
* @throws IOException if the JDK management agent cannot be found and loaded
*/
private JMXServiceURL getJMXServiceURL() throws AttachNotSupportedException, IOException {
String connectorAddress;
VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid));
try {
Properties agentProps = vm.getAgentProperties();
connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS);
if (connectorAddress == null) {
// make sure the local management agent is started and this will ensure
// localConnectorAddress is present in the agent properties.
vm.startLocalManagementAgent();
// get the connector address
agentProps = vm.getAgentProperties();
connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS);
}
} finally {
vm.detach();
}
if (connectorAddress == null) {
// should never reach here
throw new IOException("Failed to find address to attach to process");
}
return new JMXServiceURL(connectorAddress);
}
/**
* Builds the QueryExp used to identify the target MBean.
*
* @param pidAttribute the name of the MBean attribute with the process id to compare against
* @param attributes the names of additional MBean attributes to compare with expected values
* @param values the expected values of the specified MBean attributes
*
* @return the main QueryExp for matching the target MBean
*/
private QueryExp buildQueryExp(final String pidAttribute, final String[] attributes,
final Object[] values) {
QueryExp optionalAttributes = buildOptionalQueryExp(attributes, values);
QueryExp constraint;
if (optionalAttributes != null) {
constraint =
Query.and(optionalAttributes, Query.eq(Query.attr(pidAttribute), Query.value(pid)));
} else {
constraint = Query.eq(Query.attr(pidAttribute), Query.value(pid));
}
return constraint;
}
/**
* Builds an optional QueryExp to aid in matching the correct MBean using additional attributes
* with the specified values. Returns null if no attributes and values were specified during
* construction.
*
* @param attributes the names of additional MBean attributes to compare with expected values
* @param values the expected values of the specified MBean attributes
*
* @return optional QueryExp to aid in matching the correct MBean
*/
private QueryExp buildOptionalQueryExp(final String[] attributes, final Object[] values) {
QueryExp queryExp = null;
for (int i = 0; i < attributes.length; i++) {
if (values[i] instanceof Boolean) {
if (queryExp == null) {
queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i]));
} else {
queryExp = Query.and(queryExp,
Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i])));
}
} else if (values[i] instanceof Number) {
if (queryExp == null) {
queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i]));
} else {
queryExp = Query.and(queryExp,
Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i])));
}
} else if (values[i] instanceof String) {
if (queryExp == null) {
queryExp = Query.eq(Query.attr(attributes[i]), Query.value((String) values[i]));
} else {
queryExp = Query.and(queryExp,
Query.eq(Query.attr(attributes[i]), Query.value((String) values[i])));
}
}
}
return queryExp;
}
/**
* Invokes an operation on the specified MBean.
*
* @param objectName identifies the MBean
* @param method the name of the operation method invoke
*
* @return the result of invoking the operation on the MBean specified or null
*
* @throws InstanceNotFoundException if the specified MBean is not registered in the MBean server
* @throws IOException if a communication problem occurred when talking to the MBean server
* @throws MBeanException if the MBean operation throws an exception
* @throws ReflectionException if the MBean does not have the specified operation
*/
private Object invoke(final ObjectName objectName, final String method)
throws InstanceNotFoundException, IOException, MBeanException, ReflectionException {
return server.invoke(objectName, method, PARAMS, SIGNATURE);
}
}