All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.graylog2.events.ClusterEventPeriodical Maven / Gradle / Ivy

There is a newer version: 6.1.4
Show newest version
/*
 * Copyright (C) 2020 Graylog, Inc.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the Server Side Public License, version 1,
 * as published by MongoDB, Inc.
 *
 * 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
 * Server Side Public License for more details.
 *
 * You should have received a copy of the Server Side Public License
 * along with this program. If not, see
 * .
 */
package org.graylog2.events;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.eventbus.DeadEvent;
import com.google.common.eventbus.EventBus;
import com.google.common.eventbus.Subscribe;
import com.mongodb.BasicDBList;
import com.mongodb.BasicDBObject;
import com.mongodb.DB;
import com.mongodb.DBCollection;
import com.mongodb.DBObject;
import com.mongodb.MongoException;
import com.mongodb.WriteConcern;
import org.graylog2.bindings.providers.MongoJackObjectMapperProvider;
import org.graylog2.database.MongoConnection;
import org.graylog2.plugin.periodical.Periodical;
import org.graylog2.plugin.system.NodeId;
import org.graylog2.security.RestrictedChainingClassLoader;
import org.graylog2.security.SafeClasses;
import org.graylog2.security.UnsafeClassLoadingAttemptException;
import org.graylog2.shared.plugins.ChainingClassLoader;
import org.graylog2.shared.utilities.AutoValueUtils;
import org.mongojack.DBCursor;
import org.mongojack.DBSort;
import org.mongojack.DBUpdate;
import org.mongojack.JacksonDBCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.Collections;

import static com.google.common.base.Preconditions.checkNotNull;

public class ClusterEventPeriodical extends Periodical {
    private static final Logger LOG = LoggerFactory.getLogger(ClusterEventPeriodical.class);

    @VisibleForTesting
    static final String COLLECTION_NAME = "cluster_events";

    private final JacksonDBCollection dbCollection;
    private final NodeId nodeId;
    private final ObjectMapper objectMapper;
    private final EventBus serverEventBus;
    private final RestrictedChainingClassLoader chainingClassLoader;

    @Inject
    public ClusterEventPeriodical(final MongoJackObjectMapperProvider mapperProvider,
                                  final MongoConnection mongoConnection,
                                  final NodeId nodeId,
                                  final RestrictedChainingClassLoader chainingClassLoader,
                                  final EventBus serverEventBus,
                                  final ClusterEventBus clusterEventBus) {
        this(JacksonDBCollection.wrap(prepareCollection(mongoConnection), ClusterEvent.class, String.class, mapperProvider.get()),
                nodeId, mapperProvider.get(), chainingClassLoader, serverEventBus, clusterEventBus);
    }

    @Deprecated
    public ClusterEventPeriodical(final MongoJackObjectMapperProvider mapperProvider,
                                  final MongoConnection mongoConnection,
                                  final NodeId nodeId,
                                  final ChainingClassLoader chainingClassLoader,
                                  final EventBus serverEventBus,
                                  final ClusterEventBus clusterEventBus) {
        this(JacksonDBCollection.wrap(prepareCollection(mongoConnection), ClusterEvent.class, String.class,
                        mapperProvider.get()), nodeId, mapperProvider.get(),
                new RestrictedChainingClassLoader(chainingClassLoader, SafeClasses.allGraylogInternal()),
                serverEventBus, clusterEventBus);
    }

    private ClusterEventPeriodical(final JacksonDBCollection dbCollection,
                                   final NodeId nodeId,
                                   final ObjectMapper objectMapper,
                                   final RestrictedChainingClassLoader chainingClassLoader,
                                   final EventBus serverEventBus,
                                   final ClusterEventBus clusterEventBus) {
        this.nodeId = checkNotNull(nodeId);
        this.dbCollection = checkNotNull(dbCollection);
        this.objectMapper = checkNotNull(objectMapper);
        this.chainingClassLoader = chainingClassLoader;
        this.serverEventBus = checkNotNull(serverEventBus);

        checkNotNull(clusterEventBus).registerClusterEventSubscriber(this);
    }

