org.opendaylight.centinel.output.CentinelOutput.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.centinel.output.CentinelOutput.java

Source

/*
 * Copyright (c) 2015 Tata Consultancy services and others.  All rights reserved.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
 * and is available at http://www.eclipse.org/legal/epl-v10.html
 */

package org.opendaylight.centinel.output;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.inject.Inject;
import javax.json.Json;
import javax.json.JsonBuilderFactory;
import javax.json.JsonObject;
import javax.ws.rs.core.MediaType;

import org.apache.avro.Schema;
import org.apache.avro.generic.GenericData.Record;
import org.apache.avro.generic.GenericRecord;
import org.apache.avro.reflect.ReflectData;
import org.apache.commons.codec.binary.Base64;
import org.graylog2.plugin.Message;
import org.graylog2.plugin.alarms.callbacks.AlarmCallbackException;
import org.graylog2.plugin.configuration.Configuration;
import org.graylog2.plugin.configuration.ConfigurationRequest;
import org.graylog2.plugin.outputs.MessageOutput;
import org.graylog2.plugin.outputs.MessageOutputConfigurationException;
import org.graylog2.plugin.streams.Stream;
import org.graylog2.plugin.streams.StreamRule;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.google.inject.assistedinject.Assisted;

/**
 * @author Monika Verma
 * 
 *         This is the centinel-output plugin. It forwards the graylog stream
 *         events to centinel api
 * 
 */

public class CentinelOutput implements MessageOutput {

    private static final Logger LOG = LoggerFactory.getLogger(CentinelOutput.class);
    private final AtomicBoolean isRunning = new AtomicBoolean(false);
    private AtomicBoolean disconnecting = new AtomicBoolean(false);
    private AtomicBoolean needReconnect = new AtomicBoolean(false);
    private Properties properties;

    private static String outputname = "Centinel Message Output";

    Runnable reconnectHandler = new Runnable() {
        @Override
        public void run() {
            while (true) {
                try {
                    if (needReconnect.get()) {
                        reconnect();
                    }
                    Thread.sleep(5000);
                } catch (Exception e) {
                    LOG.error("centinel-new reconnect handler crashed! " + e.getMessage(), e);
                }
            }
        }
    };

    @Inject
    public CentinelOutput(@Assisted Stream stream, @Assisted Configuration configuration)
            throws MessageOutputConfigurationException {
        loadPropertiesFile();

        disconnecting.set(false);
        needReconnect.set(false);

        new Thread(reconnectHandler).start();
        isRunning.set(true);
    }

    @Override
    public boolean isRunning() {
        return isRunning.get();
    }

