/******************************************************************************* * This file is part of OpenNMS(R). * * Copyright (C) 2010-2011 The OpenNMS Group, Inc. * OpenNMS(R) is Copyright (C) 1999-2011 The OpenNMS Group, Inc. * * OpenNMS(R) is a registered trademark of The OpenNMS Group, Inc. * * OpenNMS(R) is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, * or (at your option) any later version. * * OpenNMS(R) is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with OpenNMS(R). If not, see: * http://www.gnu.org/licenses/ * * For more information contact: * OpenNMS(R) Licensing <license@opennms.org> * http://www.opennms.org/ * http://www.opennms.com/ *******************************************************************************/ package org.opennms.core.test.snmp; import static org.opennms.core.utils.InetAddressUtils.addr; import static org.opennms.core.utils.InetAddressUtils.str; import java.io.IOException; import java.lang.reflect.Method; import java.net.BindException; import java.net.InetAddress; import java.net.UnknownHostException; import java.util.HashMap; import java.util.Map; import org.opennms.core.test.snmp.annotations.JUnitSnmpAgent; import org.opennms.core.test.snmp.annotations.JUnitSnmpAgents; import org.opennms.core.utils.InetAddressUtils; import org.opennms.core.utils.LogUtils; import org.opennms.mock.snmp.MockSnmpAgent; import org.opennms.netmgt.config.SnmpAgentConfigFactory; import org.opennms.netmgt.config.SnmpAgentConfigProxyMapper; import org.opennms.netmgt.snmp.SnmpAgentAddress; import org.opennms.netmgt.snmp.mock.MockSnmpStrategy; import org.springframework.core.io.DefaultResourceLoader; import org.springframework.core.io.Resource; import org.springframework.core.io.ResourceLoader; import org.springframework.test.context.TestContext; import org.springframework.test.context.TestExecutionListener; import org.springframework.test.context.support.AbstractTestExecutionListener; /** * This {@link TestExecutionListener} looks for the {@link JUnitSnmpAgent} annotation * and uses attributes on it to launch a mock SNMP agent for use during unit testing. */ public class JUnitSnmpAgentExecutionListener extends AbstractTestExecutionListener { private static final Boolean useMockSnmpStrategyDefault = false; private static final String USE_STRATEGY_PROPERTY = "org.opennms.core.test-api.snmp.useMockSnmpStrategy"; private static final String STRATEGY_CLASS_PROPERTY = "org.opennms.snmp.strategyClass"; private static final String STRATEGY_CLASS_KEY = "org.opennms.core.test-api.snmp.strategyClass"; private static final String AGENT_KEY = "org.opennms.core.test-api.snmp.agentList"; private static final String PROVIDER_KEY = "org.opennms.core.test-api.snmp.dataProvider"; @Override public void beforeTestMethod(final TestContext testContext) throws Exception { super.beforeTestClass(testContext); final JUnitSnmpAgents agents = findAgentListAnnotation(testContext); final JUnitSnmpAgent agent = findAgentAnnotation(testContext); if (agents == null && agent == null) { // no annotations found return; } testContext.setAttribute(STRATEGY_CLASS_KEY, System.getProperty(STRATEGY_CLASS_PROPERTY)); final HashMap<SnmpAgentAddress,MockSnmpAgent> mockAgents = new HashMap<SnmpAgentAddress,MockSnmpAgent>(); testContext.setAttribute(AGENT_KEY, mockAgents); final String useMockSnmpStrategyString = System.getProperty(USE_STRATEGY_PROPERTY, useMockSnmpStrategyDefault.toString()); final Boolean useMockSnmpStrategy = Boolean.valueOf(useMockSnmpStrategyString); final MockSnmpDataProvider provider; if (useMockSnmpStrategy) { System.setProperty(STRATEGY_CLASS_PROPERTY, MockSnmpStrategy.class.getName()); provider = new MockSnmpStrategyDataProvider(); } else { provider = new MockSnmpAgentDataProvider(mockAgents); } testContext.setAttribute(PROVIDER_KEY, provider); if (agents != null) { for (final JUnitSnmpAgent a : agents.value()) { handleSnmpAgent(testContext, a, provider); } } handleSnmpAgent(testContext, findAgentAnnotation(testContext), provider); if (testContext.getTestInstance() instanceof MockSnmpDataProviderAware) { LogUtils.debugf(this, "injecting data provider into MockSnmpDataProviderAware test: %s", testContext.getTestInstance()); ((MockSnmpDataProviderAware)testContext.getTestInstance()).setMockSnmpDataProvider(provider); } } @Override public void afterTestMethod(final TestContext testContext) throws Exception { super.afterTestMethod(testContext); final MockSnmpDataProvider provider = (MockSnmpDataProvider)testContext.getAttribute(PROVIDER_KEY); if (provider != null) { provider.resetData(); } final String strategyClass = (String)testContext.getAttribute(STRATEGY_CLASS_KEY); if (strategyClass != null) { System.setProperty(STRATEGY_CLASS_PROPERTY, strategyClass); } } private void handleSnmpAgent(final TestContext testContext, final JUnitSnmpAgent config, MockSnmpDataProvider provider) throws IOException, UnknownHostException, InterruptedException { if (config == null) return; String factoryClassName = "unknown"; try { final SnmpAgentConfigFactory factory = testContext.getApplicationContext().getBean("snmpPeerFactory", SnmpAgentConfigFactory.class); factoryClassName = factory.getClass().getName(); } catch (final Throwable t) { // ignore } if (!factoryClassName.contains("ProxySnmpAgentConfigFactory")) { LogUtils.warnf(this, "SNMP Peer Factory (%s) is not the ProxySnmpAgentConfigFactory -- did you forget to include applicationContext-proxy-snmp.xml?", factoryClassName); } final String useMockSnmpStrategy = System.getProperty(USE_STRATEGY_PROPERTY, useMockSnmpStrategyDefault.toString()); LogUtils.debugf(this, "handleSnmpAgent(testContext, %s, %s)", config, useMockSnmpStrategy); String host = config.host(); if (host == null || "".equals(host)) { /* * NOTE: This call produces different results on different platforms so make * sure your client code is aware of this. If you use the {@link ProxySnmpAgentConfigFactory} * by including the <code>classpath:/META-INF/opennms/applicationContext-proxy-snmp.xml</code> * Spring context, you probably won't need to deal with this. It will override the * SnmpPeerFactory with the correct values. * * Linux: 127.0.0.1 * Mac OS: primary external interface */ host = InetAddressUtils.getLocalHostAddressAsString(); //host = "127.0.0.1"; } final ResourceLoader loader = new DefaultResourceLoader(); final Resource resource = loader.getResource(config.resource()); // NOTE: The default value for config.port is specified inside {@link JUnitSnmpAgent} final InetAddress hostAddress = addr(host); final int port = config.port(); final SnmpAgentAddress agentAddress = new SnmpAgentAddress(hostAddress, port); final InetAddress localHost = InetAddress.getLocalHost(); final SnmpAgentConfigProxyMapper mapper = SnmpAgentConfigProxyMapper.getInstance(); SnmpAgentAddress listenAddress = null; // try to find an unused port on localhost int mappedPort = 1161; do { listenAddress = new SnmpAgentAddress(localHost, mappedPort++); } while (mapper.contains(listenAddress)); if (Boolean.valueOf(useMockSnmpStrategy)) { // map to itself =) mapper.addProxy(hostAddress, agentAddress); } else { MockSnmpAgent agent = null; while (agent == null) { try { agent = MockSnmpAgent.createAgentAndRun(resource.getURL(), str(listenAddress.getAddress()) + "/" + listenAddress.getPort()); break; } catch (final InterruptedException e) { if (e.getCause() instanceof BindException && e.getCause().getMessage().contains("already in use")) { do { listenAddress = new SnmpAgentAddress(localHost, mappedPort++); } while (mapper.contains(listenAddress)); } else { throw e; } } } mapper.addProxy(hostAddress, listenAddress); LogUtils.debugf(this, "using MockSnmpAgent on %s for 'real' address %s", listenAddress, agentAddress); @SuppressWarnings("unchecked") final Map<SnmpAgentAddress,MockSnmpAgent> agents = (Map<SnmpAgentAddress,MockSnmpAgent>)testContext.getAttribute(AGENT_KEY); agents.put(agentAddress, agent); } provider.setDataForAddress(agentAddress, resource); } private JUnitSnmpAgent findAgentAnnotation(final TestContext testContext) { final Method testMethod = testContext.getTestMethod(); final JUnitSnmpAgent config = testMethod.getAnnotation(JUnitSnmpAgent.class); if (config != null) { return config; } final Class<?> testClass = testContext.getTestClass(); return testClass.getAnnotation(JUnitSnmpAgent.class); } private JUnitSnmpAgents findAgentListAnnotation(final TestContext testContext) { final Method testMethod = testContext.getTestMethod(); final JUnitSnmpAgents config = testMethod.getAnnotation(JUnitSnmpAgents.class); if (config != null) { return config; } final Class<?> testClass = testContext.getTestClass(); return testClass.getAnnotation(JUnitSnmpAgents.class); } private static final class MockSnmpStrategyDataProvider implements MockSnmpDataProvider { @Override public void setDataForAddress(final SnmpAgentAddress address, final Resource resource) { try { MockSnmpStrategy.setDataForAddress(address, resource); } catch (final Throwable t) { LogUtils.warnf(this, t, "Unable to set mock SNMP data for %s", address); } } @Override public void resetData() { MockSnmpStrategy.resetData(); } } private static final class MockSnmpAgentDataProvider implements MockSnmpDataProvider { private final Map<SnmpAgentAddress, MockSnmpAgent> m_agents; public MockSnmpAgentDataProvider(final Map<SnmpAgentAddress,MockSnmpAgent> mockAgents) { m_agents = mockAgents; } @Override public void setDataForAddress(final SnmpAgentAddress address, final Resource resource) throws IOException { final MockSnmpAgent agent = m_agents.get(address); if (agent == null) { LogUtils.warnf(this, "Unable to set mock SNMP data for %s: no such agent", address); return; } agent.updateValuesFromResource(resource.getURL()); } @Override public void resetData() { for (final MockSnmpAgent agent : m_agents.values()) { try { agent.shutDownAndWait(); } catch (final InterruptedException e) { LogUtils.debugf(this, e, "Unable to shut down agent %s", agent); Thread.currentThread().interrupt(); } } m_agents.clear(); } } }