    @VisibleForTesting
    static DBCollection prepareCollection(final MongoConnection mongoConnection) {
        final DB db = mongoConnection.getDatabase();

        DBCollection coll = db.getCollection(COLLECTION_NAME);

        coll.createIndex(DBSort
                .asc("timestamp")
                .asc("producer")
                .asc("consumers"));

        coll.setWriteConcern(WriteConcern.JOURNALED);

        return coll;
    }

    @Override
    public boolean runsForever() {
        return false;
    }

    @Override
    public boolean stopOnGracefulShutdown() {
        return true;
    }

    @Override
    public boolean leaderOnly() {
        return false;
    }

    @Override
    public boolean startOnThisNode() {
        return true;
    }

    @Override
    public boolean isDaemon() {
        return true;
    }

    @Override
    public int getInitialDelaySeconds() {
        return 0;
    }

    @Override
    public int getPeriodSeconds() {
        return 1;
    }

    @Override
    protected Logger getLogger() {
        return LOG;
    }

    @Override
    public void doRun() {
        LOG.debug("Opening MongoDB cursor on \"{}\"", COLLECTION_NAME);
        try (DBCursor cursor = eventCursor(nodeId)) {
            if (LOG.isTraceEnabled()) {
                LOG.trace("MongoDB query plan: {}", cursor.explain());
            }

            while (cursor.hasNext()) {
                ClusterEvent clusterEvent = cursor.next();
                LOG.trace("Processing cluster event: {}", clusterEvent);

                Object payload = extractPayload(clusterEvent.payload(), clusterEvent.eventClass());
                if (payload != null) {
                    serverEventBus.post(payload);
                } else {
                    LOG.warn("Couldn't extract payload of cluster event with ID <{}>", clusterEvent.id());
                    LOG.debug("Invalid payload in cluster event: {}", clusterEvent);
                }

                updateConsumers(clusterEvent.id(), nodeId);
            }
        } catch (Exception e) {
            LOG.warn("Error while reading cluster events from MongoDB, retrying.", e);
        }
    }

    @Subscribe
    public void publishClusterEvent(Object event) {
        if (event instanceof DeadEvent) {
            LOG.debug("Skipping DeadEvent on cluster event bus");
            return;
        }

        final String className = AutoValueUtils.getCanonicalName(event.getClass());
        final ClusterEvent clusterEvent = ClusterEvent.create(nodeId.getNodeId(), className, Collections.singleton(nodeId.getNodeId()), event);

        try {
            final String id = dbCollection.save(clusterEvent, WriteConcern.JOURNALED).getSavedId();
            // We are handling a locally generated event, so we can speed up processing by posting it to the local event
            // bus immediately. Due to having added the local node id to its list of consumers, it will not be picked up
            // by the db cursor again, avoiding double processing of the event. See #11263 for details.
            serverEventBus.post(event);
            LOG.debug("Published cluster event with ID <{}> and type <{}>", id, className);
        } catch (MongoException e) {
            LOG.error("Couldn't publish cluster event of type <" + className + ">", e);
        }
    }

    private DBCursor eventCursor(NodeId nodeId) {
        // Resorting to ugly MongoDB Java Client because of https://github.com/devbliss/mongojack/issues/88
        final BasicDBList consumersList = new BasicDBList();
        consumersList.add(nodeId.getNodeId());
        final DBObject query = new BasicDBObject("consumers", new BasicDBObject("$nin", consumersList));

        return dbCollection.find(query).sort(DBSort.asc("timestamp"));
    }

    private void updateConsumers(final String eventId, final NodeId nodeId) {
        dbCollection.updateById(eventId, DBUpdate.addToSet("consumers", nodeId.getNodeId()));
    }

    private Object extractPayload(Object payload, String eventClass) {
        try {
            final Class clazz = chainingClassLoader.loadClassSafely(eventClass);
            return objectMapper.convertValue(payload, clazz);
        } catch (ClassNotFoundException e) {
            LOG.debug("Couldn't load class <" + eventClass + "> for event", e);
        } catch (IllegalArgumentException e) {
            LOG.debug("Error while deserializing payload", e);
        } catch (UnsafeClassLoadingAttemptException e) {
            LOG.warn("Couldn't load class <{}>.", eventClass, e);
        }
        return null;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy