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

org.graylog.events.processor.EventDefinitionHandler Maven / Gradle / Ivy

There is a newer version: 5.2.7
Show newest version
/**
 * This file is part of Graylog.
 *
 * Graylog 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.
 *
 * Graylog 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 Graylog.  If not, see .
 */
package org.graylog.events.processor;

import org.graylog.scheduler.DBJobDefinitionService;
import org.graylog.scheduler.DBJobTriggerService;
import org.graylog.scheduler.JobDefinitionDto;
import org.graylog.scheduler.JobTriggerDto;
import org.graylog.scheduler.clock.JobSchedulerClock;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.inject.Inject;
import java.util.List;
import java.util.Optional;

import static java.util.Objects.requireNonNull;

/**
 * Handles event definitions and creates scheduler job definitions and job triggers.
 *
 * Caveat: These handlers modify different database documents without using any transactions. That means we can
 * run into concurrency issues and partial operations.
 *
 * TODO: Make use of transactions once our database library supports it
 */
public class EventDefinitionHandler {
    private static final Logger LOG = LoggerFactory.getLogger(EventDefinitionHandler.class);

    private final DBEventDefinitionService eventDefinitionService;
    private final DBJobDefinitionService jobDefinitionService;
    private final DBJobTriggerService jobTriggerService;
    private final JobSchedulerClock clock;

    @Inject
    public EventDefinitionHandler(DBEventDefinitionService eventDefinitionService,
                                  DBJobDefinitionService jobDefinitionService,
                                  DBJobTriggerService jobTriggerService,
                                  JobSchedulerClock clock) {
        this.eventDefinitionService = eventDefinitionService;
        this.jobDefinitionService = jobDefinitionService;
        this.jobTriggerService = jobTriggerService;
        this.clock = clock;
    }

    /**
     * Creates a new event definition and a corresponding scheduler job definition and trigger.
     *
     * @param unsavedEventDefinition the event definition to save
     * @return the created event definition
     */
    public EventDefinitionDto create(EventDefinitionDto unsavedEventDefinition) {
        final EventDefinitionDto eventDefinition = createEventDefinition(unsavedEventDefinition);

        try {
            createJobDefinitionAndTriggerIfScheduledType(eventDefinition);
        } catch (Exception e) {
            // Cleanup if anything goes wrong
            LOG.error("Removing event definition <{}/{}> because of an error creating the job definition",
                    eventDefinition.id(), eventDefinition.title(), e);
            eventDefinitionService.delete(eventDefinition.id());
            throw e;
        }

        return eventDefinition;
    }

    /**
     * Creates a new event definition without scheduling it. Normally the {@link #create(EventDefinitionDto)} method
     * should be used to ensure proper scheduling of the event definition. In some cases new event definitions
     * must be created without a schedule, though. (e.g. content packs)
     *
     * @param unsavedEventDefinition the event definition to save
     * @return the created event definition
     */
    public EventDefinitionDto createWithoutSchedule(EventDefinitionDto unsavedEventDefinition) {
        return createEventDefinition(unsavedEventDefinition);
    }

    /**
     * Updates an existing event definition and its corresponding scheduler job definition and trigger.
     *
     * @param updatedEventDefinition the event definition to update
     * @return the updated event definition
     */
    public EventDefinitionDto update(EventDefinitionDto updatedEventDefinition, boolean schedule) {
        // Grab the old record so we can revert to it if something goes wrong
        final Optional oldEventDefinition = eventDefinitionService.get(updatedEventDefinition.id());

        final EventDefinitionDto eventDefinition = updateEventDefinition(updatedEventDefinition);

        try {
            if (schedule) {
                if (getJobDefinition(eventDefinition).isPresent()) {
                    updateJobDefinitionAndTriggerIfScheduledType(eventDefinition);
                } else {
                    createJobDefinitionAndTriggerIfScheduledType(eventDefinition);
                }
            } else {
                unschedule(eventDefinition.id());
            }
        } catch (Exception e) {
            // Cleanup if anything goes wrong
            LOG.error("Reverting to old event definition <{}/{}> because of an error updating the job definition",
                    eventDefinition.id(), eventDefinition.title(), e);
            oldEventDefinition.ifPresent(eventDefinitionService::save);
            throw e;
        }

        return eventDefinition;
    }

