Commit 37d6d1e4 authored by Shinya Maeda's avatar Shinya Maeda

basic components

parent 5f715f1d
......@@ -9,15 +9,13 @@ module Ci
def schedule_next_run!
next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now
update(:next_run_at => next_time) if next_time.present?
if next_time.present?
update_attributes(next_run_at: next_time)
end
def valid_ref?
true #TODO:
end
def update_last_run!
update(:last_run_at => Time.now)
update_attributes(last_run_at: Time.now)
end
end
end
......@@ -2,14 +2,14 @@ module Ci
class CreatePipelineService < BaseService
attr_reader :pipeline
def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil)
def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil, scheduled_trigger: false)
@pipeline = Ci::Pipeline.new(
project: project,
ref: ref,
sha: sha,
before_sha: before_sha,
tag: tag?,
trigger_requests: Array(trigger_request),
trigger_requests: (scheduled_trigger) ? [] : Array(trigger_request),
user: current_user
)
......@@ -17,9 +17,11 @@ module Ci
return error('Pipeline is disabled')
end
unless scheduled_trigger
unless trigger_request || can?(current_user, :create_pipeline, project)
return error('Insufficient permissions to create a new pipeline')
end
end
unless branch? || tag?
return error('Reference not found')
......
......@@ -3,15 +3,15 @@ class ScheduledTriggerWorker
include CronjobQueue
def perform
# TODO: Update next_run_at
Ci::ScheduledTriggers.where("next_run_at < ?", Time.now).find_each do |trigger|
Ci::ScheduledTrigger.where("next_run_at < ?", Time.now).find_each do |trigger|
begin
Ci::CreateTriggerRequestService.new.execute(trigger.project, trigger, trigger.ref)
Ci::CreatePipelineService.new(trigger.project, trigger.owner, ref: trigger.ref).
execute(ignore_skip_ci: true, scheduled_trigger: true)
rescue => e
Rails.logger.error "#{trigger.id}: Failed to trigger job: #{e.message}"
ensure
trigger.schedule_next_run!
trigger.update_last_run!
end
end
end
......
FactoryGirl.define do
factory :ci_scheduled_trigger, class: Ci::ScheduledTrigger do
project factory: :empty_project
project factory: :project
owner factory: :user
ref 'master'
trait :force_triggable do
next_run_at Time.now - 1.month
end
trait :cron_nightly_build do
cron '0 1 * * *'
cron_time_zone 'Europe/Istanbul'
next_run_at do # TODO: Use CronParser
time = Time.now.in_time_zone(cron_time_zone)
time = time + 1.day if time.hour > 1
time = time.change(sec: 0, min: 0, hour: 1)
time
end
end
trait :cron_weekly_build do
cron '0 1 * * 5'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
trait :cron_monthly_build do
cron '0 1 22 * *'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
trait :cron_every_5_minutes do
cron '*/5 * * * *'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
trait :cron_every_5_hours do
cron '* */5 * * *'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
trait :cron_every_5_days do
cron '* * */5 * *'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
trait :cron_every_5_months do
cron '* * * */5 *'
cron_time_zone 'Europe/Istanbul'
# TODO: next_run_at
end
end
end
......@@ -6,90 +6,61 @@ module Ci
subject { described_class.new(cron, cron_time_zone).next_time_from_now }
context 'when cron and cron_time_zone are valid' do
context 'at 00:00, 00:10, 00:20, 00:30, 00:40, 00:50' do
let(:cron) { '*/10 * * * *' }
let(:cron_time_zone) { 'US/Pacific' }
context 'when specific time' do
let(:cron) { '3 4 5 6 *' }
let(:cron_time_zone) { 'Europe/London' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
time = time + 10.minutes
time = time.change(sec: 0, min: time.min-time.min%10)
is_expected.to eq(time)
it 'returns exact time in the future' do
expect(subject).to be > Time.now.in_time_zone(cron_time_zone)
expect(subject.min).to eq(3)
expect(subject.hour).to eq(4)
expect(subject.day).to eq(5)
expect(subject.month).to eq(6)
end
end
context 'at 10:00, 20:00' do
let(:cron) { '0 */10 * * *' }
let(:cron_time_zone) { 'US/Pacific' }
context 'when specific day of week' do
let(:cron) { '* * * * 0' }
let(:cron_time_zone) { 'Europe/London' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
time = time + 10.hours
time = time.change(sec: 0, min: 0, hour: time.hour-time.hour%10)
is_expected.to eq(time)
it 'returns exact day of week in the future' do
expect(subject).to be > Time.now.in_time_zone(cron_time_zone)
expect(subject.wday).to eq(0)
end
end
context 'when cron is every 10 days' do
let(:cron) { '0 0 */10 * *' }
context 'when slash used' do
let(:cron) { '*/10 */6 */10 */10 *' }
let(:cron_time_zone) { 'US/Pacific' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
time = time + 10.days
time = time.change(sec: 0, min: 0, hour: 0, day: time.day-time.day%10)
is_expected.to eq(time)
it 'returns exact minute' do
expect(subject).to be > Time.now.in_time_zone(cron_time_zone)
expect(subject.min).to be_in([0, 10, 20, 30, 40, 50])
expect(subject.hour).to be_in([0, 6, 12, 18])
expect(subject.day).to be_in([1, 11, 21, 31])
expect(subject.month).to be_in([1, 11])
end
end
context 'when cron is every week 2:00 AM' do
let(:cron) { '0 2 * * *' }
context 'when range used' do
let(:cron) { '0,20,40 * 1-5 * *' }
let(:cron_time_zone) { 'US/Pacific' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
is_expected.to eq(time.change(sec: 0, min: 0, hour: 2, day: time.day+1))
expect(subject).to be > Time.now.in_time_zone(cron_time_zone)
expect(subject.min).to be_in([0, 20, 40])
expect(subject.day).to be_in((1..5).to_a)
end
end
context 'when cron_time_zone is US/Pacific' do
let(:cron) { '0 1 * * *' }
let(:cron) { '* * * * *' }
let(:cron_time_zone) { 'US/Pacific' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
is_expected.to eq(time.change(sec: 0, min: 0, hour: 1, day: time.day+1))
end
expect(subject).to be > Time.now.in_time_zone(cron_time_zone)
expect(subject.utc_offset/60/60).to eq(-7)
end
context 'when cron_time_zone is Europe/London' do
let(:cron) { '0 1 * * *' }
let(:cron_time_zone) { 'Europe/London' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
is_expected.to eq(time.change(sec: 0, min: 0, hour: 1, day: time.day+1))
end
end
context 'when cron_time_zone is Asia/Tokyo' do
let(:cron) { '0 1 * * *' }
let(:cron_time_zone) { 'Asia/Tokyo' }
it 'returns next time from now' do
time = Time.now.in_time_zone(cron_time_zone)
is_expected.to eq(time.change(sec: 0, min: 0, hour: 1, day: time.day+1))
end
end
end
context 'when cron is given and cron_time_zone is not given' do
let(:cron) { '0 1 * * *' }
it 'returns next time from now in utc' do
obj = described_class.new(cron).next_time_from_now
time = Time.now.in_time_zone('UTC')
expect(obj).to eq(time.change(sec: 0, min: 0, hour: 1, day: time.day+1))
end
end
......
require 'spec_helper'
require 'rufus-scheduler' # Included in sidekiq-cron
describe Ci::ScheduledTrigger, models: true do
......@@ -9,30 +8,22 @@ describe Ci::ScheduledTrigger, models: true do
end
describe '#schedule_next_run!' do
context 'when cron and cron_time_zone are vaild' do
context 'when nightly build' do
it 'schedules next run' do
scheduled_trigger = create(:ci_scheduled_trigger, :cron_nightly_build)
scheduled_trigger.schedule_next_run!
puts "scheduled_trigger: #{scheduled_trigger.inspect}"
expect(scheduled_trigger.cron).to be_nil
end
end
subject { scheduled_trigger.schedule_next_run! }
context 'when weekly build' do
end
context 'when monthly build' do
let(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, next_run_at: nil) }
it 'updates next_run_at' do
is_expected.not_to be_nil
end
end
context 'when cron and cron_time_zone are invaild' do
it 'schedules nothing' do
describe '#update_last_run!' do
subject { scheduled_trigger.update_last_run! }
end
let(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, last_run_at: nil) }
it 'updates last_run_at' do
is_expected.not_to be_nil
end
end
end
......@@ -214,5 +214,9 @@ describe Ci::CreatePipelineService, services: true do
expect(Environment.find_by(name: "review/master")).not_to be_nil
end
end
context 'when scheduled_trigger' do
# TODO: spec if approved
end
end
end
require 'spec_helper'
describe ScheduledTriggerWorker do
subject { described_class.new.perform }
let(:worker) { described_class.new }
context '#perform' do # TODO:
it 'does' do
is_expected.to be_nil
before do
stub_ci_pipeline_to_return_yaml_file
end
context 'when there is a scheduled trigger within next_run_at' do
before do
create(:ci_scheduled_trigger, :cron_nightly_build, :force_triggable)
worker.perform
end
it 'creates a new pipeline' do
expect(Ci::Pipeline.last.status).to eq('pending')
end
it 'schedules next_run_at' do
scheduled_trigger2 = create(:ci_scheduled_trigger, :cron_nightly_build)
expect(Ci::ScheduledTrigger.last.next_run_at).to eq(scheduled_trigger2.next_run_at)
end
end
context 'when there are no scheduled triggers within next_run_at' do
let!(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build) }
before do
worker.perform
end
it 'do not create a new pipeline' do
expect(Ci::Pipeline.all).to be_empty
end
it 'do not reschedule next_run_at' do
expect(Ci::ScheduledTrigger.last.next_run_at).to eq(scheduled_trigger.next_run_at)
end
end
context 'when next_run_at is nil' do
let!(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, next_run_at: nil) }
before do
worker.perform
end
it 'do not create a new pipeline' do
expect(Ci::Pipeline.all).to be_empty
end
it 'do not reschedule next_run_at' do
expect(Ci::ScheduledTrigger.last.next_run_at).to eq(scheduled_trigger.next_run_at)
end
end
end
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment