Merge branch '2989-run-cicd-pipelines-on-a-schedule-idea1-basic-backend-implementation' into 'master'
Resolve "Run CI/CD pipelines on a schedule" - "Basic backend implementation" See merge request !10133
This commit is contained in:
commit
1815d44a7f
25 changed files with 478 additions and 1 deletions
3
Gemfile
3
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'
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
30
app/models/ci/trigger_schedule.rb
Normal file
30
app/models/ci/trigger_schedule.rb
Normal file
|
@ -0,0 +1,30 @@
|
|||
module Ci
|
||||
class TriggerSchedule < ActiveRecord::Base
|
||||
extend Ci::Model
|
||||
include Importable
|
||||
|
||||
acts_as_paranoid
|
||||
|
||||
belongs_to :project
|
||||
belongs_to :trigger
|
||||
|
||||
delegate :ref, to: :trigger
|
||||
|
||||
validates :trigger, presence: { unless: :importing? }
|
||||
validates :cron, cron: true, presence: { unless: :importing? }
|
||||
validates :cron_timezone, cron_timezone: true, presence: { unless: :importing? }
|
||||
validates :ref, presence: { unless: :importing? }
|
||||
|
||||
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!
|
||||
save! # with set_next_run_at
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
update_attribute(:next_run_at, nil) # update without validation
|
||||
end
|
||||
end
|
||||
end
|
9
app/validators/cron_timezone_validator.rb
Normal file
9
app/validators/cron_timezone_validator.rb
Normal file
|
@ -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
|
9
app/validators/cron_validator.rb
Normal file
9
app/validators/cron_validator.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
# CronValidator
|
||||
#
|
||||
# Custom validator for Cron.
|
||||
class CronValidator < 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_valid?
|
||||
end
|
||||
end
|
18
app/workers/trigger_schedule_worker.rb
Normal file
18
app/workers/trigger_schedule_worker.rb
Normal file
|
@ -0,0 +1,18 @@
|
|||
class TriggerScheduleWorker
|
||||
include Sidekiq::Worker
|
||||
include CronjobQueue
|
||||
|
||||
def perform
|
||||
Ci::TriggerSchedule.where("next_run_at < ?", Time.now).find_each do |trigger_schedule|
|
||||
begin
|
||||
Ci::CreateTriggerRequestService.new.execute(trigger_schedule.project,
|
||||
trigger_schedule.trigger,
|
||||
trigger_schedule.ref)
|
||||
rescue => e
|
||||
Rails.logger.error "#{trigger_schedule.id}: Failed to trigger_schedule job: #{e.message}"
|
||||
ensure
|
||||
trigger_schedule.schedule_next_run!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
title: Resolve "Run CI/CD pipelines on a schedule" - "Basic backend implementation"
|
||||
merge_request: 10133
|
||||
author: dosuken123
|
|
@ -180,6 +180,9 @@ production: &base
|
|||
# Flag stuck CI jobs as failed
|
||||
stuck_ci_jobs_worker:
|
||||
cron: "0 * * * *"
|
||||
# Execute scheduled triggers
|
||||
trigger_schedule_worker:
|
||||
cron: "0 */12 * * *"
|
||||
# Remove expired build artifacts
|
||||
expire_build_artifacts_worker:
|
||||
cron: "50 * * * *"
|
||||
|
|
|
@ -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['trigger_schedule_worker'] ||= Settingslogic.new({})
|
||||
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 * * * *'
|
||||
Settings.cron_jobs['expire_build_artifacts_worker']['job_class'] = 'ExpireBuildArtifactsWorker'
|
||||
|
|
9
db/migrate/20170329095325_add_ref_to_triggers.rb
Normal file
9
db/migrate/20170329095325_add_ref_to_triggers.rb
Normal file
|
@ -0,0 +1,9 @@
|
|||
class AddRefToTriggers < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
def change
|
||||
add_column :ci_triggers, :ref, :string
|
||||
end
|
||||
end
|
21
db/migrate/20170329095907_create_ci_trigger_schedules.rb
Normal file
21
db/migrate/20170329095907_create_ci_trigger_schedules.rb
Normal file
|
@ -0,0 +1,21 @@
|
|||
class CreateCiTriggerSchedules < ActiveRecord::Migration
|
||||
include Gitlab::Database::MigrationHelpers
|
||||
|
||||
DOWNTIME = false
|
||||
|
||||
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_timezone"
|
||||
t.datetime "next_run_at"
|
||||
end
|
||||
|
||||
add_index :ci_trigger_schedules, :next_run_at
|
||||
add_index :ci_trigger_schedules, :project_id
|
||||
end
|
||||
end
|
15
db/migrate/20170404163427_add_trigger_id_foreign_key.rb
Normal file
15
db/migrate/20170404163427_add_trigger_id_foreign_key.rb
Normal file
|
@ -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
|
16
db/schema.rb
16
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_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
|
||||
|
||||
create_table "ci_triggers", force: :cascade do |t|
|
||||
t.string "token"
|
||||
t.datetime "deleted_at"
|
||||
|
@ -308,6 +322,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
|
||||
|
@ -1313,6 +1328,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", name: "fk_90a406cc94", on_delete: :cascade
|
||||
add_foreign_key "ci_triggers", "users", column: "owner_id", name: "fk_e8e10d1964", on_delete: :cascade
|
||||
add_foreign_key "container_repositories", "projects"
|
||||
add_foreign_key "issue_metrics", "issues", on_delete: :cascade
|
||||
|
|
34
lib/gitlab/ci/cron_parser.rb
Normal file
34
lib/gitlab/ci/cron_parser.rb
Normal file
|
@ -0,0 +1,34 @@
|
|||
module Gitlab
|
||||
module Ci
|
||||
class CronParser
|
||||
VALID_SYNTAX_SAMPLE_TIME_ZONE = 'UTC'.freeze
|
||||
VALID_SYNTAX_SAMPLE_CRON = '* * * * *'.freeze
|
||||
|
||||
def initialize(cron, cron_timezone = 'UTC')
|
||||
@cron = cron
|
||||
@cron_timezone = cron_timezone
|
||||
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?
|
||||
end
|
||||
|
||||
def cron_valid?
|
||||
try_parse_cron(@cron, VALID_SYNTAX_SAMPLE_TIME_ZONE).present?
|
||||
end
|
||||
|
||||
def cron_timezone_valid?
|
||||
try_parse_cron(VALID_SYNTAX_SAMPLE_CRON, @cron_timezone).present?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def try_parse_cron(cron, cron_timezone)
|
||||
Rufus::Scheduler.parse("#{cron} #{cron_timezone}")
|
||||
rescue
|
||||
# noop
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -39,7 +39,8 @@ project_tree:
|
|||
- :author
|
||||
- :events
|
||||
- :statuses
|
||||
- :triggers
|
||||
- triggers:
|
||||
- :trigger_schedule
|
||||
- :deploy_keys
|
||||
- :services
|
||||
- :hooks
|
||||
|
|
|
@ -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',
|
||||
|
|
26
spec/factories/ci/trigger_schedules.rb
Normal file
26
spec/factories/ci/trigger_schedules.rb
Normal file
|
@ -0,0 +1,26 @@
|
|||
FactoryGirl.define do
|
||||
factory :ci_trigger_schedule, class: Ci::TriggerSchedule do
|
||||
trigger factory: :ci_trigger_for_trigger_schedule
|
||||
cron '0 1 * * *'
|
||||
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
|
||||
|
||||
after(:build) do |trigger_schedule, evaluator|
|
||||
trigger_schedule.update!(project: trigger_schedule.trigger.project)
|
||||
end
|
||||
|
||||
trait :nightly do
|
||||
cron '0 1 * * *'
|
||||
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
|
||||
end
|
||||
|
||||
trait :weekly do
|
||||
cron '0 1 * * 6'
|
||||
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
|
||||
end
|
||||
|
||||
trait :monthly do
|
||||
cron '0 1 22 * *'
|
||||
cron_timezone Gitlab::Ci::CronParser::VALID_SYNTAX_SAMPLE_TIME_ZONE
|
||||
end
|
||||
end
|
||||
end
|
|
@ -2,6 +2,13 @@ FactoryGirl.define do
|
|||
factory :ci_trigger_without_token, class: Ci::Trigger do
|
||||
factory :ci_trigger do
|
||||
token 'token'
|
||||
|
||||
factory :ci_trigger_for_trigger_schedule do
|
||||
token { SecureRandom.hex(15) }
|
||||
owner factory: :user
|
||||
project factory: :project
|
||||
ref 'master'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
116
spec/lib/gitlab/ci/cron_parser_spec.rb
Normal file
116
spec/lib/gitlab/ci/cron_parser_spec.rb
Normal file
|
@ -0,0 +1,116 @@
|
|||
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_timezone).next_time_from(Time.now) }
|
||||
|
||||
context 'when cron and cron_timezone are valid' do
|
||||
context 'when specific time' do
|
||||
let(:cron) { '3 4 5 6 *' }
|
||||
let(:cron_timezone) { '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_timezone) { '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_timezone) { '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_timezone) { '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_timezone is US/Pacific' do
|
||||
let(:cron) { '0 0 * * *' }
|
||||
let(:cron_timezone) { 'US/Pacific' }
|
||||
|
||||
it_behaves_like "returns time in the future"
|
||||
|
||||
it 'converts time in server time zone' do
|
||||
expect(subject.hour).to eq((Time.zone.now.in_time_zone(cron_timezone).utc_offset / 60 / 60).abs)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'when cron and cron_timezone are invalid' do
|
||||
let(:cron) { 'invalid_cron' }
|
||||
let(:cron_timezone) { 'invalid_cron_timezone' }
|
||||
|
||||
it 'returns nil' do
|
||||
is_expected.to be_nil
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
context 'when cron is invalid' do
|
||||
let(:cron) { '*********' }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
|
||||
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_timezone) { 'Europe/Istanbul' }
|
||||
|
||||
it { is_expected.to eq(true) }
|
||||
end
|
||||
|
||||
context 'when cron is invalid' do
|
||||
let(:cron_timezone) { 'Invalid-zone' }
|
||||
|
||||
it { is_expected.to eq(false) }
|
||||
end
|
||||
end
|
||||
end
|
|
@ -99,6 +99,9 @@ triggers:
|
|||
- project
|
||||
- trigger_requests
|
||||
- owner
|
||||
- trigger_schedule
|
||||
trigger_schedule:
|
||||
- trigger
|
||||
deploy_keys:
|
||||
- user
|
||||
- deploy_keys_projects
|
||||
|
@ -197,6 +200,7 @@ project:
|
|||
- runners
|
||||
- variables
|
||||
- triggers
|
||||
- trigger_schedules
|
||||
- environments
|
||||
- deployments
|
||||
- project_feature
|
||||
|
|
|
@ -240,6 +240,17 @@ Ci::Trigger:
|
|||
- updated_at
|
||||
- owner_id
|
||||
- description
|
||||
- ref
|
||||
Ci::TriggerSchedule:
|
||||
- id
|
||||
- project_id
|
||||
- trigger_id
|
||||
- deleted_at
|
||||
- created_at
|
||||
- updated_at
|
||||
- cron
|
||||
- cron_timezone
|
||||
- next_run_at
|
||||
DeployKey:
|
||||
- id
|
||||
- user_id
|
||||
|
|
76
spec/models/ci/trigger_schedule_spec.rb
Normal file
76
spec/models/ci/trigger_schedule_spec.rb
Normal file
|
@ -0,0 +1,76 @@
|
|||
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) }
|
||||
|
||||
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
|
||||
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
|
||||
|
||||
it 'points to proper next_run_at' do
|
||||
expect(Ci::TriggerSchedule.last.next_run_at).to eq(@expected_next_run_at)
|
||||
end
|
||||
end
|
||||
|
||||
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
|
|
@ -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
|
||||
|
|
58
spec/workers/trigger_schedule_worker_spec.rb
Normal file
58
spec/workers/trigger_schedule_worker_spec.rb
Normal file
|
@ -0,0 +1,58 @@
|
|||
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
|
||||
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 { 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 { 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
|
||||
before { create(:ci_trigger_schedule, :nightly) }
|
||||
|
||||
it 'does not create a new pipeline' do
|
||||
expect { worker.perform }.not_to change { Ci::Pipeline.count }
|
||||
end
|
||||
|
||||
it 'does not update next_run_at' do
|
||||
expect { worker.perform }.not_to change { Ci::TriggerSchedule.last.next_run_at }
|
||||
end
|
||||
end
|
||||
|
||||
context 'when next_run_at is nil' do
|
||||
before do
|
||||
trigger_schedule = create(:ci_trigger_schedule, :nightly)
|
||||
trigger_schedule.update_attribute(:next_run_at, nil)
|
||||
end
|
||||
|
||||
it 'does not create a new pipeline' do
|
||||
expect { worker.perform }.not_to change { Ci::Pipeline.count }
|
||||
end
|
||||
|
||||
it 'does not update next_run_at' do
|
||||
expect { worker.perform }.not_to change { Ci::TriggerSchedule.last.next_run_at }
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue