use of org.graylog.scheduler.JobTriggerUpdate in project graylog2-server by Graylog2.
the class EventProcessorExecutionJobTest method catchupWindowTestHelper.
private void catchupWindowTestHelper(long catchUpWindowSize, long processingHopSize, long processingWindowSize) throws Exception {
when(eventsConfigurationProvider.get()).thenReturn(EventsConfiguration.builder().eventCatchupWindow(catchUpWindowSize).build());
// for easier testing. don't run into the previous day
clock.plus(1, TimeUnit.MINUTES);
final DateTime now = clock.nowUTC();
final long processingCatchUpWindowSize = eventsConfigurationProvider.get().eventCatchupWindow();
final int scheduleIntervalSeconds = (int) processingHopSize * 1000;
final DateTime from = now.minus(processingWindowSize);
final DateTime to = now;
final DateTime triggerNextTime = now;
final Duration timeSpentInEventProcessor = Duration.standardSeconds(7);
final TestEventProcessorParameters eventProcessorParameters = TestEventProcessorParameters.create(from, to);
final JobDefinitionDto jobDefinition = JobDefinitionDto.builder().id("job-1").title("Test").description("A test").config(EventProcessorExecutionJob.Config.builder().eventDefinitionId("processor-1").processingWindowSize(processingWindowSize).processingHopSize(processingHopSize).parameters(eventProcessorParameters).build()).build();
final EventProcessorExecutionJob job = new EventProcessorExecutionJob(jobScheduleStrategies, clock, eventProcessorEngine, eventsConfigurationProvider, jobDefinition);
final JobTriggerDto trigger = JobTriggerDto.builderWithClock(clock).id("trigger-1").jobDefinitionId(jobDefinition.id()).startTime(now).nextTime(triggerNextTime).status(JobTriggerStatus.RUNNABLE).schedule(IntervalJobSchedule.builder().interval(scheduleIntervalSeconds).unit(TimeUnit.SECONDS).build()).build();
final JobExecutionContext jobExecutionContext = JobExecutionContext.builder().definition(jobDefinition).trigger(trigger).isRunning(new AtomicBoolean(true)).jobTriggerUpdates(new JobTriggerUpdates(clock, jobScheduleStrategies, trigger)).build();
doAnswer(invocation -> {
// Simulate work in the event processor
clock.plus(timeSpentInEventProcessor.getStandardSeconds(), TimeUnit.SECONDS);
return null;
}).when(eventProcessorEngine).execute(any(), any());
// Simulate that we are behind at least one `processingCatchUpWindowSize`
clock.plus(EventsConfiguration.DEFAULT_CATCH_UP_WINDOW_MS, TimeUnit.MILLISECONDS);
clock.plus(1, TimeUnit.MILLISECONDS);
final JobTriggerUpdate triggerUpdate = job.execute(jobExecutionContext);
verify(eventProcessorEngine, times(1)).execute("processor-1", eventProcessorParameters);
assertThat(triggerUpdate.nextTime()).isPresent().get().isEqualTo(clock.nowUTC().minus(timeSpentInEventProcessor));
if (catchUpWindowSize > processingWindowSize && processingHopSize <= processingWindowSize) {
// We are behind at least one chunk of catchUpWindowSize
// The new nextFrom should ignore the processingHopSize and start 1ms after the last `To` Range
// The nextTo will be one window of the processingCatchUpWindowSize
assertThat(triggerUpdate.data()).isPresent().get().isEqualTo(EventProcessorExecutionJob.Data.builder().timerangeFrom(to.plus(processingHopSize).minus(processingWindowSize)).timerangeTo(to.plus(processingCatchUpWindowSize)).build());
} else {
// If no catchup is in effect, we fall back into the configured hopping window mode.
// With a hopping window the "to" is calculated by adding the hopSize and the "from" is based on the next "to"
// minus the windowSize + 1 millisecond.
assertThat(triggerUpdate.data()).isPresent().get().isEqualTo(EventProcessorExecutionJob.Data.builder().timerangeFrom(to.plus(processingHopSize).minus(processingWindowSize)).timerangeTo(to.plus(processingHopSize)).build());
}
assertThat(triggerUpdate.status()).isNotPresent();
}
use of org.graylog.scheduler.JobTriggerUpdate in project graylog2-server by Graylog2.
the class EventProcessorExecutionJobTest method executeWithNextTimeNotBasedOnCurrentTime.
@Test
public void executeWithNextTimeNotBasedOnCurrentTime() throws Exception {
final DateTime now = clock.nowUTC();
final long processingWindowSize = Duration.standardSeconds(60).getMillis();
final long processingHopSize = Duration.standardSeconds(60).getMillis();
final int scheduleIntervalSeconds = 1;
final DateTime from = now.minus(processingWindowSize);
final DateTime to = now;
final DateTime triggerNextTime = now;
final TestEventProcessorParameters eventProcessorParameters = TestEventProcessorParameters.create(from, to);
final JobDefinitionDto jobDefinition = JobDefinitionDto.builder().id("job-1").title("Test").description("A test").config(EventProcessorExecutionJob.Config.builder().eventDefinitionId("processor-1").processingWindowSize(processingWindowSize).processingHopSize(processingHopSize).parameters(eventProcessorParameters).build()).build();
final EventProcessorExecutionJob job = new EventProcessorExecutionJob(jobScheduleStrategies, clock, eventProcessorEngine, eventsConfigurationProvider, jobDefinition);
final JobTriggerDto trigger = JobTriggerDto.builderWithClock(clock).id("trigger-1").jobDefinitionId(jobDefinition.id()).startTime(now).nextTime(triggerNextTime).status(JobTriggerStatus.RUNNABLE).schedule(IntervalJobSchedule.builder().interval(scheduleIntervalSeconds).unit(TimeUnit.SECONDS).build()).build();
final JobExecutionContext jobExecutionContext = JobExecutionContext.builder().definition(jobDefinition).trigger(trigger).isRunning(new AtomicBoolean(true)).jobTriggerUpdates(new JobTriggerUpdates(clock, jobScheduleStrategies, trigger)).build();
doAnswer(invocation -> {
// Simulate work in the event processor
clock.plus(10, TimeUnit.SECONDS);
return null;
}).when(eventProcessorEngine).execute(any(), any());
final JobTriggerUpdate triggerUpdate = job.execute(jobExecutionContext);
verify(eventProcessorEngine, times(1)).execute("processor-1", eventProcessorParameters);
// The next time should be based on the previous nextTime + the schedule. The 10 second event processor
// runtime should not be added to the new nextTime.
assertThat(triggerUpdate.nextTime()).isPresent().get().isEqualTo(triggerNextTime.plusSeconds(scheduleIntervalSeconds));
assertThat(triggerUpdate.data()).isPresent().get().isEqualTo(EventProcessorExecutionJob.Data.builder().timerangeFrom(to).timerangeTo(to.plus(processingWindowSize)).build());
assertThat(triggerUpdate.status()).isNotPresent();
}
use of org.graylog.scheduler.JobTriggerUpdate in project graylog2-server by Graylog2.
the class EventProcessorExecutionJobTest method executeWithTriggerDataTimerange.
@Test
public void executeWithTriggerDataTimerange() throws Exception {
final DateTime now = clock.nowUTC();
final long processingWindowSize = Duration.standardSeconds(60).getMillis();
final long processingHopSize = Duration.standardSeconds(60).getMillis();
final int scheduleIntervalSeconds = 1;
final DateTime from = now.minusDays(10).minus(processingWindowSize);
final DateTime to = now.minusDays(10);
final DateTime triggerFrom = now.minus(processingWindowSize);
final DateTime triggerTo = now;
final DateTime triggerNextTime = now;
final TestEventProcessorParameters eventProcessorParameters = TestEventProcessorParameters.create(from, to);
final JobDefinitionDto jobDefinition = JobDefinitionDto.builder().id("job-1").title("Test").description("A test").config(EventProcessorExecutionJob.Config.builder().eventDefinitionId("processor-1").processingWindowSize(processingWindowSize).processingHopSize(processingHopSize).parameters(eventProcessorParameters).build()).build();
final EventProcessorExecutionJob job = new EventProcessorExecutionJob(jobScheduleStrategies, clock, eventProcessorEngine, eventsConfigurationProvider, jobDefinition);
final JobTriggerDto trigger = JobTriggerDto.builderWithClock(clock).id("trigger-1").jobDefinitionId(jobDefinition.id()).startTime(now).nextTime(triggerNextTime).status(JobTriggerStatus.RUNNABLE).schedule(IntervalJobSchedule.builder().interval(scheduleIntervalSeconds).unit(TimeUnit.SECONDS).build()).data(EventProcessorExecutionJob.Data.create(triggerFrom, triggerTo)).build();
final JobExecutionContext jobExecutionContext = JobExecutionContext.builder().definition(jobDefinition).trigger(trigger).isRunning(new AtomicBoolean(true)).jobTriggerUpdates(new JobTriggerUpdates(clock, jobScheduleStrategies, trigger)).build();
doAnswer(invocation -> {
// Simulate work in the event processor
clock.plus(5, TimeUnit.SECONDS);
return null;
}).when(eventProcessorEngine).execute(any(), any());
final JobTriggerUpdate triggerUpdate = job.execute(jobExecutionContext);
// Check that we use the timerange from the trigger instead of the parameters
verify(eventProcessorEngine, times(1)).execute("processor-1", eventProcessorParameters.withTimerange(triggerFrom, triggerTo));
assertThat(triggerUpdate.nextTime()).isPresent().get().isEqualTo(triggerNextTime.plusSeconds(scheduleIntervalSeconds));
// The next timerange in the trigger update also needs to be based on the timerange from the trigger
assertThat(triggerUpdate.data()).isPresent().get().isEqualTo(EventProcessorExecutionJob.Data.builder().timerangeFrom(triggerTo).timerangeTo(triggerTo.plus(processingWindowSize)).build());
assertThat(triggerUpdate.status()).isNotPresent();
}
use of org.graylog.scheduler.JobTriggerUpdate in project graylog2-server by Graylog2.
the class EventProcessorExecutionJob method execute.
@Override
public JobTriggerUpdate execute(JobExecutionContext ctx) throws JobExecutionException {
final Optional<Data> data = ctx.trigger().data().map(d -> (Data) d);
// Use timerange from job trigger data if it exists
final EventProcessorParametersWithTimerange parameters;
if (data.isPresent()) {
LOG.trace("Using timerange from job trigger data: from={} to={} (trigger={})", data.get().timerangeFrom(), data.get().timerangeTo(), ctx.trigger().id());
parameters = config.parameters().withTimerange(data.get().timerangeFrom(), data.get().timerangeTo());
} else {
parameters = config.parameters();
}
final DateTime from = parameters.timerange().getFrom();
final DateTime to = parameters.timerange().getTo();
// The "to" timestamp must be after the "from" timestamp!
if (!to.isAfter(from)) {
// This should not happen(TM)
// If it does, set the error status to ERROR so the scheduler doesn't try to execute it until the problem
// has been resolved.
// TODO: Send an event when this happens so admins can get alerted
final JobTriggerUpdate triggerUpdate = JobTriggerUpdate.withError(ctx.trigger());
throw new JobExecutionException("Invalid time range - \"to\" timestamp <" + to.toString() + "> is not after \"from\" timestamp <" + from.toString() + ">", ctx.trigger(), triggerUpdate);
}
// We cannot run the event processor if the "to" timestamp of the timerange we want to process is in the future.
final DateTime now = clock.nowUTC();
if (now.isBefore(to)) {
LOG.error("The end of the timerange to process is in the future, re-scheduling job trigger <{}> to run at <{}>", ctx.trigger().id(), to);
return JobTriggerUpdate.withNextTime(to);
}
try {
eventProcessorEngine.execute(config.eventDefinitionId(), parameters);
// By using the processingWindowSize and the processingHopSize we can implement hopping and tumbling
// windows. (a tumbling window is simply a hopping window where windowSize and hopSize are the same)
DateTime nextTo = to.plus(config.processingHopSize());
DateTime nextFrom = nextTo.minus(config.processingWindowSize());
// If the event processor is catching up on old data (e.g. the server was shut down for a significant time),
// we can switch to a bigger scheduling window: `processingCatchUpWindowSize`.
// If engaged, we will schedule jobs with a timerange of multiple processingWindowSize chunks.
// It's the specific event processors' duty to handle being executed with this larger timerange.
// If an event processor was configured with a processingHopSize greater than the processingWindowSize
// we can't use the catchup mode.
final long catchUpSize = configurationProvider.get().eventCatchupWindow();
if (catchUpSize > 0 && catchUpSize > config.processingWindowSize() && to.plus(catchUpSize).isBefore(now) && config.processingHopSize() <= config.processingWindowSize()) {
final long chunkCount = catchUpSize / config.processingWindowSize();
// Align to multiples of the processingWindowSize
nextTo = to.plus(config.processingWindowSize() * chunkCount);
LOG.info("Event processor <{}> is catching up on old data. Combining {} search windows with catchUpWindowSize={}ms: from={} to={}", config.eventDefinitionId(), chunkCount, catchUpSize, nextFrom, nextTo);
}
LOG.trace("Set new timerange of eventproc <{}> in job trigger data: from={} to={} (hopSize={}ms windowSize={}ms)", config.eventDefinitionId(), nextFrom, nextTo, config.processingHopSize(), config.processingWindowSize());
final Data newData = data.map(Data::toBuilder).orElse(Data.builder()).timerangeFrom(nextFrom).timerangeTo(nextTo).build();
final Optional<DateTime> nextTime = scheduleStrategies.nextTime(ctx.trigger());
// The nextTime Optional can be empty if there will be no further executions of the trigger
if (nextTime.isPresent()) {
if (nextTo.isBefore(now)) {
// If the next "to" timestamp of the timerange to process is in the past, we want to schedule the next
// execution of this job as soon as possible to make sure we catch up.
LOG.trace("Set nextTime to <{}> to catch up faster - calculated nextTime was <{}>", now, nextTime.get());
return JobTriggerUpdate.withNextTimeAndData(now, newData);
} else if (nextTo.isBefore(nextTime.get())) {
LOG.trace("Set nextTime to <{}> because it's closer to the timerange time - calculated nextTime was <{}>", nextTo, nextTime.get());
return JobTriggerUpdate.withNextTimeAndData(nextTo, newData);
} else {
// Otherwise use the calculated nextTime
LOG.trace("Set nextTime to <{}>", nextTime.get());
return JobTriggerUpdate.withNextTimeAndData(nextTime.get(), newData);
}
} else {
// Or no next time if this has been a ONCE trigger
LOG.trace("No nextTime for trigger <{}>", ctx.trigger().id());
return JobTriggerUpdate.withoutNextTime();
}
} catch (EventProcessorPreconditionException e) {
// A precondition for the event processor is not ready yet. This job must be retried.
if (e.getEventDefinition().isPresent()) {
LOG.debug("Event processor <{}/{}> couldn't be executed because of a failed precondition (retry in {} ms)", e.getEventDefinition().get().title(), e.getEventDefinitionId(), RETRY_INTERVAL);
} else {
LOG.debug("Event processor <{}> couldn't be executed because of a failed precondition (retry in {} ms)", e.getEventDefinitionId(), RETRY_INTERVAL);
}
return ctx.jobTriggerUpdates().retryIn(RETRY_INTERVAL, MILLISECONDS);
} catch (EventProcessorException e) {
if (e.getEventDefinition().isPresent()) {
LOG.error("Event processor <{}/{}> failed to execute: {} (retry in {} ms)", e.getEventDefinition().get().config().type(), e.getEventDefinitionId(), e.getMessage(), RETRY_INTERVAL, e);
} else {
LOG.error("Event processor <{}> failed to execute: {} (retry in {} ms)", e.getEventDefinitionId(), e.getMessage(), RETRY_INTERVAL, e);
}
if (e.isPermanent()) {
// We cannot retry a permanent error so we have to set the job trigger status to ERROR so it doesn't
// get executed again
LOG.error("Caught a permanent error, trigger <{}> will go into ERROR state - it will not be executed anymore and needs manual intervention! (event-definition-id: {} job-definition={}/{})", ctx.trigger().id(), e.getEventDefinitionId(), ctx.definition().id(), ctx.definition().title());
return JobTriggerUpdate.withError(ctx.trigger());
}
return ctx.jobTriggerUpdates().retryIn(RETRY_INTERVAL, MILLISECONDS);
} catch (Exception e) {
LOG.error("Event processor <{}> failed to execute: parameters={} (retry in {} ms)", config.eventDefinitionId(), parameters, RETRY_INTERVAL, e);
return ctx.jobTriggerUpdates().retryIn(RETRY_INTERVAL, MILLISECONDS);
}
}
use of org.graylog.scheduler.JobTriggerUpdate in project graylog2-server by Graylog2.
the class EventProcessorExecutionJobTest method executeWithTimerangeInTheFuture.
@Test
public void executeWithTimerangeInTheFuture() throws Exception {
final DateTime now = clock.nowUTC();
final long processingWindowSize = Duration.standardSeconds(60).getMillis();
final long processingHopSize = Duration.standardSeconds(60).getMillis();
final int scheduleIntervalSeconds = 1;
final DateTime from = now;
// Set the "to" timestamp of the timerange way into the future
final DateTime to = now.plusDays(1);
final DateTime triggerNextTime = now;
final TestEventProcessorParameters eventProcessorParameters = TestEventProcessorParameters.create(from, to);
final JobDefinitionDto jobDefinition = JobDefinitionDto.builder().id("job-1").title("Test").description("A test").config(EventProcessorExecutionJob.Config.builder().eventDefinitionId("processor-1").processingWindowSize(processingWindowSize).processingHopSize(processingHopSize).parameters(eventProcessorParameters).build()).build();
final EventProcessorExecutionJob job = new EventProcessorExecutionJob(jobScheduleStrategies, clock, eventProcessorEngine, eventsConfigurationProvider, jobDefinition);
final JobTriggerDto trigger = JobTriggerDto.builderWithClock(clock).id("trigger-1").jobDefinitionId(jobDefinition.id()).startTime(now).nextTime(triggerNextTime).status(JobTriggerStatus.RUNNABLE).schedule(IntervalJobSchedule.builder().interval(scheduleIntervalSeconds).unit(TimeUnit.SECONDS).build()).build();
final JobExecutionContext jobExecutionContext = JobExecutionContext.builder().definition(jobDefinition).trigger(trigger).isRunning(new AtomicBoolean(true)).jobTriggerUpdates(new JobTriggerUpdates(clock, jobScheduleStrategies, trigger)).build();
final JobTriggerUpdate triggerUpdate = job.execute(jobExecutionContext);
// The engine should not be called because the "to" timestamp of the timerange is in the future
verify(eventProcessorEngine, never()).execute(any(), any());
// The update sets the nextTime to the "to" timestamp because that is the earliest time we can execute
// the job for the timerange
assertThat(triggerUpdate.nextTime()).isPresent().get().isEqualTo(to);
// Data should not be updated with any new timerange
assertThat(triggerUpdate.data()).isNotPresent();
assertThat(triggerUpdate.status()).isNotPresent();
}
Aggregations