    @Override
    public void write(Message message) throws Exception {

        JsonObject inputJson = null;
        String body;

        try {
            Schema schemaStream = ReflectData.get().getSchema(StreamPojo.class);
            GenericRecord avroRecordStream = new Record(schemaStream);
            Schema schemaRules = ReflectData.get().getSchema(RulesPojo.class);
            GenericRecord recordRules = new Record(schemaRules);
            Stream stream = message.getStreams().get(0);
            StreamPojo streamPojo = new StreamPojo(stream);
            avroRecordStream.put("creatoruserid", streamPojo.getCreatoruserid());
            avroRecordStream.put("matchingtype", streamPojo.getMatchingtype());
            avroRecordStream.put("description", streamPojo.getDescription());
            avroRecordStream.put("disabled", streamPojo.getDisabled());

            StreamRule streamRule = stream.getStreamRules().get(0);
            RulesPojo rule = new RulesPojo(streamRule);
            recordRules.put("field", rule.getField());
            recordRules.put("streamId", rule.getStreamId());
            recordRules.put("ruleId", rule.getId());
            recordRules.put("type", rule.getType());
            recordRules.put("inverted", rule.getInverted());
            recordRules.put("value", rule.getValue());

            avroRecordStream.put("rules", recordRules);
            avroRecordStream.put("streamId", streamPojo.getId());
            avroRecordStream.put("title", streamPojo.getTitle());

            MessagePojo messagePojo = new MessagePojo(message);
            Schema schemaMatchingMessage = ReflectData.get().getSchema(MessagePojo.class);
            GenericRecord recordMatchingMessage = new Record(schemaMatchingMessage);

            recordMatchingMessage.put("fields", messagePojo.getFields());
            recordMatchingMessage.put("id", messagePojo.getId());
            recordMatchingMessage.put("hostName", messagePojo.getHostName());
            recordMatchingMessage.put("sourceInetAddress", messagePojo.isSourceInetAddress());
            recordMatchingMessage.put("journalOffset", messagePojo.getJournalOffset());
            recordMatchingMessage.put("message", messagePojo.getMessage());
            recordMatchingMessage.put("source", messagePojo.getSource());
            recordMatchingMessage.put("sourceInput", messagePojo.getSourceInput());
            recordMatchingMessage.put("streams", avroRecordStream);
            recordMatchingMessage.put("timestamp", messagePojo.getTimestamp());
            recordMatchingMessage.put("validErrors", messagePojo.getValidErrors());

            body = recordMatchingMessage.toString();
            body = body.replace("timestamp", "event_timestamp");

            String regex = "([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9.]{6})Z";
            Pattern pattern = Pattern.compile(regex);
            Matcher matcher = pattern.matcher(body);
            if (matcher.find()) {
                String str = "\"" + matcher.group() + "\"";
                str = str.replace("T", " ");
                str = str.replace("Z", "");
                body = body.replaceAll(regex, str);
            }

        } catch (Exception e) {
            throw e;
        }

        JsonBuilderFactory factory = Json.createBuilderFactory(null);
        inputJson = factory.createObjectBuilder()
                .add("input",
                        factory.createObjectBuilder().add("eventType", "stream").add("eventBodyType", "avro")
                                .add("eventBody", body).add("eventKeys",
                                        factory.createArrayBuilder().add("event_timestamp").add("streams:streamId")
                                                .add("fields:message").add("fields:facility").add("fields:severity")
                                                .add("fields:log_time").add("fields:hostname")))
                .build();

        final URL url;
        try {
            url = new URL("http://" + properties.getProperty("centinel_ip") + ":"
                    + properties.getProperty("centinel_port") + "/restconf/operations/eventinput:notify-event");
        } catch (MalformedURLException e) {
            throw new AlarmCallbackException("Error while constructing URL of Slack API.", e);
        }

        // Create authentication string and encode it to Base64
        final String admin = "admin";
        String authStr = admin + ":" + admin;
        String encodedAuthStr = Base64.encodeBase64String(authStr.getBytes());

        try {
            // Create Http connection
            HttpURLConnection connection = (HttpURLConnection) url.openConnection();

            // Set connection properties
            connection.setRequestMethod("POST");
            connection.setRequestProperty("Authorization", "Basic " + encodedAuthStr);
            connection.setRequestProperty("Accept", MediaType.APPLICATION_JSON);
            connection.setRequestProperty("content-type", MediaType.APPLICATION_JSON);

            // Send post request
            connection.setDoOutput(true);
            OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
            out.write(inputJson.toString());
            out.flush();
            out.close();

            // Get the response from connection's inputStream
            connection.getInputStream();

        } catch (IOException e) {
            throw new AlarmCallbackException("Could not open connection to Centinel Rest API", e);
        }
    }

    @Override
    public void write(List<Message> messages) throws Exception {
        for (Message message : messages) {
            write(message);
        }
    }

    @Override
    public void stop() {
        LOG.info("Stopping Centinel output");

        disconnecting.set(true);
        isRunning.set(false);
    }

    private void reconnect() {
        try {
            LOG.info("Trying to reconnect to Centinel server...");
        } catch (Exception e) {
            LOG.error("Reconnect to Centinel server failed. " + e.getMessage(), e);
            return;
        }
        needReconnect.set(false);
    }

    public interface Factory extends MessageOutput.Factory<CentinelOutput> {
        @Override
        CentinelOutput create(Stream stream, Configuration configuration);

        @Override
        Config getConfig();

        @Override
        Descriptor getDescriptor();
    }

    public static class Config extends MessageOutput.Config {
        @Override
        public ConfigurationRequest getRequestedConfiguration() {
            return new ConfigurationRequest();
        }
    }

    public static class Descriptor extends MessageOutput.Descriptor {
        public Descriptor() {
            super(outputname, false, "", "An output plugin sending graylog events over to Centinel API");
        }
    }

    /**
     * To load property file
     */
    private void loadPropertiesFile() {
        properties = new Properties();
        try {
            InputStream inputStream = getClass().getClassLoader().getResourceAsStream("config.properties");
            properties.load(inputStream);
            LOG.info("Properties file for Graylog loaded successfully");
        } catch (IOException e) {
            LOG.error("Problem while loading Properties file for Graylog " + e.getMessage(), e);
        }
    }

    /**
     * Pojo for Message - to map message object to avro schema
     */
    public static class MessagePojo {

        private Map<String, Object> fields;
        private String source;
        private String id;
        private String hostName;
        private boolean sourceInetAddress;
        private long journalOffset;
        private String message;
        private String sourceInput;
        private List<Stream> streams;
        private DateTime timestamp;
        private String validErrors;

        public MessagePojo(Message message) {
            this.fields = message.getFields();
            this.id = message.getId();
            this.hostName = message.getInetAddress().getHostName();
            this.sourceInetAddress = message.getIsSourceInetAddress();
            this.journalOffset = message.getJournalOffset();
            this.message = message.getMessage();
            this.source = message.getSource();
            this.sourceInput = message.getSourceInputId();
            this.streams = message.getStreams();
            this.timestamp = message.getTimestamp();
            this.validErrors = message.getValidationErrors();
        }

        public Map<String, Object> getFields() {
            return fields;
        }

        public void setFields(Map<String, Object> fields) {
            this.fields = fields;
        }

        public String getSource() {
            return source;
        }

        public void setSource(String source) {
            this.source = source;
        }

        public String getId() {
            return id;
        }

        public void setId(String id) {
            this.id = id;
        }

        public String getHostName() {
            return hostName;
        }

        public void setHostName(String hostName) {
            this.hostName = hostName;
        }

        public boolean isSourceInetAddress() {
            return sourceInetAddress;
        }

        public void setSourceInetAddress(boolean sourceInetAddress) {
            this.sourceInetAddress = sourceInetAddress;
        }

        public long getJournalOffset() {
            return journalOffset;
        }

        public void setJournalOffset(long journalOffset) {
            this.journalOffset = journalOffset;
        }

        public String getMessage() {
            return message;
        }

        public void setMessage(String message) {
            this.message = message;
        }

        public String getSourceInput() {
            return sourceInput;
        }

        public void setSourceInput(String sourceInput) {
            this.sourceInput = sourceInput;
        }

        public List<Stream> getStreams() {
            return streams;
        }

        public void setStreams(List<Stream> streams) {
            this.streams = streams;
        }

        public DateTime getTimestamp() {
            return timestamp;
        }

        public void setTimestamp(DateTime timestamp) {
            this.timestamp = timestamp;
        }

        public String getValidErrors() {
            return validErrors;
        }

        public void setValidErrors(String validErrors) {
            this.validErrors = validErrors;
        }
    }

    /**
     * Pojo for Stream - to map stream object to avro schema
     */
    public static class StreamPojo {

        private String creatoruserid;
        private String matchingtype;
        private String description;
        private Boolean disabled;
        private List<StreamRule> rules;
        private String streamId;
        private String title;

        public StreamPojo(Stream stream) {
            this.creatoruserid = "admin";
            this.matchingtype = "AND";
            this.description = stream.getDescription();
            this.disabled = stream.getDisabled();
            this.rules = stream.getStreamRules();
            this.streamId = stream.getId();
            this.title = stream.getTitle();
        }

        public String getCreatoruserid() {
            return creatoruserid;
        }

        public void setCreatoruserid(String creatoruserid) {
            this.creatoruserid = creatoruserid;
        }

        public String getMatchingtype() {
            return matchingtype;
        }

        public void setMatchingtype(String matchingtype) {
            this.matchingtype = matchingtype;
        }

        public String getDescription() {
            return description;
        }

        public void setDescription(String description) {
            this.description = description;
        }

        public Boolean getDisabled() {
            return disabled;
        }

        public void setDisabled(Boolean disabled) {
            this.disabled = disabled;
        }

        public List<StreamRule> getRules() {
            return rules;
        }

        public void setRules(List<StreamRule> rules) {
            this.rules = rules;
        }

        public String getId() {
            return streamId;
        }

        public void setId(String id) {
            this.streamId = id;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }
    }

    /**
     * Pojo for StreamRule - to map streamRule object to avro schema
     */
    public static class RulesPojo {

        private String field;
        private String streamId;
        private String ruleId;
        private String type;
        private Boolean inverted;
        private String value;

        public RulesPojo(StreamRule streamRule) {
            this.field = streamRule.getField();
            this.streamId = streamRule.getStreamId();
            this.ruleId = streamRule.getId();
            this.type = streamRule.getType().toString();
            this.inverted = streamRule.getInverted();
            this.value = streamRule.getValue();
        }

        public String getField() {
            return field;
        }

        public void setField(String field) {
            this.field = field;
        }

        public String getStreamId() {
            return streamId;
        }

        public void setStreamId(String streamId) {
            this.streamId = streamId;
        }

        public String getId() {
            return ruleId;
        }

        public void setId(String id) {
            this.ruleId = id;
        }

        public String getType() {
            return type;
        }

        public void setType(String type) {
            this.type = type;
        }

        public Boolean getInverted() {
            return inverted;
        }

        public void setInverted(Boolean inverted) {
            this.inverted = inverted;
        }

        public String getValue() {
            return value;
        }

        public void setValue(String value) {
            this.value = value;
        }
    }

}