Example #1
0
        public async Task WriteNewScheduleTargets()
        {
            // Decisions about whether the once-daily run target is near enough:

            if (await WasLastRunToday())
            {
                return;
            }

            // This will be an HHmm value such as 1030 or 2200; SchedulerQueue only invokes this
            // grain at 15-minute intervals, so quit unless we're near or past the run-at time.
            var paddedUtc    = todayUtc.PlusMinutes(5);
            var paddedTime   = paddedUtc.TimeOfDay;
            var runTargetUtc = await configCache.GetValue(ConstConfigKeys.ScheduleWriterRunTargetUtc);

            var(runHour, runMinute) = DateTimeAnalysis.GetHourMinute(runTargetUtc);
            if (paddedUtc.Date == todayUtc.Date && (paddedTime.Hour < runHour || (paddedTime.Hour == runHour && paddedTime.Minute < runMinute)))
            {
                return;
            }

            // Decision made: yes, write the next day's schedule records.
            logger.LogInformation($"WriteNewScheduleTargets");

            var jobs = await scheduleRepository.GetJobsWithScheduleSettings();

            if (jobs.Count == 0)
            {
                return;
            }

            foreach (var job in jobs)
            {
                try
                {
                    var planner      = new TargetPlanner(job, today.Plus(Duration.FromDays(1)), logger);
                    var newSchedules = planner.GetSchedules();

                    if (newSchedules.Count > 0)
                    {
                        await InsertScheduleRows(newSchedules);
                    }

                    newSchedules = null;
                }
                catch
                { }
            }

            await configRepository.UpdateConfig(ConstConfigKeys.ScheduleWriterLastRunDateUtc, InstantPattern.General.Format(today));
        }
Example #2
0
        public TargetPlanner(
            JobsWithSchedulesQuery job,
            Instant forTargetDate,
            ILogger parentLogger = null)
        {
            this.job   = job;
            targetDate = forTargetDate;
            logger     = parentLogger;

            jobZone = DateTimeZoneProviders.Tzdb[job.ScheduleTimeZone];
            var zonedDate = forTargetDate.InZone(jobZone).Date;

            analysis = DateTimeAnalysis.ForZonedTargetDate(zonedDate);
        }
Example #3
0
        private async Task StartNextStep(int skipToStepNumber = 0)
        {
            var stepNum = (skipToStepNumber == 0) ? ++status.JobTypeProperties.SequenceStep : skipToStepNumber;

            currentStep = steps.Where(s => s.Step == stepNum).FirstOrDefault();

            // TODO set correct status and wrap up job processing before throwing
            if (currentStep == null)
            {
                throw new JobFacInvalidDataException($"Step {stepNum} not defined for sequence {jobDefinition.Id}");
            }

            var stepStatus = new StatusSequenceStep {
                Step = status.JobTypeProperties.SequenceStep
            };

            status.JobTypeProperties.StepStatus.Add(stepNum, stepStatus);

            if (!await ProcessStartDecision())
            {
                return;
            }

            var jobIds = Formatting.SplitCommaSeparatedList(currentStep.JobDefinitionIdList);

            foreach (var id in jobIds)
            {
                var options = new FactoryStartOptions
                {
                    DefinitionId       = id,
                    SequenceInstanceId = jobInstanceKey,
                    // TODO filter spawned-job args/payloads to the specific job?
                    ReplacementArguments = status.StartOptions.ReplacementArguments,
                    StartupPayloads      = status.StartOptions.StartupPayloads
                };
                var jobId = await jobFactory.StartJob(options);

                status.JobTypeProperties.JobInstanceStepMap.Add(jobId, stepNum);
                stepStatus.JobStatus.Add(jobId, new JobStatus <StatusExternalProcess>
                {
                    Key          = jobId,
                    StartOptions = options,
                    RunStatus    = RunStatus.Unknown,
                });
            }

            await StoreNewRunStatus(RunStatus.Running);

            if (currentStep.ExitDecision == StepExitDecision.DoNextStepWithoutWaiting)
            {
                await StartNextStep();
            }

            // local function returns value indicating whether to continue start-up
            async Task <bool> ProcessStartDecision()
            {
                // The validator ensures StartDates and StartTimes are correct.
                if (currentStep.StartDateDecision != StepStartDateDecision.NoDecision)
                {
                    var analysis = DateTimeAnalysis.Now(currentStep.StartDecisionTimeZone);

                    var  dates         = Formatting.SplitCommaSeparatedList(currentStep.StartDates);
                    bool startDecision = currentStep.StartDateDecision switch
                    {
                        // see scheduler TargetPlanner for descriptions
                        StepStartDateDecision.DaysOfWeek => analysis.InDaysOfWeek(dates),
                        StepStartDateDecision.DaysOfMonth => analysis.InDaysOfMonth(dates),
                        StepStartDateDecision.SpecificDates => analysis.InSpecificDates(dates),
                        StepStartDateDecision.DateRanges => analysis.InDateRanges(dates),
                        StepStartDateDecision.Weekdays => analysis.InWeekdays(dates),
                        _ => true,
                    };

                    if (startDecision && currentStep.StartTimeDecision != StepStartTimeDecision.NoDecision)
                    {
                        var times = Formatting.SplitCommaSeparatedList(currentStep.StartTimes);
                        startDecision = currentStep.StartTimeDecision switch
                        {
                            StepStartTimeDecision.IfHours => analysis.InHours(times),
                            StepStartTimeDecision.IfMinutes => analysis.InMinutes(times),
                            StepStartTimeDecision.IfTime => analysis.InSpecificTimes(times),
                            StepStartTimeDecision.IfTimeRange => analysis.InTimeRanges(times),
                            _ => true,
                        };
                    }

                    stepStatus.StartDecisionSuccess = startDecision;

                    if (!startDecision)
                    {
                        await historyRepo.UpdateStatus(status);

                        switch (currentStep.StartFalseAction)
                        {
                        case StepAction.DoNextStep:
                            await StartNextStep();

                            break;

                        case StepAction.DoStepNumber:
                            await StartNextStep(currentStep.StartFalseStepNumber);

                            break;

                        case StepAction.EndSequence:
                            await Stop();

                            break;
                        }
                        return(false);
                    }
                }
                return(true);
            }
        }
