/* * 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.entity.java; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; import org.apache.brooklyn.api.entity.EntitySpec; import org.apache.brooklyn.api.location.MachineLocation; import org.apache.brooklyn.core.entity.Entities; import org.apache.brooklyn.core.sensor.BasicAttributeSensor; import org.apache.brooklyn.core.test.entity.TestApplication; import org.apache.brooklyn.entity.java.UsesJmx; import org.apache.brooklyn.entity.java.VanillaJavaAppImpl; import org.apache.brooklyn.entity.java.VanillaJavaAppSshDriver; import org.apache.brooklyn.entity.java.UsesJmx.JmxAgentModes; import org.apache.brooklyn.entity.software.base.SoftwareProcess; import org.apache.brooklyn.entity.software.base.test.jmx.JmxService; import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig; import org.apache.brooklyn.feed.jmx.JmxFeed; import org.apache.brooklyn.test.EntityTestUtils; import org.apache.brooklyn.util.collections.MutableMap; import org.apache.brooklyn.util.exceptions.Exceptions; 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.apache.brooklyn.location.ssh.SshMachineLocation; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; public class EntityPollingTest { private static final Logger LOG = LoggerFactory.getLogger(EntityPollingTest.class); private JmxService jmxService; private TestApplication app; private SoftwareProcess entity; private static final BasicAttributeSensor<String> stringAttribute = new BasicAttributeSensor<String>( String.class, "brooklyn.test.stringAttribute", "Brooklyn testing int attribute"); private String objectName = "Brooklyn:type=MyTestMBean,name=myname"; private static final ObjectName jmxObjectName; static { try { jmxObjectName = new ObjectName("Brooklyn:type=MyTestMBean,name=myname"); } catch (MalformedObjectNameException e) { throw Exceptions.propagate(e); } } private static final String attributeName = "myattrib"; public static class SubVanillaJavaApp extends VanillaJavaAppImpl { private JmxFeed feed; @Override protected void connectSensors() { super.connectSensors(); // Add a sensor that we can explicitly set in jmx feed = JmxFeed.builder() .entity(this) .pollAttribute(new JmxAttributePollConfig<String>(stringAttribute) .objectName(jmxObjectName) .attributeName(attributeName)) .build(); } @Override public void disconnectSensors() { super.disconnectSensors(); if (feed != null) feed.stop(); } @SuppressWarnings({ "rawtypes", "unchecked" }) @Override public Class getDriverInterface() { return null; } @Override public VanillaJavaAppSshDriver newDriver(MachineLocation loc) { return new VanillaJavaAppSshDriver(this, (SshMachineLocation)loc) { @Override public void install() { // no-op } @Override public void customize() { // no-op } @Override public void launch() { // no-op } @Override public boolean isRunning() { return true; } @Override public void stop() { // no-op } @Override public void kill() { // no-op } }; } }; @BeforeMethod(alwaysRun=true) public void setUp() { app = TestApplication.Factory.newManagedInstanceForTests(); /* * Create an entity, using real entity code, but that swaps out the external process * for a JmxService that we can control in the test. */ entity = app.createAndManageChild(EntitySpec.create(SoftwareProcess.class).impl(SubVanillaJavaApp.class) .configure("rmiRegistryPort", 40123) .configure("mxbeanStatsEnabled", false) .configure(UsesJmx.JMX_AGENT_MODE, JmxAgentModes.JMX_RMI_CUSTOM_AGENT)); } @AfterMethod(alwaysRun=true) public void tearDown() throws Exception { if (app != null) Entities.destroyAll(app.getManagementContext()); if (jmxService != null) jmxService.shutdown(); } // Tests that the happy path works @Test(groups="Integration") public void testSimpleConnection() throws Exception { jmxService = new JmxService("localhost", 40123); jmxService.registerMBean(ImmutableMap.of(attributeName, "myval"), objectName); app.start(ImmutableList.of(new SshMachineLocation(MutableMap.of("address", "localhost")))); // Starts with value defined when registering... EntityTestUtils.assertAttributeEqualsEventually(entity, stringAttribute, "myval"); } // Test that connect will keep retrying (e.g. start script returns before the JMX server is up) @Test(groups="Integration") public void testEntityWithDelayedJmxStartupWillKeepRetrying() { // In 2 seconds time, we'll start the JMX server Thread t = new Thread(new Runnable() { public void run() { try { Thread.sleep(2000); jmxService = new JmxService("localhost", 40123); jmxService.registerMBean(ImmutableMap.of(attributeName, "myval"), objectName); } catch (Exception e) { LOG.error("Error in testEntityWithDelayedJmxStartupWillKeepRetrying", e); throw Exceptions.propagate(e); } }}); try { t.start(); app.start(ImmutableList.of(new SshMachineLocation(MutableMap.of("address", "localhost")))); EntityTestUtils.assertAttributeEqualsEventually(entity, stringAttribute, "myval"); } finally { t.interrupt(); } } @Test(groups="Integration") public void testJmxConnectionGoesDownRequiringReconnect() throws Exception { jmxService = new JmxService("localhost", 40123); jmxService.registerMBean(ImmutableMap.of(attributeName, "myval"), objectName); app.start(ImmutableList.of(new SshMachineLocation(MutableMap.of("address", "localhost")))); EntityTestUtils.assertAttributeEqualsEventually(entity, stringAttribute, "myval"); // Shutdown the MBeanServer - simulates network failure so can't connect jmxService.shutdown(); // TODO Want a better way of determining that the entity is down; ideally should have // sensor for entity-down that's wired up to a JMX attribute? Thread.sleep(5000); // Restart MBeanServer, and set attribute to different value; expect it to be polled again jmxService = new JmxService("localhost", 40123); jmxService.registerMBean(ImmutableMap.of(attributeName, "myval2"), objectName); EntityTestUtils.assertAttributeEqualsEventually(entity, stringAttribute, "myval2"); } }