    /**
     * Deletes an existing event definition and its corresponding scheduler job definition and trigger.
     *
     * @param eventDefinitionId the event definition to delete
     * @return true if the event definition got deleted, false otherwise
     */
    public boolean delete(String eventDefinitionId) {
        final Optional optionalEventDefinition = eventDefinitionService.get(eventDefinitionId);
        if (!optionalEventDefinition.isPresent()) {
            return false;
        }

        final EventDefinitionDto eventDefinition = optionalEventDefinition.get();

        getJobDefinition(eventDefinition)
                .ifPresent(jobDefinition -> deleteJobDefinitionAndTrigger(jobDefinition, eventDefinition));

        LOG.debug("Deleting event definition <{}/{}>", eventDefinition.id(), eventDefinition.title());
        return eventDefinitionService.delete(eventDefinitionId) > 0;
    }

    /**
     * Creates a job definition and a trigger to schedule the given event definition.
     *
     * @param eventDefinitionId the event definition to schedule
     */
    public void schedule(String eventDefinitionId) {
        final EventDefinitionDto eventDefinition = getEventDefinitionOrThrowIAE(eventDefinitionId);

        createJobDefinitionAndTriggerIfScheduledType(eventDefinition);
    }

    /**
     * Removes job definition and trigger for the given event definition to disable it.
     *
     * @param eventDefinitionId the event definition to unschedule
     */
    public void unschedule(String eventDefinitionId) {
        final EventDefinitionDto eventDefinition = getEventDefinitionOrThrowIAE(eventDefinitionId);

        getJobDefinition(eventDefinition)
                .ifPresent(jobDefinition -> deleteJobDefinitionAndTrigger(jobDefinition, eventDefinition));
    }


    private EventDefinitionDto createEventDefinition(EventDefinitionDto unsavedEventDefinition) {
        final EventDefinitionDto eventDefinition = eventDefinitionService.save(unsavedEventDefinition);
        LOG.debug("Created event definition <{}/{}>", eventDefinition.id(), eventDefinition.title());
        return eventDefinition;
    }

    private EventDefinitionDto getEventDefinitionOrThrowIAE(String eventDefinitionId) {
        return eventDefinitionService.get(eventDefinitionId)
                .orElseThrow(() -> new IllegalArgumentException("Event definition <" + eventDefinitionId + "> doesn't exist"));
    }

    private EventDefinitionDto updateEventDefinition(EventDefinitionDto updatedEventDefinition) {
        final EventDefinitionDto eventDefinition = eventDefinitionService.save(updatedEventDefinition);
        LOG.debug("Updated event definition <{}/{}>", eventDefinition.id(), eventDefinition.title());
        return eventDefinition;
    }

    private JobDefinitionDto newJobDefinition(EventDefinitionDto eventDefinition, EventProcessorSchedulerConfig schedulerConfig) {
        return JobDefinitionDto.builder()
                .title(eventDefinition.title())
                .description(eventDefinition.description())
                .config(schedulerConfig.jobDefinitionConfig())
                .build();
    }

    private JobDefinitionDto createJobDefinition(EventDefinitionDto eventDefinition, EventProcessorSchedulerConfig schedulerConfig) {
        final JobDefinitionDto jobDefinition = jobDefinitionService.save(newJobDefinition(eventDefinition, schedulerConfig));
        LOG.debug("Created scheduler job definition <{}/{}> for event definition <{}/{}>", jobDefinition.id(),
                jobDefinition.title(), eventDefinition.id(), eventDefinition.title());
        return jobDefinition;
    }

    private void createJobDefinitionAndTrigger(EventDefinitionDto eventDefinition,
                                               EventProcessorSchedulerConfig schedulerConfig) {
        final JobDefinitionDto jobDefinition = createJobDefinition(eventDefinition, schedulerConfig);

        try {
            createJobTrigger(eventDefinition, jobDefinition, schedulerConfig);
        } catch (Exception e) {
            // Cleanup if anything goes wrong
            LOG.error("Removing job definition <{}/{}> because of an error creating the job trigger",
                    jobDefinition.id(), jobDefinition.title(), e);
            jobDefinitionService.delete(jobDefinition.id());
            throw e;
        }
    }

    private void createJobDefinitionAndTriggerIfScheduledType(EventDefinitionDto eventDefinition) {
        getJobSchedulerConfig(eventDefinition)
                .ifPresent(schedulerConfig -> createJobDefinitionAndTrigger(eventDefinition, schedulerConfig));
    }

    private Optional getJobDefinition(EventDefinitionDto eventDefinition) {
        return jobDefinitionService.getByConfigField(EventProcessorExecutionJob.Config.FIELD_EVENT_DEFINITION_ID, eventDefinition.id());
    }

    private JobDefinitionDto getJobDefinitionOrThrowISE(EventDefinitionDto eventDefinition) {
        return getJobDefinition(eventDefinition)
                .orElseThrow(() -> new IllegalStateException("Couldn't find job definition for event definition <" + eventDefinition.id() + ">"));
    }

    private JobDefinitionDto updateJobDefinition(EventDefinitionDto eventDefinition,
                                                 JobDefinitionDto oldJobDefinition,
                                                 EventProcessorSchedulerConfig schedulerConfig) {
        // Update the existing object to make sure we keep the ID
        final JobDefinitionDto unsavedJobDefinition = oldJobDefinition.toBuilder()
                .title(eventDefinition.title())
                .description(eventDefinition.description())
                .config(schedulerConfig.jobDefinitionConfig())
                .build();

        final JobDefinitionDto jobDefinition = jobDefinitionService.save(unsavedJobDefinition);

        LOG.debug("Updated scheduler job definition <{}/{}> for event definition <{}/{}>", jobDefinition.id(),
                jobDefinition.title(), eventDefinition.id(), eventDefinition.title());

        return jobDefinition;
    }

    private void updateJobDefinitionAndTrigger(EventDefinitionDto eventDefinition,
                                               EventProcessorSchedulerConfig schedulerConfig) {
        // Grab the old record so we can revert to it if something goes wrong
        final JobDefinitionDto oldJobDefinition = getJobDefinitionOrThrowISE(eventDefinition);

        final JobDefinitionDto jobDefinition = updateJobDefinition(eventDefinition, oldJobDefinition, schedulerConfig);

        try {
            updateJobTrigger(eventDefinition, jobDefinition, oldJobDefinition, schedulerConfig);
        } catch (Exception e) {
            // Cleanup if anything goes wrong
            LOG.error("Reverting to old job definition <{}/{}> because of an error updating the job trigger",
                    jobDefinition.id(), jobDefinition.title(), e);
            jobDefinitionService.save(oldJobDefinition);
            throw e;
        }
    }

    private void updateJobDefinitionAndTriggerIfScheduledType(EventDefinitionDto eventDefinition) {
        getJobSchedulerConfig(eventDefinition)
                .ifPresent(schedulerConfig -> updateJobDefinitionAndTrigger(eventDefinition, schedulerConfig));
    }

    private void deleteJobDefinition(JobDefinitionDto jobDefinition, EventDefinitionDto eventDefinition) {
        LOG.debug("Deleting job definition <{}/{}> for event definition <{}/{}>", jobDefinition.id(),
                jobDefinition.title(), eventDefinition.id(), eventDefinition.title());
        jobDefinitionService.delete(jobDefinition.id());
    }

    private void deleteJobDefinitionAndTrigger(JobDefinitionDto jobDefinition, EventDefinitionDto eventDefinition) {
        deleteJobTrigger(jobDefinition, eventDefinition);
        deleteJobDefinition(jobDefinition, eventDefinition);
    }

    private JobTriggerDto newJobTrigger(JobDefinitionDto jobDefinition, EventProcessorSchedulerConfig schedulerConfig) {
        return JobTriggerDto.builderWithClock(clock)
                .jobDefinitionId(requireNonNull(jobDefinition.id(), "Job definition ID cannot be null"))
                .nextTime(clock.nowUTC())
                .schedule(schedulerConfig.schedule())
                .build();
    }

    private void createJobTrigger(EventDefinitionDto dto, JobDefinitionDto jobDefinition, EventProcessorSchedulerConfig schedulerConfig) {
        final JobTriggerDto jobTrigger = jobTriggerService.create(newJobTrigger(jobDefinition, schedulerConfig));
        LOG.debug("Created job trigger <{}> for job definition <{}/{}> and event definition <{}/{}>", jobTrigger.id(),
                jobDefinition.id(), jobDefinition.title(), dto.id(), dto.title());
    }

    private Optional getJobTrigger(JobDefinitionDto jobDefinition) {
        final List jobTriggers = jobTriggerService.getForJob(jobDefinition.id());

        if (jobTriggers.isEmpty()) {
            return Optional.empty();
        }

        // DBJobTriggerService#getForJob currently returns only one trigger. (raises an exception otherwise)
        // Once we allow multiple triggers per job definition, this code will fail. We need some kind of label
        // to figure out which trigger was created automatically. (e.g. event processor)
        // TODO: Fix this code for multiple triggers per job definition
        return Optional.ofNullable(jobTriggers.get(0));
    }

    private void updateJobTrigger(EventDefinitionDto eventDefinition,
                                  JobDefinitionDto jobDefinition,
                                  JobDefinitionDto oldJobDefinition,
                                  EventProcessorSchedulerConfig schedulerConfig) {
        final Optional optionalOldJobTrigger = getJobTrigger(jobDefinition);
        if (!optionalOldJobTrigger.isPresent()) {
            // Nothing to do if there are no job triggers to update
            return;
        }

        final JobTriggerDto oldJobTrigger = optionalOldJobTrigger.get();

        // Update the existing object to make sure we keep the ID
        final JobTriggerDto.Builder unsavedJobTriggerBuilder = oldJobTrigger.toBuilder()
                .jobDefinitionId(requireNonNull(jobDefinition.id(), "Job definition ID cannot be null"))
                .schedule(schedulerConfig.schedule())
                .nextTime(clock.nowUTC());

        final EventProcessorExecutionJob.Config oldConfig = (EventProcessorExecutionJob.Config) oldJobDefinition.config();
        final EventProcessorExecutionJob.Config config = (EventProcessorExecutionJob.Config) jobDefinition.config();
        // If necessary, reset the scheduling times
        if (!config.hasEqualSchedule(oldConfig)) {
            // jobDefinition has a newly created scheduling timerange.
            // Wipe the old one so EventProcessorExecutionJob.execute()
            // will fall back to the new one from the JobDefinition.
            unsavedJobTriggerBuilder.data(null);
            // schedule the next execution accordingly
            unsavedJobTriggerBuilder.nextTime(config.parameters().timerange().getTo());
        }

        final JobTriggerDto jobTrigger = unsavedJobTriggerBuilder.build();
        jobTriggerService.update(jobTrigger);
        LOG.debug("Updated scheduler job trigger <{}> for job definition <{}/{}> and event definition <{}/{}>",
                jobTrigger.id(), jobDefinition.id(), jobDefinition.title(), eventDefinition.id(), eventDefinition.title());
    }

    private void deleteJobTrigger(JobDefinitionDto jobDefinition, EventDefinitionDto eventDefinition) {
        final Optional optionalJobTrigger = getJobTrigger(jobDefinition);
        if (!optionalJobTrigger.isPresent()) {
            return;
        }

        final JobTriggerDto jobTrigger = optionalJobTrigger.get();
        LOG.debug("Deleting scheduler job trigger <{}> for job definition <{}/{}> and event definition <{}/{}>",
                jobTrigger.id(), jobDefinition.id(), jobDefinition.title(), eventDefinition.id(), eventDefinition.title());
        jobTriggerService.delete(jobTrigger.id());
    }

    private Optional getJobSchedulerConfig(EventDefinitionDto eventDefinition) {
        return eventDefinition.config().toJobSchedulerConfig(eventDefinition, clock);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy