/*
* RHQ Management Platform
* Copyright (C) 2005-2013 Red Hat, Inc.
* All rights reserved.
*
* This program 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 version 2 of the License.
*
* This program 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 this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA
*/
package org.rhq.enterprise.server.plugins.alertSnmp;
import static org.mockito.Matchers.eq;
import static org.mockito.Mockito.*;
import static org.rhq.core.domain.alert.AlertPriority.HIGH;
import static org.rhq.core.domain.alert.notification.ResultState.FAILURE;
import static org.rhq.core.domain.alert.notification.ResultState.SUCCESS;
import static org.rhq.enterprise.server.plugins.alertSnmp.SnmpInfo.PARAM_HOST;
import static org.rhq.enterprise.server.plugins.alertSnmp.SnmpInfo.PARAM_PORT;
import static org.rhq.enterprise.server.plugins.alertSnmp.SnmpInfo.PARAM_TRAP_OID;
import static org.rhq.enterprise.server.plugins.alertSnmp.SnmpInfo.PARAM_VARIABLE_BINDING_PREFIX;
import static org.testng.Assert.*;
import java.util.Arrays;
import java.util.ListIterator;
import java.util.Vector;
import java.util.concurrent.ConcurrentLinkedQueue;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.snmp4j.CommandResponder;
import org.snmp4j.CommandResponderEvent;
import org.snmp4j.PDU;
import org.snmp4j.Snmp;
import org.snmp4j.mp.SnmpConstants;
import org.snmp4j.smi.Address;
import org.snmp4j.smi.OID;
import org.snmp4j.smi.OctetString;
import org.snmp4j.smi.SMIConstants;
import org.snmp4j.smi.UdpAddress;
import org.snmp4j.smi.VariableBinding;
import org.snmp4j.transport.DefaultUdpTransportMapping;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;
import org.rhq.core.domain.alert.Alert;
import org.rhq.core.domain.alert.AlertDefinition;
import org.rhq.core.domain.alert.AlertPriority;
import org.rhq.core.domain.alert.notification.SenderResult;
import org.rhq.core.domain.configuration.Configuration;
import org.rhq.core.domain.resource.Resource;
import org.rhq.core.util.StringUtil;
import org.rhq.enterprise.server.alert.AlertManagerLocal;
import org.rhq.enterprise.server.resource.ResourceManagerLocal;
/**
* @author Thomas Segismont
*/
public class SnmpSenderTest {
private static final Log LOG = LogFactory.getLog(SnmpSenderTest.class);
private static final String TEST_TRAP_OID_PLUGIN_CONFIG = "1.3.6.1.4.1.18017";
private static final String TEST_TRAP_OID_ALERT_PARAM = "1.3.6.1.4.1.18018";
private static final String TEST_VARIABLE_BINDING_PREFIX = "1.3.6.1.4.1.18019";
private static final String TEST_HOST = "127.0.0.1";
private static final int TEST_PORT = 35162;
private static final String TEST_PORT_VARIABLE = "alert.snmp.test.port";
private ConcurrentLinkedQueue<PDU> receivedTraps;
private Snmp snmp;
@Mock
private ResourceManagerLocal resourceManager;
@Mock
private AlertManagerLocal alertManager;
private TestSnmpSender snmpSender;
private Configuration pluginConfiguration;
@BeforeMethod
public void setUp() throws Exception {
receivedTraps = new ConcurrentLinkedQueue<PDU>();
snmp = new Snmp(new DefaultUdpTransportMapping());
Address targetAddress = new UdpAddress(getTestPort());
boolean installedTrapListener = snmp.addNotificationListener(targetAddress, new CommandResponder() {
@Override
public void processPdu(CommandResponderEvent event) {
receivedTraps.offer(event.getPDU());
}
});
if (!installedTrapListener) {
throw new RuntimeException("Could not install trap listener");
}
MockitoAnnotations.initMocks(this);
pluginConfiguration = new Configuration();
pluginConfiguration.setSimpleValue("snmpVersion", "2c");
pluginConfiguration.setSimpleValue("trapOid", TEST_TRAP_OID_PLUGIN_CONFIG);
pluginConfiguration.setSimpleValue("community", "public");
snmpSender = new TestSnmpSender(resourceManager, alertManager, pluginConfiguration);
}
private static int getTestPort() {
String testPortVariable = System.getProperty(TEST_PORT_VARIABLE);
if (StringUtil.isNotBlank(testPortVariable)) {
try {
int port = Integer.parseInt(testPortVariable);
LOG.info("Using port " + testPortVariable + " for SNMP traps");
return port;
} catch (NumberFormatException e) {
LOG.warn("Invalid port variable: " + testPortVariable);
}
}
LOG.info("Using default port " + TEST_PORT + " for SNMP traps");
return TEST_PORT;
}
@AfterMethod
public void tearDown() throws Exception {
if (snmp != null) {
snmp.close();
}
}
@Test
public void shouldReturnSimpleFailureForInvalidNotificationParameters() {
Configuration alertParameters = new Configuration();
snmpSender.setAlertParameters(alertParameters);
SenderResult result = snmpSender.send(createAlertForResourceWithId(13004, "", "", HIGH));
assertNotNull(result);
assertEquals(result.getState(), FAILURE);
assertEquals(result.getFailureMessages().size(), 1);
String expectedError = SnmpInfo.load(alertParameters,pluginConfiguration).error;
assertNotNull(expectedError);
assertEquals(result.getFailureMessages().get(0), expectedError);
}
@Test
public void shouldReturnSimpleFailureWhenErrorOccurs() {
Configuration alertParameters = new Configuration();
alertParameters.setSimpleValue(PARAM_HOST, TEST_HOST);
alertParameters.setSimpleValue(PARAM_VARIABLE_BINDING_PREFIX, TEST_VARIABLE_BINDING_PREFIX);
alertParameters.setSimpleValue(PARAM_PORT, String.valueOf(getTestPort()));
snmpSender.setAlertParameters(alertParameters);
int resourceId = 13004;
Alert alert = createAlertForResourceWithId(resourceId, "", "", HIGH);
String exceptionMessage = "Test Error";
when(resourceManager.getResourceLineage(eq(resourceId))).thenThrow(new RuntimeException(exceptionMessage));
SenderResult result = snmpSender.send(alert);
assertNotNull(result);
assertEquals(result.getState(), FAILURE);
assertEquals(result.getFailureMessages().size(), 1);
String actualErrorMessage = result.getFailureMessages().get(0);
assertTrue(actualErrorMessage.endsWith(exceptionMessage), "Unexpected error message: " + actualErrorMessage);
}
@Test(timeOut = 1000 * 60)
public void testSendWithDefaultSnmpTrapOid() throws Exception {
Configuration alertParameters = new Configuration();
alertParameters.setSimpleValue(PARAM_HOST, TEST_HOST);
alertParameters.setSimpleValue(PARAM_VARIABLE_BINDING_PREFIX, TEST_VARIABLE_BINDING_PREFIX);
alertParameters.setSimpleValue(PARAM_PORT, String.valueOf(getTestPort()));
snmpSender.setAlertParameters(alertParameters);
testSendWithSnmpTrapOid(new OID(TEST_TRAP_OID_PLUGIN_CONFIG));
}
@Test(timeOut = 1000 * 60)
public void testSendWithSpecificSnmpTrapOid() throws Exception {
Configuration alertParameters = new Configuration();
alertParameters.setSimpleValue(PARAM_HOST, TEST_HOST);
alertParameters.setSimpleValue(PARAM_VARIABLE_BINDING_PREFIX, TEST_VARIABLE_BINDING_PREFIX);
alertParameters.setSimpleValue(PARAM_PORT, String.valueOf(getTestPort()));
alertParameters.setSimpleValue(PARAM_TRAP_OID, TEST_TRAP_OID_ALERT_PARAM);
snmpSender.setAlertParameters(alertParameters);
testSendWithSnmpTrapOid(new OID(TEST_TRAP_OID_ALERT_PARAM));
}
private void testSendWithSnmpTrapOid(OID snmpTrapOid) throws InterruptedException {
int resourceId = 13004;
String resourceName = "Resource " + resourceId;
String alertDefinitionName = "Alert Definition " + resourceId;
AlertPriority alertPriority = HIGH;
Alert alert = createAlertForResourceWithId(resourceId, resourceName, alertDefinitionName, alertPriority);
Resource platformResouce = new Resource();
String platformName = "Platform Resource " + resourceId;
platformResouce.setName(platformName);
when(resourceManager.getResourceLineage(eq(resourceId))).thenReturn(
Arrays.asList(platformResouce, alert.getAlertDefinition().getResource()));
String alertConditions = "Alert Conditions " + resourceId;
when(alertManager.prettyPrintAlertConditions(eq(alert), eq(false))).thenReturn(alertConditions);
String alertUrl = "https://www.rhq.com/alert/" + resourceId;
when(alertManager.prettyPrintAlertURL(eq(alert))).thenReturn(alertUrl);
assertNull(receivedTraps.peek(), "Something sent a trap before on our test port");
SenderResult result = snmpSender.send(alert);
assertEquals(result.getState(), SUCCESS, result.getFailureMessages().toString());
while (receivedTraps.peek() == null) {
Thread.sleep(1000);
}
PDU pdu = receivedTraps.poll();
assertNull(receivedTraps.peek(), "Only one trap should have been received");
assertExpectedPdu(pdu, new PduExpectedValues(snmpTrapOid, resourceName, alertDefinitionName, alertPriority,
platformName, alertConditions, alertUrl));
}
private void assertExpectedPdu(PDU pdu, PduExpectedValues expectedValues) {
Vector variableBindings = pdu.getVariableBindings();
assertTrue(variableBindings.size() == 9, "Variable bindings should contain 9 variable bindings and not "
+ variableBindings.size() + ": " + variableBindings);
@SuppressWarnings("unchecked")
ListIterator<VariableBinding> variableBindingsIterator = variableBindings.listIterator();
VariableBinding variableBinding = variableBindingsIterator.next();
assertEquals(variableBinding.getOid(), SnmpConstants.sysUpTime);
variableBinding = variableBindingsIterator.next();
assertEquals(variableBinding.getOid(), SnmpConstants.snmpTrapOID);
assertEquals(variableBinding.getVariable(), expectedValues.getSnmpTrapOid());
OID oidPrefix = new OID(TEST_VARIABLE_BINDING_PREFIX);
while (variableBindingsIterator.hasNext()) {
variableBinding = variableBindingsIterator.next();
assertVariableBindingIsPrefixed(variableBinding, oidPrefix);
assertVariableBindingHasStringValue(variableBinding);
switch (variableBindingsIterator.previousIndex()) {
case 2:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getAlertDefinitionName()));
break;
case 3:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getResourceName()));
break;
case 4:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getPlatformName()));
break;
case 5:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getAlertConditions()));
break;
case 6:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getAlertPriority()
.toString().toLowerCase()));
break;
case 7:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getAlertUrl()));
break;
case 8:
assertEquals(variableBinding.getVariable(), new OctetString(expectedValues.getPlatformName() + "::"
+ expectedValues.getResourceName() + "::"));
break;
default:
throw new RuntimeException("Unexpected index: " + variableBindingsIterator.previousIndex());
}
}
}
private void assertVariableBindingHasStringValue(VariableBinding variableBinding) {
assertEquals(variableBinding.getVariable().getSyntax(), SMIConstants.SYNTAX_OCTET_STRING,
"Variable binding value [" + variableBinding.getVariable() + "] has wrong type");
}
private void assertVariableBindingIsPrefixed(VariableBinding variableBinding, OID oidPrefix) {
assertTrue(variableBinding.getOid().startsWith(oidPrefix), "Variable binding OID [" + variableBinding.getOid()
+ "] has wrong prefix");
}
private Alert createAlertForResourceWithId(int resourceId, String resourceName, String alertDefinitionName,
AlertPriority alertPriority) {
Resource resource = new Resource();
resource.setId(resourceId);
resource.setName(resourceName);
AlertDefinition alertDefinition = new AlertDefinition();
alertDefinition.setName(alertDefinitionName);
alertDefinition.setResource(resource);
alertDefinition.setPriority(alertPriority);
Alert alert = new Alert();
alert.setAlertDefinition(alertDefinition);
return alert;
}
private static final class TestSnmpSender extends SnmpSender {
TestSnmpSender(ResourceManagerLocal resourceManager, AlertManagerLocal alertManager,
Configuration pluginConfiguration) {
super(resourceManager, alertManager);
this.preferences = pluginConfiguration;
}
void setAlertParameters(Configuration alertParameters) {
this.alertParameters = alertParameters;
}
}
private static final class PduExpectedValues {
private final OID snmpTrapOid;
private final String resourceName;
private final String alertDefinitionName;
private final AlertPriority alertPriority;
private final String platformName;
private final String alertConditions;
private final String alertUrl;
private PduExpectedValues(OID snmpTrapOid, String resourceName, String alertDefinitionName,
AlertPriority alertPriority, String platformName, String alertConditions, String alertUrl) {
this.snmpTrapOid = snmpTrapOid;
this.resourceName = resourceName;
this.alertDefinitionName = alertDefinitionName;
this.alertPriority = alertPriority;
this.platformName = platformName;
this.alertConditions = alertConditions;
this.alertUrl = alertUrl;
}
public OID getSnmpTrapOid() {
return snmpTrapOid;
}
public String getResourceName() {
return resourceName;
}
public String getAlertDefinitionName() {
return alertDefinitionName;
}
public AlertPriority getAlertPriority() {
return alertPriority;
}
public String getPlatformName() {
return platformName;
}
public String getAlertConditions() {
return alertConditions;
}
public String getAlertUrl() {
return alertUrl;
}
}
}