/* * 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.brooklyn.feed.jmx; import static org.testng.Assert.assertEquals; import static org.testng.Assert.assertFalse; import static org.testng.Assert.fail; import java.io.IOException; import java.util.List; import javax.management.DynamicMBean; import javax.management.MBeanOperationInfo; import javax.management.MBeanParameterInfo; import javax.management.Notification; import javax.management.NotificationListener; import javax.management.ObjectName; import javax.management.StandardEmitterMBean; import org.apache.brooklyn.entity.software.base.test.jmx.GeneralisedDynamicMBean; import org.apache.brooklyn.entity.software.base.test.jmx.JmxService; import org.apache.brooklyn.feed.jmx.JmxHelper; import org.apache.brooklyn.test.Asserts; import org.apache.brooklyn.test.TestUtils; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; import org.jclouds.util.Throwables2; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; import org.testng.collections.Lists; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; public class JmxHelperTest { private static final Logger log = LoggerFactory.getLogger(JmxHelperTest.class); private static final String LOCALHOST_NAME = "localhost"; private static final int TIMEOUT_MS = 5000; private static final int SHORT_WAIT_MS = 250; private JmxService jmxService; private JmxHelper jmxHelper; private String objectName = "Brooklyn:type=MyTestMBean,name=myname"; private String objectNameWithWildcard = "Brooklyn:type=MyTestMBean,name=mynam*"; private ObjectName jmxObjectName; private ObjectName jmxObjectNameWithWildcard; private String attributeName = "myattrib"; private String opName = "myop"; @BeforeMethod(alwaysRun=true) public void setUp() throws Exception { jmxObjectName = new ObjectName(objectName); jmxObjectNameWithWildcard = new ObjectName(objectNameWithWildcard); jmxService = newJmxServiceRetrying(LOCALHOST_NAME, 5); jmxHelper = new JmxHelper(jmxService.getUrl()); jmxHelper.setMinTimeBetweenReconnectAttempts(0); jmxHelper.connect(TIMEOUT_MS); } @AfterMethod(alwaysRun=true) public void tearDown() throws Exception { if (jmxHelper != null) jmxHelper.disconnect(); if (jmxService != null) jmxService.shutdown(); jmxHelper = null; jmxService = null; } @Test public void testGetAttribute() throws Exception { GeneralisedDynamicMBean mbean = jmxService.registerMBean(MutableMap.of("myattr", "myval"), objectName); assertEquals(jmxHelper.getAttribute(jmxObjectName, "myattr"), "myval"); } @Test public void testGetAttributeUsingObjectNameWildcard() throws Exception { GeneralisedDynamicMBean mbean = jmxService.registerMBean(MutableMap.of("myattr", "myval"), objectName); assertEquals(jmxHelper.getAttribute(jmxObjectNameWithWildcard, "myattr"), "myval"); } @Test public void testSetAttribute() throws Exception { DynamicMBean mbean = jmxService.registerMBean(MutableMap.of("myattr", "myval"), objectName); jmxHelper.setAttribute(jmxObjectName, "myattr", "abc"); Object actual = jmxHelper.getAttribute(jmxObjectName, "myattr"); assertEquals(actual, "abc"); } @Test public void testSetAttributeUsingObjectNameWildcard() throws Exception { DynamicMBean mbean = jmxService.registerMBean(MutableMap.of("myattr", "myval"), objectName); jmxHelper.setAttribute(jmxObjectNameWithWildcard, "myattr", "abc"); Object actual = jmxHelper.getAttribute(jmxObjectName, "myattr"); assertEquals(actual, "abc"); } @Test public void testInvokeOperationWithNoArgs() throws Exception { final String opReturnVal = "my result"; MBeanOperationInfo opInfo = new MBeanOperationInfo(opName, "my descr", new MBeanParameterInfo[0], String.class.getName(), MBeanOperationInfo.ACTION); Function<Object[], String> opImpl = new Function<Object[], String>() { @Override public String apply(Object[] args) { assertEquals(args.length, 0, "args="+args); return opReturnVal; } }; GeneralisedDynamicMBean mbean = jmxService.registerMBean(ImmutableMap.of(), ImmutableMap.of(opInfo, opImpl), objectName); assertEquals(jmxHelper.operation(objectName, opName), opReturnVal); } @Test public void testInvokeOperationUsingObjectNameWildcard() throws Exception { final String opReturnVal = "my result"; MBeanOperationInfo opInfo = new MBeanOperationInfo(opName, "my descr", new MBeanParameterInfo[0], String.class.getName(), MBeanOperationInfo.ACTION); Function<Object[], String> opImpl = new Function<Object[], String>() { @Override public String apply(Object[] args) { assertEquals(args.length, 0, "args="+args); return opReturnVal; } }; GeneralisedDynamicMBean mbean = jmxService.registerMBean(ImmutableMap.of(), ImmutableMap.of(opInfo, opImpl), objectName); assertEquals(jmxHelper.operation(objectNameWithWildcard, opName), opReturnVal); } @Test public void testInvokeOperationWithArgs() throws Exception { final String opReturnPrefix = "my result prefix/"; String opParam1 = "my param 1"; MBeanOperationInfo opInfo = new MBeanOperationInfo( opName, "my descr", new MBeanParameterInfo[] {new MBeanParameterInfo("myParam1", String.class.getName(), "my param1 descr")}, String.class.getName(), MBeanOperationInfo.ACTION); Function<Object[],String> opImpl = new Function<Object[],String>() { public String apply(Object[] input) { return opReturnPrefix+input[0]; } }; GeneralisedDynamicMBean mbean = jmxService.registerMBean(ImmutableMap.of(), ImmutableMap.of(opInfo, opImpl), objectName); assertEquals(jmxHelper.operation(objectName, opName, opParam1), opReturnPrefix+opParam1); } @Test public void testReconnectsOnJmxServerTemporaryFailure() throws Exception { int port = jmxService.getJmxPort(); GeneralisedDynamicMBean mbean = jmxService.registerMBean(MutableMap.of("myattr", "myval"), objectName); assertEquals(jmxHelper.getAttribute(jmxObjectName, "myattr"), "myval"); // Simulate temporary network-failure jmxService.shutdown(); // Ensure that we have a failed query while the "network is down" try { jmxHelper.getAttribute(jmxObjectName, attributeName); fail(); } catch (Exception e) { if (Throwables2.getFirstThrowableOfType(e, IOException.class) == null) { throw e; } } // Simulate the network restarting jmxService = new JmxService(LOCALHOST_NAME, port); GeneralisedDynamicMBean mbean2 = jmxService.registerMBean(MutableMap.of("myattr", "myval2"), objectName); assertEquals(jmxHelper.getAttribute(jmxObjectName, "myattr"), "myval2"); } @Test(expectedExceptions = {IllegalStateException.class}) public void testJmxCheckInstanceExistsEventuallyThrowsIfNotFound() throws Exception { jmxHelper.assertMBeanExistsEventually(new ObjectName("Brooklyn:type=DoesNotExist,name=doesNotExist"), 1L); } @Test public void testJmxObjectCheckExistsEventuallyReturnsIfFoundImmediately() throws Exception { GeneralisedDynamicMBean mbean = jmxService.registerMBean(objectName); jmxHelper.assertMBeanExistsEventually(jmxObjectName, 1L); } @Test public void testJmxObjectCheckExistsEventuallyTakingLongReturnsIfFoundImmediately() throws Exception { GeneralisedDynamicMBean mbean = jmxService.registerMBean(objectName); jmxHelper.assertMBeanExistsEventually(jmxObjectName, 1L); } @Test public void testJmxObjectCheckExistsEventuallyReturnsIfCreatedDuringPolling() throws Exception { Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(SHORT_WAIT_MS); GeneralisedDynamicMBean mbean = jmxService.registerMBean(objectName); } catch (InterruptedException e) { return; // graceful return } catch (Exception e) { throw Exceptions.propagate(e); } }}); try { t.start(); jmxHelper.assertMBeanExistsEventually(jmxObjectName, TIMEOUT_MS); } finally { t.interrupt(); t.join(TIMEOUT_MS); assertFalse(t.isAlive()); } } @Test public void testSubscribeToJmxNotificationsDirectlyWithJmxHelper() throws Exception { StandardEmitterMBean mbean = jmxService.registerMBean(ImmutableList.of("one"), objectName); int sequence = 0; final List<Notification> received = Lists.newArrayList(); jmxHelper.addNotificationListener(jmxObjectName, new NotificationListener() { public void handleNotification(Notification notif, Object callback) { received.add(notif); }}); final Notification notif = sendNotification(mbean, "one", sequence++, "abc"); Asserts.succeedsEventually(ImmutableMap.of("timeout", TIMEOUT_MS), new Runnable() { public void run() { assertEquals(received.size(), 1); assertNotificationsEqual(received.get(0), notif); }}); } // Visual-inspection test that LOG.warn happens only once; TODO setup a listener to the logging output @Test public void testMBeanNotFoundLoggedOnlyOncePerUrl() throws Exception { ObjectName wrongObjectName = new ObjectName("DoesNotExist:type=DoesNotExist"); // Expect just one log message about: // JMX object DoesNotExist:type=DoesNotExist not found at service:jmx:rmi://localhost:1099/jndi/rmi://localhost:9001/jmxrmi" for (int i = 0; i < 10; i++) { jmxHelper.findMBean(wrongObjectName); } jmxService.shutdown(); jmxHelper.disconnect(); jmxService = newJmxServiceRetrying(LOCALHOST_NAME, 5); jmxHelper = new JmxHelper(jmxService.getUrl()); jmxHelper.connect(); // Expect just one log message about: // JMX object DoesNotExist:type=DoesNotExist not found at service:jmx:rmi://localhost:1099/jndi/rmi://localhost:9001/jmxrmi" for (int i = 0; i < 10; i++) { jmxHelper.findMBean(wrongObjectName); } } private JmxService newJmxServiceRetrying(String host, int retries) throws Exception { Exception lastexception = null; for (int i = 0; i < retries; i++) { try { return new JmxService(host, (int)(11000+(500*Math.random()))); } catch (Exception e) { log.debug("Unable to create JMX service during test - "+retries+" retries remaining"); lastexception = e; } } throw lastexception; } private Notification sendNotification(StandardEmitterMBean mbean, String type, long seq, Object userData) { Notification notif = new Notification(type, mbean, seq); notif.setUserData(userData); mbean.sendNotification(notif); return notif; } private void assertNotificationsEqual(Notification n1, Notification n2) { assertEquals(n1.getType(), n2.getType()); assertEquals(n1.getSequenceNumber(), n2.getSequenceNumber()); assertEquals(n1.getUserData(), n2.getUserData()); assertEquals(n1.getTimeStamp(), n2.getTimeStamp()); assertEquals(n1.getMessage(), n2.getMessage()); } }