From 37d6d1e46130f44f2fe05171b814b5682696839c Mon Sep 17 00:00:00 2001 From: Shinya Maeda <gitlab.shinyamaeda@gmail.com> Date: Fri, 24 Mar 2017 00:18:13 +0900 Subject: [PATCH] basic components --- app/models/ci/scheduled_trigger.rb | 10 +- app/services/ci/create_pipeline_service.rb | 10 +- app/workers/scheduled_trigger_worker.rb | 8 +- spec/factories/ci/scheduled_triggers.rb | 18 +++- spec/lib/ci/cron_parser_spec.rb | 91 +++++++------------ spec/models/ci/scheduled_trigger_spec.rb | 31 +++---- .../ci/create_pipeline_service_spec.rb | 4 + spec/workers/scheduled_trigger_worker_spec.rb | 54 ++++++++++- 8 files changed, 127 insertions(+), 99 deletions(-) diff --git a/app/models/ci/scheduled_trigger.rb b/app/models/ci/scheduled_trigger.rb index 5b1ff7bd7a4..9af274243a5 100644 --- a/app/models/ci/scheduled_trigger.rb +++ b/app/models/ci/scheduled_trigger.rb @@ -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? - end - - def valid_ref? - true #TODO: + if next_time.present? + update_attributes(next_run_at: next_time) + end end def update_last_run! - update(:last_run_at => Time.now) + update_attributes(last_run_at: Time.now) end end end diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 38a85e9fc42..6e3880e1e63 100644 --- a/app/services/ci/create_pipeline_service.rb +++ b/app/services/ci/create_pipeline_service.rb @@ -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,8 +17,10 @@ module Ci return error('Pipeline is disabled') end - unless trigger_request || can?(current_user, :create_pipeline, project) - return error('Insufficient permissions to create a new pipeline') + 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? diff --git a/app/workers/scheduled_trigger_worker.rb b/app/workers/scheduled_trigger_worker.rb index 7dc17aa4332..5c2f03dee79 100644 --- a/app/workers/scheduled_trigger_worker.rb +++ b/app/workers/scheduled_trigger_worker.rb @@ -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 diff --git a/spec/factories/ci/scheduled_triggers.rb b/spec/factories/ci/scheduled_triggers.rb index 9d45f4b4962..c97b2d14bd1 100644 --- a/spec/factories/ci/scheduled_triggers.rb +++ b/spec/factories/ci/scheduled_triggers.rb @@ -1,42 +1,58 @@ 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 diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb index 58eb26c9421..f8c7e88edb3 100644 --- a/spec/lib/ci/cron_parser_spec.rb +++ b/spec/lib/ci/cron_parser_spec.rb @@ -6,91 +6,62 @@ 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 - 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)) + expect(subject).to be > Time.now.in_time_zone(cron_time_zone) + expect(subject.utc_offset/60/60).to eq(-7) 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 context 'when cron and cron_time_zone are invalid' do diff --git a/spec/models/ci/scheduled_trigger_spec.rb b/spec/models/ci/scheduled_trigger_spec.rb index 68ba9c379b8..bb5e969fa44 100644 --- a/spec/models/ci/scheduled_trigger_spec.rb +++ b/spec/models/ci/scheduled_trigger_spec.rb @@ -1,5 +1,4 @@ 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}" + subject { scheduled_trigger.schedule_next_run! } - expect(scheduled_trigger.cron).to be_nil - end - end + let(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, next_run_at: nil) } - context 'when weekly build' do - - end - - context 'when monthly build' do - - end + it 'updates next_run_at' do + is_expected.not_to be_nil end + end + + describe '#update_last_run!' do + subject { scheduled_trigger.update_last_run! } - context 'when cron and cron_time_zone are invaild' do - it 'schedules nothing' do + let(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, last_run_at: nil) } - end + it 'updates last_run_at' do + is_expected.not_to be_nil end end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index d2f0337c260..4e34acc3585 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -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 diff --git a/spec/workers/scheduled_trigger_worker_spec.rb b/spec/workers/scheduled_trigger_worker_spec.rb index c17536720a4..ffcb27602a1 100644 --- a/spec/workers/scheduled_trigger_worker_spec.rb +++ b/spec/workers/scheduled_trigger_worker_spec.rb @@ -1,11 +1,57 @@ 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 -- 2.30.9