Example #4
0
        private void CreateSchedulesForDate()
        {
            var todayUtc          = SystemClock.Instance.GetCurrentInstant().InUtc();
            var targetDateIsToday = (todayUtc.Date == targetDate.InUtc().Date);

            // When the schedules are being created for the current date, don't
            // create new schedules targeting times which have already passed.
            var filterHour   = todayUtc.Hour;
            var filterMinute = todayUtc.Minute;

            var times = Formatting.SplitCommaSeparatedList(job.ScheduleTimes);

            switch (job.ScheduleTimeMode)
            {
            // every hour at the indicated minutes (eg. 00,15,30,45)
            case ScheduleTimeMode.Minutes:
                for (int hour = 0; hour < 24; hour++)
                {
                    foreach (var min in times)
                    {
                        var minute = int.Parse(min);
                        TryAddUtcEntry(hour, minute);
                    }
                }
                break;

            // specific times: 1130,1400,2245
            case ScheduleTimeMode.HoursMinutes:
                foreach (var time in times)
                {
                    var(hour, minute) = DateTimeAnalysis.GetHourMinute(time);
                    TryAddUtcEntry(hour, minute);
                }
                break;

            // a single value, every N minutes starting after midnight (eg. 30)
            case ScheduleTimeMode.Interval:
                for (int hour = 0; hour < 24; hour++)
                {
                    int interval = int.Parse(times[0]);
                    for (int minute = 0; minute < 59; minute += interval)
                    {
                        TryAddUtcEntry(hour, minute);
                    }
                }
                break;
            }

            void TryAddUtcEntry(int hour, int minute)
            {
                var local = new LocalDateTime(analysis.Date.Year, analysis.Month, analysis.Day, hour, minute);

                try
                {
                    var scheduleTarget = local
                                         .InZoneStrictly(jobZone)    // TODO make InZoneStrictly vs Leniently a config option
                                         .WithZone(DateTimeZone.Utc)
                                         .ToDateTimeOffset();

                    if (!targetDateIsToday || filterHour < scheduleTarget.Hour || (filterHour == scheduleTarget.Hour && filterMinute <= scheduleTarget.Minute))
                    {
                        newSchedules.Add(new ScheduledJobsTable
                        {
                            DefinitionId   = job.Id,
                            ScheduleTarget = scheduleTarget
                        });
                    }
                }
                catch (Exception ex)
                    when(ex is SkippedTimeException || ex is AmbiguousTimeException)
                    {
                        logger?.LogWarning($"Skipping invalid/ambiguous results applying timezone {jobZone.Id} to {LocalDateTimePattern.GeneralIso.Format(local)}");
                    }
            }
        }