/*******************************************************************************
* This file is part of OpenNMS(R).
*
* Copyright (C) 2009-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.netmgt.ncs.northbounder;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.net.ssl.SSLContext;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import org.apache.http.HttpEntity;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.HttpVersion;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.HttpEntityEnclosingRequestBase;
import org.apache.http.client.params.ClientPNames;
import org.apache.http.client.params.CookiePolicy;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.CoreConnectionPNames;
import org.apache.http.params.CoreProtocolPNames;
import org.apache.http.params.HttpParams;
import org.opennms.core.utils.EmptyKeyRelaxedTrustProvider;
import org.opennms.core.utils.EmptyKeyRelaxedTrustSSLContext;
import org.opennms.core.utils.HttpResponseRange;
import org.opennms.core.utils.LogUtils;
import org.opennms.netmgt.alarmd.api.NorthboundAlarm;
import org.opennms.netmgt.alarmd.api.NorthboundAlarm.AlarmType;
import org.opennms.netmgt.alarmd.api.NorthbounderException;
import org.opennms.netmgt.alarmd.api.support.AbstractNorthbounder;
import org.opennms.netmgt.ncs.northbounder.transfer.ServiceAlarm;
import org.opennms.netmgt.ncs.northbounder.transfer.ServiceAlarmNotification;
/**
* Forwards north bound alarms via HTTP.
* FIXME: Needs lots of work still :(
*
* @author <a mailto:david@opennms.org>David Hustace</a>
*/
public class NCSNorthbounder extends AbstractNorthbounder {
//FIXME: This should be wired with Spring but is implmented as was in the PSM
// Make sure that the {@link EmptyKeyRelaxedTrustSSLContext} algorithm
// is available to JSSE
static {
//this is a safe call because the method returns -1 if it is already installed (by PageSequenceMonitor, etc.)
java.security.Security.addProvider(new EmptyKeyRelaxedTrustProvider());
}
private static final String COMPONENT_NAME = "componentName";
private static final String COMPONENT_FOREIGN_ID = "componentForeignId";
private static final String COMPONENT_FOREIGN_SOURCE = "componentForeignSource";
private static final String COMPONENT_TYPE = "componentType";
private NCSNorthbounderConfig m_config;
private JAXBContext m_context;
private Marshaller m_marshaller;
public NCSNorthbounder(NCSNorthbounderConfig config) throws JAXBException {
super("NCSNorthbounder");
m_context = JAXBContext.newInstance(ServiceAlarmNotification.class);
m_marshaller = m_context.createMarshaller();
m_marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true);
m_config = config;
setNaglesDelay(m_config.getNaglesDelay());
}
@Override
public boolean accepts(NorthboundAlarm alarm) {
if (!m_config.isEnabled()) return false;
if (alarm.getAlarmType() == null) return false;
if (alarm.getAlarmType() == AlarmType.NOTIFICATION) return false;
if(m_config.getAcceptableUeis() != null && m_config.getAcceptableUeis().size() != 0 && !m_config.getAcceptableUeis().contains(alarm.getUei())) return false;
Map<String, String> alarmParms = getParameterMap(alarm.getEventParms());
// in order to determine the service we need to have the following parameters set in the events
if (!alarmParms.containsKey(COMPONENT_TYPE)) return false;
if (!alarmParms.containsKey(COMPONENT_FOREIGN_SOURCE)) return false;
if (!alarmParms.containsKey(COMPONENT_FOREIGN_ID)) return false;
if (!alarmParms.containsKey(COMPONENT_NAME)) return false;
// we only send events for "Service" components
if (!"Service".equals(alarmParms.get(COMPONENT_TYPE))) return false;
return true;
}
private ServiceAlarmNotification toServiceAlarms(List<NorthboundAlarm> alarms) {
List<ServiceAlarm> serviceAlarms = new ArrayList<ServiceAlarm>(alarms.size());
for(NorthboundAlarm alarm : alarms) {
serviceAlarms.add(toServiceAlarm(alarm));
}
return new ServiceAlarmNotification(serviceAlarms);
}
private ServiceAlarm toServiceAlarm(NorthboundAlarm alarm) {
AlarmType alarmType = alarm.getAlarmType();
Map<String, String> alarmParms = getParameterMap(alarm.getEventParms());
String id = alarmParms.get(COMPONENT_FOREIGN_SOURCE)+":"+alarmParms.get(COMPONENT_FOREIGN_ID);
String name = alarmParms.get(COMPONENT_NAME);
return new ServiceAlarm(id, name, alarmType == AlarmType.PROBLEM ? "Down" : "Up");
}
Map<String, String> getParameterMap(String parmString) {
Map<String, String> parmMap = new HashMap<String, String>();
String[] parms = parmString.split(";");
for(String parm : parms) {
if (parm.endsWith("(string,text)")) {
// we only include string valued keys in the map
parm = parm.substring(0, parm.length()-"(string,text)".length());
int eq = parm.indexOf('=');
if (0 < eq && eq < parm.length()) {
String key = parm.substring(0, eq);
String val = parm.substring(eq+1);
parmMap.put(key, val);
}
}
}
return parmMap;
}
@Override
public void forwardAlarms(List<NorthboundAlarm> alarms) throws NorthbounderException {
if (!m_config.isEnabled()) return;
LogUtils.infof(this, "Forwarding %d alarms", alarms.size());
HttpEntity entity = createEntity(alarms);
postAlarms(entity);
}
private void postAlarms(HttpEntity entity) {
//Need a configuration bean for these
int connectionTimeout = 3000;
int socketTimeout = 3000;
Integer retryCount = Integer.valueOf(3);
HttpVersion httpVersion = determineHttpVersion(m_config.getHttpVersion());
String policy = CookiePolicy.BROWSER_COMPATIBILITY;
URI uri = m_config.getURI();
DefaultHttpClient client = new DefaultHttpClient(buildParams(httpVersion, connectionTimeout,
socketTimeout, policy, m_config.getVirtualHost()));
client.setHttpRequestRetryHandler(new DefaultHttpRequestRetryHandler(retryCount, false));
if ("https".equals(uri.getScheme())) {
final SchemeRegistry registry = client.getConnectionManager().getSchemeRegistry();
final Scheme https = registry.getScheme("https");
// Override the trust validation with a lenient implementation
SSLSocketFactory factory = null;
try {
factory = new SSLSocketFactory(SSLContext.getInstance(EmptyKeyRelaxedTrustSSLContext.ALGORITHM), SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
} catch (Throwable e) {
throw new NorthbounderException(e);
}
final Scheme lenient = new Scheme(https.getName(), https.getDefaultPort(), factory);
// This will replace the existing "https" schema
registry.register(lenient);
}
HttpEntityEnclosingRequestBase method = m_config.getMethod().getRequestMethod(uri);
method.setEntity(entity);
method.getParams().setParameter(CoreProtocolPNames.USER_AGENT, m_config.getUserAgent());
HttpResponse response = null;
try {
response = client.execute(method);
} catch (ClientProtocolException e) {
throw new NorthbounderException(e);
} catch (IOException e) {
throw new NorthbounderException(e);
}
if (response != null) {
int code = response.getStatusLine().getStatusCode();
HttpResponseRange range = new HttpResponseRange("200-399");
if (!range.contains(code)) {
System.err.println("response code out of range for uri:" + uri + ". Expected " + range + " but received " + code);
throw new NorthbounderException("response code out of range for uri:" + uri + ". Expected " + range + " but received " + code);
}
}
System.err.println(response != null ? response.getStatusLine().getReasonPhrase() : "Response was null");
LogUtils.debugf(this, response != null ? response.getStatusLine().getReasonPhrase() : "Response was null");
}
private HttpEntity createEntity(List<NorthboundAlarm> alarms) {
try {
ByteArrayOutputStream out = new ByteArrayOutputStream();
// marshall the output
m_marshaller.marshal(toServiceAlarms(alarms), out);
// verify its matches the expected results
byte[] utf8 = out.toByteArray();
ByteArrayEntity entity = new ByteArrayEntity(utf8);
entity.setContentType("application/xml");
return entity;
} catch (JAXBException e) {
throw new NorthbounderException("failed to convert alarms to xml", e);
}
}
private HttpVersion determineHttpVersion(String version) {
HttpVersion httpVersion = null;
if (version != "1.0") {
httpVersion = HttpVersion.HTTP_1_1;
} else {
httpVersion = HttpVersion.HTTP_1_0;
}
return httpVersion;
}
private HttpParams buildParams(HttpVersion protocolVersion,
int connectionTimeout, int socketTimeout, String policy,
String vHost) {
HttpParams parms = new BasicHttpParams();
parms.setParameter(CoreProtocolPNames.PROTOCOL_VERSION, protocolVersion);
parms.setIntParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, connectionTimeout);
parms.setIntParameter(CoreConnectionPNames.SO_TIMEOUT, socketTimeout);
parms.setParameter(ClientPNames.COOKIE_POLICY, policy);
if (vHost != null) {
parms.setParameter(ClientPNames.VIRTUAL_HOST, new HttpHost(vHost, 8080));
}
return parms;
}
public NCSNorthbounderConfig getConfig() {
return m_config;
}
public void setConfig(NCSNorthbounderConfig config) {
m_config = config;
}
}