| /* |
| * 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.test.junit.rules; |
| |
| import static java.util.stream.Collectors.toList; |
| import static org.apache.geode.test.awaitility.GeodeAwaitility.await; |
| import static org.junit.Assert.assertEquals; |
| |
| import java.io.IOException; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.function.Supplier; |
| |
| import javax.management.JMX; |
| import javax.management.MBeanServerConnection; |
| import javax.management.MalformedObjectNameException; |
| import javax.management.ObjectInstance; |
| import javax.management.ObjectName; |
| import javax.management.Query; |
| import javax.management.QueryExp; |
| import javax.management.remote.JMXConnector; |
| import javax.management.remote.JMXConnectorFactory; |
| import javax.management.remote.JMXServiceURL; |
| |
| import org.junit.runner.Description; |
| |
| import org.apache.geode.management.internal.security.AccessControlMXBean; |
| |
| /** |
| * This rules handles connection to the MBean Server. If used with {@link ConnectionConfiguration}, |
| * you will need to construct the rule with a port number, then the rule will call connect for you |
| * before running your test. |
| * |
| * <p> |
| * If constructed with no port number, you can connect to any port in your test at anytime, and the |
| * rule will handle the closing of the connection for you. |
| */ |
| public class MBeanServerConnectionRule extends DescribedExternalResource { |
| |
| private Supplier<Integer> portSupplier; |
| private JMXConnector jmxConnector; |
| private MBeanServerConnection con; |
| |
| public MBeanServerConnectionRule() { |
| // nothing |
| } |
| |
| /** |
| * Rule constructor |
| * |
| * @param portSupplier The JMX server port to connect to |
| */ |
| public MBeanServerConnectionRule(Supplier<Integer> portSupplier) { |
| this.portSupplier = portSupplier; |
| } |
| |
| @Override |
| protected void before(Description description) throws Exception { |
| // do not auto connect if port is not set |
| if (portSupplier == null) |
| return; |
| |
| // do not auto connect if no ConnectionConfiguration is defined. |
| ConnectionConfiguration config = description.getAnnotation(ConnectionConfiguration.class); |
| if (config == null) |
| return; |
| |
| connect(portSupplier.get(), config.user(), config.password()); |
| } |
| |
| @Override |
| protected void after(Description description) throws Exception { |
| disconnect(); |
| } |
| |
| /** |
| * Retrieve a new proxy MXBean |
| * |
| * @return A new proxy MXBean of the same type with which the class was constructed |
| */ |
| public <T> T getProxyMXBean(Class<T> proxyClass) |
| throws MalformedObjectNameException, IOException { |
| return getProxyMXBean(proxyClass, null); |
| } |
| |
| /** |
| * Retrieve a new proxy MXBean |
| * |
| * @return A new proxy MXBean of the same type with which the class was constructed |
| */ |
| public <T> T getProxyMXBean(Class<T> proxyClass, String beanQueryName) |
| throws MalformedObjectNameException, IOException { |
| return JMX.newMXBeanProxy(con, getObjectName(proxyClass, beanQueryName), proxyClass); |
| } |
| |
| /** |
| * Retrieve a new proxy MBean |
| * |
| * @return A new proxy MBean of the same type with which the class was constructed |
| */ |
| public <T> T getProxyMBean(Class<T> proxyClass, String beanQueryName) |
| throws IOException, MalformedObjectNameException { |
| return JMX.newMBeanProxy(con, getObjectName(proxyClass, beanQueryName), proxyClass); |
| } |
| |
| /** |
| * Returns a list of remote MBeans from the given member. The MBeans are filtered to exclude the |
| * member's local MBeans. The resulting list includes only MBeans that all locators in the system |
| * should have. |
| **/ |
| public List<ObjectName> getGemfireFederatedBeans() throws IOException { |
| Set<ObjectName> allBeans = con.queryNames(null, null); |
| // Each locator will have a "Manager" bean that is a part of the above query, |
| // representing the ManagementAdapter. |
| // This bean is registered (and so included in its own queries), |
| // but *not* federated (and so is not included in another locator's bean queries). |
| return allBeans.stream() |
| .filter(b -> b.toString().contains("GemFire")) |
| .filter(b -> !b.toString().contains("service=Manager,type=Member,member=locator")) |
| .sorted() |
| .collect(toList()); |
| } |
| |
| /** |
| * Retrieve a new proxy MBean |
| * |
| * @return A new proxy MBean of the same type with which the class was constructed |
| */ |
| public <T> T getProxyMBean(Class<T> proxyClass) throws MalformedObjectNameException, IOException { |
| return getProxyMBean(proxyClass, null); |
| } |
| |
| private ObjectName getObjectName(Class<?> proxyClass, String beanQueryName) |
| throws MalformedObjectNameException, IOException { |
| ObjectName name = null; |
| QueryExp query = null; |
| |
| if (proxyClass != null) { |
| query = Query.isInstanceOf(Query.value(proxyClass.getName())); |
| } |
| |
| if (beanQueryName != null) { |
| name = ObjectName.getInstance(beanQueryName); |
| } |
| |
| Set<ObjectInstance> beans = con.queryMBeans(name, query); |
| assertEquals("failed to find only one instance of type " + proxyClass.getName() + " with name " |
| + beanQueryName, 1, beans.size()); |
| |
| return ((ObjectInstance) beans.toArray()[0]).getObjectName(); |
| } |
| |
| public AccessControlMXBean getAccessControlMBean() throws Exception { |
| return JMX.newMXBeanProxy(con, new ObjectName("GemFire:service=AccessControl,type=Distributed"), |
| AccessControlMXBean.class); |
| } |
| |
| public MBeanServerConnection getMBeanServerConnection() throws IOException { |
| return con; |
| } |
| |
| public void connect(int port, String username, String password) throws Exception { |
| Map<String, String[]> env = new HashMap<>(); |
| env.put(JMXConnector.CREDENTIALS, new String[] {username, password}); |
| connect(null, port, env); |
| } |
| |
| public void connect(int jmxPort) throws Exception { |
| connect(null, jmxPort, null); |
| } |
| |
| public void connect(int jmxPort, Map<String, ?> environment) throws Exception { |
| connect(null, jmxPort, environment); |
| } |
| |
| public void connect(String jmxServer, int jmxPort) throws Exception { |
| connect(jmxServer, jmxPort, null); |
| } |
| |
| public void connect(String jmxServer, int jmxPort, final Map<String, ?> environment) |
| throws Exception { |
| if (jmxServer == null) { |
| jmxServer = ""; |
| } |
| |
| // ServiceUrl: service:jmx:rmi:///jndi/rmi://<TARGET_MACHINE>:<RMI_REGISTRY_PORT>/jmxrmi |
| JMXServiceURL url = |
| new JMXServiceURL("service:jmx:rmi:///jndi/rmi://" + jmxServer + ":" + jmxPort + "/jmxrmi"); |
| |
| // same as GfshShellConnectorRule: if we connect before the RMI server is ready, we get "Failed |
| // to retrieve RMIServer stub: javax.naming.CommunicationException [Root exception is |
| // java.rmi.NoSuchObjectException: no such object in table]" Exception |
| // Have to implement a wait mechanism here. We can use Awaitility here |
| await().until(() -> { |
| Map<String, ?> env = new HashMap<>(); |
| if (environment != null) { |
| env = new HashMap<>(environment); |
| } |
| try { |
| jmxConnector = JMXConnectorFactory.connect(url, env); |
| } catch (Exception e) { |
| if (e.getMessage().contains("no such object in table")) { |
| // keep waiting |
| return false; |
| } |
| throw e; |
| } |
| return true; |
| }); |
| con = jmxConnector.getMBeanServerConnection(); |
| } |
| |
| public void disconnect() throws Exception { |
| if (jmxConnector != null) { |
| jmxConnector.close(); |
| con = null; |
| jmxConnector = null; |
| } |
| } |
| } |