From 5f715f1d32c6f5ce25b3721bde8f476173afadc8 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 23 Mar 2017 03:54:49 +0900 Subject: Add scheduled_trigger model. Add cron parser. Plus, specs. --- app/models/ci/scheduled_trigger.rb | 23 ++++ app/workers/scheduled_trigger_worker.rb | 18 +++ .../20170322070910_create_ci_scheduled_triggers.rb | 45 ++++++++ db/schema.rb | 24 +++- lib/ci/cron_parser.rb | 30 +++++ spec/factories/ci/scheduled_triggers.rb | 42 +++++++ spec/lib/ci/cron_parser_spec.rb | 128 +++++++++++++++++++++ spec/models/ci/scheduled_trigger_spec.rb | 38 ++++++ spec/workers/scheduled_trigger_worker_spec.rb | 11 ++ 9 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 app/models/ci/scheduled_trigger.rb create mode 100644 app/workers/scheduled_trigger_worker.rb create mode 100644 db/migrate/20170322070910_create_ci_scheduled_triggers.rb create mode 100644 lib/ci/cron_parser.rb create mode 100644 spec/factories/ci/scheduled_triggers.rb create mode 100644 spec/lib/ci/cron_parser_spec.rb create mode 100644 spec/models/ci/scheduled_trigger_spec.rb create mode 100644 spec/workers/scheduled_trigger_worker_spec.rb diff --git a/app/models/ci/scheduled_trigger.rb b/app/models/ci/scheduled_trigger.rb new file mode 100644 index 00000000000..5b1ff7bd7a4 --- /dev/null +++ b/app/models/ci/scheduled_trigger.rb @@ -0,0 +1,23 @@ +module Ci + class ScheduledTrigger < ActiveRecord::Base + extend Ci::Model + + acts_as_paranoid + + belongs_to :project + belongs_to :owner, class_name: "User" + + 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: + end + + def update_last_run! + update(:last_run_at => Time.now) + end + end +end diff --git a/app/workers/scheduled_trigger_worker.rb b/app/workers/scheduled_trigger_worker.rb new file mode 100644 index 00000000000..7dc17aa4332 --- /dev/null +++ b/app/workers/scheduled_trigger_worker.rb @@ -0,0 +1,18 @@ +class ScheduledTriggerWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + # TODO: Update next_run_at + + Ci::ScheduledTriggers.where("next_run_at < ?", Time.now).find_each do |trigger| + begin + Ci::CreateTriggerRequestService.new.execute(trigger.project, trigger, trigger.ref) + rescue => e + Rails.logger.error "#{trigger.id}: Failed to trigger job: #{e.message}" + ensure + trigger.schedule_next_run! + end + end + end +end diff --git a/db/migrate/20170322070910_create_ci_scheduled_triggers.rb b/db/migrate/20170322070910_create_ci_scheduled_triggers.rb new file mode 100644 index 00000000000..91e4b42d2af --- /dev/null +++ b/db/migrate/20170322070910_create_ci_scheduled_triggers.rb @@ -0,0 +1,45 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateCiScheduledTriggers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + create_table :ci_scheduled_triggers do |t| + t.integer "project_id" + t.datetime "deleted_at" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "owner_id" + t.string "description" + t.string "cron" + t.string "cron_time_zone" + t.datetime "next_run_at" + t.datetime "last_run_at" + t.string "ref" + end + + add_index :ci_scheduled_triggers, ["next_run_at"], name: "index_ci_scheduled_triggers_on_next_run_at", using: :btree + add_index :ci_scheduled_triggers, ["project_id"], name: "index_ci_scheduled_triggers_on_project_id", using: :btree + add_foreign_key :ci_scheduled_triggers, :users, column: :owner_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index 582f68cbee7..a101ce280fe 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,7 +61,6 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" - t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false @@ -111,6 +110,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.string "plantuml_url" t.boolean "plantuml_enabled" t.integer "terminal_max_session_time", default: 0, null: false + t.integer "max_pages_size", default: 100, null: false t.string "default_artifacts_expire_in", default: "0", null: false t.integer "unique_ips_limit_per_user" t.integer "unique_ips_limit_time_window" @@ -290,6 +290,23 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree + create_table "ci_scheduled_triggers", force: :cascade do |t| + t.integer "project_id" + t.datetime "deleted_at" + t.datetime "created_at" + t.datetime "updated_at" + t.integer "owner_id" + t.string "description" + t.string "cron" + t.string "cron_time_zone" + t.datetime "next_run_at" + t.datetime "last_run_at" + t.string "ref" + end + + add_index "ci_scheduled_triggers", ["next_run_at"], name: "index_ci_scheduled_triggers_on_next_run_at", using: :btree + add_index "ci_scheduled_triggers", ["project_id"], name: "index_ci_scheduled_triggers_on_project_id", using: :btree + create_table "ci_trigger_requests", force: :cascade do |t| t.integer "trigger_id", null: false t.text "variables" @@ -689,8 +706,8 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: false, null: false t.datetime "deleted_at" - t.text "description_html" t.boolean "lfs_enabled" + t.text "description_html" t.integer "parent_id" end @@ -1242,8 +1259,8 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.datetime "otp_grace_period_started_at" t.boolean "ldap_email", default: false, null: false t.boolean "external", default: false - t.string "incoming_email_token" t.string "organization" + t.string "incoming_email_token" t.boolean "authorized_projects_populated" t.boolean "ghost" t.boolean "notified_of_own_activity" @@ -1298,6 +1315,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_foreign_key "boards", "projects" add_foreign_key "chat_teams", "namespaces", on_delete: :cascade + add_foreign_key "ci_scheduled_triggers", "users", column: "owner_id", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb new file mode 100644 index 00000000000..163cfc86aa7 --- /dev/null +++ b/lib/ci/cron_parser.rb @@ -0,0 +1,30 @@ +require 'rufus-scheduler' # Included in sidekiq-cron + +module Ci + class CronParser + def initialize(cron, cron_time_zone = 'UTC') + @cron = cron + @cron_time_zone = cron_time_zone + end + + def next_time_from_now + cronLine = try_parse_cron + return nil unless cronLine.present? + cronLine.next_time + end + + def valid_syntax? + try_parse_cron.present? ? true : false + end + + private + + def try_parse_cron + begin + Rufus::Scheduler.parse("#{@cron} #{@cron_time_zone}") + rescue + nil + end + end + end +end diff --git a/spec/factories/ci/scheduled_triggers.rb b/spec/factories/ci/scheduled_triggers.rb new file mode 100644 index 00000000000..9d45f4b4962 --- /dev/null +++ b/spec/factories/ci/scheduled_triggers.rb @@ -0,0 +1,42 @@ +FactoryGirl.define do + factory :ci_scheduled_trigger, class: Ci::ScheduledTrigger do + project factory: :empty_project + owner factory: :user + ref 'master' + + trait :cron_nightly_build do + cron '0 1 * * *' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_weekly_build do + cron '0 1 * * 5' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_monthly_build do + cron '0 1 22 * *' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_every_5_minutes do + cron '*/5 * * * *' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_every_5_hours do + cron '* */5 * * *' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_every_5_days do + cron '* * */5 * *' + cron_time_zone 'Europe/Istanbul' + end + + trait :cron_every_5_months do + cron '* * * */5 *' + cron_time_zone 'Europe/Istanbul' + end + end +end diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb new file mode 100644 index 00000000000..58eb26c9421 --- /dev/null +++ b/spec/lib/ci/cron_parser_spec.rb @@ -0,0 +1,128 @@ +require 'spec_helper' + +module Ci + describe CronParser, lib: true do + describe '#next_time_from_now' do + 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' } + + 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) + end + end + + context 'at 10:00, 20:00' do + let(:cron) { '0 */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.hours + time = time.change(sec: 0, min: 0, hour: time.hour-time.hour%10) + is_expected.to eq(time) + end + end + + context 'when cron is every 10 days' do + let(:cron) { '0 0 */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) + end + end + + context 'when cron is every week 2:00 AM' do + let(:cron) { '0 2 * * *' } + 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)) + end + end + + context 'when cron_time_zone is US/Pacific' do + let(:cron) { '0 1 * * *' } + 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)) + 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 + let(:cron) { 'hack' } + let(:cron_time_zone) { 'hack' } + + it 'returns nil' do + is_expected.to be_nil + end + end + end + + describe '#valid_syntax?' do + subject { described_class.new(cron, cron_time_zone).valid_syntax? } + + context 'when cron and cron_time_zone are valid' do + let(:cron) { '* * * * *' } + let(:cron_time_zone) { 'Europe/Istanbul' } + + it 'returns true' do + is_expected.to eq(true) + end + end + + context 'when cron and cron_time_zone are invalid' do + let(:cron) { 'hack' } + let(:cron_time_zone) { 'hack' } + + it 'returns false' do + is_expected.to eq(false) + end + end + end + end +end diff --git a/spec/models/ci/scheduled_trigger_spec.rb b/spec/models/ci/scheduled_trigger_spec.rb new file mode 100644 index 00000000000..68ba9c379b8 --- /dev/null +++ b/spec/models/ci/scheduled_trigger_spec.rb @@ -0,0 +1,38 @@ +require 'spec_helper' +require 'rufus-scheduler' # Included in sidekiq-cron + +describe Ci::ScheduledTrigger, models: true do + + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:owner) } + 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 + + context 'when weekly build' do + + end + + context 'when monthly build' do + + end + end + + context 'when cron and cron_time_zone are invaild' do + it 'schedules nothing' do + + end + end + end +end diff --git a/spec/workers/scheduled_trigger_worker_spec.rb b/spec/workers/scheduled_trigger_worker_spec.rb new file mode 100644 index 00000000000..c17536720a4 --- /dev/null +++ b/spec/workers/scheduled_trigger_worker_spec.rb @@ -0,0 +1,11 @@ +require 'spec_helper' + +describe ScheduledTriggerWorker do + subject { described_class.new.perform } + + context '#perform' do # TODO: + it 'does' do + is_expected.to be_nil + end + end +end -- cgit v1.2.1 From 37d6d1e46130f44f2fe05171b814b5682696839c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 24 Mar 2017 00:18:13 +0900 Subject: 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 +++----- spec/services/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 -- cgit v1.2.1 From 531af92dd3759a78959c28c5307d9e73beac1901 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 24 Mar 2017 00:35:54 +0900 Subject: Add config for worker --- config/gitlab.yml.example | 3 +++ config/initializers/1_settings.rb | 3 +++ 2 files changed, 6 insertions(+) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 4314e902564..892064949ce 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -180,6 +180,9 @@ production: &base # Flag stuck CI jobs as failed stuck_ci_jobs_worker: cron: "0 * * * *" + # Execute scheduled triggers + scheduled_trigger_worker: + cron: "0 * * * *" # Remove expired build artifacts expire_build_artifacts_worker: cron: "50 * * * *" diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index f7cae84088e..15e6b382eb7 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -315,6 +315,9 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_jobs_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['stuck_ci_jobs_worker']['job_class'] = 'StuckCiJobsWorker' +Settings.cron_jobs['scheduled_trigger_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['scheduled_trigger_worker']['cron'] ||= '0 * * * *' +Settings.cron_jobs['scheduled_trigger_worker']['job_class'] = 'ScheduledTriggerWorker' Settings.cron_jobs['expire_build_artifacts_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *' Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker' -- cgit v1.2.1 From 8f798e81843c155db3b6661a8d30505b3fe1d098 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 18:50:08 +0900 Subject: Rollback --- db/schema.rb | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index a101ce280fe..7f51f4f2fdb 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -290,23 +290,6 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_index "ci_runners", ["locked"], name: "index_ci_runners_on_locked", using: :btree add_index "ci_runners", ["token"], name: "index_ci_runners_on_token", using: :btree - create_table "ci_scheduled_triggers", force: :cascade do |t| - t.integer "project_id" - t.datetime "deleted_at" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "owner_id" - t.string "description" - t.string "cron" - t.string "cron_time_zone" - t.datetime "next_run_at" - t.datetime "last_run_at" - t.string "ref" - end - - add_index "ci_scheduled_triggers", ["next_run_at"], name: "index_ci_scheduled_triggers_on_next_run_at", using: :btree - add_index "ci_scheduled_triggers", ["project_id"], name: "index_ci_scheduled_triggers_on_project_id", using: :btree - create_table "ci_trigger_requests", force: :cascade do |t| t.integer "trigger_id", null: false t.text "variables" @@ -1315,7 +1298,6 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_foreign_key "boards", "projects" add_foreign_key "chat_teams", "namespaces", on_delete: :cascade - add_foreign_key "ci_scheduled_triggers", "users", column: "owner_id", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade -- cgit v1.2.1 From b4da589ee902654225fe915c22e0859522511e66 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 18:50:54 +0900 Subject: Remove old migration file --- .../20170322070910_create_ci_scheduled_triggers.rb | 45 ---------------------- 1 file changed, 45 deletions(-) delete mode 100644 db/migrate/20170322070910_create_ci_scheduled_triggers.rb diff --git a/db/migrate/20170322070910_create_ci_scheduled_triggers.rb b/db/migrate/20170322070910_create_ci_scheduled_triggers.rb deleted file mode 100644 index 91e4b42d2af..00000000000 --- a/db/migrate/20170322070910_create_ci_scheduled_triggers.rb +++ /dev/null @@ -1,45 +0,0 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - -class CreateCiScheduledTriggers < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - # Set this constant to true if this migration requires downtime. - DOWNTIME = false - - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - - def change - create_table :ci_scheduled_triggers do |t| - t.integer "project_id" - t.datetime "deleted_at" - t.datetime "created_at" - t.datetime "updated_at" - t.integer "owner_id" - t.string "description" - t.string "cron" - t.string "cron_time_zone" - t.datetime "next_run_at" - t.datetime "last_run_at" - t.string "ref" - end - - add_index :ci_scheduled_triggers, ["next_run_at"], name: "index_ci_scheduled_triggers_on_next_run_at", using: :btree - add_index :ci_scheduled_triggers, ["project_id"], name: "index_ci_scheduled_triggers_on_project_id", using: :btree - add_foreign_key :ci_scheduled_triggers, :users, column: :owner_id, on_delete: :cascade - end -end -- cgit v1.2.1 From 4c2435f58e8b46c25af64b85345eb49dc5341f5a Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 18:54:59 +0900 Subject: Add ref to ci_triggers --- db/migrate/20170329095325_add_ref_to_triggers.rb | 29 ++++++++++++++++++++++++ db/schema.rb | 1 + 2 files changed, 30 insertions(+) create mode 100644 db/migrate/20170329095325_add_ref_to_triggers.rb diff --git a/db/migrate/20170329095325_add_ref_to_triggers.rb b/db/migrate/20170329095325_add_ref_to_triggers.rb new file mode 100644 index 00000000000..f8236b5a711 --- /dev/null +++ b/db/migrate/20170329095325_add_ref_to_triggers.rb @@ -0,0 +1,29 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class AddRefToTriggers < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + add_column :ci_triggers, :ref, :string + end +end diff --git a/db/schema.rb b/db/schema.rb index 7f51f4f2fdb..f20f9bdfb17 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -308,6 +308,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.integer "project_id" t.integer "owner_id" t.string "description" + t.string "ref" end add_index "ci_triggers", ["project_id"], name: "index_ci_triggers_on_project_id", using: :btree -- cgit v1.2.1 From e32c1a5c92a80c350bbf3b70552be5cf29501fe7 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 19:07:25 +0900 Subject: Add ci_trigger_schedules table --- .../20170329095907_create_ci_trigger_schedules.rb | 42 ++++++++++++++++++++++ db/schema.rb | 15 ++++++++ 2 files changed, 57 insertions(+) create mode 100644 db/migrate/20170329095907_create_ci_trigger_schedules.rb diff --git a/db/migrate/20170329095907_create_ci_trigger_schedules.rb b/db/migrate/20170329095907_create_ci_trigger_schedules.rb new file mode 100644 index 00000000000..42f9497cac7 --- /dev/null +++ b/db/migrate/20170329095907_create_ci_trigger_schedules.rb @@ -0,0 +1,42 @@ +# See http://doc.gitlab.com/ce/development/migration_style_guide.html +# for more information on how to write migrations for GitLab. + +class CreateCiTriggerSchedules < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + # Set this constant to true if this migration requires downtime. + DOWNTIME = false + + # When a migration requires downtime you **must** uncomment the following + # constant and define a short and easy to understand explanation as to why the + # migration requires downtime. + # DOWNTIME_REASON = '' + + # When using the methods "add_concurrent_index" or "add_column_with_default" + # you must disable the use of transactions as these methods can not run in an + # existing transaction. When using "add_concurrent_index" make sure that this + # method is the _only_ method called in the migration, any other changes + # should go in a separate migration. This ensures that upon failure _only_ the + # index creation fails and can be retried or reverted easily. + # + # To disable transactions uncomment the following line and remove these + # comments: + # disable_ddl_transaction! + + def change + create_table :ci_trigger_schedules do |t| + t.integer "project_id" + t.integer "trigger_id", null: false + t.datetime "deleted_at" + t.datetime "created_at" + t.datetime "updated_at" + t.string "cron" + t.string "cron_time_zone" + t.datetime "next_run_at" + end + + add_index :ci_trigger_schedules, ["next_run_at"], name: "index_ci_trigger_schedules_on_next_run_at", using: :btree + add_index :ci_trigger_schedules, ["project_id"], name: "index_ci_trigger_schedules_on_project_id", using: :btree + add_foreign_key :ci_trigger_schedules, :ci_triggers, column: :trigger_id, on_delete: :cascade + end +end diff --git a/db/schema.rb b/db/schema.rb index f20f9bdfb17..8f3b3110548 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -300,6 +300,20 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_index "ci_trigger_requests", ["commit_id"], name: "index_ci_trigger_requests_on_commit_id", using: :btree + create_table "ci_trigger_schedules", force: :cascade do |t| + t.integer "project_id" + t.integer "trigger_id", null: false + t.datetime "deleted_at" + t.datetime "created_at" + t.datetime "updated_at" + t.string "cron" + t.string "cron_time_zone" + t.datetime "next_run_at" + end + + add_index "ci_trigger_schedules", ["next_run_at"], name: "index_ci_trigger_schedules_on_next_run_at", using: :btree + add_index "ci_trigger_schedules", ["project_id"], name: "index_ci_trigger_schedules_on_project_id", using: :btree + create_table "ci_triggers", force: :cascade do |t| t.string "token" t.datetime "deleted_at" @@ -1299,6 +1313,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_foreign_key "boards", "projects" add_foreign_key "chat_teams", "namespaces", on_delete: :cascade + add_foreign_key "ci_trigger_schedules", "ci_triggers", column: "trigger_id", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade -- cgit v1.2.1 From c426763c42d41c9c0c9a9cfe544f3185eeaa984f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 20:49:47 +0900 Subject: Rename ScheduledTrigger to TriggerSchedule. Because table structure changed. --- app/models/ci/scheduled_trigger.rb | 21 ---------- app/models/ci/trigger.rb | 1 + app/models/ci/trigger_schedule.rb | 21 ++++++++++ app/workers/scheduled_trigger_worker.rb | 18 --------- app/workers/trigger_schedule_worker.rb | 17 ++++++++ config/gitlab.yml.example | 2 +- config/initializers/1_settings.rb | 6 +-- spec/factories/ci/scheduled_triggers.rb | 5 +-- spec/models/ci/scheduled_trigger_spec.rb | 29 -------------- spec/models/ci/trigger_schedule_spec.rb | 29 ++++++++++++++ spec/models/ci/trigger_spec.rb | 1 + spec/workers/scheduled_trigger_worker_spec.rb | 57 --------------------------- spec/workers/trigger_schedule_worker_spec.rb | 57 +++++++++++++++++++++++++++ 13 files changed, 132 insertions(+), 132 deletions(-) delete mode 100644 app/models/ci/scheduled_trigger.rb create mode 100644 app/models/ci/trigger_schedule.rb delete mode 100644 app/workers/scheduled_trigger_worker.rb create mode 100644 app/workers/trigger_schedule_worker.rb delete mode 100644 spec/models/ci/scheduled_trigger_spec.rb create mode 100644 spec/models/ci/trigger_schedule_spec.rb delete mode 100644 spec/workers/scheduled_trigger_worker_spec.rb create mode 100644 spec/workers/trigger_schedule_worker_spec.rb diff --git a/app/models/ci/scheduled_trigger.rb b/app/models/ci/scheduled_trigger.rb deleted file mode 100644 index 9af274243a5..00000000000 --- a/app/models/ci/scheduled_trigger.rb +++ /dev/null @@ -1,21 +0,0 @@ -module Ci - class ScheduledTrigger < ActiveRecord::Base - extend Ci::Model - - acts_as_paranoid - - belongs_to :project - belongs_to :owner, class_name: "User" - - def schedule_next_run! - next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now - if next_time.present? - update_attributes(next_run_at: next_time) - end - end - - def update_last_run! - update_attributes(last_run_at: Time.now) - end - end -end diff --git a/app/models/ci/trigger.rb b/app/models/ci/trigger.rb index cba1d81a861..0a89f3e0640 100644 --- a/app/models/ci/trigger.rb +++ b/app/models/ci/trigger.rb @@ -8,6 +8,7 @@ module Ci belongs_to :owner, class_name: "User" has_many :trigger_requests, dependent: :destroy + has_one :trigger_schedule, dependent: :destroy validates :token, presence: true, uniqueness: true diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb new file mode 100644 index 00000000000..7efaa228a93 --- /dev/null +++ b/app/models/ci/trigger_schedule.rb @@ -0,0 +1,21 @@ +module Ci + class TriggerSchedule < ActiveRecord::Base + extend Ci::Model + + acts_as_paranoid + + belongs_to :project + belongs_to :trigger + + def schedule_next_run! + next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now + if next_time.present? + update_attributes(next_run_at: next_time) + end + end + + # def update_last_run! + # update_attributes(last_run_at: Time.now) + # end + end +end diff --git a/app/workers/scheduled_trigger_worker.rb b/app/workers/scheduled_trigger_worker.rb deleted file mode 100644 index 5c2f03dee79..00000000000 --- a/app/workers/scheduled_trigger_worker.rb +++ /dev/null @@ -1,18 +0,0 @@ -class ScheduledTriggerWorker - include Sidekiq::Worker - include CronjobQueue - - def perform - Ci::ScheduledTrigger.where("next_run_at < ?", Time.now).find_each do |trigger| - begin - 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 -end diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb new file mode 100644 index 00000000000..d55e9378e02 --- /dev/null +++ b/app/workers/trigger_schedule_worker.rb @@ -0,0 +1,17 @@ +class TriggerScheduleWorker + include Sidekiq::Worker + include CronjobQueue + + def perform + Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger| + begin + 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! + end + end + end +end diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 892064949ce..6fb67426c8b 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -181,7 +181,7 @@ production: &base stuck_ci_jobs_worker: cron: "0 * * * *" # Execute scheduled triggers - scheduled_trigger_worker: + trigger_schedule_worker: cron: "0 * * * *" # Remove expired build artifacts expire_build_artifacts_worker: diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 15e6b382eb7..71342f435f1 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -315,9 +315,9 @@ Settings['cron_jobs'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_jobs_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['stuck_ci_jobs_worker']['job_class'] = 'StuckCiJobsWorker' -Settings.cron_jobs['scheduled_trigger_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['scheduled_trigger_worker']['cron'] ||= '0 * * * *' -Settings.cron_jobs['scheduled_trigger_worker']['job_class'] = 'ScheduledTriggerWorker' +Settings.cron_jobs['trigger_schedule_worker'] ||= Settingslogic.new({}) +Settings.cron_jobs['trigger_schedule_worker']['cron'] ||= '0 * * * *' +Settings.cron_jobs['trigger_schedule_worker']['job_class'] = 'TriggerScheduleWorker' Settings.cron_jobs['expire_build_artifacts_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *' Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker' diff --git a/spec/factories/ci/scheduled_triggers.rb b/spec/factories/ci/scheduled_triggers.rb index c97b2d14bd1..f909e343bf2 100644 --- a/spec/factories/ci/scheduled_triggers.rb +++ b/spec/factories/ci/scheduled_triggers.rb @@ -1,8 +1,7 @@ FactoryGirl.define do - factory :ci_scheduled_trigger, class: Ci::ScheduledTrigger do + factory :ci_trigger_schedule, class: Ci::TriggerSchedule do project factory: :project - owner factory: :user - ref 'master' + trigger factory: :ci_trigger trait :force_triggable do next_run_at Time.now - 1.month diff --git a/spec/models/ci/scheduled_trigger_spec.rb b/spec/models/ci/scheduled_trigger_spec.rb deleted file mode 100644 index bb5e969fa44..00000000000 --- a/spec/models/ci/scheduled_trigger_spec.rb +++ /dev/null @@ -1,29 +0,0 @@ -require 'spec_helper' - -describe Ci::ScheduledTrigger, models: true do - - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to belong_to(:owner) } - end - - describe '#schedule_next_run!' do - subject { scheduled_trigger.schedule_next_run! } - - 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 - - describe '#update_last_run!' do - subject { scheduled_trigger.update_last_run! } - - 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 diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb new file mode 100644 index 00000000000..14b8530a65b --- /dev/null +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -0,0 +1,29 @@ +require 'spec_helper' + +describe Ci::TriggerSchedule, models: true do + + describe 'associations' do + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:trigger) } + end + + describe '#schedule_next_run!' do + subject { trigger_schedule.schedule_next_run! } + + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } + + 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! } + + # 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 diff --git a/spec/models/ci/trigger_spec.rb b/spec/models/ci/trigger_spec.rb index 1bcb673cb16..42170de0180 100644 --- a/spec/models/ci/trigger_spec.rb +++ b/spec/models/ci/trigger_spec.rb @@ -7,6 +7,7 @@ describe Ci::Trigger, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:owner) } it { is_expected.to have_many(:trigger_requests) } + it { is_expected.to have_one(:trigger_schedule) } end describe 'before_validation' do diff --git a/spec/workers/scheduled_trigger_worker_spec.rb b/spec/workers/scheduled_trigger_worker_spec.rb deleted file mode 100644 index ffcb27602a1..00000000000 --- a/spec/workers/scheduled_trigger_worker_spec.rb +++ /dev/null @@ -1,57 +0,0 @@ -require 'spec_helper' - -describe ScheduledTriggerWorker do - let(:worker) { described_class.new } - - 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 diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb new file mode 100644 index 00000000000..6c7521e8339 --- /dev/null +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -0,0 +1,57 @@ +require 'spec_helper' + +describe TriggerScheduleWorker do + let(:worker) { described_class.new } + + 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_trigger_schedule, :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 + trigger_schedule2 = create(:ci_trigger_schedule, :cron_nightly_build) + expect(Ci::TriggerSchedule.last.next_run_at).to eq(trigger_schedule2.next_run_at) + end + end + + context 'when there are no scheduled triggers within next_run_at' do + let!(:trigger_schedule) { create(:ci_trigger_schedule, :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::TriggerSchedule.last.next_run_at).to eq(trigger_schedule.next_run_at) + end + end + + context 'when next_run_at is nil' do + let!(:trigger_schedule) { create(:ci_trigger_schedule, :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::TriggerSchedule.last.next_run_at).to eq(trigger_schedule.next_run_at) + end + end +end -- cgit v1.2.1 From 3d1bc4a44cf7197d3148d829c4f527e9afbf1ea6 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 22:14:58 +0900 Subject: Fixed strcture for db change --- app/services/ci/create_pipeline_service.rb | 10 ++--- app/workers/trigger_schedule_worker.rb | 11 ++--- spec/factories/ci/scheduled_triggers.rb | 57 ------------------------ spec/factories/ci/trigger_schedules.rb | 57 ++++++++++++++++++++++++ spec/factories/ci/triggers.rb | 2 +- spec/services/ci/create_pipeline_service_spec.rb | 4 -- spec/workers/trigger_schedule_worker_spec.rb | 14 ++++-- 7 files changed, 79 insertions(+), 76 deletions(-) delete mode 100644 spec/factories/ci/scheduled_triggers.rb create mode 100644 spec/factories/ci/trigger_schedules.rb diff --git a/app/services/ci/create_pipeline_service.rb b/app/services/ci/create_pipeline_service.rb index 6e3880e1e63..38a85e9fc42 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, scheduled_trigger: false) + def execute(ignore_skip_ci: false, save_on_errors: true, trigger_request: nil) @pipeline = Ci::Pipeline.new( project: project, ref: ref, sha: sha, before_sha: before_sha, tag: tag?, - trigger_requests: (scheduled_trigger) ? [] : Array(trigger_request), + trigger_requests: Array(trigger_request), user: current_user ) @@ -17,10 +17,8 @@ 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 + unless trigger_request || can?(current_user, :create_pipeline, project) + return error('Insufficient permissions to create a new pipeline') end unless branch? || tag? diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb index d55e9378e02..04a53a38adb 100644 --- a/app/workers/trigger_schedule_worker.rb +++ b/app/workers/trigger_schedule_worker.rb @@ -3,14 +3,15 @@ class TriggerScheduleWorker include CronjobQueue def perform - Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger| + Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger_schedule| begin - Ci::CreatePipelineService.new(trigger.project, trigger.owner, ref: trigger.ref). - execute(ignore_skip_ci: true, scheduled_trigger: true) + Ci::CreateTriggerRequestService.new.execute(trigger_schedule.trigger.project, + trigger_schedule.trigger, + trigger_schedule.trigger.ref) rescue => e - Rails.logger.error "#{trigger.id}: Failed to trigger job: #{e.message}" + Rails.logger.error "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" ensure - trigger.schedule_next_run! + trigger_schedule.schedule_next_run! end end end diff --git a/spec/factories/ci/scheduled_triggers.rb b/spec/factories/ci/scheduled_triggers.rb deleted file mode 100644 index f909e343bf2..00000000000 --- a/spec/factories/ci/scheduled_triggers.rb +++ /dev/null @@ -1,57 +0,0 @@ -FactoryGirl.define do - factory :ci_trigger_schedule, class: Ci::TriggerSchedule do - project factory: :project - trigger factory: :ci_trigger - - 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/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb new file mode 100644 index 00000000000..f909e343bf2 --- /dev/null +++ b/spec/factories/ci/trigger_schedules.rb @@ -0,0 +1,57 @@ +FactoryGirl.define do + factory :ci_trigger_schedule, class: Ci::TriggerSchedule do + project factory: :project + trigger factory: :ci_trigger + + 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/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index a27b04424e5..1feaa9b9fa1 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do - token 'token' + token { SecureRandom.hex(10) } end end end diff --git a/spec/services/ci/create_pipeline_service_spec.rb b/spec/services/ci/create_pipeline_service_spec.rb index 4e34acc3585..d2f0337c260 100644 --- a/spec/services/ci/create_pipeline_service_spec.rb +++ b/spec/services/ci/create_pipeline_service_spec.rb @@ -214,9 +214,5 @@ 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/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 6c7521e8339..2cf51a31c71 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -8,18 +8,26 @@ describe TriggerScheduleWorker do end context 'when there is a scheduled trigger within next_run_at' do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:trigger) { create(:ci_trigger, owner: user, project: project, ref: 'master') } + let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable, trigger: trigger, project: project) } + before do - create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable) worker.perform end + it 'creates a new trigger request' do + expect(Ci::TriggerRequest.first.trigger_id).to eq(trigger.id) + end + it 'creates a new pipeline' do expect(Ci::Pipeline.last.status).to eq('pending') end it 'schedules next_run_at' do - trigger_schedule2 = create(:ci_trigger_schedule, :cron_nightly_build) - expect(Ci::TriggerSchedule.last.next_run_at).to eq(trigger_schedule2.next_run_at) + next_time = Ci::CronParser.new('0 1 * * *', 'Europe/Istanbul').next_time_from_now + expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end -- cgit v1.2.1 From 75f5e710434fbe6d709e6895c8c9328c9e92962e Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 29 Mar 2017 22:22:40 +0900 Subject: Add rufus-scheduler to Gemfile --- Gemfile | 3 +++ Gemfile.lock | 1 + lib/ci/cron_parser.rb | 2 -- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Gemfile b/Gemfile index 6a45b3d7339..b16505b3aa2 100644 --- a/Gemfile +++ b/Gemfile @@ -144,6 +144,9 @@ gem 'sidekiq-cron', '~> 0.4.4' gem 'redis-namespace', '~> 1.5.2' gem 'sidekiq-limit_fetch', '~> 3.4' +# Cron Parser +gem 'rufus-scheduler', '~> 3.1.10' + # HTTP requests gem 'httparty', '~> 0.13.3' diff --git a/Gemfile.lock b/Gemfile.lock index 50ca9af7a7a..e4603df5f7f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -987,6 +987,7 @@ DEPENDENCIES rubocop-rspec (~> 1.12.0) ruby-fogbugz (~> 0.2.1) ruby-prof (~> 0.16.2) + rufus-scheduler (~> 3.1.10) rugged (~> 0.25.1.1) sanitize (~> 2.0) sass-rails (~> 5.0.6) diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index 163cfc86aa7..24c4dd676dc 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -1,5 +1,3 @@ -require 'rufus-scheduler' # Included in sidekiq-cron - module Ci class CronParser def initialize(cron, cron_time_zone = 'UTC') -- cgit v1.2.1 From 2a1a7310d04f6d64a983d2438fdcc515e7118d91 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 30 Mar 2017 03:33:23 +0900 Subject: Add validation to Ci::TriggerSchedule (Halfway) --- app/models/ci/trigger_schedule.rb | 35 ++++++++++++++++++++++++++++---- lib/ci/cron_parser.rb | 12 ++++++----- spec/factories/ci/trigger_schedules.rb | 16 ++++++++------- spec/factories/ci/triggers.rb | 2 +- spec/models/ci/trigger_schedule_spec.rb | 36 ++++++++++++++++++++++----------- 5 files changed, 72 insertions(+), 29 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 7efaa228a93..1b3b73971bb 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -7,15 +7,42 @@ module Ci belongs_to :project belongs_to :trigger + validates :trigger, presence: true + validates :cron, presence: true + validates :cron_time_zone, presence: true + validate :check_cron + validate :check_ref + + after_create :schedule_next_run! + def schedule_next_run! next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now if next_time.present? - update_attributes(next_run_at: next_time) + update!(next_run_at: next_time) end end - # def update_last_run! - # update_attributes(last_run_at: Time.now) - # end + private + + def check_cron + cron_parser = Ci::CronParser.new(cron, cron_time_zone) + is_valid_cron, is_valid_cron_time_zone = cron_parser.validation + + if !is_valid_cron + self.errors.add(:cron, " is invalid syntax") + elsif !is_valid_cron_time_zone + self.errors.add(:cron_time_zone, " is invalid timezone") + elsif (cron_parser.next_time_from_now - Time.now).abs < 1.hour + self.errors.add(:cron, " can not be less than 1 hour") + end + end + + def check_ref + if !trigger.ref.present? + self.errors.add(:ref, " is empty") + elsif trigger.project.repository.ref_exists?(trigger.ref) + self.errors.add(:ref, " does not exist") + end + end end end diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index 24c4dd676dc..b0ffb48dabc 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -6,20 +6,22 @@ module Ci end def next_time_from_now - cronLine = try_parse_cron + cronLine = try_parse_cron(@cron, @cron_time_zone) return nil unless cronLine.present? cronLine.next_time end - def valid_syntax? - try_parse_cron.present? ? true : false + def validation + is_valid_cron = try_parse_cron(@cron, 'Europe/Istanbul').present? + is_valid_cron_time_zone = try_parse_cron('* * * * *', @cron_time_zone).present? + return is_valid_cron, is_valid_cron_time_zone end private - def try_parse_cron + def try_parse_cron(cron, cron_time_zone) begin - Rufus::Scheduler.parse("#{@cron} #{@cron_time_zone}") + Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") rescue nil end diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index f909e343bf2..0dd397435c1 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -4,18 +4,20 @@ FactoryGirl.define do trigger factory: :ci_trigger trait :force_triggable do - next_run_at Time.now - 1.month + after(:create) do |trigger_schedule, evaluator| + trigger_schedule.next_run_at -= 1.month + end 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 + # 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 diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index 1feaa9b9fa1..d38800b58f7 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do - token { SecureRandom.hex(10) } + token { SecureRandom.hex(15) } end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 14b8530a65b..d8b6bd93954 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -1,29 +1,41 @@ require 'spec_helper' describe Ci::TriggerSchedule, models: true do + let(:user) { create(:user) } + let(:project) { create(:project) } + let(:trigger) { create(:ci_trigger, owner: user, project: project, ref: 'master') } describe 'associations' do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:trigger) } end - describe '#schedule_next_run!' do - subject { trigger_schedule.schedule_next_run! } + describe 'validation' do + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, trigger: trigger) } - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } + it { expect(trigger_schedule).to validate_presence_of(:trigger) } + it { is_expected.to validate_presence_of(:cron) } + it { is_expected.to validate_presence_of(:cron_time_zone) } - it 'updates next_run_at' do - is_expected.not_to be_nil + it '#check_cron' do + subject.cron = 'Hack' + subject.valid? + subject.errors[:screen_name].to include(' is invalid syntax') + end + + it '#check_ref' do end end - # describe '#update_last_run!' do - # subject { scheduled_trigger.update_last_run! } + describe '#schedule_next_run!' do + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil, trigger: trigger) } - # let(:scheduled_trigger) { create(:ci_scheduled_trigger, :cron_nightly_build, last_run_at: nil) } + before do + trigger_schedule.schedule_next_run! + end - # it 'updates last_run_at' do - # is_expected.not_to be_nil - # end - # end + it 'updates next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).not_to be_nil + end + end end -- cgit v1.2.1 From 36ee4877788b8c78d67839b8d917d94a431b1db1 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 30 Mar 2017 20:51:05 +0900 Subject: Change configuration in gitlab.com as trigger_schedule_worker will perform twice a day --- config/gitlab.yml.example | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/gitlab.yml.example b/config/gitlab.yml.example index 6fb67426c8b..3c70f35b9d0 100644 --- a/config/gitlab.yml.example +++ b/config/gitlab.yml.example @@ -182,7 +182,7 @@ production: &base cron: "0 * * * *" # Execute scheduled triggers trigger_schedule_worker: - cron: "0 * * * *" + cron: "0 */12 * * *" # Remove expired build artifacts expire_build_artifacts_worker: cron: "50 * * * *" -- cgit v1.2.1 From da8db28d17c91d5e751aaa9dbd9d756d2be9b5db Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 30 Mar 2017 20:56:10 +0900 Subject: Change configuration in gitlab.com as trigger_schedule_worker will perform twice a day 2 --- config/initializers/1_settings.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb index 71342f435f1..4c9d829aa9f 100644 --- a/config/initializers/1_settings.rb +++ b/config/initializers/1_settings.rb @@ -316,7 +316,7 @@ Settings.cron_jobs['stuck_ci_jobs_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['stuck_ci_jobs_worker']['cron'] ||= '0 * * * *' Settings.cron_jobs['stuck_ci_jobs_worker']['job_class'] = 'StuckCiJobsWorker' Settings.cron_jobs['trigger_schedule_worker'] ||= Settingslogic.new({}) -Settings.cron_jobs['trigger_schedule_worker']['cron'] ||= '0 * * * *' +Settings.cron_jobs['trigger_schedule_worker']['cron'] ||= '0 */12 * * *' Settings.cron_jobs['trigger_schedule_worker']['job_class'] = 'TriggerScheduleWorker' Settings.cron_jobs['expire_build_artifacts_worker'] ||= Settingslogic.new({}) Settings.cron_jobs['expire_build_artifacts_worker']['cron'] ||= '50 * * * *' -- cgit v1.2.1 From 13bac4c252d2e84484bd0038a6c463e8b0a9cced Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 30 Mar 2017 20:59:20 +0900 Subject: Use delegate for ref on ci_trigger --- app/models/ci/trigger_schedule.rb | 2 ++ app/workers/trigger_schedule_worker.rb | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 1b3b73971bb..b9a99d86500 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -7,6 +7,8 @@ module Ci belongs_to :project belongs_to :trigger + delegate :ref, to: :trigger + validates :trigger, presence: true validates :cron, presence: true validates :cron_time_zone, presence: true diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb index 04a53a38adb..440c579b99d 100644 --- a/app/workers/trigger_schedule_worker.rb +++ b/app/workers/trigger_schedule_worker.rb @@ -5,9 +5,9 @@ class TriggerScheduleWorker def perform Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger_schedule| begin - Ci::CreateTriggerRequestService.new.execute(trigger_schedule.trigger.project, + Ci::CreateTriggerRequestService.new.execute(trigger_schedule.project, trigger_schedule.trigger, - trigger_schedule.trigger.ref) + trigger_schedule.ref) rescue => e Rails.logger.error "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" ensure -- cgit v1.2.1 From d48658e340c6d8d8b5e028afa6d5962ec7616e24 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 30 Mar 2017 21:03:25 +0900 Subject: Use constraint for #validation --- lib/ci/cron_parser.rb | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index b0ffb48dabc..be41474198d 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -12,8 +12,10 @@ module Ci end def validation - is_valid_cron = try_parse_cron(@cron, 'Europe/Istanbul').present? - is_valid_cron_time_zone = try_parse_cron('* * * * *', @cron_time_zone).present? + VALID_SYNTAX_TIME_ZONE = 'Europe/Istanbul' + VALID_SYNTAX_CRON = '* * * * *' + is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_TIME_ZONE).present? + is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_CRON, @cron_time_zone).present? return is_valid_cron, is_valid_cron_time_zone end -- cgit v1.2.1 From 9573bb44bc94261814dbdbb384b9ad7acf2907ff Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 31 Mar 2017 03:16:24 +0900 Subject: real_next_run (WIP) --- app/models/ci/trigger_schedule.rb | 19 ++++++- lib/ci/cron_parser.rb | 9 ++-- spec/factories/ci/trigger_schedules.rb | 42 ++------------- spec/factories/ci/triggers.rb | 4 ++ spec/models/ci/trigger_schedule_spec.rb | 94 +++++++++++++++++++++++++-------- 5 files changed, 102 insertions(+), 66 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index b9a99d86500..0df0a03d63e 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -18,12 +18,27 @@ module Ci after_create :schedule_next_run! def schedule_next_run! + puts "cron: #{cron.inspect} | cron_time_zone: #{cron_time_zone.inspect}" next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now if next_time.present? update!(next_run_at: next_time) end end + def real_next_run(worker_cron: nil, worker_time_zone: nil) + puts "worker_cron: #{worker_cron.inspect} | worker_time_zone: #{worker_time_zone.inspect}" + worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? + worker_time_zone = Time.zone.name unless worker_time_zone.present? + worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now + + puts "next_run_at: #{next_run_at.inspect} | worker_next_time: #{worker_next_time.inspect}" + if next_run_at > worker_next_time + next_run_at + else + worker_next_time + end + end + private def check_cron @@ -40,9 +55,9 @@ module Ci end def check_ref - if !trigger.ref.present? + if !ref.present? self.errors.add(:ref, " is empty") - elsif trigger.project.repository.ref_exists?(trigger.ref) + elsif project.repository.ref_exists?(ref) self.errors.add(:ref, " does not exist") end end diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index be41474198d..a569de293b3 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -1,5 +1,8 @@ module Ci class CronParser + VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC' + VALID_SYNTAX_SAMPLE_CRON = '* * * * *' + def initialize(cron, cron_time_zone = 'UTC') @cron = cron @cron_time_zone = cron_time_zone @@ -12,10 +15,8 @@ module Ci end def validation - VALID_SYNTAX_TIME_ZONE = 'Europe/Istanbul' - VALID_SYNTAX_CRON = '* * * * *' - is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_TIME_ZONE).present? - is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_CRON, @cron_time_zone).present? + is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? + is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? return is_valid_cron, is_valid_cron_time_zone end diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 0dd397435c1..f572540d9e3 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -1,7 +1,7 @@ FactoryGirl.define do factory :ci_trigger_schedule, class: Ci::TriggerSchedule do project factory: :project - trigger factory: :ci_trigger + trigger factory: :ci_trigger_with_ref trait :force_triggable do after(:create) do |trigger_schedule, evaluator| @@ -11,49 +11,17 @@ FactoryGirl.define do 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 + cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end trait :cron_weekly_build do - cron '0 1 * * 5' - cron_time_zone 'Europe/Istanbul' - # TODO: next_run_at + cron '0 1 * * 6' + cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE 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 + cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end end end diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index d38800b58f7..c9d2687b28e 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -2,6 +2,10 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do token { SecureRandom.hex(15) } + + factory :ci_trigger_with_ref do + ref 'master' + end end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index d8b6bd93954..11e8083fc86 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -5,37 +5,85 @@ describe Ci::TriggerSchedule, models: true do let(:project) { create(:project) } let(:trigger) { create(:ci_trigger, owner: user, project: project, ref: 'master') } - describe 'associations' do - it { is_expected.to belong_to(:project) } - it { is_expected.to belong_to(:trigger) } - end + it { is_expected.to belong_to(:project) } + it { is_expected.to belong_to(:trigger) } + # it { is_expected.to validate_presence_of :cron } + # it { is_expected.to validate_presence_of :cron_time_zone } + it { is_expected.to respond_to :ref } - describe 'validation' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, trigger: trigger) } + # describe '#schedule_next_run!' do + # let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil, trigger: trigger) } - it { expect(trigger_schedule).to validate_presence_of(:trigger) } - it { is_expected.to validate_presence_of(:cron) } - it { is_expected.to validate_presence_of(:cron_time_zone) } + # before do + # trigger_schedule.schedule_next_run! + # end - it '#check_cron' do - subject.cron = 'Hack' - subject.valid? - subject.errors[:screen_name].to include(' is invalid syntax') - end + # it 'updates next_run_at' do + # expect(Ci::TriggerSchedule.last.next_run_at).not_to be_nil + # end + # end - it '#check_ref' do - end - end + describe '#real_next_run' do + subject { trigger_schedule.real_next_run(worker_cron: worker_cron, worker_time_zone: worker_time_zone) } + + context 'when next_run_at > worker_next_time' do + let(:worker_cron) { '0 */12 * * *' } # each 00:00, 12:00 + let(:worker_time_zone) { 'UTC' } + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_weekly_build, cron_time_zone: user_time_zone, trigger: trigger) } + + context 'when user is in Europe/London(+00:00)' do + let(:user_time_zone) { 'Europe/London' } + + it 'returns next_run_at' do + is_expected.to eq(trigger_schedule.next_run_at) + end + end + + context 'when user is in Asia/Hong_Kong(+08:00)' do + let(:user_time_zone) { 'Asia/Hong_Kong' } - describe '#schedule_next_run!' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil, trigger: trigger) } + it 'returns next_run_at' do + is_expected.to eq(trigger_schedule.next_run_at) + end + end - before do - trigger_schedule.schedule_next_run! + context 'when user is in Canada/Pacific(-08:00)' do + let(:user_time_zone) { 'Canada/Pacific' } + + it 'returns next_run_at' do + is_expected.to eq(trigger_schedule.next_run_at) + end + end end - it 'updates next_run_at' do - expect(Ci::TriggerSchedule.last.next_run_at).not_to be_nil + context 'when worker_next_time > next_run_at' do + let(:worker_cron) { '0 0 */2 * *' } # every 2 days + let(:worker_time_zone) { 'UTC' } + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, cron_time_zone: user_time_zone, trigger: trigger) } + + context 'when user is in Europe/London(+00:00)' do + let(:user_time_zone) { 'Europe/London' } + + it 'returns worker_next_time' do + is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) + end + end + + context 'when user is in Asia/Hong_Kong(+08:00)' do + let(:user_time_zone) { 'Asia/Hong_Kong' } + + it 'returns worker_next_time' do + is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) + end + end + + context 'when user is in Canada/Pacific(-08:00)' do + let(:user_time_zone) { 'Canada/Pacific' } + + it 'returns worker_next_time' do + is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) + end + end end end end -- cgit v1.2.1 From d65c816ed78910eabd7ecbc9282e85d6b6f21796 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 31 Mar 2017 19:08:39 +0900 Subject: Brush up --- app/models/ci/trigger_schedule.rb | 21 ++++++++++----- app/workers/trigger_schedule_worker.rb | 3 +++ lib/ci/cron_parser.rb | 9 ++++--- spec/factories/ci/trigger_schedules.rb | 9 ++++--- spec/factories/ci/triggers.rb | 4 ++- spec/lib/ci/cron_parser_spec.rb | 38 +++++++++++++--------------- spec/models/ci/trigger_schedule_spec.rb | 31 ++++++++++++++--------- spec/workers/trigger_schedule_worker_spec.rb | 24 ++++++++---------- 8 files changed, 80 insertions(+), 59 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 0df0a03d63e..fc144d3958d 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -18,20 +18,22 @@ module Ci after_create :schedule_next_run! def schedule_next_run! - puts "cron: #{cron.inspect} | cron_time_zone: #{cron_time_zone.inspect}" - next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from_now - if next_time.present? + # puts "cron: #{cron.inspect} | cron_time_zone: #{cron_time_zone.inspect}" + next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) + + if next_time.present? && !less_than_1_hour_from_now?(next_time) update!(next_run_at: next_time) end end def real_next_run(worker_cron: nil, worker_time_zone: nil) - puts "worker_cron: #{worker_cron.inspect} | worker_time_zone: #{worker_time_zone.inspect}" worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? worker_time_zone = Time.zone.name unless worker_time_zone.present? - worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now + # puts "real_next_run: worker_cron: #{worker_cron.inspect} | worker_time_zone: #{worker_time_zone.inspect}" + + worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from(Time.now) - puts "next_run_at: #{next_run_at.inspect} | worker_next_time: #{worker_next_time.inspect}" + # puts "real_next_run: next_run_at: #{next_run_at.inspect} | worker_next_time: #{worker_next_time.inspect}" if next_run_at > worker_next_time next_run_at else @@ -41,15 +43,20 @@ module Ci private + def less_than_1_hour_from_now?(time) + ((time - Time.now).abs < 1.hour) ? true : false + end + def check_cron cron_parser = Ci::CronParser.new(cron, cron_time_zone) is_valid_cron, is_valid_cron_time_zone = cron_parser.validation + next_time = cron_parser.next_time_from(Time.now) if !is_valid_cron self.errors.add(:cron, " is invalid syntax") elsif !is_valid_cron_time_zone self.errors.add(:cron_time_zone, " is invalid timezone") - elsif (cron_parser.next_time_from_now - Time.now).abs < 1.hour + elsif less_than_1_hour_from_now?(next_time) self.errors.add(:cron, " can not be less than 1 hour") end end diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb index 440c579b99d..9216103b8da 100644 --- a/app/workers/trigger_schedule_worker.rb +++ b/app/workers/trigger_schedule_worker.rb @@ -4,11 +4,14 @@ class TriggerScheduleWorker def perform Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger_schedule| + next if Ci::Pipeline.where(project: trigger_schedule.project, ref: trigger_schedule.ref).running_or_pending.count > 0 + begin Ci::CreateTriggerRequestService.new.execute(trigger_schedule.project, trigger_schedule.trigger, trigger_schedule.ref) rescue => e + puts "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" # TODO: Remove before merge Rails.logger.error "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" ensure trigger_schedule.schedule_next_run! diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index a569de293b3..2919543bbef 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -8,10 +8,13 @@ module Ci @cron_time_zone = cron_time_zone end - def next_time_from_now + def next_time_from(time) cronLine = try_parse_cron(@cron, @cron_time_zone) - return nil unless cronLine.present? - cronLine.next_time + if cronLine.present? + cronLine.next_time(time) + else + nil + end end def validation diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index f572540d9e3..7143db6961c 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -1,11 +1,14 @@ FactoryGirl.define do factory :ci_trigger_schedule, class: Ci::TriggerSchedule do - project factory: :project - trigger factory: :ci_trigger_with_ref + trigger factory: :ci_trigger_for_trigger_schedule + + after(:build) do |trigger_schedule, evaluator| + trigger_schedule.update!(project: trigger_schedule.trigger.project) + end trait :force_triggable do after(:create) do |trigger_schedule, evaluator| - trigger_schedule.next_run_at -= 1.month + trigger_schedule.update!(next_run_at: trigger_schedule.next_run_at - 1.year) end end diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index c9d2687b28e..c9fedf8a857 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -3,7 +3,9 @@ FactoryGirl.define do factory :ci_trigger do token { SecureRandom.hex(15) } - factory :ci_trigger_with_ref do + factory :ci_trigger_for_trigger_schedule do + owner factory: :user + project factory: :project ref 'master' end end diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb index f8c7e88edb3..dd1449b5b02 100644 --- a/spec/lib/ci/cron_parser_spec.rb +++ b/spec/lib/ci/cron_parser_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' module Ci describe CronParser, lib: true do - describe '#next_time_from_now' do - subject { described_class.new(cron, cron_time_zone).next_time_from_now } + describe '#next_time_from' do + subject { described_class.new(cron, cron_time_zone).next_time_from(Time.now) } context 'when cron and cron_time_zone are valid' do context 'when specific time' do @@ -65,8 +65,8 @@ module Ci end context 'when cron and cron_time_zone are invalid' do - let(:cron) { 'hack' } - let(:cron_time_zone) { 'hack' } + let(:cron) { 'invalid_cron' } + let(:cron_time_zone) { 'invalid_cron_time_zone' } it 'returns nil' do is_expected.to be_nil @@ -74,25 +74,23 @@ module Ci end end - describe '#valid_syntax?' do - subject { described_class.new(cron, cron_time_zone).valid_syntax? } - - context 'when cron and cron_time_zone are valid' do - let(:cron) { '* * * * *' } - let(:cron_time_zone) { 'Europe/Istanbul' } - - it 'returns true' do - is_expected.to eq(true) - end + describe '#validation' do + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Europe/Istanbul').validation + expect(is_valid_cron).to eq(true) + expect(is_valid_cron_time_zone).to eq(true) end - context 'when cron and cron_time_zone are invalid' do - let(:cron) { 'hack' } - let(:cron_time_zone) { 'hack' } + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('*********', 'Europe/Istanbul').validation + expect(is_valid_cron).to eq(false) + expect(is_valid_cron_time_zone).to eq(true) + end - it 'returns false' do - is_expected.to eq(false) - end + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Invalid-zone').validation + expect(is_valid_cron).to eq(true) + expect(is_valid_cron_time_zone).to eq(false) end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 11e8083fc86..d47ab529bc0 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -1,9 +1,6 @@ require 'spec_helper' describe Ci::TriggerSchedule, models: true do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:trigger) { create(:ci_trigger, owner: user, project: project, ref: 'master') } it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:trigger) } @@ -11,17 +8,27 @@ describe Ci::TriggerSchedule, models: true do # it { is_expected.to validate_presence_of :cron_time_zone } it { is_expected.to respond_to :ref } - # describe '#schedule_next_run!' do - # let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil, trigger: trigger) } + it 'should validate less_than_1_hour_from_now' do + trigger_schedule = create(:ci_trigger_schedule, :cron_nightly_build) + trigger_schedule.cron = '* * * * *' + trigger_schedule.valid? + expect(trigger_schedule.errors[:cron].first).to include('can not be less than 1 hour') + end + + describe '#schedule_next_run!' do + context 'when more_than_1_hour_from_now' do + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } - # before do - # trigger_schedule.schedule_next_run! - # end + before do + trigger_schedule.schedule_next_run! + end - # it 'updates next_run_at' do - # expect(Ci::TriggerSchedule.last.next_run_at).not_to be_nil - # end - # end + it 'updates next_run_at' do + next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) + end + end + end describe '#real_next_run' do subject { trigger_schedule.real_next_run(worker_cron: worker_cron, worker_time_zone: worker_time_zone) } diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 2cf51a31c71..f0c7eeaedae 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -8,38 +8,36 @@ describe TriggerScheduleWorker do end context 'when there is a scheduled trigger within next_run_at' do - let(:user) { create(:user) } - let(:project) { create(:project) } - let(:trigger) { create(:ci_trigger, owner: user, project: project, ref: 'master') } - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable, trigger: trigger, project: project) } + let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable) } before do worker.perform end it 'creates a new trigger request' do - expect(Ci::TriggerRequest.first.trigger_id).to eq(trigger.id) + expect(trigger_schedule.trigger.id).to eq(Ci::TriggerRequest.first.trigger_id) end it 'creates a new pipeline' do expect(Ci::Pipeline.last.status).to eq('pending') end - it 'schedules next_run_at' do - next_time = Ci::CronParser.new('0 1 * * *', 'Europe/Istanbul').next_time_from_now + it 'updates next_run_at' do + next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end - context 'when there are no scheduled triggers within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } + context 'when there is a scheduled trigger within next_run_at and a runnign pipeline' do + let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable) } before do + create(:ci_pipeline, project: trigger_schedule.project, ref: trigger_schedule.ref, status: 'running') worker.perform end it 'do not create a new pipeline' do - expect(Ci::Pipeline.all).to be_empty + expect(Ci::Pipeline.count).to eq(1) end it 'do not reschedule next_run_at' do @@ -47,15 +45,15 @@ describe TriggerScheduleWorker do end end - context 'when next_run_at is nil' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } + context 'when there are no scheduled triggers within next_run_at' do + let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } before do worker.perform end it 'do not create a new pipeline' do - expect(Ci::Pipeline.all).to be_empty + expect(Ci::Pipeline.count).to eq(0) end it 'do not reschedule next_run_at' do -- cgit v1.2.1 From 5720919cd0cd457aa83fa3e3c36e34867b0eed60 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Fri, 31 Mar 2017 23:18:07 +0900 Subject: Brush up 2 --- app/models/ci/trigger_schedule.rb | 5 +- app/workers/trigger_schedule_worker.rb | 2 - lib/ci/cron_parser.rb | 2 +- spec/lib/ci/cron_parser_spec.rb | 25 +++++---- spec/models/ci/trigger_schedule_spec.rb | 93 ++++++++++++++------------------- 5 files changed, 55 insertions(+), 72 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index fc144d3958d..b861f41fed1 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -18,7 +18,6 @@ module Ci after_create :schedule_next_run! def schedule_next_run! - # puts "cron: #{cron.inspect} | cron_time_zone: #{cron_time_zone.inspect}" next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) if next_time.present? && !less_than_1_hour_from_now?(next_time) @@ -29,11 +28,9 @@ module Ci def real_next_run(worker_cron: nil, worker_time_zone: nil) worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? worker_time_zone = Time.zone.name unless worker_time_zone.present? - # puts "real_next_run: worker_cron: #{worker_cron.inspect} | worker_time_zone: #{worker_time_zone.inspect}" worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from(Time.now) - # puts "real_next_run: next_run_at: #{next_run_at.inspect} | worker_next_time: #{worker_next_time.inspect}" if next_run_at > worker_next_time next_run_at else @@ -64,7 +61,7 @@ module Ci def check_ref if !ref.present? self.errors.add(:ref, " is empty") - elsif project.repository.ref_exists?(ref) + elsif !project.repository.branch_exists?(ref) self.errors.add(:ref, " does not exist") end end diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb index 9216103b8da..36e640592a7 100644 --- a/app/workers/trigger_schedule_worker.rb +++ b/app/workers/trigger_schedule_worker.rb @@ -4,8 +4,6 @@ class TriggerScheduleWorker def perform Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger_schedule| - next if Ci::Pipeline.where(project: trigger_schedule.project, ref: trigger_schedule.ref).running_or_pending.count > 0 - begin Ci::CreateTriggerRequestService.new.execute(trigger_schedule.project, trigger_schedule.trigger, diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index 2919543bbef..eb348306436 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -11,7 +11,7 @@ module Ci def next_time_from(time) cronLine = try_parse_cron(@cron, @cron_time_zone) if cronLine.present? - cronLine.next_time(time) + cronLine.next_time(time).in_time_zone(Time.zone) else nil end diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb index dd1449b5b02..11d1e1c8a78 100644 --- a/spec/lib/ci/cron_parser_spec.rb +++ b/spec/lib/ci/cron_parser_spec.rb @@ -8,10 +8,10 @@ module Ci context 'when cron and cron_time_zone are valid' do context 'when specific time' do let(:cron) { '3 4 5 6 *' } - let(:cron_time_zone) { 'Europe/London' } + let(:cron_time_zone) { 'UTC' } it 'returns exact time in the future' do - expect(subject).to be > Time.now.in_time_zone(cron_time_zone) + expect(subject).to be > Time.now expect(subject.min).to eq(3) expect(subject.hour).to eq(4) expect(subject.day).to eq(5) @@ -21,20 +21,20 @@ module Ci context 'when specific day of week' do let(:cron) { '* * * * 0' } - let(:cron_time_zone) { 'Europe/London' } + let(:cron_time_zone) { 'UTC' } it 'returns exact day of week in the future' do - expect(subject).to be > Time.now.in_time_zone(cron_time_zone) + expect(subject).to be > Time.now expect(subject.wday).to eq(0) end end context 'when slash used' do let(:cron) { '*/10 */6 */10 */10 *' } - let(:cron_time_zone) { 'US/Pacific' } + let(:cron_time_zone) { 'UTC' } it 'returns exact minute' do - expect(subject).to be > Time.now.in_time_zone(cron_time_zone) + expect(subject).to be > Time.now 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]) @@ -44,22 +44,25 @@ module Ci context 'when range used' do let(:cron) { '0,20,40 * 1-5 * *' } - let(:cron_time_zone) { 'US/Pacific' } + let(:cron_time_zone) { 'UTC' } it 'returns next time from now' do - expect(subject).to be > Time.now.in_time_zone(cron_time_zone) + expect(subject).to be > Time.now 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) { '* * * * *' } + let(:cron) { '0 0 * * *' } let(:cron_time_zone) { 'US/Pacific' } it 'returns next time from now' do - expect(subject).to be > Time.now.in_time_zone(cron_time_zone) - expect(subject.utc_offset/60/60).to eq(-7) + expect(subject).to be > Time.now + end + + it 'converts time in server time zone' do + expect(subject.hour).to eq(7) end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index d47ab529bc0..da23611f1a0 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -8,11 +8,36 @@ describe Ci::TriggerSchedule, models: true do # it { is_expected.to validate_presence_of :cron_time_zone } it { is_expected.to respond_to :ref } - it 'should validate less_than_1_hour_from_now' do + it 'should validate ref existence' do trigger_schedule = create(:ci_trigger_schedule, :cron_nightly_build) - trigger_schedule.cron = '* * * * *' + trigger_schedule.trigger.ref = 'invalid-ref' trigger_schedule.valid? - expect(trigger_schedule.errors[:cron].first).to include('can not be less than 1 hour') + expect(trigger_schedule.errors[:ref].first).to include('does not exist') + end + + describe 'cron limitation' do + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } + + before do + trigger_schedule.cron = cron + trigger_schedule.valid? + end + + context 'when every hour' do + let(:cron) { '0 * * * *' } # 00:00, 01:00, 02:00, ..., 23:00 + + it 'fails' do + expect(trigger_schedule.errors[:cron].first).to include('can not be less than 1 hour') + end + end + + context 'when each six hours' do + let(:cron) { '0 */6 * * *' } # 00:00, 06:00, 12:00, 18:00 + + it 'succeeds' do + expect(trigger_schedule.errors[:cron]).to be_empty + end + end end describe '#schedule_next_run!' do @@ -31,65 +56,25 @@ describe Ci::TriggerSchedule, models: true do end describe '#real_next_run' do - subject { trigger_schedule.real_next_run(worker_cron: worker_cron, worker_time_zone: worker_time_zone) } - - context 'when next_run_at > worker_next_time' do - let(:worker_cron) { '0 */12 * * *' } # each 00:00, 12:00 - let(:worker_time_zone) { 'UTC' } - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_weekly_build, cron_time_zone: user_time_zone, trigger: trigger) } + let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } - context 'when user is in Europe/London(+00:00)' do - let(:user_time_zone) { 'Europe/London' } + subject { trigger_schedule.real_next_run(worker_cron: worker_cron, worker_time_zone: 'UTC') } - it 'returns next_run_at' do - is_expected.to eq(trigger_schedule.next_run_at) - end - end - - context 'when user is in Asia/Hong_Kong(+08:00)' do - let(:user_time_zone) { 'Asia/Hong_Kong' } - - it 'returns next_run_at' do - is_expected.to eq(trigger_schedule.next_run_at) - end - end - - context 'when user is in Canada/Pacific(-08:00)' do - let(:user_time_zone) { 'Canada/Pacific' } + context 'when next_run_at > worker_next_time' do + let(:worker_cron) { '* * * * *' } # every minutes + let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st - it 'returns next_run_at' do - is_expected.to eq(trigger_schedule.next_run_at) - end + it 'returns next_run_at' do + is_expected.to eq(trigger_schedule.next_run_at) end end context 'when worker_next_time > next_run_at' do - let(:worker_cron) { '0 0 */2 * *' } # every 2 days - let(:worker_time_zone) { 'UTC' } - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, cron_time_zone: user_time_zone, trigger: trigger) } - - context 'when user is in Europe/London(+00:00)' do - let(:user_time_zone) { 'Europe/London' } - - it 'returns worker_next_time' do - is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) - end - end - - context 'when user is in Asia/Hong_Kong(+08:00)' do - let(:user_time_zone) { 'Asia/Hong_Kong' } - - it 'returns worker_next_time' do - is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) - end - end - - context 'when user is in Canada/Pacific(-08:00)' do - let(:user_time_zone) { 'Canada/Pacific' } + let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st + let(:user_cron) { '0 */6 * * *' } # each six hours - it 'returns worker_next_time' do - is_expected.to eq(Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from_now) - end + it 'returns worker_next_time' do + is_expected.to eq(Ci::CronParser.new(worker_cron, 'UTC').next_time_from(Time.now)) end end end -- cgit v1.2.1 From 21cabf381b55ab2747d773ae1eeb70d2bb40e9a5 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 00:19:46 +0900 Subject: Move real_next_run to helper --- app/helpers/triggers_helper.rb | 13 +++++++++++++ app/models/ci/trigger_schedule.rb | 13 ------------- spec/helpers/triggers_helper_spec.rb | 27 +++++++++++++++++++++++++++ spec/models/ci/trigger_schedule_spec.rb | 24 ------------------------ 4 files changed, 40 insertions(+), 37 deletions(-) create mode 100644 spec/helpers/triggers_helper_spec.rb diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb index a48d4475e97..be5cce9aea0 100644 --- a/app/helpers/triggers_helper.rb +++ b/app/helpers/triggers_helper.rb @@ -10,4 +10,17 @@ module TriggersHelper def service_trigger_url(service) "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger" end + + def real_next_run(trigger_schedule, worker_cron: nil, worker_time_zone: nil) + worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? + worker_time_zone = Time.zone.name unless worker_time_zone.present? + + worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from(Time.now) + + if trigger_schedule.next_run_at > worker_next_time + trigger_schedule.next_run_at + else + worker_next_time + end + end end diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index b861f41fed1..aedba8bdf54 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -25,19 +25,6 @@ module Ci end end - def real_next_run(worker_cron: nil, worker_time_zone: nil) - worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? - worker_time_zone = Time.zone.name unless worker_time_zone.present? - - worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from(Time.now) - - if next_run_at > worker_next_time - next_run_at - else - worker_next_time - end - end - private def less_than_1_hour_from_now?(time) diff --git a/spec/helpers/triggers_helper_spec.rb b/spec/helpers/triggers_helper_spec.rb new file mode 100644 index 00000000000..ce17f4442ab --- /dev/null +++ b/spec/helpers/triggers_helper_spec.rb @@ -0,0 +1,27 @@ +require 'rails_helper' + +describe TriggersHelper do + describe '#real_next_run' do + let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } + + subject { helper.real_next_run(trigger_schedule, worker_cron: worker_cron, worker_time_zone: 'UTC') } + + context 'when next_run_at > worker_next_time' do + let(:worker_cron) { '* * * * *' } # every minutes + let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st + + it 'returns next_run_at' do + is_expected.to eq(trigger_schedule.next_run_at) + end + end + + context 'when worker_next_time > next_run_at' do + let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st + let(:user_cron) { '0 */6 * * *' } # each six hours + + it 'returns worker_next_time' do + is_expected.to eq(Ci::CronParser.new(worker_cron, 'UTC').next_time_from(Time.now)) + end + end + end +end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index da23611f1a0..6a586a4e9b1 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -54,28 +54,4 @@ describe Ci::TriggerSchedule, models: true do end end end - - describe '#real_next_run' do - let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } - - subject { trigger_schedule.real_next_run(worker_cron: worker_cron, worker_time_zone: 'UTC') } - - context 'when next_run_at > worker_next_time' do - let(:worker_cron) { '* * * * *' } # every minutes - let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st - - it 'returns next_run_at' do - is_expected.to eq(trigger_schedule.next_run_at) - end - end - - context 'when worker_next_time > next_run_at' do - let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st - let(:user_cron) { '0 */6 * * *' } # each six hours - - it 'returns worker_next_time' do - is_expected.to eq(Ci::CronParser.new(worker_cron, 'UTC').next_time_from(Time.now)) - end - end - end end -- cgit v1.2.1 From 57d082f3589060c90c2841dd52dda77574f5d984 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 02:02:26 +0900 Subject: Add validator --- app/models/ci/trigger_schedule.rb | 26 ++++++-------------------- app/validators/cron_validator.rb | 16 ++++++++++++++++ app/validators/ref_validator.rb | 10 ++++++++++ spec/models/ci/trigger_schedule_spec.rb | 6 ++---- spec/workers/trigger_schedule_worker_spec.rb | 17 ----------------- 5 files changed, 34 insertions(+), 41 deletions(-) create mode 100644 app/validators/cron_validator.rb create mode 100644 app/validators/ref_validator.rb diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index aedba8bdf54..6529e364fe8 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -10,10 +10,10 @@ module Ci delegate :ref, to: :trigger validates :trigger, presence: true - validates :cron, presence: true + validates :cron, cron: true, presence: true validates :cron_time_zone, presence: true - validate :check_cron - validate :check_ref + validates :ref, ref: true, presence: true + validate :check_cron_frequency after_create :schedule_next_run! @@ -31,26 +31,12 @@ module Ci ((time - Time.now).abs < 1.hour) ? true : false end - def check_cron - cron_parser = Ci::CronParser.new(cron, cron_time_zone) - is_valid_cron, is_valid_cron_time_zone = cron_parser.validation - next_time = cron_parser.next_time_from(Time.now) + def check_cron_frequency + next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) - if !is_valid_cron - self.errors.add(:cron, " is invalid syntax") - elsif !is_valid_cron_time_zone - self.errors.add(:cron_time_zone, " is invalid timezone") - elsif less_than_1_hour_from_now?(next_time) + if less_than_1_hour_from_now?(next_time) self.errors.add(:cron, " can not be less than 1 hour") end end - - def check_ref - if !ref.present? - self.errors.add(:ref, " is empty") - elsif !project.repository.branch_exists?(ref) - self.errors.add(:ref, " does not exist") - end - end end end diff --git a/app/validators/cron_validator.rb b/app/validators/cron_validator.rb new file mode 100644 index 00000000000..ad70e0897ba --- /dev/null +++ b/app/validators/cron_validator.rb @@ -0,0 +1,16 @@ +# CronValidator +# +# Custom validator for Cron. +class CronValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + cron_parser = Ci::CronParser.new(record.cron, record.cron_time_zone) + is_valid_cron, is_valid_cron_time_zone = cron_parser.validation + next_time = cron_parser.next_time_from(Time.now) + + if !is_valid_cron + record.errors.add(:cron, " is invalid syntax") + elsif !is_valid_cron_time_zone + record.errors.add(:cron_time_zone, " is invalid timezone") + end + end +end diff --git a/app/validators/ref_validator.rb b/app/validators/ref_validator.rb new file mode 100644 index 00000000000..2024255a770 --- /dev/null +++ b/app/validators/ref_validator.rb @@ -0,0 +1,10 @@ +# RefValidator +# +# Custom validator for Ref. +class RefValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + unless record.project.repository.branch_exists?(value) + record.errors.add(attribute, " does not exist") + end + end +end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 6a586a4e9b1..30972f2295e 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -4,8 +4,6 @@ describe Ci::TriggerSchedule, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:trigger) } - # it { is_expected.to validate_presence_of :cron } - # it { is_expected.to validate_presence_of :cron_time_zone } it { is_expected.to respond_to :ref } it 'should validate ref existence' do @@ -26,7 +24,7 @@ describe Ci::TriggerSchedule, models: true do context 'when every hour' do let(:cron) { '0 * * * *' } # 00:00, 01:00, 02:00, ..., 23:00 - it 'fails' do + it 'gets an error' do expect(trigger_schedule.errors[:cron].first).to include('can not be less than 1 hour') end end @@ -34,7 +32,7 @@ describe Ci::TriggerSchedule, models: true do context 'when each six hours' do let(:cron) { '0 */6 * * *' } # 00:00, 06:00, 12:00, 18:00 - it 'succeeds' do + it 'gets no errors' do expect(trigger_schedule.errors[:cron]).to be_empty end end diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index f0c7eeaedae..950f72a68d9 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -28,23 +28,6 @@ describe TriggerScheduleWorker do end end - context 'when there is a scheduled trigger within next_run_at and a runnign pipeline' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable) } - - before do - create(:ci_pipeline, project: trigger_schedule.project, ref: trigger_schedule.ref, status: 'running') - worker.perform - end - - it 'do not create a new pipeline' do - expect(Ci::Pipeline.count).to eq(1) - end - - it 'do not reschedule next_run_at' do - expect(Ci::TriggerSchedule.last.next_run_at).to eq(trigger_schedule.next_run_at) - end - end - context 'when there are no scheduled triggers within next_run_at' do let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } -- cgit v1.2.1 From d9574c0cce97d859ca605d70374633283c93f3fa Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 02:31:58 +0900 Subject: Maintain MR --- app/workers/trigger_schedule_worker.rb | 1 - ...-on-a-schedule-idea1-basic-backend-implementation.yml | 4 ++++ db/migrate/20170329095325_add_ref_to_triggers.rb | 16 ---------------- db/migrate/20170329095907_create_ci_trigger_schedules.rb | 16 ---------------- spec/helpers/triggers_helper_spec.rb | 8 ++++---- 5 files changed, 8 insertions(+), 37 deletions(-) create mode 100644 changelogs/unreleased/2989-run-cicd-pipelines-on-a-schedule-idea1-basic-backend-implementation.yml diff --git a/app/workers/trigger_schedule_worker.rb b/app/workers/trigger_schedule_worker.rb index 36e640592a7..440c579b99d 100644 --- a/app/workers/trigger_schedule_worker.rb +++ b/app/workers/trigger_schedule_worker.rb @@ -9,7 +9,6 @@ class TriggerScheduleWorker trigger_schedule.trigger, trigger_schedule.ref) rescue => e - puts "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" # TODO: Remove before merge Rails.logger.error "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}" ensure trigger_schedule.schedule_next_run! diff --git a/changelogs/unreleased/2989-run-cicd-pipelines-on-a-schedule-idea1-basic-backend-implementation.yml b/changelogs/unreleased/2989-run-cicd-pipelines-on-a-schedule-idea1-basic-backend-implementation.yml new file mode 100644 index 00000000000..dd56409c35b --- /dev/null +++ b/changelogs/unreleased/2989-run-cicd-pipelines-on-a-schedule-idea1-basic-backend-implementation.yml @@ -0,0 +1,4 @@ +--- +title: Resolve "Run CI/CD pipelines on a schedule" - "Basic backend implementation" +merge_request: 10133 +author: dosuken123 diff --git a/db/migrate/20170329095325_add_ref_to_triggers.rb b/db/migrate/20170329095325_add_ref_to_triggers.rb index f8236b5a711..6900ded4277 100644 --- a/db/migrate/20170329095325_add_ref_to_triggers.rb +++ b/db/migrate/20170329095325_add_ref_to_triggers.rb @@ -7,22 +7,6 @@ class AddRefToTriggers < ActiveRecord::Migration # Set this constant to true if this migration requires downtime. DOWNTIME = false - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change add_column :ci_triggers, :ref, :string end diff --git a/db/migrate/20170329095907_create_ci_trigger_schedules.rb b/db/migrate/20170329095907_create_ci_trigger_schedules.rb index 42f9497cac7..7b2e2e2098b 100644 --- a/db/migrate/20170329095907_create_ci_trigger_schedules.rb +++ b/db/migrate/20170329095907_create_ci_trigger_schedules.rb @@ -7,22 +7,6 @@ class CreateCiTriggerSchedules < ActiveRecord::Migration # Set this constant to true if this migration requires downtime. DOWNTIME = false - # When a migration requires downtime you **must** uncomment the following - # constant and define a short and easy to understand explanation as to why the - # migration requires downtime. - # DOWNTIME_REASON = '' - - # When using the methods "add_concurrent_index" or "add_column_with_default" - # you must disable the use of transactions as these methods can not run in an - # existing transaction. When using "add_concurrent_index" make sure that this - # method is the _only_ method called in the migration, any other changes - # should go in a separate migration. This ensures that upon failure _only_ the - # index creation fails and can be retried or reverted easily. - # - # To disable transactions uncomment the following line and remove these - # comments: - # disable_ddl_transaction! - def change create_table :ci_trigger_schedules do |t| t.integer "project_id" diff --git a/spec/helpers/triggers_helper_spec.rb b/spec/helpers/triggers_helper_spec.rb index ce17f4442ab..d801760335b 100644 --- a/spec/helpers/triggers_helper_spec.rb +++ b/spec/helpers/triggers_helper_spec.rb @@ -7,8 +7,8 @@ describe TriggersHelper do subject { helper.real_next_run(trigger_schedule, worker_cron: worker_cron, worker_time_zone: 'UTC') } context 'when next_run_at > worker_next_time' do - let(:worker_cron) { '* * * * *' } # every minutes - let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st + let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st + let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st it 'returns next_run_at' do is_expected.to eq(trigger_schedule.next_run_at) @@ -16,8 +16,8 @@ describe TriggersHelper do end context 'when worker_next_time > next_run_at' do - let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st - let(:user_cron) { '0 */6 * * *' } # each six hours + let(:worker_cron) { '1 0 1 1 *' } # every 00:01, January 1st + let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st it 'returns worker_next_time' do is_expected.to eq(Ci::CronParser.new(worker_cron, 'UTC').next_time_from(Time.now)) -- cgit v1.2.1 From 62480461c943b4ca4c72830c04932cd5bba9f4e7 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 02:55:55 +0900 Subject: Fixed failed tests --- spec/models/ci/trigger_schedule_spec.rb | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 30972f2295e..1d6d602ebda 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -21,7 +21,7 @@ describe Ci::TriggerSchedule, models: true do trigger_schedule.valid? end - context 'when every hour' do + context 'when cron frequency is too short' do let(:cron) { '0 * * * *' } # 00:00, 01:00, 02:00, ..., 23:00 it 'gets an error' do @@ -29,8 +29,8 @@ describe Ci::TriggerSchedule, models: true do end end - context 'when each six hours' do - let(:cron) { '0 */6 * * *' } # 00:00, 06:00, 12:00, 18:00 + context 'when cron frequency is eligible' do + let(:cron) { '0 0 1 1 *' } # every 00:00, January 1st it 'gets no errors' do expect(trigger_schedule.errors[:cron]).to be_empty @@ -39,17 +39,15 @@ describe Ci::TriggerSchedule, models: true do end describe '#schedule_next_run!' do - context 'when more_than_1_hour_from_now' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } - before do - trigger_schedule.schedule_next_run! - end + before do + trigger_schedule.schedule_next_run! + end - it 'updates next_run_at' do - next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) - expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) - end + it 'updates next_run_at' do + next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end end -- cgit v1.2.1 From 934e949726adf4428a03970d78e23555cc1d7a72 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 17:57:52 +0900 Subject: Fix rubocop issues. Use add_concurrent_foreign_key. --- app/validators/cron_validator.rb | 1 - db/migrate/20170329095325_add_ref_to_triggers.rb | 4 ---- db/migrate/20170329095907_create_ci_trigger_schedules.rb | 15 +++++++++------ db/schema.rb | 2 +- lib/ci/cron_parser.rb | 10 +++++----- spec/models/ci/trigger_schedule_spec.rb | 1 - 6 files changed, 15 insertions(+), 18 deletions(-) diff --git a/app/validators/cron_validator.rb b/app/validators/cron_validator.rb index ad70e0897ba..4d9a0d62a4c 100644 --- a/app/validators/cron_validator.rb +++ b/app/validators/cron_validator.rb @@ -5,7 +5,6 @@ class CronValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) cron_parser = Ci::CronParser.new(record.cron, record.cron_time_zone) is_valid_cron, is_valid_cron_time_zone = cron_parser.validation - next_time = cron_parser.next_time_from(Time.now) if !is_valid_cron record.errors.add(:cron, " is invalid syntax") diff --git a/db/migrate/20170329095325_add_ref_to_triggers.rb b/db/migrate/20170329095325_add_ref_to_triggers.rb index 6900ded4277..4aa52dd8f8f 100644 --- a/db/migrate/20170329095325_add_ref_to_triggers.rb +++ b/db/migrate/20170329095325_add_ref_to_triggers.rb @@ -1,10 +1,6 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class AddRefToTriggers < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false def change diff --git a/db/migrate/20170329095907_create_ci_trigger_schedules.rb b/db/migrate/20170329095907_create_ci_trigger_schedules.rb index 7b2e2e2098b..3dcd05175c0 100644 --- a/db/migrate/20170329095907_create_ci_trigger_schedules.rb +++ b/db/migrate/20170329095907_create_ci_trigger_schedules.rb @@ -1,13 +1,11 @@ -# See http://doc.gitlab.com/ce/development/migration_style_guide.html -# for more information on how to write migrations for GitLab. - class CreateCiTriggerSchedules < ActiveRecord::Migration include Gitlab::Database::MigrationHelpers - # Set this constant to true if this migration requires downtime. DOWNTIME = false - def change + disable_ddl_transaction! + + def up create_table :ci_trigger_schedules do |t| t.integer "project_id" t.integer "trigger_id", null: false @@ -21,6 +19,11 @@ class CreateCiTriggerSchedules < ActiveRecord::Migration add_index :ci_trigger_schedules, ["next_run_at"], name: "index_ci_trigger_schedules_on_next_run_at", using: :btree add_index :ci_trigger_schedules, ["project_id"], name: "index_ci_trigger_schedules_on_project_id", using: :btree - add_foreign_key :ci_trigger_schedules, :ci_triggers, column: :trigger_id, on_delete: :cascade + add_concurrent_foreign_key :ci_trigger_schedules, :ci_triggers, column: :trigger_id, on_delete: :cascade + end + + def down + remove_foreign_key :ci_trigger_schedules, column: :trigger_id + drop_table :ci_trigger_schedules end end diff --git a/db/schema.rb b/db/schema.rb index 8f3b3110548..7d9f969c2e1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -1313,7 +1313,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do add_foreign_key "boards", "projects" add_foreign_key "chat_teams", "namespaces", on_delete: :cascade - add_foreign_key "ci_trigger_schedules", "ci_triggers", column: "trigger_id", on_delete: :cascade + add_foreign_key "ci_trigger_schedules", "ci_triggers", column: "trigger_id", name: "fk_90a406cc94", on_delete: :cascade add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade add_foreign_key "issue_metrics", "issues", on_delete: :cascade add_foreign_key "label_priorities", "labels", on_delete: :cascade diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb index eb348306436..e0d589956a8 100644 --- a/lib/ci/cron_parser.rb +++ b/lib/ci/cron_parser.rb @@ -1,7 +1,7 @@ module Ci class CronParser - VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC' - VALID_SYNTAX_SAMPLE_CRON = '* * * * *' + VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze + VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze def initialize(cron, cron_time_zone = 'UTC') @cron = cron @@ -9,9 +9,9 @@ module Ci end def next_time_from(time) - cronLine = try_parse_cron(@cron, @cron_time_zone) - if cronLine.present? - cronLine.next_time(time).in_time_zone(Time.zone) + cron_line = try_parse_cron(@cron, @cron_time_zone) + if cron_line.present? + cron_line.next_time(time).in_time_zone(Time.zone) else nil end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 1d6d602ebda..57ebcdfb3f1 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -1,7 +1,6 @@ require 'spec_helper' describe Ci::TriggerSchedule, models: true do - it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:trigger) } it { is_expected.to respond_to :ref } -- cgit v1.2.1 From b5f252bdf53365b02bea4415b0b8581ad59d0587 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 18:02:46 +0900 Subject: Fix spec/factories_spec.rb --- spec/factories/ci/trigger_schedules.rb | 2 ++ 1 file changed, 2 insertions(+) diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 7143db6961c..2e6a35c6416 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -1,6 +1,8 @@ FactoryGirl.define do factory :ci_trigger_schedule, class: Ci::TriggerSchedule do trigger factory: :ci_trigger_for_trigger_schedule + cron '0 1 * * *' + cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE after(:build) do |trigger_schedule, evaluator| trigger_schedule.update!(project: trigger_schedule.trigger.project) -- cgit v1.2.1 From 01cea0d59dd52ab6db2c7fb19faa2b8c71cf6052 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 20:27:37 +0900 Subject: Suppress Import/Export function warnings. Add comment for confirmation. --- lib/gitlab/import_export/import_export.yml | 2 +- spec/lib/gitlab/import_export/all_models.yml | 2 ++ spec/lib/gitlab/import_export/safe_model_attributes.yml | 10 ++++++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index f69288f7d67..8d74418ed14 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -39,7 +39,7 @@ project_tree: - :author - :events - :statuses - - :triggers + - :triggers # TODO: Need to confirm - :deploy_keys - :services - :hooks diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 24654bf6afd..06e8cd5a1cd 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -99,6 +99,7 @@ triggers: - project - trigger_requests - owner +- trigger_schedule deploy_keys: - user - deploy_keys_projects @@ -194,6 +195,7 @@ project: - runners - variables - triggers +- trigger_schedules - environments - deployments - project_feature diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 1ad16a9b57d..ecd8f2990c6 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -240,6 +240,16 @@ Ci::Trigger: - updated_at - owner_id - description +- ref +Ci::TriggerSchedule: +- id +- project_id +- trigger_id +- deleted_at +- created_at +- updated_at +- cron +- next_run_at DeployKey: - id - user_id -- cgit v1.2.1 From a2c4a2a26621539787712c62d6b58e79de7f4fb8 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 20:30:35 +0900 Subject: Resolve error on trigger.spec --- spec/factories/ci/triggers.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/factories/ci/triggers.rb b/spec/factories/ci/triggers.rb index c9fedf8a857..2295455575d 100644 --- a/spec/factories/ci/triggers.rb +++ b/spec/factories/ci/triggers.rb @@ -1,9 +1,10 @@ FactoryGirl.define do factory :ci_trigger_without_token, class: Ci::Trigger do factory :ci_trigger do - token { SecureRandom.hex(15) } + token 'token' factory :ci_trigger_for_trigger_schedule do + token { SecureRandom.hex(15) } owner factory: :user project factory: :project ref 'master' -- cgit v1.2.1 From 1bd5494942853decbcd67434ef592ca2523a777b Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 21:18:07 +0900 Subject: Improve cron_parser_spec --- spec/lib/ci/cron_parser_spec.rb | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb index 11d1e1c8a78..fb4a5ec4d13 100644 --- a/spec/lib/ci/cron_parser_spec.rb +++ b/spec/lib/ci/cron_parser_spec.rb @@ -2,6 +2,10 @@ require 'spec_helper' module Ci describe CronParser, lib: true do + shared_examples_for "returns time in the future" do + it { is_expected.to be > Time.now } + end + describe '#next_time_from' do subject { described_class.new(cron, cron_time_zone).next_time_from(Time.now) } @@ -10,8 +14,9 @@ module Ci let(:cron) { '3 4 5 6 *' } let(:cron_time_zone) { 'UTC' } - it 'returns exact time in the future' do - expect(subject).to be > Time.now + it_behaves_like "returns time in the future" + + it 'returns exact time' do expect(subject.min).to eq(3) expect(subject.hour).to eq(4) expect(subject.day).to eq(5) @@ -23,8 +28,9 @@ module Ci let(:cron) { '* * * * 0' } let(:cron_time_zone) { 'UTC' } - it 'returns exact day of week in the future' do - expect(subject).to be > Time.now + it_behaves_like "returns time in the future" + + it 'returns exact day of week' do expect(subject.wday).to eq(0) end end @@ -33,8 +39,9 @@ module Ci let(:cron) { '*/10 */6 */10 */10 *' } let(:cron_time_zone) { 'UTC' } - it 'returns exact minute' do - expect(subject).to be > Time.now + it_behaves_like "returns time in the future" + + it 'returns specific time' do 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]) @@ -46,8 +53,9 @@ module Ci let(:cron) { '0,20,40 * 1-5 * *' } let(:cron_time_zone) { 'UTC' } - it 'returns next time from now' do - expect(subject).to be > Time.now + it_behaves_like "returns time in the future" + + it 'returns specific time' do expect(subject.min).to be_in([0, 20, 40]) expect(subject.day).to be_in((1..5).to_a) end @@ -57,9 +65,7 @@ module Ci let(:cron) { '0 0 * * *' } let(:cron_time_zone) { 'US/Pacific' } - it 'returns next time from now' do - expect(subject).to be > Time.now - end + it_behaves_like "returns time in the future" it 'converts time in server time zone' do expect(subject.hour).to eq(7) -- cgit v1.2.1 From 97cc6777368bfe171198af383bf715629e9b076f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Sat, 1 Apr 2017 23:27:24 +0900 Subject: Commentout check_cron_frequency --- app/models/ci/trigger_schedule.rb | 26 ++++++++++++++------------ spec/models/ci/trigger_schedule_spec.rb | 27 +-------------------------- 2 files changed, 15 insertions(+), 38 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 6529e364fe8..be547af2114 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -13,30 +13,32 @@ module Ci validates :cron, cron: true, presence: true validates :cron_time_zone, presence: true validates :ref, ref: true, presence: true - validate :check_cron_frequency + # validate :check_cron_frequency after_create :schedule_next_run! def schedule_next_run! next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) - if next_time.present? && !less_than_1_hour_from_now?(next_time) + # if next_time.present? && !less_than_1_hour_from_now?(next_time) + if next_time.present? update!(next_run_at: next_time) end end - private + # private - def less_than_1_hour_from_now?(time) - ((time - Time.now).abs < 1.hour) ? true : false - end + # def less_than_1_hour_from_now?(time) + # puts "diff: #{(time - Time.now).abs.inspect}" + # ((time - Time.now).abs < 1.hour) ? true : false + # end - def check_cron_frequency - next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) + # def check_cron_frequency + # next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) - if less_than_1_hour_from_now?(next_time) - self.errors.add(:cron, " can not be less than 1 hour") - end - end + # if less_than_1_hour_from_now?(next_time) + # self.errors.add(:cron, " can not be less than 1 hour") + # end + # end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 57ebcdfb3f1..8b27ca1c8b2 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -12,33 +12,8 @@ describe Ci::TriggerSchedule, models: true do expect(trigger_schedule.errors[:ref].first).to include('does not exist') end - describe 'cron limitation' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } - - before do - trigger_schedule.cron = cron - trigger_schedule.valid? - end - - context 'when cron frequency is too short' do - let(:cron) { '0 * * * *' } # 00:00, 01:00, 02:00, ..., 23:00 - - it 'gets an error' do - expect(trigger_schedule.errors[:cron].first).to include('can not be less than 1 hour') - end - end - - context 'when cron frequency is eligible' do - let(:cron) { '0 0 1 1 *' } # every 00:00, January 1st - - it 'gets no errors' do - expect(trigger_schedule.errors[:cron]).to be_empty - end - end - end - describe '#schedule_next_run!' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } + let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } before do trigger_schedule.schedule_next_run! -- cgit v1.2.1 From a67aff6d398467099121e7a7b4a542ff531d3f45 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 16:55:14 +0900 Subject: Add Import/Export Setting for trigger_schedule. Remove ref validation. --- app/models/ci/trigger_schedule.rb | 9 +++++---- app/validators/ref_validator.rb | 10 ---------- lib/gitlab/import_export/import_export.yml | 3 ++- lib/gitlab/import_export/relation_factory.rb | 1 + spec/lib/gitlab/import_export/all_models.yml | 2 ++ spec/lib/gitlab/import_export/safe_model_attributes.yml | 1 + spec/models/ci/trigger_schedule_spec.rb | 7 ------- 7 files changed, 11 insertions(+), 22 deletions(-) delete mode 100644 app/validators/ref_validator.rb diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index be547af2114..58337b34d80 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -1,6 +1,7 @@ module Ci class TriggerSchedule < ActiveRecord::Base extend Ci::Model + include Importable acts_as_paranoid @@ -9,10 +10,10 @@ module Ci delegate :ref, to: :trigger - validates :trigger, presence: true - validates :cron, cron: true, presence: true - validates :cron_time_zone, presence: true - validates :ref, ref: true, presence: true + validates :trigger, presence: { unless: :importing? } + validates :cron, cron: true, presence: { unless: :importing? } + validates :cron_time_zone, presence: { unless: :importing? } + validates :ref, presence: { unless: :importing? } # validate :check_cron_frequency after_create :schedule_next_run! diff --git a/app/validators/ref_validator.rb b/app/validators/ref_validator.rb deleted file mode 100644 index 2024255a770..00000000000 --- a/app/validators/ref_validator.rb +++ /dev/null @@ -1,10 +0,0 @@ -# RefValidator -# -# Custom validator for Ref. -class RefValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - unless record.project.repository.branch_exists?(value) - record.errors.add(attribute, " does not exist") - end - end -end diff --git a/lib/gitlab/import_export/import_export.yml b/lib/gitlab/import_export/import_export.yml index 8d74418ed14..f5e1e385ff9 100644 --- a/lib/gitlab/import_export/import_export.yml +++ b/lib/gitlab/import_export/import_export.yml @@ -39,7 +39,8 @@ project_tree: - :author - :events - :statuses - - :triggers # TODO: Need to confirm + - triggers: + - :trigger_schedule - :deploy_keys - :services - :hooks diff --git a/lib/gitlab/import_export/relation_factory.rb b/lib/gitlab/import_export/relation_factory.rb index fb43e7ccdbb..2ba12f5f924 100644 --- a/lib/gitlab/import_export/relation_factory.rb +++ b/lib/gitlab/import_export/relation_factory.rb @@ -5,6 +5,7 @@ module Gitlab pipelines: 'Ci::Pipeline', statuses: 'commit_status', triggers: 'Ci::Trigger', + trigger_schedule: 'Ci::TriggerSchedule', builds: 'Ci::Build', hooks: 'ProjectHook', merge_access_levels: 'ProtectedBranch::MergeAccessLevel', diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 06e8cd5a1cd..488ae9655bb 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -100,6 +100,8 @@ triggers: - trigger_requests - owner - trigger_schedule +trigger_schedule: +- trigger deploy_keys: - user - deploy_keys_projects diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index ecd8f2990c6..42082ff3dee 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -249,6 +249,7 @@ Ci::TriggerSchedule: - created_at - updated_at - cron +- cron_time_zone - next_run_at DeployKey: - id diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 8b27ca1c8b2..99668ff17b8 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -5,13 +5,6 @@ describe Ci::TriggerSchedule, models: true do it { is_expected.to belong_to(:trigger) } it { is_expected.to respond_to :ref } - it 'should validate ref existence' do - trigger_schedule = create(:ci_trigger_schedule, :cron_nightly_build) - trigger_schedule.trigger.ref = 'invalid-ref' - trigger_schedule.valid? - expect(trigger_schedule.errors[:ref].first).to include('does not exist') - end - describe '#schedule_next_run!' do let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } -- cgit v1.2.1 From f6be8c048555f2d1086e7beed336b6187edb4d58 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 16:58:02 +0900 Subject: Remove less_than_1_hour_from_now comments. Dry up def schedule_next_run! --- app/models/ci/trigger_schedule.rb | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 58337b34d80..6e7c0b4f6c2 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -14,32 +14,12 @@ module Ci validates :cron, cron: true, presence: { unless: :importing? } validates :cron_time_zone, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } - # validate :check_cron_frequency after_create :schedule_next_run! def schedule_next_run! next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) - - # if next_time.present? && !less_than_1_hour_from_now?(next_time) - if next_time.present? - update!(next_run_at: next_time) - end + update!(next_run_at: next_time) if next_time.present? end - - # private - - # def less_than_1_hour_from_now?(time) - # puts "diff: #{(time - Time.now).abs.inspect}" - # ((time - Time.now).abs < 1.hour) ? true : false - # end - - # def check_cron_frequency - # next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) - - # if less_than_1_hour_from_now?(next_time) - # self.errors.add(:cron, " can not be less than 1 hour") - # end - # end end end -- cgit v1.2.1 From 27f981b2901098f894e587bbd96b09e2a0f0c404 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 18:31:19 +0900 Subject: Improve real_next_run. Improve triggers_helper_spec. --- app/helpers/triggers_helper.rb | 16 +++++----------- spec/helpers/triggers_helper_spec.rb | 27 ++++++++++++++++++++------- 2 files changed, 25 insertions(+), 18 deletions(-) diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb index be5cce9aea0..932ba595b73 100644 --- a/app/helpers/triggers_helper.rb +++ b/app/helpers/triggers_helper.rb @@ -11,16 +11,10 @@ module TriggersHelper "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger" end - def real_next_run(trigger_schedule, worker_cron: nil, worker_time_zone: nil) - worker_cron = Settings.cron_jobs['trigger_schedule_worker']['cron'] unless worker_cron.present? - worker_time_zone = Time.zone.name unless worker_time_zone.present? - - worker_next_time = Ci::CronParser.new(worker_cron, worker_time_zone).next_time_from(Time.now) - - if trigger_schedule.next_run_at > worker_next_time - trigger_schedule.next_run_at - else - worker_next_time - end + def real_next_run(trigger_schedule, + worker_cron: Settings.cron_jobs['trigger_schedule_worker']['cron'], + worker_time_zone: Time.zone.name) + Ci::CronParser.new(worker_cron, worker_time_zone) + .next_time_from(trigger_schedule.next_run_at) end end diff --git a/spec/helpers/triggers_helper_spec.rb b/spec/helpers/triggers_helper_spec.rb index d801760335b..61d233421b2 100644 --- a/spec/helpers/triggers_helper_spec.rb +++ b/spec/helpers/triggers_helper_spec.rb @@ -4,23 +4,36 @@ describe TriggersHelper do describe '#real_next_run' do let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } - subject { helper.real_next_run(trigger_schedule, worker_cron: worker_cron, worker_time_zone: 'UTC') } + subject { helper.real_next_run(trigger_schedule, arguments) } context 'when next_run_at > worker_next_time' do - let(:worker_cron) { '0 0 1 1 *' } # every 00:00, January 1st + let(:arguments) { { worker_cron: '0 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:00, January 1st let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st - it 'returns next_run_at' do - is_expected.to eq(trigger_schedule.next_run_at) + it 'returns nearest worker_next_time from next_run_at' do + is_expected.to eq(Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) end end context 'when worker_next_time > next_run_at' do - let(:worker_cron) { '1 0 1 1 *' } # every 00:01, January 1st + let(:arguments) { { worker_cron: '1 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:01, January 1st let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st - it 'returns worker_next_time' do - is_expected.to eq(Ci::CronParser.new(worker_cron, 'UTC').next_time_from(Time.now)) + it 'returns nearest worker_next_time from next_run_at' do + is_expected.to eq(Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) + end + end + + context 'when worker_cron and worker_time_zone are ommited' do + let(:arguments) { {} } + let(:user_cron) { '* * * * *' } # every minutes + + it 'returns nearest worker_next_time from next_run_at by server configuration' do + is_expected.to eq(Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], + Time.zone.name) + .next_time_from(trigger_schedule.next_run_at)) end end end -- cgit v1.2.1 From 914bef671f54c04a0d36d8e0f8c9830d6dea7b03 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 18:44:25 +0900 Subject: Move Ci::CronParser to Gitlab::Ci::CronParser --- app/helpers/triggers_helper.rb | 4 +-- app/models/ci/trigger_schedule.rb | 2 +- app/validators/cron_validator.rb | 2 +- lib/ci/cron_parser.rb | 36 -------------------------- lib/gitlab/ci/cron_parser.rb | 38 ++++++++++++++++++++++++++++ spec/factories/ci/trigger_schedules.rb | 8 +++--- spec/helpers/triggers_helper_spec.rb | 14 +++++----- spec/models/ci/trigger_schedule_spec.rb | 2 +- spec/workers/trigger_schedule_worker_spec.rb | 2 +- 9 files changed, 55 insertions(+), 53 deletions(-) delete mode 100644 lib/ci/cron_parser.rb create mode 100644 lib/gitlab/ci/cron_parser.rb diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb index 932ba595b73..a415ac11893 100644 --- a/app/helpers/triggers_helper.rb +++ b/app/helpers/triggers_helper.rb @@ -14,7 +14,7 @@ module TriggersHelper def real_next_run(trigger_schedule, worker_cron: Settings.cron_jobs['trigger_schedule_worker']['cron'], worker_time_zone: Time.zone.name) - Ci::CronParser.new(worker_cron, worker_time_zone) - .next_time_from(trigger_schedule.next_run_at) + Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone) + .next_time_from(trigger_schedule.next_run_at) end end diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 6e7c0b4f6c2..092338be9ce 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -18,7 +18,7 @@ module Ci after_create :schedule_next_run! def schedule_next_run! - next_time = Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) update!(next_run_at: next_time) if next_time.present? end end diff --git a/app/validators/cron_validator.rb b/app/validators/cron_validator.rb index 4d9a0d62a4c..31eaa4147a5 100644 --- a/app/validators/cron_validator.rb +++ b/app/validators/cron_validator.rb @@ -3,7 +3,7 @@ # Custom validator for Cron. class CronValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - cron_parser = Ci::CronParser.new(record.cron, record.cron_time_zone) + cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_time_zone) is_valid_cron, is_valid_cron_time_zone = cron_parser.validation if !is_valid_cron diff --git a/lib/ci/cron_parser.rb b/lib/ci/cron_parser.rb deleted file mode 100644 index e0d589956a8..00000000000 --- a/lib/ci/cron_parser.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Ci - class CronParser - VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze - VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze - - def initialize(cron, cron_time_zone = 'UTC') - @cron = cron - @cron_time_zone = cron_time_zone - end - - def next_time_from(time) - cron_line = try_parse_cron(@cron, @cron_time_zone) - if cron_line.present? - cron_line.next_time(time).in_time_zone(Time.zone) - else - nil - end - end - - def validation - is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? - is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? - return is_valid_cron, is_valid_cron_time_zone - end - - private - - def try_parse_cron(cron, cron_time_zone) - begin - Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") - rescue - nil - end - end - end -end diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb new file mode 100644 index 00000000000..01f37142510 --- /dev/null +++ b/lib/gitlab/ci/cron_parser.rb @@ -0,0 +1,38 @@ +module Gitlab + module Ci + class CronParser + VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze + VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze + + def initialize(cron, cron_time_zone = 'UTC') + @cron = cron + @cron_time_zone = cron_time_zone + end + + def next_time_from(time) + cron_line = try_parse_cron(@cron, @cron_time_zone) + if cron_line.present? + cron_line.next_time(time).in_time_zone(Time.zone) + else + nil + end + end + + def validation + is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? + is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? + return is_valid_cron, is_valid_cron_time_zone + end + + private + + def try_parse_cron(cron, cron_time_zone) + begin + Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") + rescue + nil + end + end + end + end +end \ No newline at end of file diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 2e6a35c6416..8aafbc1f81b 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :ci_trigger_schedule, class: Ci::TriggerSchedule do trigger factory: :ci_trigger_for_trigger_schedule cron '0 1 * * *' - cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE after(:build) do |trigger_schedule, evaluator| trigger_schedule.update!(project: trigger_schedule.trigger.project) @@ -16,17 +16,17 @@ FactoryGirl.define do trait :cron_nightly_build do cron '0 1 * * *' - cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end trait :cron_weekly_build do cron '0 1 * * 6' - cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end trait :cron_monthly_build do cron '0 1 22 * *' - cron_time_zone Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end end end diff --git a/spec/helpers/triggers_helper_spec.rb b/spec/helpers/triggers_helper_spec.rb index 61d233421b2..ee3fd3fea0f 100644 --- a/spec/helpers/triggers_helper_spec.rb +++ b/spec/helpers/triggers_helper_spec.rb @@ -11,8 +11,8 @@ describe TriggersHelper do let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) + is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) end end @@ -21,8 +21,8 @@ describe TriggersHelper do let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) + is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) end end @@ -31,9 +31,9 @@ describe TriggersHelper do let(:user_cron) { '* * * * *' } # every minutes it 'returns nearest worker_next_time from next_run_at by server configuration' do - is_expected.to eq(Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], - Time.zone.name) - .next_time_from(trigger_schedule.next_run_at)) + is_expected.to eq(Gitlab::Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], + Time.zone.name) + .next_time_from(trigger_schedule.next_run_at)) end end end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 99668ff17b8..81104cb26b6 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -13,7 +13,7 @@ describe Ci::TriggerSchedule, models: true do end it 'updates next_run_at' do - next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 950f72a68d9..4df5731709b 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -23,7 +23,7 @@ describe TriggerScheduleWorker do end it 'updates next_run_at' do - next_time = Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end -- cgit v1.2.1 From 3d3df09713dcb70baceaeba7603fa49b89fc8007 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 18:49:52 +0900 Subject: Dry up next_time_from. Move cron_parser_spec to appropriate location. --- lib/gitlab/ci/cron_parser.rb | 6 +- spec/lib/ci/cron_parser_spec.rb | 106 --------------------------------- spec/lib/gitlab/ci/cron_parser_spec.rb | 104 ++++++++++++++++++++++++++++++++ 3 files changed, 105 insertions(+), 111 deletions(-) delete mode 100644 spec/lib/ci/cron_parser_spec.rb create mode 100644 spec/lib/gitlab/ci/cron_parser_spec.rb diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 01f37142510..2d9ea26faf0 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -11,11 +11,7 @@ module Gitlab def next_time_from(time) cron_line = try_parse_cron(@cron, @cron_time_zone) - if cron_line.present? - cron_line.next_time(time).in_time_zone(Time.zone) - else - nil - end + cron_line.next_time(time).in_time_zone(Time.zone) if cron_line.present? end def validation diff --git a/spec/lib/ci/cron_parser_spec.rb b/spec/lib/ci/cron_parser_spec.rb deleted file mode 100644 index fb4a5ec4d13..00000000000 --- a/spec/lib/ci/cron_parser_spec.rb +++ /dev/null @@ -1,106 +0,0 @@ -require 'spec_helper' - -module Ci - describe CronParser, lib: true do - shared_examples_for "returns time in the future" do - it { is_expected.to be > Time.now } - end - - describe '#next_time_from' do - subject { described_class.new(cron, cron_time_zone).next_time_from(Time.now) } - - context 'when cron and cron_time_zone are valid' do - context 'when specific time' do - let(:cron) { '3 4 5 6 *' } - let(:cron_time_zone) { 'UTC' } - - it_behaves_like "returns time in the future" - - it 'returns exact time' do - 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 'when specific day of week' do - let(:cron) { '* * * * 0' } - let(:cron_time_zone) { 'UTC' } - - it_behaves_like "returns time in the future" - - it 'returns exact day of week' do - expect(subject.wday).to eq(0) - end - end - - context 'when slash used' do - let(:cron) { '*/10 */6 */10 */10 *' } - let(:cron_time_zone) { 'UTC' } - - it_behaves_like "returns time in the future" - - it 'returns specific time' do - 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 range used' do - let(:cron) { '0,20,40 * 1-5 * *' } - let(:cron_time_zone) { 'UTC' } - - it_behaves_like "returns time in the future" - - it 'returns specific time' do - 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 0 * * *' } - let(:cron_time_zone) { 'US/Pacific' } - - it_behaves_like "returns time in the future" - - it 'converts time in server time zone' do - expect(subject.hour).to eq(7) - end - end - end - - context 'when cron and cron_time_zone are invalid' do - let(:cron) { 'invalid_cron' } - let(:cron_time_zone) { 'invalid_cron_time_zone' } - - it 'returns nil' do - is_expected.to be_nil - end - end - end - - describe '#validation' do - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Europe/Istanbul').validation - expect(is_valid_cron).to eq(true) - expect(is_valid_cron_time_zone).to eq(true) - end - - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('*********', 'Europe/Istanbul').validation - expect(is_valid_cron).to eq(false) - expect(is_valid_cron_time_zone).to eq(true) - end - - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Invalid-zone').validation - expect(is_valid_cron).to eq(true) - expect(is_valid_cron_time_zone).to eq(false) - end - end - end -end diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb new file mode 100644 index 00000000000..62d1ea3f087 --- /dev/null +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -0,0 +1,104 @@ +require 'spec_helper' + +describe Gitlab::Ci::CronParser do + shared_examples_for "returns time in the future" do + it { is_expected.to be > Time.now } + end + + describe '#next_time_from' do + subject { described_class.new(cron, cron_time_zone).next_time_from(Time.now) } + + context 'when cron and cron_time_zone are valid' do + context 'when specific time' do + let(:cron) { '3 4 5 6 *' } + let(:cron_time_zone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns exact time' do + 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 'when specific day of week' do + let(:cron) { '* * * * 0' } + let(:cron_time_zone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns exact day of week' do + expect(subject.wday).to eq(0) + end + end + + context 'when slash used' do + let(:cron) { '*/10 */6 */10 */10 *' } + let(:cron_time_zone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns specific time' do + 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 range used' do + let(:cron) { '0,20,40 * 1-5 * *' } + let(:cron_time_zone) { 'UTC' } + + it_behaves_like "returns time in the future" + + it 'returns specific time' do + 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 0 * * *' } + let(:cron_time_zone) { 'US/Pacific' } + + it_behaves_like "returns time in the future" + + it 'converts time in server time zone' do + expect(subject.hour).to eq(7) + end + end + end + + context 'when cron and cron_time_zone are invalid' do + let(:cron) { 'invalid_cron' } + let(:cron_time_zone) { 'invalid_cron_time_zone' } + + it 'returns nil' do + is_expected.to be_nil + end + end + end + + describe '#validation' do + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Europe/Istanbul').validation + expect(is_valid_cron).to eq(true) + expect(is_valid_cron_time_zone).to eq(true) + end + + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('*********', 'Europe/Istanbul').validation + expect(is_valid_cron).to eq(false) + expect(is_valid_cron_time_zone).to eq(true) + end + + it 'returns results' do + is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Invalid-zone').validation + expect(is_valid_cron).to eq(true) + expect(is_valid_cron_time_zone).to eq(false) + end + end +end -- cgit v1.2.1 From 4949e2b291bc59ee3855882a29df3bff9edfd4e5 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 19:14:49 +0900 Subject: Separate cron_valid? and cron_time_zone_valid? --- app/models/ci/trigger_schedule.rb | 2 +- app/validators/cron_time_zone_validator.rb | 9 +++++++ app/validators/cron_validator.rb | 8 +------ lib/gitlab/ci/cron_parser.rb | 10 ++++---- spec/lib/gitlab/ci/cron_parser_spec.rb | 38 ++++++++++++++++++++---------- 5 files changed, 42 insertions(+), 25 deletions(-) create mode 100644 app/validators/cron_time_zone_validator.rb diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 092338be9ce..9b1dfce969a 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -12,7 +12,7 @@ module Ci validates :trigger, presence: { unless: :importing? } validates :cron, cron: true, presence: { unless: :importing? } - validates :cron_time_zone, presence: { unless: :importing? } + validates :cron_time_zone, cron_time_zone: true, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } after_create :schedule_next_run! diff --git a/app/validators/cron_time_zone_validator.rb b/app/validators/cron_time_zone_validator.rb new file mode 100644 index 00000000000..9d4bbe1d458 --- /dev/null +++ b/app/validators/cron_time_zone_validator.rb @@ -0,0 +1,9 @@ +# CronTimeZoneValidator +# +# Custom validator for CronTimeZone. +class CronTimeZoneValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_time_zone) + record.errors.add(attribute, " is invalid syntax") unless cron_parser.cron_time_zone_valid? + end +end diff --git a/app/validators/cron_validator.rb b/app/validators/cron_validator.rb index 31eaa4147a5..cc07011d56b 100644 --- a/app/validators/cron_validator.rb +++ b/app/validators/cron_validator.rb @@ -4,12 +4,6 @@ class CronValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_time_zone) - is_valid_cron, is_valid_cron_time_zone = cron_parser.validation - - if !is_valid_cron - record.errors.add(:cron, " is invalid syntax") - elsif !is_valid_cron_time_zone - record.errors.add(:cron_time_zone, " is invalid timezone") - end + record.errors.add(attribute, " is invalid syntax") unless cron_parser.cron_valid? end end diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 2d9ea26faf0..521e556f769 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -14,10 +14,12 @@ module Gitlab cron_line.next_time(time).in_time_zone(Time.zone) if cron_line.present? end - def validation - is_valid_cron = try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? - is_valid_cron_time_zone = try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? - return is_valid_cron, is_valid_cron_time_zone + def cron_valid? + try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? + end + + def cron_time_zone_valid? + try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? end private diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 62d1ea3f087..1cdd8c1d2e7 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -82,23 +82,35 @@ describe Gitlab::Ci::CronParser do end end - describe '#validation' do - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Europe/Istanbul').validation - expect(is_valid_cron).to eq(true) - expect(is_valid_cron_time_zone).to eq(true) + describe '#cron_valid?' do + subject { described_class.new(cron, Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE).cron_valid? } + + context 'when cron is valid' do + let(:cron) { '* * * * *' } + + it { is_expected.to eq(true) } end - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('*********', 'Europe/Istanbul').validation - expect(is_valid_cron).to eq(false) - expect(is_valid_cron_time_zone).to eq(true) + context 'when cron is invalid' do + let(:cron) { '*********' } + + it { is_expected.to eq(false) } end + end + + describe '#cron_time_zone_valid?' do + subject { described_class.new(Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_CRON, cron_time_zone).cron_time_zone_valid? } + + context 'when cron is valid' do + let(:cron_time_zone) { 'Europe/Istanbul' } + + it { is_expected.to eq(true) } + end + + context 'when cron is invalid' do + let(:cron_time_zone) { 'Invalid-zone' } - it 'returns results' do - is_valid_cron, is_valid_cron_time_zone = described_class.new('* * * * *', 'Invalid-zone').validation - expect(is_valid_cron).to eq(true) - expect(is_valid_cron_time_zone).to eq(false) + it { is_expected.to eq(false) } end end end -- cgit v1.2.1 From 0c153af73df3d5b10915cf9d32af728f9b6e8e98 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 21:18:51 +0900 Subject: Ommit begin block in try_parse_cron --- lib/gitlab/ci/cron_parser.rb | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 521e556f769..69dd8ad0fce 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -25,11 +25,9 @@ module Gitlab private def try_parse_cron(cron, cron_time_zone) - begin - Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") - rescue - nil - end + Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") + rescue + # noop end end end -- cgit v1.2.1 From 4a5c6a8e2953baeceb33d281d23d2c305ff884fa Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 21:22:12 +0900 Subject: Rename cron_nightly_build to nightly --- spec/factories/ci/trigger_schedules.rb | 6 +++--- spec/models/ci/trigger_schedule_spec.rb | 2 +- spec/workers/trigger_schedule_worker_spec.rb | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 8aafbc1f81b..9c16d45b49a 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -14,17 +14,17 @@ FactoryGirl.define do end end - trait :cron_nightly_build do + trait :nightly do cron '0 1 * * *' cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end - trait :cron_weekly_build do + trait :weekly do cron '0 1 * * 6' cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end - trait :cron_monthly_build do + trait :monthly do cron '0 1 22 * *' cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 81104cb26b6..9a4bf122bf0 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -6,7 +6,7 @@ describe Ci::TriggerSchedule, models: true do it { is_expected.to respond_to :ref } describe '#schedule_next_run!' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, next_run_at: nil) } + let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly, next_run_at: nil) } before do trigger_schedule.schedule_next_run! diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 4df5731709b..75a98e42ac5 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -8,7 +8,7 @@ describe TriggerScheduleWorker do end context 'when there is a scheduled trigger within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build, :force_triggable) } + let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly, :force_triggable) } before do worker.perform @@ -29,7 +29,7 @@ describe TriggerScheduleWorker do end context 'when there are no scheduled triggers within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :cron_nightly_build) } + let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } before do worker.perform -- cgit v1.2.1 From f229290ac828d1d5743f86c5a4977168a0406365 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Tue, 4 Apr 2017 23:43:09 +0900 Subject: Remove triggers_helper. Add trigger_schedule_presenter. --- app/helpers/triggers_helper.rb | 7 --- app/models/ci/trigger_schedule.rb | 1 + app/presenters/ci/trigger_schedule_presenter.rb | 11 ++++ spec/helpers/triggers_helper_spec.rb | 40 -------------- .../ci/trigger_schedule_presenter_spec.rb | 64 ++++++++++++++++++++++ 5 files changed, 76 insertions(+), 47 deletions(-) create mode 100644 app/presenters/ci/trigger_schedule_presenter.rb delete mode 100644 spec/helpers/triggers_helper_spec.rb create mode 100644 spec/presenters/ci/trigger_schedule_presenter_spec.rb diff --git a/app/helpers/triggers_helper.rb b/app/helpers/triggers_helper.rb index a415ac11893..a48d4475e97 100644 --- a/app/helpers/triggers_helper.rb +++ b/app/helpers/triggers_helper.rb @@ -10,11 +10,4 @@ module TriggersHelper def service_trigger_url(service) "#{Settings.gitlab.url}/api/v3/projects/#{service.project_id}/services/#{service.to_param}/trigger" end - - def real_next_run(trigger_schedule, - worker_cron: Settings.cron_jobs['trigger_schedule_worker']['cron'], - worker_time_zone: Time.zone.name) - Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone) - .next_time_from(trigger_schedule.next_run_at) - end end diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 9b1dfce969a..2cf041df8a7 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -2,6 +2,7 @@ module Ci class TriggerSchedule < ActiveRecord::Base extend Ci::Model include Importable + include Presentable acts_as_paranoid diff --git a/app/presenters/ci/trigger_schedule_presenter.rb b/app/presenters/ci/trigger_schedule_presenter.rb new file mode 100644 index 00000000000..faef6a3d66f --- /dev/null +++ b/app/presenters/ci/trigger_schedule_presenter.rb @@ -0,0 +1,11 @@ +module Ci + class TriggerSchedulePresenter < Gitlab::View::Presenter::Delegated + presents :trigger_schedule + + def real_next_run(worker_cron: Settings.cron_jobs['trigger_schedule_worker']['cron'], + worker_time_zone: Time.zone.name) + Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone) + .next_time_from(next_run_at) + end + end +end diff --git a/spec/helpers/triggers_helper_spec.rb b/spec/helpers/triggers_helper_spec.rb deleted file mode 100644 index ee3fd3fea0f..00000000000 --- a/spec/helpers/triggers_helper_spec.rb +++ /dev/null @@ -1,40 +0,0 @@ -require 'rails_helper' - -describe TriggersHelper do - describe '#real_next_run' do - let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } - - subject { helper.real_next_run(trigger_schedule, arguments) } - - context 'when next_run_at > worker_next_time' do - let(:arguments) { { worker_cron: '0 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:00, January 1st - let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st - - it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) - end - end - - context 'when worker_next_time > next_run_at' do - let(:arguments) { { worker_cron: '1 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:01, January 1st - let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st - - it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) - end - end - - context 'when worker_cron and worker_time_zone are ommited' do - let(:arguments) { {} } - let(:user_cron) { '* * * * *' } # every minutes - - it 'returns nearest worker_next_time from next_run_at by server configuration' do - is_expected.to eq(Gitlab::Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], - Time.zone.name) - .next_time_from(trigger_schedule.next_run_at)) - end - end - end -end diff --git a/spec/presenters/ci/trigger_schedule_presenter_spec.rb b/spec/presenters/ci/trigger_schedule_presenter_spec.rb new file mode 100644 index 00000000000..92e38441a8a --- /dev/null +++ b/spec/presenters/ci/trigger_schedule_presenter_spec.rb @@ -0,0 +1,64 @@ +require 'spec_helper' + +describe Ci::TriggerSchedulePresenter do + let(:trigger_schedule) { create(:ci_trigger_schedule) } + + subject(:presenter) do + described_class.new(trigger_schedule) + end + + it 'inherits from Gitlab::View::Presenter::Delegated' do + expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) + end + + describe '#initialize' do + it 'takes a trigger_schedule and optional params' do + expect { presenter }.not_to raise_error + end + + it 'exposes trigger_schedule' do + expect(presenter.trigger_schedule).to eq(trigger_schedule) + end + + it 'forwards missing methods to trigger_schedule' do + expect(presenter.ref).to eq('master') + end + end + + describe '#real_next_run' do + let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } + + subject { described_class.new(trigger_schedule).real_next_run(arguments) } + + context 'when next_run_at > worker_next_time' do + let(:arguments) { { worker_cron: '0 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:00, January 1st + let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st + + it 'returns nearest worker_next_time from next_run_at' do + is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) + end + end + + context 'when worker_next_time > next_run_at' do + let(:arguments) { { worker_cron: '1 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:01, January 1st + let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st + + it 'returns nearest worker_next_time from next_run_at' do + is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) + .next_time_from(trigger_schedule.next_run_at)) + end + end + + context 'when worker_cron and worker_time_zone are ommited' do + let(:arguments) { {} } + let(:user_cron) { '* * * * *' } # every minutes + + it 'returns nearest worker_next_time from next_run_at by server configuration' do + is_expected.to eq(Gitlab::Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], + Time.zone.name) + .next_time_from(trigger_schedule.next_run_at)) + end + end + end +end -- cgit v1.2.1 From 1dbc888e3306f30ca0882aece86ccd1a817e0ab8 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:29:10 +0900 Subject: Remove TriggerSchedulePresenter. This will go in another MR. --- app/models/ci/trigger_schedule.rb | 1 - app/presenters/ci/trigger_schedule_presenter.rb | 11 ---- .../ci/trigger_schedule_presenter_spec.rb | 64 ---------------------- 3 files changed, 76 deletions(-) delete mode 100644 app/presenters/ci/trigger_schedule_presenter.rb delete mode 100644 spec/presenters/ci/trigger_schedule_presenter_spec.rb diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 2cf041df8a7..9b1dfce969a 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -2,7 +2,6 @@ module Ci class TriggerSchedule < ActiveRecord::Base extend Ci::Model include Importable - include Presentable acts_as_paranoid diff --git a/app/presenters/ci/trigger_schedule_presenter.rb b/app/presenters/ci/trigger_schedule_presenter.rb deleted file mode 100644 index faef6a3d66f..00000000000 --- a/app/presenters/ci/trigger_schedule_presenter.rb +++ /dev/null @@ -1,11 +0,0 @@ -module Ci - class TriggerSchedulePresenter < Gitlab::View::Presenter::Delegated - presents :trigger_schedule - - def real_next_run(worker_cron: Settings.cron_jobs['trigger_schedule_worker']['cron'], - worker_time_zone: Time.zone.name) - Gitlab::Ci::CronParser.new(worker_cron, worker_time_zone) - .next_time_from(next_run_at) - end - end -end diff --git a/spec/presenters/ci/trigger_schedule_presenter_spec.rb b/spec/presenters/ci/trigger_schedule_presenter_spec.rb deleted file mode 100644 index 92e38441a8a..00000000000 --- a/spec/presenters/ci/trigger_schedule_presenter_spec.rb +++ /dev/null @@ -1,64 +0,0 @@ -require 'spec_helper' - -describe Ci::TriggerSchedulePresenter do - let(:trigger_schedule) { create(:ci_trigger_schedule) } - - subject(:presenter) do - described_class.new(trigger_schedule) - end - - it 'inherits from Gitlab::View::Presenter::Delegated' do - expect(described_class.superclass).to eq(Gitlab::View::Presenter::Delegated) - end - - describe '#initialize' do - it 'takes a trigger_schedule and optional params' do - expect { presenter }.not_to raise_error - end - - it 'exposes trigger_schedule' do - expect(presenter.trigger_schedule).to eq(trigger_schedule) - end - - it 'forwards missing methods to trigger_schedule' do - expect(presenter.ref).to eq('master') - end - end - - describe '#real_next_run' do - let(:trigger_schedule) { create(:ci_trigger_schedule, cron: user_cron, cron_time_zone: 'UTC') } - - subject { described_class.new(trigger_schedule).real_next_run(arguments) } - - context 'when next_run_at > worker_next_time' do - let(:arguments) { { worker_cron: '0 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:00, January 1st - let(:user_cron) { '1 0 1 1 *' } # every 00:01, January 1st - - it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) - end - end - - context 'when worker_next_time > next_run_at' do - let(:arguments) { { worker_cron: '1 0 1 1 *', worker_time_zone: 'UTC' } } # every 00:01, January 1st - let(:user_cron) { '0 0 1 1 *' } # every 00:00, January 1st - - it 'returns nearest worker_next_time from next_run_at' do - is_expected.to eq(Gitlab::Ci::CronParser.new(arguments[:worker_cron], arguments[:worker_time_zone]) - .next_time_from(trigger_schedule.next_run_at)) - end - end - - context 'when worker_cron and worker_time_zone are ommited' do - let(:arguments) { {} } - let(:user_cron) { '* * * * *' } # every minutes - - it 'returns nearest worker_next_time from next_run_at by server configuration' do - is_expected.to eq(Gitlab::Ci::CronParser.new(Settings.cron_jobs['trigger_schedule_worker']['cron'], - Time.zone.name) - .next_time_from(trigger_schedule.next_run_at)) - end - end - end -end -- cgit v1.2.1 From 4688eb47c6fe135fb9baad5a5d56b1dfa685cc7f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:54:45 +0900 Subject: Rename cron_time_zone to cron_timezone. Separate add_concurrent_foreign_key. --- app/models/ci/trigger_schedule.rb | 4 ++-- app/validators/cron_time_zone_validator.rb | 9 ------- app/validators/cron_timezone_validator.rb | 9 +++++++ app/validators/cron_validator.rb | 2 +- .../20170329095907_create_ci_trigger_schedules.rb | 16 ++++--------- .../20170404163427_add_trigger_id_foreign_key.rb | 15 ++++++++++++ db/schema.rb | 2 +- lib/gitlab/ci/cron_parser.rb | 14 +++++------ spec/factories/ci/trigger_schedules.rb | 8 +++---- spec/lib/gitlab/ci/cron_parser_spec.rb | 28 +++++++++++----------- .../gitlab/import_export/safe_model_attributes.yml | 2 +- spec/models/ci/trigger_schedule_spec.rb | 2 +- spec/workers/trigger_schedule_worker_spec.rb | 2 +- 13 files changed, 60 insertions(+), 53 deletions(-) delete mode 100644 app/validators/cron_time_zone_validator.rb create mode 100644 app/validators/cron_timezone_validator.rb create mode 100644 db/migrate/20170404163427_add_trigger_id_foreign_key.rb diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 9b1dfce969a..d18dbea284e 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -12,13 +12,13 @@ module Ci validates :trigger, presence: { unless: :importing? } validates :cron, cron: true, presence: { unless: :importing? } - validates :cron_time_zone, cron_time_zone: true, presence: { unless: :importing? } + validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } after_create :schedule_next_run! def schedule_next_run! - next_time = Gitlab::Ci::CronParser.new(cron, cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now) update!(next_run_at: next_time) if next_time.present? end end diff --git a/app/validators/cron_time_zone_validator.rb b/app/validators/cron_time_zone_validator.rb deleted file mode 100644 index 9d4bbe1d458..00000000000 --- a/app/validators/cron_time_zone_validator.rb +++ /dev/null @@ -1,9 +0,0 @@ -# CronTimeZoneValidator -# -# Custom validator for CronTimeZone. -class CronTimeZoneValidator < ActiveModel::EachValidator - def validate_each(record, attribute, value) - cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_time_zone) - record.errors.add(attribute, " is invalid syntax") unless cron_parser.cron_time_zone_valid? - end -end diff --git a/app/validators/cron_timezone_validator.rb b/app/validators/cron_timezone_validator.rb new file mode 100644 index 00000000000..542c7d006ad --- /dev/null +++ b/app/validators/cron_timezone_validator.rb @@ -0,0 +1,9 @@ +# CronTimezoneValidator +# +# Custom validator for CronTimezone. +class CronTimezoneValidator < ActiveModel::EachValidator + def validate_each(record, attribute, value) + cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_timezone) + record.errors.add(attribute, " is invalid syntax") unless cron_parser.cron_timezone_valid? + end +end diff --git a/app/validators/cron_validator.rb b/app/validators/cron_validator.rb index cc07011d56b..981fade47a6 100644 --- a/app/validators/cron_validator.rb +++ b/app/validators/cron_validator.rb @@ -3,7 +3,7 @@ # Custom validator for Cron. class CronValidator < ActiveModel::EachValidator def validate_each(record, attribute, value) - cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_time_zone) + cron_parser = Gitlab::Ci::CronParser.new(record.cron, record.cron_timezone) record.errors.add(attribute, " is invalid syntax") unless cron_parser.cron_valid? end end diff --git a/db/migrate/20170329095907_create_ci_trigger_schedules.rb b/db/migrate/20170329095907_create_ci_trigger_schedules.rb index 3dcd05175c0..cfcfa27ebb5 100644 --- a/db/migrate/20170329095907_create_ci_trigger_schedules.rb +++ b/db/migrate/20170329095907_create_ci_trigger_schedules.rb @@ -3,9 +3,7 @@ class CreateCiTriggerSchedules < ActiveRecord::Migration DOWNTIME = false - disable_ddl_transaction! - - def up + def change create_table :ci_trigger_schedules do |t| t.integer "project_id" t.integer "trigger_id", null: false @@ -13,17 +11,11 @@ class CreateCiTriggerSchedules < ActiveRecord::Migration t.datetime "created_at" t.datetime "updated_at" t.string "cron" - t.string "cron_time_zone" + t.string "cron_timezone" t.datetime "next_run_at" end - add_index :ci_trigger_schedules, ["next_run_at"], name: "index_ci_trigger_schedules_on_next_run_at", using: :btree - add_index :ci_trigger_schedules, ["project_id"], name: "index_ci_trigger_schedules_on_project_id", using: :btree - add_concurrent_foreign_key :ci_trigger_schedules, :ci_triggers, column: :trigger_id, on_delete: :cascade - end - - def down - remove_foreign_key :ci_trigger_schedules, column: :trigger_id - drop_table :ci_trigger_schedules + add_index :ci_trigger_schedules, :next_run_at + add_index :ci_trigger_schedules, :project_id end end diff --git a/db/migrate/20170404163427_add_trigger_id_foreign_key.rb b/db/migrate/20170404163427_add_trigger_id_foreign_key.rb new file mode 100644 index 00000000000..6679a95ca11 --- /dev/null +++ b/db/migrate/20170404163427_add_trigger_id_foreign_key.rb @@ -0,0 +1,15 @@ +class AddTriggerIdForeignKey < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_foreign_key :ci_trigger_schedules, :ci_triggers, column: :trigger_id, on_delete: :cascade + end + + def down + remove_foreign_key :ci_trigger_schedules, column: :trigger_id + end +end diff --git a/db/schema.rb b/db/schema.rb index 7d9f969c2e1..a564a4b6a12 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -307,7 +307,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.datetime "created_at" t.datetime "updated_at" t.string "cron" - t.string "cron_time_zone" + t.string "cron_timezone" t.datetime "next_run_at" end diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 69dd8ad0fce..d1877b76598 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -4,13 +4,13 @@ module Gitlab VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze - def initialize(cron, cron_time_zone = 'UTC') + def initialize(cron, cron_timezone = 'UTC') @cron = cron - @cron_time_zone = cron_time_zone + @cron_timezone = cron_timezone end def next_time_from(time) - cron_line = try_parse_cron(@cron, @cron_time_zone) + cron_line = try_parse_cron(@cron, @cron_timezone) cron_line.next_time(time).in_time_zone(Time.zone) if cron_line.present? end @@ -18,14 +18,14 @@ module Gitlab try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present? end - def cron_time_zone_valid? - try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_time_zone).present? + def cron_timezone_valid? + try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_timezone).present? end private - def try_parse_cron(cron, cron_time_zone) - Rufus::Scheduler.parse("#{cron} #{cron_time_zone}") + def try_parse_cron(cron, cron_timezone) + Rufus::Scheduler.parse("#{cron} #{cron_timezone}") rescue # noop end diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 9c16d45b49a..49d2b29727f 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -2,7 +2,7 @@ FactoryGirl.define do factory :ci_trigger_schedule, class: Ci::TriggerSchedule do trigger factory: :ci_trigger_for_trigger_schedule cron '0 1 * * *' - cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE after(:build) do |trigger_schedule, evaluator| trigger_schedule.update!(project: trigger_schedule.trigger.project) @@ -16,17 +16,17 @@ FactoryGirl.define do trait :nightly do cron '0 1 * * *' - cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end trait :weekly do cron '0 1 * * 6' - cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end trait :monthly do cron '0 1 22 * *' - cron_time_zone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE + cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE end end end diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index 1cdd8c1d2e7..b07b84027fc 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -6,12 +6,12 @@ describe Gitlab::Ci::CronParser do end describe '#next_time_from' do - subject { described_class.new(cron, cron_time_zone).next_time_from(Time.now) } + subject { described_class.new(cron, cron_timezone).next_time_from(Time.now) } - context 'when cron and cron_time_zone are valid' do + context 'when cron and cron_timezone are valid' do context 'when specific time' do let(:cron) { '3 4 5 6 *' } - let(:cron_time_zone) { 'UTC' } + let(:cron_timezone) { 'UTC' } it_behaves_like "returns time in the future" @@ -25,7 +25,7 @@ describe Gitlab::Ci::CronParser do context 'when specific day of week' do let(:cron) { '* * * * 0' } - let(:cron_time_zone) { 'UTC' } + let(:cron_timezone) { 'UTC' } it_behaves_like "returns time in the future" @@ -36,7 +36,7 @@ describe Gitlab::Ci::CronParser do context 'when slash used' do let(:cron) { '*/10 */6 */10 */10 *' } - let(:cron_time_zone) { 'UTC' } + let(:cron_timezone) { 'UTC' } it_behaves_like "returns time in the future" @@ -50,7 +50,7 @@ describe Gitlab::Ci::CronParser do context 'when range used' do let(:cron) { '0,20,40 * 1-5 * *' } - let(:cron_time_zone) { 'UTC' } + let(:cron_timezone) { 'UTC' } it_behaves_like "returns time in the future" @@ -60,9 +60,9 @@ describe Gitlab::Ci::CronParser do end end - context 'when cron_time_zone is US/Pacific' do + context 'when cron_timezone is US/Pacific' do let(:cron) { '0 0 * * *' } - let(:cron_time_zone) { 'US/Pacific' } + let(:cron_timezone) { 'US/Pacific' } it_behaves_like "returns time in the future" @@ -72,9 +72,9 @@ describe Gitlab::Ci::CronParser do end end - context 'when cron and cron_time_zone are invalid' do + context 'when cron and cron_timezone are invalid' do let(:cron) { 'invalid_cron' } - let(:cron_time_zone) { 'invalid_cron_time_zone' } + let(:cron_timezone) { 'invalid_cron_timezone' } it 'returns nil' do is_expected.to be_nil @@ -98,17 +98,17 @@ describe Gitlab::Ci::CronParser do end end - describe '#cron_time_zone_valid?' do - subject { described_class.new(Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_CRON, cron_time_zone).cron_time_zone_valid? } + describe '#cron_timezone_valid?' do + subject { described_class.new(Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_CRON, cron_timezone).cron_timezone_valid? } context 'when cron is valid' do - let(:cron_time_zone) { 'Europe/Istanbul' } + let(:cron_timezone) { 'Europe/Istanbul' } it { is_expected.to eq(true) } end context 'when cron is invalid' do - let(:cron_time_zone) { 'Invalid-zone' } + let(:cron_timezone) { 'Invalid-zone' } it { is_expected.to eq(false) } end diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 42082ff3dee..0c43c5662e8 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -249,7 +249,7 @@ Ci::TriggerSchedule: - created_at - updated_at - cron -- cron_time_zone +- cron_timezone - next_run_at DeployKey: - id diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 9a4bf122bf0..fc01d702f65 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -13,7 +13,7 @@ describe Ci::TriggerSchedule, models: true do end it 'updates next_run_at' do - next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 75a98e42ac5..9d8fd9305ca 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -23,7 +23,7 @@ describe TriggerScheduleWorker do end it 'updates next_run_at' do - next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_time_zone).next_time_from(Time.now) + next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end -- cgit v1.2.1 From 1a244c64ebe7fae8b13ffc8e663118265eb76f5c Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:57:06 +0900 Subject: Remove next_run_at: nil from trigger_schedule_spec --- spec/models/ci/trigger_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index fc01d702f65..01db6b49fee 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -6,7 +6,7 @@ describe Ci::TriggerSchedule, models: true do it { is_expected.to respond_to :ref } describe '#schedule_next_run!' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly, next_run_at: nil) } + let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } before do trigger_schedule.schedule_next_run! -- cgit v1.2.1 From cc6ac794adfd66de4aa3ed28db7bf89cb6b46cb2 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:57:41 +0900 Subject: Define next_time as let in trigger_schedule_spec --- spec/models/ci/trigger_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 01db6b49fee..53c1349d9f8 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -7,13 +7,13 @@ describe Ci::TriggerSchedule, models: true do describe '#schedule_next_run!' do let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } + let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) } before do trigger_schedule.schedule_next_run! end it 'updates next_run_at' do - next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end -- cgit v1.2.1 From 3b58966b6c9f0cb183e8ec3652ec0cd7e20ce99e Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:58:42 +0900 Subject: Use parenthesis for respond_to :ref --- spec/models/ci/trigger_schedule_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 53c1349d9f8..22c0790688c 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' describe Ci::TriggerSchedule, models: true do it { is_expected.to belong_to(:project) } it { is_expected.to belong_to(:trigger) } - it { is_expected.to respond_to :ref } + it { is_expected.to respond_to(:ref) } describe '#schedule_next_run!' do let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } -- cgit v1.2.1 From 31bd3962d7e37f6143721ab0d09d94daf9dd0481 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 01:59:24 +0900 Subject: Add empty line in cron_parser.rb --- lib/gitlab/ci/cron_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index d1877b76598..59272dbeca5 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -31,4 +31,4 @@ module Gitlab end end end -end \ No newline at end of file +end -- cgit v1.2.1 From 7b09a484f1ecc422ce14602d9ee15e5a59100264 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 02:31:15 +0900 Subject: Fix unnecessary changes in schema.rb --- db/schema.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/db/schema.rb b/db/schema.rb index a564a4b6a12..58425d637c1 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -61,6 +61,7 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.boolean "shared_runners_enabled", default: true, null: false t.integer "max_artifacts_size", default: 100, null: false t.string "runners_registration_token" + t.integer "max_pages_size", default: 100, null: false t.boolean "require_two_factor_authentication", default: false t.integer "two_factor_grace_period", default: 48 t.boolean "metrics_enabled", default: false @@ -110,7 +111,6 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.string "plantuml_url" t.boolean "plantuml_enabled" t.integer "terminal_max_session_time", default: 0, null: false - t.integer "max_pages_size", default: 100, null: false t.string "default_artifacts_expire_in", default: "0", null: false t.integer "unique_ips_limit_per_user" t.integer "unique_ips_limit_time_window" @@ -704,8 +704,8 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.integer "visibility_level", default: 20, null: false t.boolean "request_access_enabled", default: false, null: false t.datetime "deleted_at" - t.boolean "lfs_enabled" t.text "description_html" + t.boolean "lfs_enabled" t.integer "parent_id" end @@ -1257,8 +1257,8 @@ ActiveRecord::Schema.define(version: 20170405080720) do t.datetime "otp_grace_period_started_at" t.boolean "ldap_email", default: false, null: false t.boolean "external", default: false - t.string "organization" t.string "incoming_email_token" + t.string "organization" t.boolean "authorized_projects_populated" t.boolean "ghost" t.boolean "notified_of_own_activity" -- cgit v1.2.1 From cb082ae291ba13120bfffeac5cd5a7c4532142ff Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 16:47:20 +0900 Subject: Improve instantiate recursion in cron_parser.rb --- lib/gitlab/ci/cron_parser.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/gitlab/ci/cron_parser.rb b/lib/gitlab/ci/cron_parser.rb index 59272dbeca5..a3cc350ef22 100644 --- a/lib/gitlab/ci/cron_parser.rb +++ b/lib/gitlab/ci/cron_parser.rb @@ -10,8 +10,8 @@ module Gitlab end def next_time_from(time) - cron_line = try_parse_cron(@cron, @cron_timezone) - cron_line.next_time(time).in_time_zone(Time.zone) if cron_line.present? + @cron_line ||= try_parse_cron(@cron, @cron_timezone) + @cron_line.next_time(time).in_time_zone(Time.zone) if @cron_line.present? end def cron_valid? -- cgit v1.2.1 From c2c3ee1bf9101b68b59685fa046759729eeadda1 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 17:48:28 +0900 Subject: Clean up trigger_schedule_worker_spec.rb --- spec/workers/trigger_schedule_worker_spec.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 9d8fd9305ca..3997369489f 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -9,6 +9,7 @@ describe TriggerScheduleWorker do context 'when there is a scheduled trigger within next_run_at' do let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly, :force_triggable) } + let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) } before do worker.perform @@ -23,7 +24,6 @@ describe TriggerScheduleWorker do end it 'updates next_run_at' do - next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) end end @@ -35,12 +35,12 @@ describe TriggerScheduleWorker do worker.perform end - it 'do not create a new pipeline' do + it 'does not create a new pipeline' do expect(Ci::Pipeline.count).to eq(0) end - it 'do not reschedule next_run_at' do - expect(Ci::TriggerSchedule.last.next_run_at).to eq(trigger_schedule.next_run_at) + it 'does not update next_run_at' do + expect(trigger_schedule.next_run_at).to eq(Ci::TriggerSchedule.last.next_run_at) end end end -- cgit v1.2.1 From 12a5380f5cb2ce8b652344f053b2333f6f5e80a9 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Wed, 5 Apr 2017 18:29:15 +0900 Subject: Implement a offset calculation on cron_parser_spec --- spec/lib/gitlab/ci/cron_parser_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/lib/gitlab/ci/cron_parser_spec.rb b/spec/lib/gitlab/ci/cron_parser_spec.rb index b07b84027fc..0864bc7258d 100644 --- a/spec/lib/gitlab/ci/cron_parser_spec.rb +++ b/spec/lib/gitlab/ci/cron_parser_spec.rb @@ -67,7 +67,7 @@ describe Gitlab::Ci::CronParser do it_behaves_like "returns time in the future" it 'converts time in server time zone' do - expect(subject.hour).to eq(7) + expect(subject.hour).to eq((Time.zone.now.in_time_zone(cron_timezone).utc_offset / 60 / 60).abs) end end end -- cgit v1.2.1 From 48e07eab5755770bff9d5ee1aca33526e4120637 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 6 Apr 2017 18:52:48 +0900 Subject: Improve trigger_schedule.rb --- app/models/ci/trigger_schedule.rb | 11 +++-- spec/factories/ci/trigger_schedules.rb | 6 --- spec/models/ci/trigger_schedule_spec.rb | 68 +++++++++++++++++++++++++--- spec/workers/trigger_schedule_worker_spec.rb | 23 +++++++++- 4 files changed, 91 insertions(+), 17 deletions(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index d18dbea284e..256e609f0d1 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -15,11 +15,16 @@ module Ci validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? } validates :ref, presence: { unless: :importing? } - after_create :schedule_next_run! + before_save :set_next_run_at + + def set_next_run_at + self.next_run_at = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now) + end def schedule_next_run! - next_time = Gitlab::Ci::CronParser.new(cron, cron_timezone).next_time_from(Time.now) - update!(next_run_at: next_time) if next_time.present? + save! # with set_next_run_at + rescue ActiveRecord::RecordInvalid => invalid + update_attribute(:next_run_at, nil) # update without validation end end end diff --git a/spec/factories/ci/trigger_schedules.rb b/spec/factories/ci/trigger_schedules.rb index 49d2b29727f..315bce16995 100644 --- a/spec/factories/ci/trigger_schedules.rb +++ b/spec/factories/ci/trigger_schedules.rb @@ -8,12 +8,6 @@ FactoryGirl.define do trigger_schedule.update!(project: trigger_schedule.trigger.project) end - trait :force_triggable do - after(:create) do |trigger_schedule, evaluator| - trigger_schedule.update!(next_run_at: trigger_schedule.next_run_at - 1.year) - end - end - trait :nightly do cron '0 1 * * *' cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE diff --git a/spec/models/ci/trigger_schedule_spec.rb b/spec/models/ci/trigger_schedule_spec.rb index 22c0790688c..75d21541cee 100644 --- a/spec/models/ci/trigger_schedule_spec.rb +++ b/spec/models/ci/trigger_schedule_spec.rb @@ -5,16 +5,72 @@ describe Ci::TriggerSchedule, models: true do it { is_expected.to belong_to(:trigger) } it { is_expected.to respond_to(:ref) } + describe '#set_next_run_at' do + context 'when creates new TriggerSchedule' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + @expected_next_run_at = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone) + .next_time_from(Time.now) + end + + it 'updates next_run_at automatically' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end + end + + context 'when updates cron of exsisted TriggerSchedule' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + new_cron = '0 0 1 1 *' + trigger_schedule.update!(cron: new_cron) # Subject + @expected_next_run_at = Gitlab::Ci::CronParser.new(new_cron, trigger_schedule.cron_timezone) + .next_time_from(Time.now) + end + + it 'updates next_run_at automatically' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end + end + end + describe '#schedule_next_run!' do - let(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } - let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) } + context 'when reschedules after 10 days from now' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + time_future = Time.now + 10.days + allow(Time).to receive(:now).and_return(time_future) + trigger_schedule.schedule_next_run! # Subject + @expected_next_run_at = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone) + .next_time_from(time_future) + end - before do - trigger_schedule.schedule_next_run! + it 'points to proper next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at) + end end - it 'updates next_run_at' do - expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) + context 'when cron is invalid' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + trigger_schedule.cron = 'Invalid-cron' + trigger_schedule.schedule_next_run! # Subject + end + + it 'sets nil to next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to be_nil + end + end + + context 'when cron_timezone is invalid' do + before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) + trigger_schedule.cron_timezone = 'Invalid-cron_timezone' + trigger_schedule.schedule_next_run! # Subject + end + + it 'sets nil to next_run_at' do + expect(Ci::TriggerSchedule.last.next_run_at).to be_nil + end end end end diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 3997369489f..c48f1406c34 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -8,10 +8,12 @@ describe TriggerScheduleWorker do end context 'when there is a scheduled trigger within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly, :force_triggable) } - let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(Time.now) } + let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } + let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(@time_future) } before do + @time_future = Time.now + 10.days + allow(Time).to receive(:now).and_return(@time_future) worker.perform end @@ -43,4 +45,21 @@ describe TriggerScheduleWorker do expect(trigger_schedule.next_run_at).to eq(Ci::TriggerSchedule.last.next_run_at) end end + + context 'when next_run_at is nil' do + let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } + + before do + trigger_schedule.update_attribute(:next_run_at, nil) + worker.perform + end + + it 'does not create a new pipeline' do + expect(Ci::Pipeline.count).to eq(0) + end + + it 'does not update next_run_at' do + expect(trigger_schedule.next_run_at).to eq(Ci::TriggerSchedule.last.next_run_at) + end + end end -- cgit v1.2.1 From 059ec792cbd670d2b716c111b6faa50e5782ae9f Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 6 Apr 2017 18:55:16 +0900 Subject: Use be_pending --- spec/workers/trigger_schedule_worker_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index c48f1406c34..0bb0bcc5c42 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -22,7 +22,7 @@ describe TriggerScheduleWorker do end it 'creates a new pipeline' do - expect(Ci::Pipeline.last.status).to eq('pending') + expect(Ci::Pipeline.last).to be_pending end it 'updates next_run_at' do -- cgit v1.2.1 From fff6afbad1180cb39fd0a8d9032de91397ba6471 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 6 Apr 2017 19:13:29 +0900 Subject: Use change direction in spec --- spec/workers/trigger_schedule_worker_spec.rb | 33 +++++++++++----------------- 1 file changed, 13 insertions(+), 20 deletions(-) diff --git a/spec/workers/trigger_schedule_worker_spec.rb b/spec/workers/trigger_schedule_worker_spec.rb index 0bb0bcc5c42..151e1c2f7b9 100644 --- a/spec/workers/trigger_schedule_worker_spec.rb +++ b/spec/workers/trigger_schedule_worker_spec.rb @@ -8,58 +8,51 @@ describe TriggerScheduleWorker do end context 'when there is a scheduled trigger within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } - let(:next_time) { Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(@time_future) } - before do - @time_future = Time.now + 10.days - allow(Time).to receive(:now).and_return(@time_future) - worker.perform + trigger_schedule = create(:ci_trigger_schedule, :nightly) + time_future = Time.now + 10.days + allow(Time).to receive(:now).and_return(time_future) + @next_time = Gitlab::Ci::CronParser.new(trigger_schedule.cron, trigger_schedule.cron_timezone).next_time_from(time_future) end it 'creates a new trigger request' do - expect(trigger_schedule.trigger.id).to eq(Ci::TriggerRequest.first.trigger_id) + expect { worker.perform }.to change { Ci::TriggerRequest.count }.by(1) end it 'creates a new pipeline' do + expect { worker.perform }.to change { Ci::Pipeline.count }.by(1) expect(Ci::Pipeline.last).to be_pending end it 'updates next_run_at' do - expect(Ci::TriggerSchedule.last.next_run_at).to eq(next_time) + expect { worker.perform }.to change { Ci::TriggerSchedule.last.next_run_at }.to(@next_time) end end context 'when there are no scheduled triggers within next_run_at' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } - - before do - worker.perform - end + before { create(:ci_trigger_schedule, :nightly) } it 'does not create a new pipeline' do - expect(Ci::Pipeline.count).to eq(0) + expect { worker.perform }.not_to change { Ci::Pipeline.count } end it 'does not update next_run_at' do - expect(trigger_schedule.next_run_at).to eq(Ci::TriggerSchedule.last.next_run_at) + expect { worker.perform }.not_to change { Ci::TriggerSchedule.last.next_run_at } end end context 'when next_run_at is nil' do - let!(:trigger_schedule) { create(:ci_trigger_schedule, :nightly) } - before do + trigger_schedule = create(:ci_trigger_schedule, :nightly) trigger_schedule.update_attribute(:next_run_at, nil) - worker.perform end it 'does not create a new pipeline' do - expect(Ci::Pipeline.count).to eq(0) + expect { worker.perform }.not_to change { Ci::Pipeline.count } end it 'does not update next_run_at' do - expect(trigger_schedule.next_run_at).to eq(Ci::TriggerSchedule.last.next_run_at) + expect { worker.perform }.not_to change { Ci::TriggerSchedule.last.next_run_at } end end end -- cgit v1.2.1 From 9441c01484e668892d06f387fc0f85fe2d4ff4b4 Mon Sep 17 00:00:00 2001 From: Shinya Maeda Date: Thu, 6 Apr 2017 21:01:30 +0900 Subject: Fix rubocop --- app/models/ci/trigger_schedule.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/trigger_schedule.rb b/app/models/ci/trigger_schedule.rb index 256e609f0d1..10ea381ee31 100644 --- a/app/models/ci/trigger_schedule.rb +++ b/app/models/ci/trigger_schedule.rb @@ -23,7 +23,7 @@ module Ci def schedule_next_run! save! # with set_next_run_at - rescue ActiveRecord::RecordInvalid => invalid + rescue ActiveRecord::RecordInvalid update_attribute(:next_run_at, nil) # update without validation end end -- cgit v1.2.1