From fb0dec4e00f1efd637692982ba031f479103cc35 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 19 Feb 2018 16:48:01 +0100 Subject: [PATCH 01/48] Add new column to ci_runners table --- ...219153455_add_job_upper_timeout_to_ci_runners.rb | 13 +++++++++++++ db/schema.rb | 1 + 2 files changed, 14 insertions(+) create mode 100644 db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb diff --git a/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb new file mode 100644 index 00000000000..418ec7ac1d9 --- /dev/null +++ b/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb @@ -0,0 +1,13 @@ +class AddJobUpperTimeoutToCiRunners < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column :ci_runners, :job_upper_timeout, :integer + end + + def down + remove_column :ci_runners, :job_upper_timeout + end +end diff --git a/db/schema.rb b/db/schema.rb index 3bf42080870..413f42df40b 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -459,6 +459,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.boolean "locked", default: false, null: false t.integer "access_level", default: 0, null: false t.string "ip_address" + t.integer "job_upper_timeout" end add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree From 7b82f4bab1661d7f7e7cb044730c977329275240 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 19 Feb 2018 17:21:00 +0100 Subject: [PATCH 02/48] Add support for job_upper_timeout in API --- doc/api/runners.md | 7 +++++-- lib/api/entities.rb | 1 + lib/api/runner.rb | 3 ++- lib/api/runners.rb | 1 + spec/requests/api/runner_spec.rb | 10 ++++++++++ spec/requests/api/runners_spec.rb | 5 ++++- 6 files changed, 23 insertions(+), 4 deletions(-) diff --git a/doc/api/runners.md b/doc/api/runners.md index 7495c6cdedb..4c6fc029a66 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -153,7 +153,8 @@ Example response: "mysql" ], "version": null, - "access_level": "ref_protected" + "access_level": "ref_protected", + "job_upper_timeout": 3600 } ``` @@ -174,6 +175,7 @@ PUT /runners/:id | `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs | | `locked` | boolean | no | Flag indicating the runner is locked | | `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` | +| `job_upper_timeout` | integer | no | Upper timeout set when this Runner will handle the job | ``` curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" @@ -211,7 +213,8 @@ Example response: "tag2" ], "version": null, - "access_level": "ref_protected" + "access_level": "ref_protected", + "job_upper_timeout": null } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 16147ee90c9..becd4f22a03 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -951,6 +951,7 @@ module API expose :tag_list expose :run_untagged expose :locked + expose :job_upper_timeout expose :access_level expose :version, :revision, :platform, :architecture expose :contacted_at diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 8da97a97754..14dd3b81dd0 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,9 +14,10 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) + optional :job_upper_timeout, type: Integer, desc: 'Upper timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list]) + attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :job_upper_timeout]) .merge(get_runner_details_from_request) runner = diff --git a/lib/api/runners.rb b/lib/api/runners.rb index 996457c5dfe..bc91b7cfd54 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -57,6 +57,7 @@ module API optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :access_level, type: String, values: Ci::Runner.access_levels.keys, desc: 'The access_level of the runner' + optional :job_upper_timeout, type: Integer, desc: 'Upper timeout set when this Runner will handle the job' at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level end put ':id' do diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index f3dd121faa9..8c8f4bb7018 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -109,6 +109,16 @@ describe API::Runner do end end + context 'when job upper timeout is specified' do + it 'creates runner' do + post api('/runners'), token: registration_token, + job_upper_timeout: 7200 + + expect(response).to have_gitlab_http_status 201 + expect(Ci::Runner.first.job_upper_timeout).to eq(7200) + end + end + %w(name version revision platform architecture).each do |param| context "when info parameter '#{param}' info is present" do let(:value) { "#{param}_value" } diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index ec5cad4f4fd..0444880a300 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -123,6 +123,7 @@ describe API::Runners do expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(shared_runner.description) + expect(json_response['job_upper_timeout']).to be_nil end end @@ -192,7 +193,8 @@ describe API::Runners do tag_list: ['ruby2.1', 'pgsql', 'mysql'], run_untagged: 'false', locked: 'true', - access_level: 'ref_protected') + access_level: 'ref_protected', + job_upper_timeout: 1234 ) shared_runner.reload expect(response).to have_gitlab_http_status(200) @@ -204,6 +206,7 @@ describe API::Runners do expect(shared_runner.ref_protected?).to be_truthy expect(shared_runner.ensure_runner_queue_value) .not_to eq(runner_queue_value) + expect(shared_runner.job_upper_timeout).to eq(1234) end end From 834f473821b816515504abb7c6bc91ab2dee4450 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 19 Feb 2018 18:23:12 +0100 Subject: [PATCH 03/48] Override project-defined timeout with runner-defined one --- app/models/ci/build.rb | 7 +++++++ app/models/ci/runner.rb | 4 ++++ spec/models/ci/build_spec.rb | 24 +++++++++++++++++++++- spec/models/ci/runner_spec.rb | 18 ++++++++++++++++ spec/requests/api/runner_spec.rb | 35 ++++++++++++++++++++++++++++++++ 5 files changed, 87 insertions(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 08bb5915d10..e15c4bc6ceb 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -232,9 +232,16 @@ module Ci end def timeout + return runner.job_upper_timeout if should_use_runner_timeout + project.build_timeout end + def should_use_runner_timeout + runner && runner.defines_job_upper_timeout? && runner.job_upper_timeout < project.build_timeout + end + private :should_use_runner_timeout + def triggered_by?(current_user) user == current_user end diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 7173f88f1c7..b28e892dcb6 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -167,6 +167,10 @@ module Ci end end + def defines_job_upper_timeout? + job_upper_timeout && job_upper_timeout > 0 + end + private def cleanup_runner_queue diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 7d935cf8d76..d13421a107f 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1272,8 +1272,30 @@ describe Ci::Build do describe 'project settings' do describe '#timeout' do + set(:project2) { create(:project, :repository, group: group, build_timeout: 1000) } + set(:pipeline2) { create(:ci_pipeline, project: project2, sha: project2.commit.id, ref: project2.default_branch, status: 'success') } + let(:build) { create(:ci_build, :manual, pipeline: pipeline2) } + it 'returns project timeout configuration' do - expect(build.timeout).to eq(project.build_timeout) + expect(build.timeout).to eq(project2.build_timeout) + end + + context 'when runner sets timeout to bigger value' do + let(:runner2) { create(:ci_runner, job_upper_timeout: 2000) } + let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } + + it 'returns project timeout configuration' do + expect(build.timeout).to eq(project2.build_timeout) + end + end + + context 'when runner sets timeout to smaller value' do + let(:runner2) { create(:ci_runner, job_upper_timeout: 500) } + let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } + + it 'returns project timeout configuration' do + expect(build.timeout).to eq(runner2.job_upper_timeout) + end end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index ab170e6351c..80d7cd92fdb 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -556,6 +556,24 @@ describe Ci::Runner do end end + describe '#defines_job_upper_timeout?' do + context 'when job upper timeout is specified' do + subject { create(:ci_runner, job_upper_timeout: 1234) } + + it 'should return true' do + expect(subject.defines_job_upper_timeout?).to be_truthy + end + end + + context 'when job upper timeout is not specified' do + subject { create(:ci_runner) } + + it 'should return false' do + expect(subject.defines_job_upper_timeout?).to be_falsey + end + end + end + describe '.search' do let(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') } diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 8c8f4bb7018..3eb0e88d095 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -658,6 +658,41 @@ describe API::Runner do end end end + + describe 'timeout support' do + context 'when project specifies job timeout' do + let(:project) { create(:project, shared_runners_enabled: false, build_timeout: 1234) } + + it 'contains info about timeout taken from project' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['runner_info']).to include({ 'timeout' => 1234 }) + end + + context 'when runner specifies lower timeout' do + let(:runner) { create(:ci_runner, job_upper_timeout: 1000) } + + it 'contains info about timeout overridden by runner' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['runner_info']).to include({ 'timeout' => 1000 }) + end + end + + context 'when runner specifies bigger timeout' do + let(:runner) { create(:ci_runner, job_upper_timeout: 2000) } + + it 'contains info about timeout not overridden by runner' do + request_job + + expect(response).to have_gitlab_http_status(201) + expect(json_response['runner_info']).to include({ 'timeout' => 1234 }) + end + end + end + end end def request_job(token = runner.token, **params) From b6d26f979c29ce594a8645b81ec0fd1028e79b64 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 19 Feb 2018 18:41:50 +0100 Subject: [PATCH 04/48] Add UI support for per-runner job timeout --- app/models/ci/runner.rb | 2 +- app/views/admin/runners/_runner.html.haml | 5 +++++ app/views/admin/runners/index.html.haml | 1 + app/views/projects/runners/_form.html.haml | 6 ++++++ app/views/projects/runners/_runner.html.haml | 2 ++ app/views/projects/runners/show.html.haml | 3 +++ 6 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index b28e892dcb6..be15fb0f729 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -8,7 +8,7 @@ module Ci ONLINE_CONTACT_TIMEOUT = 1.hour UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes AVAILABLE_SCOPES = %w[specific shared active paused online].freeze - FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level].freeze + FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level job_upper_timeout].freeze has_many :builds has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index e1cee584929..14aee600809 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -18,6 +18,11 @@ = runner.version %td = runner.ip_address + %td + - if runner.defines_job_upper_timeout? + = runner.job_upper_timeout + - else + n/a %td - if runner.shared? n/a diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 9f13dbbbd82..95fd7fe7ebe 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -61,6 +61,7 @@ %th Description %th Version %th IP Address + %th Timeout %th Projects %th Jobs %th Tags diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 49c90869146..4fb4323dab4 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -39,6 +39,12 @@ Description .col-sm-10 = f.text_field :description, class: 'form-control' + .form-group + = label_tag :job_upper_timeout, class: 'control-label' do + Job upper timeout + .col-sm-10 + = f.text_field :job_upper_timeout, class: 'form-control' + .help-block This timeout will take precedence when lower than Project-defined timeout .form-group = label_tag :tag_list, class: 'control-label' do Tags diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 6376496ee1a..e3107fecfad 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -36,6 +36,8 @@ - if runner.description.present? %p.runner-description = runner.description + - if runner.defines_job_upper_timeout? + %p Job upper timeout: #{runner.job_upper_timeout} - if runner.tag_list.present? %p - runner.tag_list.sort.each do |tag| diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index 4e57f5f844d..e0223eeb729 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -55,6 +55,9 @@ %tr %td Description %td= @runner.description + %tr + %td Job upper timeout + %td= @runner.job_upper_timeout %tr %td Last contact %td From 3c23cefae0a323887503101d3c227a914dd8f7c4 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 19 Feb 2018 19:18:06 +0100 Subject: [PATCH 05/48] Add CHANGELOG entry --- changelogs/unreleased/add-per-runner-job-timeout.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 changelogs/unreleased/add-per-runner-job-timeout.yml diff --git a/changelogs/unreleased/add-per-runner-job-timeout.yml b/changelogs/unreleased/add-per-runner-job-timeout.yml new file mode 100644 index 00000000000..336b4d15ddf --- /dev/null +++ b/changelogs/unreleased/add-per-runner-job-timeout.yml @@ -0,0 +1,5 @@ +--- +title: Add per-runner configured job timeout +merge_request: 17221 +author: +type: added From a4ea9a93db98461479dcb8e1d7b8425a77018f1e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Feb 2018 01:09:59 +0100 Subject: [PATCH 06/48] Add ChroniDurationAttribute concern --- app/models/ci/runner.rb | 3 ++ .../concerns/chronic_duration_attribute.rb | 25 +++++++++++++++++ .../chronic_duration_attribute_spec.rb | 28 +++++++++++++++++++ 3 files changed, 56 insertions(+) create mode 100644 app/models/concerns/chronic_duration_attribute.rb create mode 100644 spec/models/concerns/chronic_duration_attribute_spec.rb diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index be15fb0f729..cf91ed3c9dc 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -3,6 +3,7 @@ module Ci extend Gitlab::Ci::Model include Gitlab::SQL::Pattern include RedisCacheable + include ChronicDurationAttribute RUNNER_QUEUE_EXPIRY_TIME = 60.minutes ONLINE_CONTACT_TIMEOUT = 1.hour @@ -51,6 +52,8 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address + chronic_duration_attribute :job_upper_timeout_user_readable, :job_upper_timeout + # Searches for runners matching the given query. # # This method uses ILIKE on PostgreSQL and LIKE on MySQL. diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb new file mode 100644 index 00000000000..2bf33174640 --- /dev/null +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -0,0 +1,25 @@ +module ChronicDurationAttribute + extend ActiveSupport::Concern + + class_methods do + def chronic_duration_attribute(virtual_attribute, source_attribute) + chronic_duration_attribute_reader(virtual_attribute, source_attribute) + chronic_duration_attribute_writer(virtual_attribute, source_attribute) + end + + def chronic_duration_attribute_reader(virtual_attribute, source_attribute) + define_method(virtual_attribute) do + value = self.send(source_attribute) # rubocop:disable GitlabSecurity/PublicSend + ChronicDuration.output(value, format: :short) unless value.nil? + end + end + + def chronic_duration_attribute_writer(virtual_attribute, source_attribute) + define_method("#{virtual_attribute}=") do |value| + new_value = ChronicDuration.parse(value).to_i + self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend + new_value + end + end + end +end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb new file mode 100644 index 00000000000..1a352537aaf --- /dev/null +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -0,0 +1,28 @@ +require 'spec_helper' + +shared_examples 'ChronicDurationAttribute' do + describe 'dynamically defined methods' do + it { expect(subject.class).to be_public_method_defined(virtual_field) } + it { expect(subject.class).to be_public_method_defined("#{virtual_field}=") } + + it 'parses chronic duration input' do + subject.send("#{virtual_field}=", "10m") + + expect(subject.send(source_field)).to eq(600) + end + + it 'outputs chronic duration formated value' do + subject.send("#{source_field}=", 120) + + expect(subject.send(virtual_field)).to eq('2m') + end + end +end + +describe 'ChronicDurationAttribute' do + let(:source_field) { :maximum_job_timeout } + let(:virtual_field) { :maximum_job_timeout_user_readable } + subject { Ci::Runner.new } + + it_behaves_like 'ChronicDurationAttribute' +end From d633bc8134fe472137fb668c1eb78de45dc9bb57 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Feb 2018 01:21:59 +0100 Subject: [PATCH 07/48] Rename job_upper_timeout to maximum_job_timeout --- app/models/ci/build.rb | 4 ++-- app/models/ci/runner.rb | 8 ++++---- app/views/admin/runners/_runner.html.haml | 4 ++-- app/views/admin/runners/index.html.haml | 2 +- app/views/projects/runners/_form.html.haml | 6 +++--- app/views/projects/runners/_runner.html.haml | 4 ++-- app/views/projects/runners/show.html.haml | 4 ++-- ...219153455_add_job_upper_timeout_to_ci_runners.rb | 13 ------------- ...9153455_add_maximum_job_timeout_to_ci_runners.rb | 13 +++++++++++++ db/schema.rb | 2 +- doc/api/runners.md | 6 +++--- lib/api/entities.rb | 2 +- lib/api/runner.rb | 4 ++-- lib/api/runners.rb | 2 +- spec/models/ci/build_spec.rb | 6 +++--- spec/models/ci/runner_spec.rb | 12 ++++++------ spec/requests/api/runner_spec.rb | 10 +++++----- spec/requests/api/runners_spec.rb | 6 +++--- 18 files changed, 54 insertions(+), 54 deletions(-) delete mode 100644 db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb create mode 100644 db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index e15c4bc6ceb..61a0ef08dde 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -232,13 +232,13 @@ module Ci end def timeout - return runner.job_upper_timeout if should_use_runner_timeout + return runner.maximum_job_timeout if should_use_runner_timeout project.build_timeout end def should_use_runner_timeout - runner && runner.defines_job_upper_timeout? && runner.job_upper_timeout < project.build_timeout + !runner.nil? && runner.defines_maximum_job_timeout? && runner.maximum_job_timeout < project.build_timeout end private :should_use_runner_timeout diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index cf91ed3c9dc..3d83e00ccfe 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -9,7 +9,7 @@ module Ci ONLINE_CONTACT_TIMEOUT = 1.hour UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes AVAILABLE_SCOPES = %w[specific shared active paused online].freeze - FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level job_upper_timeout].freeze + FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_job_timeout_user_readable].freeze has_many :builds has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -52,7 +52,7 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address - chronic_duration_attribute :job_upper_timeout_user_readable, :job_upper_timeout + chronic_duration_attribute :maximum_job_timeout_user_readable, :maximum_job_timeout # Searches for runners matching the given query. # @@ -170,8 +170,8 @@ module Ci end end - def defines_job_upper_timeout? - job_upper_timeout && job_upper_timeout > 0 + def defines_maximum_job_timeout? + !maximum_job_timeout.nil? && maximum_job_timeout > 0 end private diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 14aee600809..5f0fb5079d9 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -19,8 +19,8 @@ %td = runner.ip_address %td - - if runner.defines_job_upper_timeout? - = runner.job_upper_timeout + - if runner.defines_maximum_job_timeout? + = runner.maximum_job_timeout_user_readable - else n/a %td diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index 95fd7fe7ebe..a0c7d8f5fa9 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -61,7 +61,7 @@ %th Description %th Version %th IP Address - %th Timeout + %th Maximum timeout %th Projects %th Jobs %th Tags diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 4fb4323dab4..8fb8e6e0ebf 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -40,10 +40,10 @@ .col-sm-10 = f.text_field :description, class: 'form-control' .form-group - = label_tag :job_upper_timeout, class: 'control-label' do - Job upper timeout + = label_tag :maximum_job_timeout_user_readable, class: 'control-label' do + Maximum job timeout .col-sm-10 - = f.text_field :job_upper_timeout, class: 'form-control' + = f.text_field :maximum_job_timeout_user_readable, class: 'form-control' .help-block This timeout will take precedence when lower than Project-defined timeout .form-group = label_tag :tag_list, class: 'control-label' do diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index e3107fecfad..f7c41fe44c0 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -36,8 +36,8 @@ - if runner.description.present? %p.runner-description = runner.description - - if runner.defines_job_upper_timeout? - %p Job upper timeout: #{runner.job_upper_timeout} + - if runner.defines_maximum_job_timeout? + %p Maximum job timeout: #{runner.maximum_job_timeout_user_readable} - if runner.tag_list.present? %p - runner.tag_list.sort.each do |tag| diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index e0223eeb729..0d39236680c 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -56,8 +56,8 @@ %td Description %td= @runner.description %tr - %td Job upper timeout - %td= @runner.job_upper_timeout + %td Maximum job timeout + %td= @runner.maximum_job_timeout_user_readable %tr %td Last contact %td diff --git a/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb deleted file mode 100644 index 418ec7ac1d9..00000000000 --- a/db/migrate/20180219153455_add_job_upper_timeout_to_ci_runners.rb +++ /dev/null @@ -1,13 +0,0 @@ -class AddJobUpperTimeoutToCiRunners < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - def up - add_column :ci_runners, :job_upper_timeout, :integer - end - - def down - remove_column :ci_runners, :job_upper_timeout - end -end diff --git a/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb new file mode 100644 index 00000000000..5656970ede9 --- /dev/null +++ b/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb @@ -0,0 +1,13 @@ +class AddMaximumJobTimeoutToCiRunners < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column :ci_runners, :maximum_job_timeout, :integer + end + + def down + remove_column :ci_runners, :maximum_job_timeout + end +end diff --git a/db/schema.rb b/db/schema.rb index 413f42df40b..806e829dcbd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -459,7 +459,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.boolean "locked", default: false, null: false t.integer "access_level", default: 0, null: false t.string "ip_address" - t.integer "job_upper_timeout" + t.integer "maximum_job_timeout" end add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree diff --git a/doc/api/runners.md b/doc/api/runners.md index 4c6fc029a66..348fd499af2 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -154,7 +154,7 @@ Example response: ], "version": null, "access_level": "ref_protected", - "job_upper_timeout": 3600 + "maximum_job_timeout": 3600 } ``` @@ -175,7 +175,7 @@ PUT /runners/:id | `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs | | `locked` | boolean | no | Flag indicating the runner is locked | | `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` | -| `job_upper_timeout` | integer | no | Upper timeout set when this Runner will handle the job | +| `maximum_job_timeout` | integer | no | Maximum timeout set when this Runner will handle the job | ``` curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" @@ -214,7 +214,7 @@ Example response: ], "version": null, "access_level": "ref_protected", - "job_upper_timeout": null + "maximum_job_timeout": null } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index becd4f22a03..bb18fa00dc6 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -951,7 +951,7 @@ module API expose :tag_list expose :run_untagged expose :locked - expose :job_upper_timeout + expose :maximum_job_timeout expose :access_level expose :version, :revision, :platform, :architecture expose :contacted_at diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 14dd3b81dd0..3a26155be6d 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,10 +14,10 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :job_upper_timeout, type: Integer, desc: 'Upper timeout set when this Runner will handle the job' + optional :maximum_job_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :job_upper_timeout]) + attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_job_timeout]) .merge(get_runner_details_from_request) runner = diff --git a/lib/api/runners.rb b/lib/api/runners.rb index bc91b7cfd54..b3037235353 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -57,7 +57,7 @@ module API optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :access_level, type: String, values: Ci::Runner.access_levels.keys, desc: 'The access_level of the runner' - optional :job_upper_timeout, type: Integer, desc: 'Upper timeout set when this Runner will handle the job' + optional :maximum_job_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level end put ':id' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index d13421a107f..115106548f0 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1281,7 +1281,7 @@ describe Ci::Build do end context 'when runner sets timeout to bigger value' do - let(:runner2) { create(:ci_runner, job_upper_timeout: 2000) } + let(:runner2) { create(:ci_runner, maximum_job_timeout: 2000) } let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do @@ -1290,11 +1290,11 @@ describe Ci::Build do end context 'when runner sets timeout to smaller value' do - let(:runner2) { create(:ci_runner, job_upper_timeout: 500) } + let(:runner2) { create(:ci_runner, maximum_job_timeout: 500) } let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do - expect(build.timeout).to eq(runner2.job_upper_timeout) + expect(build.timeout).to eq(runner2.maximum_job_timeout) end end end diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 80d7cd92fdb..5b5fa7fac01 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -556,20 +556,20 @@ describe Ci::Runner do end end - describe '#defines_job_upper_timeout?' do - context 'when job upper timeout is specified' do - subject { create(:ci_runner, job_upper_timeout: 1234) } + describe '#defines_maximum_job_timeout?' do + context 'when maximum job timeout is specified' do + subject { create(:ci_runner, maximum_job_timeout: 1234) } it 'should return true' do - expect(subject.defines_job_upper_timeout?).to be_truthy + expect(subject.defines_maximum_job_timeout?).to be_truthy end end - context 'when job upper timeout is not specified' do + context 'when maximum job timeout is not specified' do subject { create(:ci_runner) } it 'should return false' do - expect(subject.defines_job_upper_timeout?).to be_falsey + expect(subject.defines_maximum_job_timeout?).to be_falsey end end end diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 3eb0e88d095..a6a4f510406 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -109,13 +109,13 @@ describe API::Runner do end end - context 'when job upper timeout is specified' do + context 'when maximum job timeout is specified' do it 'creates runner' do post api('/runners'), token: registration_token, - job_upper_timeout: 7200 + maximum_job_timeout: 7200 expect(response).to have_gitlab_http_status 201 - expect(Ci::Runner.first.job_upper_timeout).to eq(7200) + expect(Ci::Runner.first.maximum_job_timeout).to eq(7200) end end @@ -671,7 +671,7 @@ describe API::Runner do end context 'when runner specifies lower timeout' do - let(:runner) { create(:ci_runner, job_upper_timeout: 1000) } + let(:runner) { create(:ci_runner, maximum_job_timeout: 1000) } it 'contains info about timeout overridden by runner' do request_job @@ -682,7 +682,7 @@ describe API::Runner do end context 'when runner specifies bigger timeout' do - let(:runner) { create(:ci_runner, job_upper_timeout: 2000) } + let(:runner) { create(:ci_runner, maximum_job_timeout: 2000) } it 'contains info about timeout not overridden by runner' do request_job diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 0444880a300..836b22f5657 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -123,7 +123,7 @@ describe API::Runners do expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(shared_runner.description) - expect(json_response['job_upper_timeout']).to be_nil + expect(json_response['maximum_job_timeout']).to be_nil end end @@ -194,7 +194,7 @@ describe API::Runners do run_untagged: 'false', locked: 'true', access_level: 'ref_protected', - job_upper_timeout: 1234 ) + maximum_job_timeout: 1234 ) shared_runner.reload expect(response).to have_gitlab_http_status(200) @@ -206,7 +206,7 @@ describe API::Runners do expect(shared_runner.ref_protected?).to be_truthy expect(shared_runner.ensure_runner_queue_value) .not_to eq(runner_queue_value) - expect(shared_runner.job_upper_timeout).to eq(1234) + expect(shared_runner.maximum_job_timeout).to eq(1234) end end From 78a4189ece4f8d125eefbfdf6619d3452820bb8e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Feb 2018 04:03:12 +0100 Subject: [PATCH 08/48] Show timeout information on job's page --- .../jobs/components/sidebar_details_block.vue | 15 ++++++ app/models/ci/build.rb | 12 +++-- app/serializers/build_details_entity.rb | 5 ++ ...and_timeout_source_columns_to_ci_builds.rb | 15 ++++++ db/schema.rb | 2 + .../import_export/safe_model_attributes.yml | 2 + .../chronic_duration_attribute_spec.rb | 50 +++++++++++++------ spec/services/ci/retry_build_service_spec.rb | 3 +- 8 files changed, 84 insertions(+), 20 deletions(-) create mode 100644 db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 56814a52525..94c2084623b 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -39,6 +39,15 @@ runnerId() { return `#${this.job.runner.id}`; }, + timeout() { + let t = `${this.job.timeout.value}`; + + if (this.job.timeout.source != null) { + t += ` (from ${this.job.timeout.source})`; + } + + return t; + }, renderBlock() { return this.job.merge_request || this.job.duration || @@ -114,6 +123,12 @@ title="Queued" :value="queued" /> + (*) { !build.used_timeout.nil? } do |build| + { value: build.used_timeout_user_readable, + source: build.timeout_source } + end + expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| erase_project_job_path(project, build) diff --git a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb new file mode 100644 index 00000000000..cb8651b1cfd --- /dev/null +++ b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb @@ -0,0 +1,15 @@ +class AddUsedTimeoutAndTimeoutSourceColumnsToCiBuilds < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def up + add_column :ci_builds, :used_timeout, :integer + add_column :ci_builds, :timeout_source, :string + end + + def down + remove_column :ci_builds, :used_timeout + remove_column :ci_builds, :timeout_source + end +end diff --git a/db/schema.rb b/db/schema.rb index 806e829dcbd..61f15944f5a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -311,6 +311,8 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.integer "artifacts_metadata_store" t.boolean "protected" t.integer "failure_reason" + t.integer "used_timeout" + t.string "timeout_source" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index 0716852f57f..bd7e60a5d9e 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -283,6 +283,8 @@ CommitStatus: - retried - protected - failure_reason +- used_timeout +- timeout_source Ci::Variable: - id - project_id diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 1a352537aaf..85adfaf4487 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -1,28 +1,46 @@ require 'spec_helper' -shared_examples 'ChronicDurationAttribute' do - describe 'dynamically defined methods' do - it { expect(subject.class).to be_public_method_defined(virtual_field) } - it { expect(subject.class).to be_public_method_defined("#{virtual_field}=") } +shared_examples 'ChronicDurationAttribute reader' do + it 'contains dynamically created reader method' do + expect(subject.class).to be_public_method_defined(virtual_field) + end - it 'parses chronic duration input' do - subject.send("#{virtual_field}=", "10m") + it 'outputs chronic duration formated value' do + subject.send("#{source_field}=", 120) - expect(subject.send(source_field)).to eq(600) - end + expect(subject.send(virtual_field)).to eq('2m') + end +end - it 'outputs chronic duration formated value' do - subject.send("#{source_field}=", 120) +shared_examples 'ChronicDurationAttribute writer' do + it 'contains dynamically created writer method' do + expect(subject.class).to be_public_method_defined("#{virtual_field}=") + end - expect(subject.send(virtual_field)).to eq('2m') - end + it 'parses chronic duration input' do + subject.send("#{virtual_field}=", "10m") + + expect(subject.send(source_field)).to eq(600) end end describe 'ChronicDurationAttribute' do - let(:source_field) { :maximum_job_timeout } - let(:virtual_field) { :maximum_job_timeout_user_readable } - subject { Ci::Runner.new } + let(:source_field) {:maximum_job_timeout} + let(:virtual_field) {:maximum_job_timeout_user_readable} + subject {Ci::Runner.new} - it_behaves_like 'ChronicDurationAttribute' + it_behaves_like 'ChronicDurationAttribute reader' + it_behaves_like 'ChronicDurationAttribute writer' +end + +describe 'ChronicDurationAttribute - reader' do + let(:source_field) {:used_timeout} + let(:virtual_field) {:used_timeout_user_readable} + subject {Ci::Build.new} + + it "doesn't contain dynamically created writer method" do + expect(subject.class).not_to be_public_method_defined("#{virtual_field}=") + end + + it_behaves_like 'ChronicDurationAttribute reader' end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index b86a3d72bb4..e425e80e51e 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -29,7 +29,8 @@ describe Ci::RetryBuildService do commit_id deployments erased_by_id last_deployment project_id runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason - artifacts_file_store artifacts_metadata_store].freeze + artifacts_file_store artifacts_metadata_store + used_timeout timeout_source].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } From 42d2551dda0caf22c642fd8a85948fe77d5483d5 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Feb 2018 05:28:08 +0100 Subject: [PATCH 09/48] Update documentation --- doc/ci/runners/README.md | 45 +++++++++++++++++--- doc/ci/runners/img/shared_runners_admin.png | Bin 29192 -> 87490 bytes doc/user/project/pipelines/settings.md | 9 ++++ 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/doc/ci/runners/README.md b/doc/ci/runners/README.md index 7a7b50b294d..b91aa334ff3 100644 --- a/doc/ci/runners/README.md +++ b/doc/ci/runners/README.md @@ -231,6 +231,38 @@ To make a Runner pick tagged/untagged jobs: 1. Check the **Run untagged jobs** option 1. Click **Save changes** for the changes to take effect +### Setting maximum job timeout for a Runner + +For each Runner you can specify a _maximum job timeout_. Such timeout, +if smaller than [project defined timeout], will take the precedence. This +feature can be used to prevent Shared Runner from being appropriated +by a project by setting a ridiculous big timeout (e.g. one week). + +When not configured, Runner will not override project timeout. + +How this feature will work: + +**Example 1 - Runner timeout bigger than project timeout** + +1. You set the _maximum job timeout_ for a Runner to 24 hours +1. You set the _CI/CD Timeout_ for a project to **2 hours** +1. You start a job +1. The job, if running longer, will be timeouted after **2 hours** + +**Example 2 - Runner timeout not configured** + +1. You remove the _maximum job timeout_ configuration from a Runner +1. You set the _CI/CD Timeout_ for a project to **2 hours** +1. You start a job +1. The job, if running longer, will be timeouted after **2 hours** + +**Example 3 - Runner timeout smaller than project timeout** + +1. You set the _maximum job timeout_ for a Runner to **30 minutes** +1. You set the _CI/CD Timeout_ for a project to 2 hours +1. You start a job +1. The job, if running longer, will be timeouted after **30 minutes** + ### Be careful with sensitive information With some [Runner Executors](https://docs.gitlab.com/runner/executors/README.html), @@ -259,12 +291,6 @@ Mentioned briefly earlier, but the following things of Runners can be exploited. We're always looking for contributions that can mitigate these [Security Considerations](https://docs.gitlab.com/runner/security/). -[install]: http://docs.gitlab.com/runner/install/ -[fifo]: https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics) -[register]: http://docs.gitlab.com/runner/register/ -[protected branches]: ../../user/project/protected_branches.md -[protected tags]: ../../user/project/protected_tags.md - ## Determining the IP address of a Runner > [Introduced](https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17286) in GitLab 10.6. @@ -297,3 +323,10 @@ You can find the IP address of a Runner for a specific project by: 1. On the details page you should see a row for "IP Address" ![specific Runner IP address](img/specific_runner_ip_address.png) + +[install]: http://docs.gitlab.com/runner/install/ +[fifo]: https://en.wikipedia.org/wiki/FIFO_(computing_and_electronics) +[register]: http://docs.gitlab.com/runner/register/ +[protected branches]: ../../user/project/protected_branches.md +[protected tags]: ../../user/project/protected_tags.md +[project defined timeout]: ../../user/project/pipelines/settings.html#timeout diff --git a/doc/ci/runners/img/shared_runners_admin.png b/doc/ci/runners/img/shared_runners_admin.png index e049b339b36feed0e2eb84c008714c1c334a22ce..4145cee354ba1c7a8fdeca54f1d490c79c61594d 100644 GIT binary patch literal 87490 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 U=LSR7i2h3E*1enAHw>Qr50PU=%m4rY literal 29192 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 Jl!zPp{|7(6s?7iZ diff --git a/doc/user/project/pipelines/settings.md b/doc/user/project/pipelines/settings.md index 43451844f2d..1052c9efa25 100644 --- a/doc/user/project/pipelines/settings.md +++ b/doc/user/project/pipelines/settings.md @@ -27,6 +27,13 @@ The default value is 60 minutes. Decrease the time limit if you want to impose a hard limit on your jobs' running time or increase it otherwise. In any case, if the job surpasses the threshold, it is marked as failed. +### Timeout overriding on Runner level + +> - [Introduced][ce-17221] in GitLab 10.6. + +Project defined timeout (either specific timeout set by user or the default +60 minutes timeout) may be [overridden on Runner level][timeout overriding]. + ## Custom CI config path > - [Introduced][ce-12509] in GitLab 9.4. @@ -152,5 +159,7 @@ into your `README.md`: [var]: ../../../ci/yaml/README.md#git-strategy [coverage report]: #test-coverage-parsing +[timeout overriding]: ../../../ci/runners/README.html#setting-maximum-job-timeout-for-a-runner [ce-9362]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/9362 [ce-12509]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/12509 +[ce-17221]: https://gitlab.com/gitlab-org/gitlab-ce/merge_requests/17221 From c21e817a9081e01bdbf27f4a28bdb7af791a7e8a Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Feb 2018 05:28:31 +0100 Subject: [PATCH 10/48] Add link to timeout overriding documentation from job page sidebar --- .../jobs/components/sidebar_detail_row.vue | 20 +++++++++++++++++++ .../jobs/components/sidebar_details_block.vue | 1 + 2 files changed, 21 insertions(+) diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index a6819aaeb12..a24a0c5e779 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -11,11 +11,19 @@ type: String, required: true, }, + helpUrl: { + type: String, + required: false, + default: '', + }, }, computed: { hasTitle() { return this.title.length > 0; }, + hasHelpURL() { + return this.helpUrl.length > 0; + }, }, }; @@ -28,5 +36,17 @@ {{ title }}: {{ value }} + + + + + +

diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 94c2084623b..c979e11b469 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -127,6 +127,7 @@ class="js-job-timeout" v-if="job.timeout" title="Timeout" + help-url="/help/ci/runners/README.html#setting-maximum-job-timeout-for-a-runner" :value="timeout" /> Date: Mon, 26 Feb 2018 16:26:39 +0100 Subject: [PATCH 11/48] Rename chronic_duration_attribute* to chronic_duration_attr* --- app/models/ci/build.rb | 2 +- app/models/ci/runner.rb | 2 +- app/models/concerns/chronic_duration_attribute.rb | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 3072817f443..5b9e06ab203 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -91,7 +91,7 @@ module Ci after_commit :update_project_statistics_after_save, on: [:create, :update] after_commit :update_project_statistics, on: :destroy - chronic_duration_attribute_reader :used_timeout_user_readable, :used_timeout + chronic_duration_attr_reader :used_timeout_user_readable, :used_timeout class << self # This is needed for url_for to work, diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 3d83e00ccfe..f95afd4c40c 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -52,7 +52,7 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address - chronic_duration_attribute :maximum_job_timeout_user_readable, :maximum_job_timeout + chronic_duration_attr :maximum_job_timeout_user_readable, :maximum_job_timeout # Searches for runners matching the given query. # diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index 2bf33174640..ae3aeda1709 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -2,19 +2,19 @@ module ChronicDurationAttribute extend ActiveSupport::Concern class_methods do - def chronic_duration_attribute(virtual_attribute, source_attribute) - chronic_duration_attribute_reader(virtual_attribute, source_attribute) - chronic_duration_attribute_writer(virtual_attribute, source_attribute) + def chronic_duration_attr(virtual_attribute, source_attribute) + chronic_duration_attr_reader(virtual_attribute, source_attribute) + chronic_duration_attr_writer(virtual_attribute, source_attribute) end - def chronic_duration_attribute_reader(virtual_attribute, source_attribute) + def chronic_duration_attr_reader(virtual_attribute, source_attribute) define_method(virtual_attribute) do value = self.send(source_attribute) # rubocop:disable GitlabSecurity/PublicSend ChronicDuration.output(value, format: :short) unless value.nil? end end - def chronic_duration_attribute_writer(virtual_attribute, source_attribute) + def chronic_duration_attr_writer(virtual_attribute, source_attribute) define_method("#{virtual_attribute}=") do |value| new_value = ChronicDuration.parse(value).to_i self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend From 36753b78c065a54d7501f37f69fb49506f26688c Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 16:35:08 +0100 Subject: [PATCH 12/48] Replace user_readable with human_readable --- app/models/ci/build.rb | 2 +- app/models/ci/runner.rb | 4 ++-- app/serializers/build_details_entity.rb | 2 +- app/views/admin/runners/_runner.html.haml | 2 +- app/views/projects/runners/_form.html.haml | 4 ++-- app/views/projects/runners/_runner.html.haml | 2 +- app/views/projects/runners/show.html.haml | 2 +- spec/models/concerns/chronic_duration_attribute_spec.rb | 4 ++-- 8 files changed, 11 insertions(+), 11 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 5b9e06ab203..f47cbe0a206 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -91,7 +91,7 @@ module Ci after_commit :update_project_statistics_after_save, on: [:create, :update] after_commit :update_project_statistics, on: :destroy - chronic_duration_attr_reader :used_timeout_user_readable, :used_timeout + chronic_duration_attr_reader :used_timeout_human_readable, :used_timeout class << self # This is needed for url_for to work, diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index f95afd4c40c..baf57423682 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -9,7 +9,7 @@ module Ci ONLINE_CONTACT_TIMEOUT = 1.hour UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes AVAILABLE_SCOPES = %w[specific shared active paused online].freeze - FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_job_timeout_user_readable].freeze + FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_job_timeout_human_readable].freeze has_many :builds has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -52,7 +52,7 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address - chronic_duration_attr :maximum_job_timeout_user_readable, :maximum_job_timeout + chronic_duration_attr :maximum_job_timeout_human_readable, :maximum_job_timeout # Searches for runners matching the given query. # diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index d1a4a9561d2..17769790371 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -6,7 +6,7 @@ class BuildDetailsEntity < JobEntity expose :pipeline, using: PipelineEntity expose :timeout, if: -> (*) { !build.used_timeout.nil? } do |build| - { value: build.used_timeout_user_readable, + { value: build.used_timeout_human_readable, source: build.timeout_source } end diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index 5f0fb5079d9..fc6ad6dfe95 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -20,7 +20,7 @@ = runner.ip_address %td - if runner.defines_maximum_job_timeout? - = runner.maximum_job_timeout_user_readable + = runner.maximum_job_timeout_human_readable - else n/a %td diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 8fb8e6e0ebf..7e9435e0110 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -40,10 +40,10 @@ .col-sm-10 = f.text_field :description, class: 'form-control' .form-group - = label_tag :maximum_job_timeout_user_readable, class: 'control-label' do + = label_tag :maximum_job_timeout_human_readable, class: 'control-label' do Maximum job timeout .col-sm-10 - = f.text_field :maximum_job_timeout_user_readable, class: 'form-control' + = f.text_field :maximum_job_timeout_human_readable, class: 'form-control' .help-block This timeout will take precedence when lower than Project-defined timeout .form-group = label_tag :tag_list, class: 'control-label' do diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index f7c41fe44c0..91d6b172566 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -37,7 +37,7 @@ %p.runner-description = runner.description - if runner.defines_maximum_job_timeout? - %p Maximum job timeout: #{runner.maximum_job_timeout_user_readable} + %p Maximum job timeout: #{runner.maximum_job_timeout_human_readable} - if runner.tag_list.present? %p - runner.tag_list.sort.each do |tag| diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index 0d39236680c..67084e3d66a 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -57,7 +57,7 @@ %td= @runner.description %tr %td Maximum job timeout - %td= @runner.maximum_job_timeout_user_readable + %td= @runner.maximum_job_timeout_human_readable %tr %td Last contact %td diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 85adfaf4487..0d9b45e36e8 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -26,7 +26,7 @@ end describe 'ChronicDurationAttribute' do let(:source_field) {:maximum_job_timeout} - let(:virtual_field) {:maximum_job_timeout_user_readable} + let(:virtual_field) {:maximum_job_timeout_human_readable} subject {Ci::Runner.new} it_behaves_like 'ChronicDurationAttribute reader' @@ -35,7 +35,7 @@ end describe 'ChronicDurationAttribute - reader' do let(:source_field) {:used_timeout} - let(:virtual_field) {:used_timeout_user_readable} + let(:virtual_field) {:used_timeout_human_readable} subject {Ci::Build.new} it "doesn't contain dynamically created writer method" do From 27266db06b90ac97f75a902a7255185838d2104d Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 16:38:11 +0100 Subject: [PATCH 13/48] Remove information about maximum_job_timeout from runners lists --- app/views/admin/runners/_runner.html.haml | 5 ----- app/views/admin/runners/index.html.haml | 1 - app/views/projects/runners/_runner.html.haml | 2 -- 3 files changed, 8 deletions(-) diff --git a/app/views/admin/runners/_runner.html.haml b/app/views/admin/runners/_runner.html.haml index fc6ad6dfe95..e1cee584929 100644 --- a/app/views/admin/runners/_runner.html.haml +++ b/app/views/admin/runners/_runner.html.haml @@ -18,11 +18,6 @@ = runner.version %td = runner.ip_address - %td - - if runner.defines_maximum_job_timeout? - = runner.maximum_job_timeout_human_readable - - else - n/a %td - if runner.shared? n/a diff --git a/app/views/admin/runners/index.html.haml b/app/views/admin/runners/index.html.haml index a0c7d8f5fa9..9f13dbbbd82 100644 --- a/app/views/admin/runners/index.html.haml +++ b/app/views/admin/runners/index.html.haml @@ -61,7 +61,6 @@ %th Description %th Version %th IP Address - %th Maximum timeout %th Projects %th Jobs %th Tags diff --git a/app/views/projects/runners/_runner.html.haml b/app/views/projects/runners/_runner.html.haml index 91d6b172566..6376496ee1a 100644 --- a/app/views/projects/runners/_runner.html.haml +++ b/app/views/projects/runners/_runner.html.haml @@ -36,8 +36,6 @@ - if runner.description.present? %p.runner-description = runner.description - - if runner.defines_maximum_job_timeout? - %p Maximum job timeout: #{runner.maximum_job_timeout_human_readable} - if runner.tag_list.present? %p - runner.tag_list.sort.each do |tag| From 40f57b9b62f768f5aa66ef6d3e8d0922ffe7ec0f Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 16:45:12 +0100 Subject: [PATCH 14/48] Use change instead of up/down in simple migrations --- ...20180219153455_add_maximum_job_timeout_to_ci_runners.rb | 6 +----- ...used_timeout_and_timeout_source_columns_to_ci_builds.rb | 7 +------ 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb index 5656970ede9..1ad7bb86e72 100644 --- a/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb +++ b/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb @@ -3,11 +3,7 @@ class AddMaximumJobTimeoutToCiRunners < ActiveRecord::Migration DOWNTIME = false - def up + def change add_column :ci_runners, :maximum_job_timeout, :integer end - - def down - remove_column :ci_runners, :maximum_job_timeout - end end diff --git a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb index cb8651b1cfd..435fba712aa 100644 --- a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb +++ b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb @@ -3,13 +3,8 @@ class AddUsedTimeoutAndTimeoutSourceColumnsToCiBuilds < ActiveRecord::Migration DOWNTIME = false - def up + def change add_column :ci_builds, :used_timeout, :integer add_column :ci_builds, :timeout_source, :string end - - def down - remove_column :ci_builds, :used_timeout - remove_column :ci_builds, :timeout_source - end end From f18eb6dae43b8407ceb8bcbdecd6df30af2a66ac Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 16:53:23 +0100 Subject: [PATCH 15/48] Add styling improvements in specs --- spec/models/ci/build_spec.rb | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 115106548f0..cceb2033cbb 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1274,10 +1274,13 @@ describe Ci::Build do describe '#timeout' do set(:project2) { create(:project, :repository, group: group, build_timeout: 1000) } set(:pipeline2) { create(:ci_pipeline, project: project2, sha: project2.commit.id, ref: project2.default_branch, status: 'success') } + let(:build) { create(:ci_build, :manual, pipeline: pipeline2) } + subject { build.timeout } + it 'returns project timeout configuration' do - expect(build.timeout).to eq(project2.build_timeout) + is_expected.to eq(project2.build_timeout) end context 'when runner sets timeout to bigger value' do @@ -1285,7 +1288,7 @@ describe Ci::Build do let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do - expect(build.timeout).to eq(project2.build_timeout) + is_expected.to eq(project2.build_timeout) end end @@ -1294,7 +1297,7 @@ describe Ci::Build do let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do - expect(build.timeout).to eq(runner2.maximum_job_timeout) + is_expected.to eq(runner2.maximum_job_timeout) end end end From e75614e8cb391f0e499b71b261de7c8148f1b6a4 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 17:33:43 +0100 Subject: [PATCH 16/48] Add few frontend improvements --- .../javascripts/jobs/components/sidebar_detail_row.vue | 6 +++++- .../javascripts/jobs/components/sidebar_details_block.vue | 7 ++++++- app/assets/javascripts/jobs/job_details_bundle.js | 1 + app/views/projects/jobs/show.html.haml | 2 +- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue index a24a0c5e779..dfe87d89a39 100644 --- a/app/assets/javascripts/jobs/components/sidebar_detail_row.vue +++ b/app/assets/javascripts/jobs/components/sidebar_detail_row.vue @@ -44,8 +44,12 @@ - +

diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index c979e11b469..ad859679a1e 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -22,6 +22,11 @@ type: Boolean, required: true, }, + runnerHelpUrl: { + type: String, + required: false, + default: '', + }, }, computed: { shouldRenderContent() { @@ -127,7 +132,7 @@ class="js-job-timeout" v-if="job.timeout" title="Timeout" - help-url="/help/ci/runners/README.html#setting-maximum-job-timeout-for-a-runner" + :help-url="runnerHelpUrl" :value="timeout" /> { props: { isLoading: this.mediator.state.isLoading, job: this.mediator.store.state.job, + runnerHelpUrl: dataset.runnerHelpUrl, }, }); }, diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 849c273db8c..4d2bc3a04a4 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -111,4 +111,4 @@ .js-build-options{ data: javascript_build_options } -#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json) } } +#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: '/help/ci/runners/README.html#setting-maximum-job-timeout-for-a-runner' } } From 3e7f3bc7980018910f6c6e7a19f706ad18ab2793 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 18:51:56 +0100 Subject: [PATCH 17/48] Refactor timeout selection mechanism --- app/models/ci/build.rb | 10 ++------ spec/models/ci/build_spec.rb | 46 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f47cbe0a206..f9a83965199 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -124,7 +124,7 @@ module Ci after_transition pending: :running do |build| build.used_timeout = build.timeout - build.timeout_source = build.should_use_runner_timeout? ? 'Runner' : 'Project' + build.timeout_source = build.timeout < build.project.build_timeout ? 'Runner' : 'Project' build.save! build.run_after_commit do @@ -239,13 +239,7 @@ module Ci end def timeout - return runner.maximum_job_timeout if should_use_runner_timeout? - - project.build_timeout - end - - def should_use_runner_timeout? - !runner.nil? && runner.defines_maximum_job_timeout? && runner.maximum_job_timeout < project.build_timeout + [project.build_timeout, runner&.maximum_job_timeout].compact.min end def triggered_by?(current_user) diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index cceb2033cbb..78842bd8a92 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2036,6 +2036,52 @@ describe Ci::Build do end end + describe 'state transition: pending: :running' do + let(:runner) { create(:ci_runner) } + let(:job) { create(:ci_build, :pending, runner: runner) } + + before do + job.project.build_timeout = 1800 + job.project.save! + end + + shared_examples 'saves data on transition' do + it 'saves used_timeout and timeout_source on transition' do + expect(job.used_timeout).to be_nil + expect(job.timeout_source).to be_nil + + job.run! + + expect(job.used_timeout).to eq(expected_timeout) + expect(job.timeout_source).to eq(expected_timeout_source) + end + end + + context 'when runner timeout overrides project timeout' do + let(:expected_timeout) { 900 } + let(:expected_timeout_source) { 'Runner' } + + before do + runner.maximum_job_timeout = 900 + runner.save! + end + + it_behaves_like 'saves data on transition' + end + + context "when runner timeout doesn't override project timeout" do + let(:expected_timeout) { 1800 } + let(:expected_timeout_source) { 'Project' } + + before do + runner.maximum_job_timeout = 3600 + runner.save! + end + + it_behaves_like 'saves data on transition' + end + end + describe 'state transition: any => [:running]' do shared_examples 'validation is active' do context 'when depended job has not been completed yet' do From cb502b626659001af1e0ca82288a2b427e346d18 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 18:56:39 +0100 Subject: [PATCH 18/48] Remove unused method --- app/models/ci/runner.rb | 4 ---- spec/models/ci/runner_spec.rb | 18 ------------------ 2 files changed, 22 deletions(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index baf57423682..413607e0eff 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -170,10 +170,6 @@ module Ci end end - def defines_maximum_job_timeout? - !maximum_job_timeout.nil? && maximum_job_timeout > 0 - end - private def cleanup_runner_queue diff --git a/spec/models/ci/runner_spec.rb b/spec/models/ci/runner_spec.rb index 5b5fa7fac01..ab170e6351c 100644 --- a/spec/models/ci/runner_spec.rb +++ b/spec/models/ci/runner_spec.rb @@ -556,24 +556,6 @@ describe Ci::Runner do end end - describe '#defines_maximum_job_timeout?' do - context 'when maximum job timeout is specified' do - subject { create(:ci_runner, maximum_job_timeout: 1234) } - - it 'should return true' do - expect(subject.defines_maximum_job_timeout?).to be_truthy - end - end - - context 'when maximum job timeout is not specified' do - subject { create(:ci_runner) } - - it 'should return false' do - expect(subject.defines_maximum_job_timeout?).to be_falsey - end - end - end - describe '.search' do let(:runner) { create(:ci_runner, token: '123abc', description: 'test runner') } From 14549703bf2c6e1a1ac1c23327c452e5e68ee959 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Feb 2018 19:18:01 +0100 Subject: [PATCH 19/48] Revert change in doc/ci/runners/img/shared_runners_admin.png --- doc/ci/runners/img/shared_runners_admin.png | Bin 87490 -> 29192 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/doc/ci/runners/img/shared_runners_admin.png b/doc/ci/runners/img/shared_runners_admin.png index 4145cee354ba1c7a8fdeca54f1d490c79c61594d..e049b339b36feed0e2eb84c008714c1c334a22ce 100644 GIT binary patch literal 29192 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 Jl!zPp{|7(6s?7iZ literal 87490 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 U=LSR7i2h3E*1enAHw>Qr50PU=%m4rY From 7ef24a47714802edd8ad5470a6147b60172a0a28 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 28 Feb 2018 20:56:14 +0100 Subject: [PATCH 20/48] Properly handle empty value for chronic duration attribute --- .../concerns/chronic_duration_attribute.rb | 8 +++++++- .../concerns/chronic_duration_attribute_spec.rb | 16 ++++++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index ae3aeda1709..87c60a2b7d5 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -10,14 +10,20 @@ module ChronicDurationAttribute def chronic_duration_attr_reader(virtual_attribute, source_attribute) define_method(virtual_attribute) do value = self.send(source_attribute) # rubocop:disable GitlabSecurity/PublicSend - ChronicDuration.output(value, format: :short) unless value.nil? + + return '' if value.nil? + + ChronicDuration.output(value, format: :short) end end def chronic_duration_attr_writer(virtual_attribute, source_attribute) define_method("#{virtual_attribute}=") do |value| new_value = ChronicDuration.parse(value).to_i + new_value = nil if new_value <= 0 + self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend + new_value end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 0d9b45e36e8..31db7d055cc 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -5,11 +5,17 @@ shared_examples 'ChronicDurationAttribute reader' do expect(subject.class).to be_public_method_defined(virtual_field) end - it 'outputs chronic duration formated value' do + it 'outputs chronic duration formatted value' do subject.send("#{source_field}=", 120) expect(subject.send(virtual_field)).to eq('2m') end + + it 'outputs empty string when value set to nil' do + subject.send("#{source_field}=", nil) + + expect(subject.send(virtual_field)).to be_empty + end end shared_examples 'ChronicDurationAttribute writer' do @@ -18,10 +24,16 @@ shared_examples 'ChronicDurationAttribute writer' do end it 'parses chronic duration input' do - subject.send("#{virtual_field}=", "10m") + subject.send("#{virtual_field}=", '10m') expect(subject.send(source_field)).to eq(600) end + + it 'writes null when empty input is used' do + subject.send("#{virtual_field}=", '') + + expect(subject.send(source_field)).to be_nil + end end describe 'ChronicDurationAttribute' do From dbd7455583a10679f0e365cfeacd4729a4b543ec Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 28 Feb 2018 20:56:35 +0100 Subject: [PATCH 21/48] Use _human_readable for Runner's registration API --- lib/api/runner.rb | 4 ++-- spec/requests/api/runner_spec.rb | 14 ++++++++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 3a26155be6d..74ddaf26bb6 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,10 +14,10 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :maximum_job_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_job_timeout_human_readable, type: String, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_job_timeout]) + attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_job_timeout_human_readable]) .merge(get_runner_details_from_request) runner = diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index a6a4f510406..55c4150a393 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -112,10 +112,20 @@ describe API::Runner do context 'when maximum job timeout is specified' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_job_timeout: 7200 + maximum_job_timeout_human_readable: '2h 30m' expect(response).to have_gitlab_http_status 201 - expect(Ci::Runner.first.maximum_job_timeout).to eq(7200) + expect(Ci::Runner.first.maximum_job_timeout).to eq(9000) + end + + context 'when maximum job timeout is empty' do + it 'creates runner' do + post api('/runners'), token: registration_token, + maximum_job_timeout_human_readable: '' + + expect(response).to have_gitlab_http_status 201 + expect(Ci::Runner.first.maximum_job_timeout).to be_nil + end end end From 8ffa48098b77e1f35f9c01f6977d9ccf1d166a24 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 28 Feb 2018 21:07:57 +0100 Subject: [PATCH 22/48] Downcase timeout_source value --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index f9a83965199..2f01a098b0e 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -124,7 +124,7 @@ module Ci after_transition pending: :running do |build| build.used_timeout = build.timeout - build.timeout_source = build.timeout < build.project.build_timeout ? 'Runner' : 'Project' + build.timeout_source = build.timeout < build.project.build_timeout ? 'runner' : 'project' build.save! build.run_after_commit do From 1b0b8b9c02642ac19b9f5019cdd38fcec280c2a7 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 28 Feb 2018 21:36:01 +0100 Subject: [PATCH 23/48] Change timeout_source to enum --- app/models/ci/build.rb | 15 +++++++++++---- app/presenters/ci/build_presenter.rb | 14 ++++++++++++++ app/serializers/build_details_entity.rb | 2 +- ...ut_and_timeout_source_columns_to_ci_builds.rb | 2 +- db/schema.rb | 2 +- spec/models/ci/build_spec.rb | 16 +++++++--------- 6 files changed, 35 insertions(+), 16 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 2f01a098b0e..cbd2cc6c58f 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -93,6 +93,12 @@ module Ci chronic_duration_attr_reader :used_timeout_human_readable, :used_timeout + enum timeout_source: { + unknown_timeout_source: nil, + project_timeout_source: 1, + runner_timeout_source: 2 + } + class << self # This is needed for url_for to work, # as the controller is JobsController @@ -123,10 +129,6 @@ module Ci end after_transition pending: :running do |build| - build.used_timeout = build.timeout - build.timeout_source = build.timeout < build.project.build_timeout ? 'runner' : 'project' - build.save! - build.run_after_commit do BuildHooksWorker.perform_async(id) end @@ -160,6 +162,11 @@ module Ci before_transition any => [:running] do |build| build.validates_dependencies! unless Feature.enabled?('ci_disable_validates_dependencies') end + + before_transition pending: :running do |build| + build.used_timeout = build.timeout + build.timeout_source = build.timeout < build.project.build_timeout ? :runner_timeout_source : :project_timeout_source + end end def detailed_status(current_user) diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 255475e1fe6..be6cc2e70b1 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,5 +1,12 @@ module Ci class BuildPresenter < Gitlab::View::Presenter::Delegated + + TIMEOUT_SOURCES = { + unknown_timeout_source: nil, + project_timeout_source: 'project', + runner_timeout_source: 'runner' + }.freeze + presents :build def erased_by_user? @@ -18,6 +25,13 @@ module Ci end end + def timeout_source + return unless build.timeout_source? + + TIMEOUT_SOURCES[build.timeout_source.to_sym] || + build.timeout_source + end + def trigger_variables return [] unless trigger_request diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 17769790371..0ffc537dfd8 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -7,7 +7,7 @@ class BuildDetailsEntity < JobEntity expose :timeout, if: -> (*) { !build.used_timeout.nil? } do |build| { value: build.used_timeout_human_readable, - source: build.timeout_source } + source: build.present.timeout_source } end expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity diff --git a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb index 435fba712aa..18c4fd5bae4 100644 --- a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb +++ b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb @@ -5,6 +5,6 @@ class AddUsedTimeoutAndTimeoutSourceColumnsToCiBuilds < ActiveRecord::Migration def change add_column :ci_builds, :used_timeout, :integer - add_column :ci_builds, :timeout_source, :string + add_column :ci_builds, :timeout_source, :integer end end diff --git a/db/schema.rb b/db/schema.rb index 61f15944f5a..9b126385045 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -312,7 +312,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.boolean "protected" t.integer "failure_reason" t.integer "used_timeout" - t.string "timeout_source" + t.integer "timeout_source" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 78842bd8a92..c3624158b76 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2046,20 +2046,18 @@ describe Ci::Build do end shared_examples 'saves data on transition' do - it 'saves used_timeout and timeout_source on transition' do - expect(job.used_timeout).to be_nil - expect(job.timeout_source).to be_nil + it 'saves used_timeout' do + expect { job.run! }.to change { job.reload.used_timeout }.from(nil).to(expected_timeout) + end - job.run! - - expect(job.used_timeout).to eq(expected_timeout) - expect(job.timeout_source).to eq(expected_timeout_source) + it 'saves timeout_source' do + expect { job.run! }.to change { job.reload.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) end end context 'when runner timeout overrides project timeout' do let(:expected_timeout) { 900 } - let(:expected_timeout_source) { 'Runner' } + let(:expected_timeout_source) { 'runner_timeout_source' } before do runner.maximum_job_timeout = 900 @@ -2071,7 +2069,7 @@ describe Ci::Build do context "when runner timeout doesn't override project timeout" do let(:expected_timeout) { 1800 } - let(:expected_timeout_source) { 'Project' } + let(:expected_timeout_source) { 'project_timeout_source' } before do runner.maximum_job_timeout = 3600 From d34e937b93b103435a59e6759a9f30e9f8addc11 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 28 Feb 2018 22:30:06 +0100 Subject: [PATCH 24/48] Add tests for frontend changes --- spec/javascripts/jobs/mock_data.js | 4 ++++ .../javascripts/jobs/sidebar_detail_row_spec.js | 17 +++++++++++++++++ .../jobs/sidebar_details_block_spec.js | 6 ++++++ 3 files changed, 27 insertions(+) diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 43589d54be4..6c3f39f0193 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -115,6 +115,10 @@ export default { commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', }, }, + timeout: { + value: '1m 40s', + source: 'runner', + }, merge_request: { iid: 2, path: '/root/ci-mock/merge_requests/2', diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js index 3ac65709c4a..e1f0fe334ac 100644 --- a/spec/javascripts/jobs/sidebar_detail_row_spec.js +++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js @@ -37,4 +37,21 @@ describe('Sidebar detail row', () => { vm.$el.textContent.replace(/\s+/g, ' ').trim(), ).toEqual('this is the title: this is the value'); }); + + it('should not render help when helpUrl not provided', () => { + expect(vm.$el.querySelector('.help-button')).toBeUndefined(); + }); + + beforeEach(() => { + vm = new SidebarDetailRow({ + propsData: { + helpUrl: 'help url', + }, + }).$mount(); + }); + + it('should render help when helpUrl is provided', () => { + expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); + }); + }); diff --git a/spec/javascripts/jobs/sidebar_details_block_spec.js b/spec/javascripts/jobs/sidebar_details_block_spec.js index 95532ef5382..602dae514b1 100644 --- a/spec/javascripts/jobs/sidebar_details_block_spec.js +++ b/spec/javascripts/jobs/sidebar_details_block_spec.js @@ -96,6 +96,12 @@ describe('Sidebar details block', () => { ).toEqual('Runner: #1'); }); + it('should render timeout information', () => { + expect( + trimWhitespace(vm.$el.querySelector('.js-job-timeout')), + ).toEqual('Timeout: 1m 40s (from runner)'); + }); + it('should render coverage', () => { expect( trimWhitespace(vm.$el.querySelector('.js-job-coverage')), From 1dde609ca6b130aa0a3d39e929edee7e770e62fc Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Mar 2018 03:12:32 +0100 Subject: [PATCH 25/48] Move job timeout information to new ci_builds_metadata table --- .../jobs/components/sidebar_details_block.vue | 8 +++--- app/models/ci/build.rb | 18 ++++++------- app/models/ci/build_metadata.rb | 25 +++++++++++++++++++ app/presenters/ci/build_metadata_presenter.rb | 20 +++++++++++++++ app/presenters/ci/build_presenter.rb | 13 ---------- app/serializers/build_details_entity.rb | 5 +--- app/serializers/build_metadata_entity.rb | 10 ++++++++ ...and_timeout_source_columns_to_ci_builds.rb | 10 -------- ...1010859_create_ci_builds_metadata_table.rb | 13 ++++++++++ ...build_foreign_key_to_ci_builds_metadata.rb | 15 +++++++++++ db/schema.rb | 11 +++++--- spec/javascripts/jobs/mock_data.js | 6 ++--- spec/models/ci/build_spec.rb | 4 +-- .../chronic_duration_attribute_spec.rb | 2 +- spec/services/ci/retry_build_service_spec.rb | 2 +- 15 files changed, 110 insertions(+), 52 deletions(-) create mode 100644 app/models/ci/build_metadata.rb create mode 100644 app/presenters/ci/build_metadata_presenter.rb create mode 100644 app/serializers/build_metadata_entity.rb delete mode 100644 db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb create mode 100644 db/migrate/20180301010859_create_ci_builds_metadata_table.rb create mode 100644 db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index ad859679a1e..15584922d1f 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -45,10 +45,10 @@ return `#${this.job.runner.id}`; }, timeout() { - let t = `${this.job.timeout.value}`; + let t = `${this.job.metadata.used_timeout_human_readable}`; - if (this.job.timeout.source != null) { - t += ` (from ${this.job.timeout.source})`; + if (this.job.metadata.timeout_source != null) { + t += ` (from ${this.job.metadata.timeout_source})`; } return t; @@ -130,7 +130,7 @@ /> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id + has_one :metadata, class_name: 'Ci::BuildMetadata' + # The "environment" field for builds is a String, and is the unexpanded name def persisted_environment @persisted_environment ||= Environment.find_by( @@ -84,6 +85,10 @@ module Ci before_save :ensure_token before_destroy { unscoped_project } + before_create do |build| + build.metadata = Ci::BuildMetadata.new + end + after_create unless: :importing? do |build| run_after_commit { BuildHooksWorker.perform_async(build.id) } end @@ -91,14 +96,6 @@ module Ci after_commit :update_project_statistics_after_save, on: [:create, :update] after_commit :update_project_statistics, on: :destroy - chronic_duration_attr_reader :used_timeout_human_readable, :used_timeout - - enum timeout_source: { - unknown_timeout_source: nil, - project_timeout_source: 1, - runner_timeout_source: 2 - } - class << self # This is needed for url_for to work, # as the controller is JobsController @@ -164,8 +161,7 @@ module Ci end before_transition pending: :running do |build| - build.used_timeout = build.timeout - build.timeout_source = build.timeout < build.project.build_timeout ? :runner_timeout_source : :project_timeout_source + build.metadata.save_timeout_state! end end diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb new file mode 100644 index 00000000000..7a1315dfcf9 --- /dev/null +++ b/app/models/ci/build_metadata.rb @@ -0,0 +1,25 @@ +module Ci + class BuildMetadata < ActiveRecord::Base + extend Gitlab::Ci::Model + include Presentable + include ChronicDurationAttribute + + self.table_name = 'ci_builds_metadata' + + belongs_to :build, class_name: 'Ci::Build' + + chronic_duration_attr_reader :used_timeout_human_readable, :used_timeout + + enum timeout_source: { + unknown_timeout_source: nil, + project_timeout_source: 1, + runner_timeout_source: 2 + } + + def save_timeout_state! + self.used_timeout = build.timeout + self.timeout_source = build.timeout < build.project.build_timeout ? :runner_timeout_source : :project_timeout_source + save! + end + end +end diff --git a/app/presenters/ci/build_metadata_presenter.rb b/app/presenters/ci/build_metadata_presenter.rb new file mode 100644 index 00000000000..e80eb19ea19 --- /dev/null +++ b/app/presenters/ci/build_metadata_presenter.rb @@ -0,0 +1,20 @@ +module Ci + class BuildMetadataPresenter < Gitlab::View::Presenter::Delegated + + TIMEOUT_SOURCES = { + unknown_timeout_source: nil, + project_timeout_source: 'project', + runner_timeout_source: 'runner' + }.freeze + + presents :metadata + + def timeout_source + return unless metadata.timeout_source? + + TIMEOUT_SOURCES[metadata.timeout_source.to_sym] || + metadata.timeout_source + end + + end +end diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index be6cc2e70b1..9345e5069bc 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,12 +1,6 @@ module Ci class BuildPresenter < Gitlab::View::Presenter::Delegated - TIMEOUT_SOURCES = { - unknown_timeout_source: nil, - project_timeout_source: 'project', - runner_timeout_source: 'runner' - }.freeze - presents :build def erased_by_user? @@ -25,13 +19,6 @@ module Ci end end - def timeout_source - return unless build.timeout_source? - - TIMEOUT_SOURCES[build.timeout_source.to_sym] || - build.timeout_source - end - def trigger_variables return [] unless trigger_request diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 0ffc537dfd8..ca4480fe2b1 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -5,10 +5,7 @@ class BuildDetailsEntity < JobEntity expose :runner, using: RunnerEntity expose :pipeline, using: PipelineEntity - expose :timeout, if: -> (*) { !build.used_timeout.nil? } do |build| - { value: build.used_timeout_human_readable, - source: build.present.timeout_source } - end + expose :metadata, using: BuildMetadataEntity expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| diff --git a/app/serializers/build_metadata_entity.rb b/app/serializers/build_metadata_entity.rb new file mode 100644 index 00000000000..71c7295ba9f --- /dev/null +++ b/app/serializers/build_metadata_entity.rb @@ -0,0 +1,10 @@ +class BuildMetadataEntity < Grape::Entity + + expose :used_timeout_human_readable do |metadata| + metadata.used_timeout_human_readable unless metadata.used_timeout.nil? + end + + expose :timeout_source do |metadata| + metadata.present.timeout_source + end +end diff --git a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb b/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb deleted file mode 100644 index 18c4fd5bae4..00000000000 --- a/db/migrate/20180221022556_add_used_timeout_and_timeout_source_columns_to_ci_builds.rb +++ /dev/null @@ -1,10 +0,0 @@ -class AddUsedTimeoutAndTimeoutSourceColumnsToCiBuilds < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - def change - add_column :ci_builds, :used_timeout, :integer - add_column :ci_builds, :timeout_source, :integer - end -end diff --git a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb new file mode 100644 index 00000000000..9d5e9c1779b --- /dev/null +++ b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb @@ -0,0 +1,13 @@ +class CreateCiBuildsMetadataTable < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + create_table :ci_builds_metadata do |t| + t.integer :build_id, null: false + t.integer :used_timeout + t.integer :timeout_source + end + end +end diff --git a/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb b/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb new file mode 100644 index 00000000000..feda2d6e9c9 --- /dev/null +++ b/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb @@ -0,0 +1,15 @@ +class AddBuildForeignKeyToCiBuildsMetadata < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + disable_ddl_transaction! + + def up + add_concurrent_foreign_key(:ci_builds_metadata, :ci_builds, column: :build_id) + end + + def down + remove_foreign_key(:ci_builds_metadata, column: :build_id) + end +end diff --git a/db/schema.rb b/db/schema.rb index 9b126385045..5463b3f1219 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -311,8 +311,6 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.integer "artifacts_metadata_store" t.boolean "protected" t.integer "failure_reason" - t.integer "used_timeout" - t.integer "timeout_source" end add_index "ci_builds", ["artifacts_expire_at"], name: "index_ci_builds_on_artifacts_expire_at", where: "(artifacts_file <> ''::text)", using: :btree @@ -331,6 +329,12 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree + create_table "ci_builds_metadata", force: :cascade do |t| + t.integer "build_id", null: false + t.integer "used_timeout" + t.integer "timeout_source" + end + create_table "ci_group_variables", force: :cascade do |t| t.string "key", null: false t.text "value" @@ -460,8 +464,8 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.boolean "run_untagged", default: true, null: false t.boolean "locked", default: false, null: false t.integer "access_level", default: 0, null: false - t.string "ip_address" t.integer "maximum_job_timeout" + t.string "ip_address" end add_index "ci_runners", ["contacted_at"], name: "index_ci_runners_on_contacted_at", using: :btree @@ -2031,6 +2035,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade + add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", name: "fk_e20479742e", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade diff --git a/spec/javascripts/jobs/mock_data.js b/spec/javascripts/jobs/mock_data.js index 6c3f39f0193..8a7ba50830c 100644 --- a/spec/javascripts/jobs/mock_data.js +++ b/spec/javascripts/jobs/mock_data.js @@ -115,9 +115,9 @@ export default { commit_path: '/root/ci-mock/commit/c58647773a6b5faf066d4ad6ff2c9fbba5f180f6', }, }, - timeout: { - value: '1m 40s', - source: 'runner', + metadata: { + used_timeout_human_readable: '1m 40s', + timeout_source: 'runner', }, merge_request: { iid: 2, diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index c3624158b76..1da84d6caa9 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2047,11 +2047,11 @@ describe Ci::Build do shared_examples 'saves data on transition' do it 'saves used_timeout' do - expect { job.run! }.to change { job.reload.used_timeout }.from(nil).to(expected_timeout) + expect { job.run! }.to change { job.reload.metadata.used_timeout }.from(nil).to(expected_timeout) end it 'saves timeout_source' do - expect { job.run! }.to change { job.reload.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) + expect { job.run! }.to change { job.reload.metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index 31db7d055cc..fbbbcd374ee 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -48,7 +48,7 @@ end describe 'ChronicDurationAttribute - reader' do let(:source_field) {:used_timeout} let(:virtual_field) {:used_timeout_human_readable} - subject {Ci::Build.new} + subject {Ci::BuildMetadata.new} it "doesn't contain dynamically created writer method" do expect(subject.class).not_to be_public_method_defined("#{virtual_field}=") diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index e425e80e51e..8de0bdf92e2 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -30,7 +30,7 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason artifacts_file_store artifacts_metadata_store - used_timeout timeout_source].freeze + metadata].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } From 2ed1d5418b61f09e27173b364b9ad5f064a07823 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Mar 2018 19:43:10 +0100 Subject: [PATCH 26/48] Fix static analysis offences --- app/presenters/ci/build_metadata_presenter.rb | 4 +--- app/presenters/ci/build_presenter.rb | 1 - app/serializers/build_metadata_entity.rb | 1 - spec/javascripts/jobs/sidebar_detail_row_spec.js | 1 - spec/models/concerns/chronic_duration_attribute_spec.rb | 3 ++- 5 files changed, 3 insertions(+), 7 deletions(-) diff --git a/app/presenters/ci/build_metadata_presenter.rb b/app/presenters/ci/build_metadata_presenter.rb index e80eb19ea19..5048f967ea8 100644 --- a/app/presenters/ci/build_metadata_presenter.rb +++ b/app/presenters/ci/build_metadata_presenter.rb @@ -1,6 +1,5 @@ module Ci class BuildMetadataPresenter < Gitlab::View::Presenter::Delegated - TIMEOUT_SOURCES = { unknown_timeout_source: nil, project_timeout_source: 'project', @@ -13,8 +12,7 @@ module Ci return unless metadata.timeout_source? TIMEOUT_SOURCES[metadata.timeout_source.to_sym] || - metadata.timeout_source + metadata.timeout_source end - end end diff --git a/app/presenters/ci/build_presenter.rb b/app/presenters/ci/build_presenter.rb index 9345e5069bc..255475e1fe6 100644 --- a/app/presenters/ci/build_presenter.rb +++ b/app/presenters/ci/build_presenter.rb @@ -1,6 +1,5 @@ module Ci class BuildPresenter < Gitlab::View::Presenter::Delegated - presents :build def erased_by_user? diff --git a/app/serializers/build_metadata_entity.rb b/app/serializers/build_metadata_entity.rb index 71c7295ba9f..4b6a538665d 100644 --- a/app/serializers/build_metadata_entity.rb +++ b/app/serializers/build_metadata_entity.rb @@ -1,5 +1,4 @@ class BuildMetadataEntity < Grape::Entity - expose :used_timeout_human_readable do |metadata| metadata.used_timeout_human_readable unless metadata.used_timeout.nil? end diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js index e1f0fe334ac..e66cb4d9809 100644 --- a/spec/javascripts/jobs/sidebar_detail_row_spec.js +++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js @@ -53,5 +53,4 @@ describe('Sidebar detail row', () => { it('should render help when helpUrl is provided', () => { expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); }); - }); diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index fbbbcd374ee..b1ffeb0c74f 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -39,7 +39,8 @@ end describe 'ChronicDurationAttribute' do let(:source_field) {:maximum_job_timeout} let(:virtual_field) {:maximum_job_timeout_human_readable} - subject {Ci::Runner.new} + + subject { Ci::Runner.new } it_behaves_like 'ChronicDurationAttribute reader' it_behaves_like 'ChronicDurationAttribute writer' From 27c4cdbe288e3dba35eebdd50dbc6e103346a8c4 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Mar 2018 20:03:48 +0100 Subject: [PATCH 27/48] Update tests - remove unneeded change --- spec/lib/gitlab/import_export/safe_model_attributes.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/spec/lib/gitlab/import_export/safe_model_attributes.yml b/spec/lib/gitlab/import_export/safe_model_attributes.yml index bd7e60a5d9e..0716852f57f 100644 --- a/spec/lib/gitlab/import_export/safe_model_attributes.yml +++ b/spec/lib/gitlab/import_export/safe_model_attributes.yml @@ -283,8 +283,6 @@ CommitStatus: - retried - protected - failure_reason -- used_timeout -- timeout_source Ci::Variable: - id - project_id From 9ef86b1b12e80bcc3a6277e2b6a36b793c828489 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 1 Mar 2018 20:04:15 +0100 Subject: [PATCH 28/48] Fix transition failure when metadata not exists --- app/models/ci/build.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 4b4a988d600..cc29c6150d3 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -161,7 +161,7 @@ module Ci end before_transition pending: :running do |build| - build.metadata.save_timeout_state! + build.metadata.save_timeout_state! unless build.metadata.nil? end end From bb64b20a1f7dd831e4c200343b531f9d048c02a8 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 5 Mar 2018 17:35:06 +0100 Subject: [PATCH 29/48] Refactorize Ci::Build and Ci::BuildMetadata models --- app/models/ci/build.rb | 14 +++++++------- app/models/ci/build_metadata.rb | 16 +++++++++++----- ...0301010859_create_ci_builds_metadata_table.rb | 7 +++++-- ...dd_build_foreign_key_to_ci_builds_metadata.rb | 15 --------------- db/schema.rb | 7 +++---- spec/models/ci/build_spec.rb | 10 +++++++--- 6 files changed, 33 insertions(+), 36 deletions(-) delete mode 100644 db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index cc29c6150d3..7a12d7a3deb 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -24,7 +24,7 @@ module Ci has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id - has_one :metadata, class_name: 'Ci::BuildMetadata' + has_one :build_metadata, class_name: 'Ci::BuildMetadata' # The "environment" field for builds is a String, and is the unexpanded name def persisted_environment @@ -85,10 +85,6 @@ module Ci before_save :ensure_token before_destroy { unscoped_project } - before_create do |build| - build.metadata = Ci::BuildMetadata.new - end - after_create unless: :importing? do |build| run_after_commit { BuildHooksWorker.perform_async(build.id) } end @@ -161,10 +157,14 @@ module Ci end before_transition pending: :running do |build| - build.metadata.save_timeout_state! unless build.metadata.nil? + build.metadata.save_timeout_state! end end + def metadata + self.build_metadata ||= Ci::BuildMetadata.new + end + def detailed_status(current_user) Gitlab::Ci::Status::Build::Factory .new(self, current_user) @@ -242,7 +242,7 @@ module Ci end def timeout - [project.build_timeout, runner&.maximum_job_timeout].compact.min + metadata.used_timeout end def triggered_by?(current_user) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 7a1315dfcf9..9043bed86bf 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -1,4 +1,6 @@ module Ci + # The purpose of this class is to store Build related data that can be disposed. + # Data that should be persisted forever, should be stored with Ci::Build model. class BuildMetadata < ActiveRecord::Base extend Gitlab::Ci::Model include Presentable @@ -11,14 +13,18 @@ module Ci chronic_duration_attr_reader :used_timeout_human_readable, :used_timeout enum timeout_source: { - unknown_timeout_source: nil, - project_timeout_source: 1, - runner_timeout_source: 2 + unknown_timeout_source: 1, + project_timeout_source: 2, + runner_timeout_source: 3 } def save_timeout_state! - self.used_timeout = build.timeout - self.timeout_source = build.timeout < build.project.build_timeout ? :runner_timeout_source : :project_timeout_source + project_timeout = build.project&.build_timeout + timeout = [project_timeout, build.runner&.maximum_job_timeout].compact.min + + self.used_timeout = timeout + self.timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source + save! end end diff --git a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb index 9d5e9c1779b..cd7824d7788 100644 --- a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb +++ b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb @@ -4,10 +4,13 @@ class CreateCiBuildsMetadataTable < ActiveRecord::Migration DOWNTIME = false def change - create_table :ci_builds_metadata do |t| + create_table :ci_builds_metadata, id: false do |t| t.integer :build_id, null: false t.integer :used_timeout - t.integer :timeout_source + t.integer :timeout_source, null: false, default: 1 + + t.primary_key :build_id + t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade end end end diff --git a/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb b/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb deleted file mode 100644 index feda2d6e9c9..00000000000 --- a/db/migrate/20180301011323_add_build_foreign_key_to_ci_builds_metadata.rb +++ /dev/null @@ -1,15 +0,0 @@ -class AddBuildForeignKeyToCiBuildsMetadata < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - disable_ddl_transaction! - - def up - add_concurrent_foreign_key(:ci_builds_metadata, :ci_builds, column: :build_id) - end - - def down - remove_foreign_key(:ci_builds_metadata, column: :build_id) - end -end diff --git a/db/schema.rb b/db/schema.rb index 5463b3f1219..920d71e0110 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -329,10 +329,9 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree - create_table "ci_builds_metadata", force: :cascade do |t| - t.integer "build_id", null: false + create_table "ci_builds_metadata", primary_key: "build_id", force: :cascade do |t| t.integer "used_timeout" - t.integer "timeout_source" + t.integer "timeout_source", default: 1, null: false end create_table "ci_group_variables", force: :cascade do |t| @@ -2035,7 +2034,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_foreign_key "ci_builds", "ci_pipelines", column: "auto_canceled_by_id", name: "fk_a2141b1522", on_delete: :nullify add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade - add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", name: "fk_e20479742e", on_delete: :cascade + add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 1da84d6caa9..670803a0883 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1275,17 +1275,21 @@ describe Ci::Build do set(:project2) { create(:project, :repository, group: group, build_timeout: 1000) } set(:pipeline2) { create(:ci_pipeline, project: project2, sha: project2.commit.id, ref: project2.default_branch, status: 'success') } - let(:build) { create(:ci_build, :manual, pipeline: pipeline2) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline2) } subject { build.timeout } + before do + build.run! + end + it 'returns project timeout configuration' do is_expected.to eq(project2.build_timeout) end context 'when runner sets timeout to bigger value' do let(:runner2) { create(:ci_runner, maximum_job_timeout: 2000) } - let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do is_expected.to eq(project2.build_timeout) @@ -1294,7 +1298,7 @@ describe Ci::Build do context 'when runner sets timeout to smaller value' do let(:runner2) { create(:ci_runner, maximum_job_timeout: 500) } - let(:build) { create(:ci_build, :manual, pipeline: pipeline2, runner: runner2) } + let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do is_expected.to eq(runner2.maximum_job_timeout) From d02694c5c53265ccd9e8a51a52dc8bb8bcbff656 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 5 Mar 2018 17:38:58 +0100 Subject: [PATCH 30/48] Use help_page_path helper to generate runner_help_url --- app/views/projects/jobs/show.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/projects/jobs/show.html.haml b/app/views/projects/jobs/show.html.haml index 4d2bc3a04a4..fa27ded7cc2 100644 --- a/app/views/projects/jobs/show.html.haml +++ b/app/views/projects/jobs/show.html.haml @@ -111,4 +111,4 @@ .js-build-options{ data: javascript_build_options } -#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: '/help/ci/runners/README.html#setting-maximum-job-timeout-for-a-runner' } } +#js-job-details-vue{ data: { endpoint: project_job_path(@project, @build, format: :json), runner_help_url: help_page_path('ci/runners/README.html', anchor: 'setting-maximum-job-timeout-for-a-runner') } } From 62f053e4e50dd04933d49622a74dcb89ebe8174e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 5 Mar 2018 17:49:40 +0100 Subject: [PATCH 31/48] Update runner registration API --- lib/api/runner.rb | 5 +++-- spec/requests/api/runner_spec.rb | 4 ++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 74ddaf26bb6..73d5993701c 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,11 +14,12 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :maximum_job_timeout_human_readable, type: String, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_job_timeout, type: String, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_job_timeout_human_readable]) + attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list]) .merge(get_runner_details_from_request) + .merge(maximum_job_timeout_human_readable: params[:maximum_job_timeout]) runner = if runner_registration_token_valid? diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 55c4150a393..0ca7ba7154e 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -112,7 +112,7 @@ describe API::Runner do context 'when maximum job timeout is specified' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_job_timeout_human_readable: '2h 30m' + maximum_job_timeout: '2h 30m' expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.maximum_job_timeout).to eq(9000) @@ -121,7 +121,7 @@ describe API::Runner do context 'when maximum job timeout is empty' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_job_timeout_human_readable: '' + maximum_job_timeout: '' expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.maximum_job_timeout).to be_nil From 1e138767a652d86458d38665b98c9c2e5d4c3cb8 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 5 Mar 2018 20:22:00 +0100 Subject: [PATCH 32/48] Fix tests failures --- .../concerns/chronic_duration_attribute.rb | 4 ++-- .../jobs/sidebar_detail_row_spec.js | 1 + .../chronic_duration_attribute_spec.rb | 20 ++++++++++++++++++- spec/services/ci/retry_build_service_spec.rb | 2 +- 4 files changed, 23 insertions(+), 4 deletions(-) diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index 87c60a2b7d5..15095d0b758 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -19,8 +19,8 @@ module ChronicDurationAttribute def chronic_duration_attr_writer(virtual_attribute, source_attribute) define_method("#{virtual_attribute}=") do |value| - new_value = ChronicDuration.parse(value).to_i - new_value = nil if new_value <= 0 + new_value = ChronicDuration.parse(value).to_i unless value.nil? + new_value = nil if !new_value.nil? && new_value <= 0 self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js index e66cb4d9809..9fe5cf78b8b 100644 --- a/spec/javascripts/jobs/sidebar_detail_row_spec.js +++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js @@ -46,6 +46,7 @@ describe('Sidebar detail row', () => { vm = new SidebarDetailRow({ propsData: { helpUrl: 'help url', + value: 'foo', }, }).$mount(); }); diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index b1ffeb0c74f..b25475d4fdb 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -29,11 +29,29 @@ shared_examples 'ChronicDurationAttribute writer' do expect(subject.send(source_field)).to eq(600) end - it 'writes null when empty input is used' do + it 'writes nil when empty input is used' do subject.send("#{virtual_field}=", '') expect(subject.send(source_field)).to be_nil end + + it 'writes nil when negative input is used' do + allow(ChronicDuration).to receive(:parse).and_return(-10) + + subject.send("#{virtual_field}=", '-10m') + + expect(subject.send(source_field)).to be_nil + end + + it 'writes nil when nil input is used' do + subject.send("#{virtual_field}=", nil) + + expect(subject.send(source_field)).to be_nil + end + + it "doesn't raise exception when nil input is used" do + expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError) + end end describe 'ChronicDurationAttribute' do diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 8de0bdf92e2..415302e84f2 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -30,7 +30,7 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason artifacts_file_store artifacts_metadata_store - metadata].freeze + build_metadata].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } From f5e602ee0f8d95617adf6fb9b5a1a132a471fb12 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Mar 2018 16:14:23 +0100 Subject: [PATCH 33/48] Rename maximum_job_timeout to maximum_timeout --- app/models/ci/build_metadata.rb | 2 +- app/models/ci/runner.rb | 4 ++-- app/views/projects/runners/_form.html.haml | 4 ++-- app/views/projects/runners/show.html.haml | 2 +- ...19153455_add_maximum_job_timeout_to_ci_runners.rb | 9 --------- ...180219153455_add_maximum_timeout_to_ci_runners.rb | 9 +++++++++ db/schema.rb | 2 +- doc/api/runners.md | 6 +++--- lib/api/entities.rb | 2 +- lib/api/runner.rb | 4 ++-- lib/api/runners.rb | 2 +- spec/models/ci/build_spec.rb | 10 +++++----- .../concerns/chronic_duration_attribute_spec.rb | 4 ++-- spec/requests/api/runner_spec.rb | 12 ++++++------ spec/requests/api/runners_spec.rb | 6 +++--- 15 files changed, 39 insertions(+), 39 deletions(-) delete mode 100644 db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb create mode 100644 db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 9043bed86bf..89fee37b1b6 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -20,7 +20,7 @@ module Ci def save_timeout_state! project_timeout = build.project&.build_timeout - timeout = [project_timeout, build.runner&.maximum_job_timeout].compact.min + timeout = [project_timeout, build.runner&.maximum_timeout].compact.min self.used_timeout = timeout self.timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 413607e0eff..62cc636db22 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -9,7 +9,7 @@ module Ci ONLINE_CONTACT_TIMEOUT = 1.hour UPDATE_DB_RUNNER_INFO_EVERY = 40.minutes AVAILABLE_SCOPES = %w[specific shared active paused online].freeze - FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_job_timeout_human_readable].freeze + FORM_EDITABLE = %i[description tag_list active run_untagged locked access_level maximum_timeout_human_readable].freeze has_many :builds has_many :runner_projects, dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent @@ -52,7 +52,7 @@ module Ci cached_attr_reader :version, :revision, :platform, :architecture, :contacted_at, :ip_address - chronic_duration_attr :maximum_job_timeout_human_readable, :maximum_job_timeout + chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout # Searches for runners matching the given query. # diff --git a/app/views/projects/runners/_form.html.haml b/app/views/projects/runners/_form.html.haml index 7e9435e0110..6a681736b6f 100644 --- a/app/views/projects/runners/_form.html.haml +++ b/app/views/projects/runners/_form.html.haml @@ -40,10 +40,10 @@ .col-sm-10 = f.text_field :description, class: 'form-control' .form-group - = label_tag :maximum_job_timeout_human_readable, class: 'control-label' do + = label_tag :maximum_timeout_human_readable, class: 'control-label' do Maximum job timeout .col-sm-10 - = f.text_field :maximum_job_timeout_human_readable, class: 'form-control' + = f.text_field :maximum_timeout_human_readable, class: 'form-control' .help-block This timeout will take precedence when lower than Project-defined timeout .form-group = label_tag :tag_list, class: 'control-label' do diff --git a/app/views/projects/runners/show.html.haml b/app/views/projects/runners/show.html.haml index 67084e3d66a..f33e7e25b68 100644 --- a/app/views/projects/runners/show.html.haml +++ b/app/views/projects/runners/show.html.haml @@ -57,7 +57,7 @@ %td= @runner.description %tr %td Maximum job timeout - %td= @runner.maximum_job_timeout_human_readable + %td= @runner.maximum_timeout_human_readable %tr %td Last contact %td diff --git a/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb deleted file mode 100644 index 1ad7bb86e72..00000000000 --- a/db/migrate/20180219153455_add_maximum_job_timeout_to_ci_runners.rb +++ /dev/null @@ -1,9 +0,0 @@ -class AddMaximumJobTimeoutToCiRunners < ActiveRecord::Migration - include Gitlab::Database::MigrationHelpers - - DOWNTIME = false - - def change - add_column :ci_runners, :maximum_job_timeout, :integer - end -end diff --git a/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb b/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb new file mode 100644 index 00000000000..072e696a43e --- /dev/null +++ b/db/migrate/20180219153455_add_maximum_timeout_to_ci_runners.rb @@ -0,0 +1,9 @@ +class AddMaximumTimeoutToCiRunners < ActiveRecord::Migration + include Gitlab::Database::MigrationHelpers + + DOWNTIME = false + + def change + add_column :ci_runners, :maximum_timeout, :integer + end +end diff --git a/db/schema.rb b/db/schema.rb index 920d71e0110..a2967409703 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -463,7 +463,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do t.boolean "run_untagged", default: true, null: false t.boolean "locked", default: false, null: false t.integer "access_level", default: 0, null: false - t.integer "maximum_job_timeout" + t.integer "maximum_timeout" t.string "ip_address" end diff --git a/doc/api/runners.md b/doc/api/runners.md index 348fd499af2..f384ac57bfe 100644 --- a/doc/api/runners.md +++ b/doc/api/runners.md @@ -154,7 +154,7 @@ Example response: ], "version": null, "access_level": "ref_protected", - "maximum_job_timeout": 3600 + "maximum_timeout": 3600 } ``` @@ -175,7 +175,7 @@ PUT /runners/:id | `run_untagged` | boolean | no | Flag indicating the runner can execute untagged jobs | | `locked` | boolean | no | Flag indicating the runner is locked | | `access_level` | string | no | The access_level of the runner; `not_protected` or `ref_protected` | -| `maximum_job_timeout` | integer | no | Maximum timeout set when this Runner will handle the job | +| `maximum_timeout` | integer | no | Maximum timeout set when this Runner will handle the job | ``` curl --request PUT --header "PRIVATE-TOKEN: 9koXpg98eAheJpvBs5tK" "https://gitlab.example.com/api/v4/runners/6" --form "description=test-1-20150125-test" --form "tag_list=ruby,mysql,tag1,tag2" @@ -214,7 +214,7 @@ Example response: ], "version": null, "access_level": "ref_protected", - "maximum_job_timeout": null + "maximum_timeout": null } ``` diff --git a/lib/api/entities.rb b/lib/api/entities.rb index bb18fa00dc6..324e14f5654 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -951,7 +951,7 @@ module API expose :tag_list expose :run_untagged expose :locked - expose :maximum_job_timeout + expose :maximum_timeout expose :access_level expose :version, :revision, :platform, :architecture expose :contacted_at diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 73d5993701c..95f57e6402d 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,12 +14,12 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :maximum_job_timeout, type: String, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_timeout, type: String, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list]) .merge(get_runner_details_from_request) - .merge(maximum_job_timeout_human_readable: params[:maximum_job_timeout]) + .merge(maximum_timeout_human_readable: params[:maximum_timeout]) runner = if runner_registration_token_valid? diff --git a/lib/api/runners.rb b/lib/api/runners.rb index b3037235353..5f2a9567605 100644 --- a/lib/api/runners.rb +++ b/lib/api/runners.rb @@ -57,7 +57,7 @@ module API optional :locked, type: Boolean, desc: 'Flag indicating the runner is locked' optional :access_level, type: String, values: Ci::Runner.access_levels.keys, desc: 'The access_level of the runner' - optional :maximum_job_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' at_least_one_of :description, :active, :tag_list, :run_untagged, :locked, :access_level end put ':id' do diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 670803a0883..abb2d0b5ecb 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1288,7 +1288,7 @@ describe Ci::Build do end context 'when runner sets timeout to bigger value' do - let(:runner2) { create(:ci_runner, maximum_job_timeout: 2000) } + let(:runner2) { create(:ci_runner, maximum_timeout: 2000) } let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do @@ -1297,11 +1297,11 @@ describe Ci::Build do end context 'when runner sets timeout to smaller value' do - let(:runner2) { create(:ci_runner, maximum_job_timeout: 500) } + let(:runner2) { create(:ci_runner, maximum_timeout: 500) } let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do - is_expected.to eq(runner2.maximum_job_timeout) + is_expected.to eq(runner2.maximum_timeout) end end end @@ -2064,7 +2064,7 @@ describe Ci::Build do let(:expected_timeout_source) { 'runner_timeout_source' } before do - runner.maximum_job_timeout = 900 + runner.maximum_timeout = 900 runner.save! end @@ -2076,7 +2076,7 @@ describe Ci::Build do let(:expected_timeout_source) { 'project_timeout_source' } before do - runner.maximum_job_timeout = 3600 + runner.maximum_timeout = 3600 runner.save! end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index b25475d4fdb..c991d12a5fc 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -55,8 +55,8 @@ shared_examples 'ChronicDurationAttribute writer' do end describe 'ChronicDurationAttribute' do - let(:source_field) {:maximum_job_timeout} - let(:virtual_field) {:maximum_job_timeout_human_readable} + let(:source_field) {:maximum_timeout} + let(:virtual_field) {:maximum_timeout_human_readable} subject { Ci::Runner.new } diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 0ca7ba7154e..6676ceaab66 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -112,19 +112,19 @@ describe API::Runner do context 'when maximum job timeout is specified' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_job_timeout: '2h 30m' + maximum_timeout: '2h 30m' expect(response).to have_gitlab_http_status 201 - expect(Ci::Runner.first.maximum_job_timeout).to eq(9000) + expect(Ci::Runner.first.maximum_timeout).to eq(9000) end context 'when maximum job timeout is empty' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_job_timeout: '' + maximum_timeout: '' expect(response).to have_gitlab_http_status 201 - expect(Ci::Runner.first.maximum_job_timeout).to be_nil + expect(Ci::Runner.first.maximum_timeout).to be_nil end end end @@ -681,7 +681,7 @@ describe API::Runner do end context 'when runner specifies lower timeout' do - let(:runner) { create(:ci_runner, maximum_job_timeout: 1000) } + let(:runner) { create(:ci_runner, maximum_timeout: 1000) } it 'contains info about timeout overridden by runner' do request_job @@ -692,7 +692,7 @@ describe API::Runner do end context 'when runner specifies bigger timeout' do - let(:runner) { create(:ci_runner, maximum_job_timeout: 2000) } + let(:runner) { create(:ci_runner, maximum_timeout: 2000) } it 'contains info about timeout not overridden by runner' do request_job diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 836b22f5657..247e8795ed4 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -123,7 +123,7 @@ describe API::Runners do expect(response).to have_gitlab_http_status(200) expect(json_response['description']).to eq(shared_runner.description) - expect(json_response['maximum_job_timeout']).to be_nil + expect(json_response['maximum_timeout']).to be_nil end end @@ -194,7 +194,7 @@ describe API::Runners do run_untagged: 'false', locked: 'true', access_level: 'ref_protected', - maximum_job_timeout: 1234 ) + maximum_timeout: 1234 ) shared_runner.reload expect(response).to have_gitlab_http_status(200) @@ -206,7 +206,7 @@ describe API::Runners do expect(shared_runner.ref_protected?).to be_truthy expect(shared_runner.ensure_runner_queue_value) .not_to eq(runner_queue_value) - expect(shared_runner.maximum_job_timeout).to eq(1234) + expect(shared_runner.maximum_timeout).to eq(1234) end end From d58d3098f159a17fbcf1ae27165c249722990988 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 6 Mar 2018 16:25:13 +0100 Subject: [PATCH 34/48] Rename used_timeout to timeout --- .../javascripts/jobs/components/sidebar_details_block.vue | 4 ++-- app/models/ci/build.rb | 2 +- app/models/ci/build_metadata.rb | 4 ++-- app/serializers/build_metadata_entity.rb | 4 ++-- db/migrate/20180301010859_create_ci_builds_metadata_table.rb | 2 +- db/schema.rb | 4 ++-- spec/javascripts/jobs/mock_data.js | 2 +- spec/models/ci/build_spec.rb | 4 ++-- spec/models/concerns/chronic_duration_attribute_spec.rb | 4 ++-- 9 files changed, 15 insertions(+), 15 deletions(-) diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 15584922d1f..6ff3fa6e099 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -45,7 +45,7 @@ return `#${this.job.runner.id}`; }, timeout() { - let t = `${this.job.metadata.used_timeout_human_readable}`; + let t = `${this.job.metadata.timeout_human_readable}`; if (this.job.metadata.timeout_source != null) { t += ` (from ${this.job.metadata.timeout_source})`; @@ -130,7 +130,7 @@ /> Date: Tue, 6 Mar 2018 16:43:44 +0100 Subject: [PATCH 35/48] BuildMetadata styling improvements --- app/models/ci/build_metadata.rb | 10 +++++----- spec/models/ci/build_spec.rb | 15 +++++++-------- 2 files changed, 12 insertions(+), 13 deletions(-) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 335209e8ec2..424b6da1014 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -19,13 +19,13 @@ module Ci } def save_timeout_state! + return unless build.runner.present? + project_timeout = build.project&.build_timeout - timeout = [project_timeout, build.runner&.maximum_timeout].compact.min + timeout = [project_timeout, build.runner.maximum_timeout].compact.min + timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source - self.timeout = timeout - self.timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source - - save! + update_attributes(timeout: timeout, timeout_source: timeout_source) end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index cdc2c5a8679..6affd05d8aa 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1283,8 +1283,10 @@ describe Ci::Build do build.run! end - it 'returns project timeout configuration' do - is_expected.to eq(project2.build_timeout) + context 'when runner is not assigned' do + it 'returns project timeout configuration' do + is_expected.to be_nil + end end context 'when runner sets timeout to bigger value' do @@ -2045,8 +2047,7 @@ describe Ci::Build do let(:job) { create(:ci_build, :pending, runner: runner) } before do - job.project.build_timeout = 1800 - job.project.save! + job.project.update_attribute(:build_timeout, 1800) end shared_examples 'saves data on transition' do @@ -2064,8 +2065,7 @@ describe Ci::Build do let(:expected_timeout_source) { 'runner_timeout_source' } before do - runner.maximum_timeout = 900 - runner.save! + runner.update_attribute(:maximum_timeout, 900) end it_behaves_like 'saves data on transition' @@ -2076,8 +2076,7 @@ describe Ci::Build do let(:expected_timeout_source) { 'project_timeout_source' } before do - runner.maximum_timeout = 3600 - runner.save! + runner.update_attribute(:maximum_timeout, 3600) end it_behaves_like 'saves data on transition' From 4c3482345a3bcc9ac5d29f5dbc55c966ca5a0c72 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 7 Mar 2018 23:53:15 +0100 Subject: [PATCH 36/48] Fix sidebar_detail_row_spec.js --- .../jobs/sidebar_detail_row_spec.js | 28 +++++++++++-------- 1 file changed, 16 insertions(+), 12 deletions(-) diff --git a/spec/javascripts/jobs/sidebar_detail_row_spec.js b/spec/javascripts/jobs/sidebar_detail_row_spec.js index 9fe5cf78b8b..e6bfb0c4adc 100644 --- a/spec/javascripts/jobs/sidebar_detail_row_spec.js +++ b/spec/javascripts/jobs/sidebar_detail_row_spec.js @@ -38,20 +38,24 @@ describe('Sidebar detail row', () => { ).toEqual('this is the title: this is the value'); }); - it('should not render help when helpUrl not provided', () => { - expect(vm.$el.querySelector('.help-button')).toBeUndefined(); + describe('when helpUrl not provided', () => { + it('should not render help', () => { + expect(vm.$el.querySelector('.help-button')).toBeNull(); + }); }); - beforeEach(() => { - vm = new SidebarDetailRow({ - propsData: { - helpUrl: 'help url', - value: 'foo', - }, - }).$mount(); - }); + describe('when helpUrl provided', () => { + beforeEach(() => { + vm = new SidebarDetailRow({ + propsData: { + helpUrl: 'help url', + value: 'foo', + }, + }).$mount(); + }); - it('should render help when helpUrl is provided', () => { - expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); + it('should render help', () => { + expect(vm.$el.querySelector('.help-button a').getAttribute('href')).toEqual('help url'); + }); }); }); From afcc57abfdcb11001803655f938187cbdc96b67c Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Tue, 20 Mar 2018 23:21:17 +0100 Subject: [PATCH 37/48] Rename metadata relation and methods --- app/models/ci/build.rb | 10 +++++----- app/serializers/build_details_entity.rb | 4 +++- spec/models/ci/build_spec.rb | 4 ++-- spec/services/ci/retry_build_service_spec.rb | 2 +- 4 files changed, 11 insertions(+), 9 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 405c89d0103..0b3c6ac4fee 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -24,7 +24,7 @@ module Ci has_one :job_artifacts_metadata, -> { where(file_type: Ci::JobArtifact.file_types[:metadata]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id has_one :job_artifacts_trace, -> { where(file_type: Ci::JobArtifact.file_types[:trace]) }, class_name: 'Ci::JobArtifact', inverse_of: :job, foreign_key: :job_id - has_one :build_metadata, class_name: 'Ci::BuildMetadata' + has_one :metadata, class_name: 'Ci::BuildMetadata' # The "environment" field for builds is a String, and is the unexpanded name def persisted_environment @@ -157,12 +157,12 @@ module Ci end before_transition pending: :running do |build| - build.metadata.save_timeout_state! + build.ensure_metadata.save_timeout_state! end end - def metadata - self.build_metadata ||= Ci::BuildMetadata.new + def ensure_metadata + metadata || build_metadata end def detailed_status(current_user) @@ -242,7 +242,7 @@ module Ci end def timeout - metadata.timeout + ensure_metadata.timeout end def triggered_by?(current_user) diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index ca4480fe2b1..99ca0bd158b 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -5,7 +5,9 @@ class BuildDetailsEntity < JobEntity expose :runner, using: RunnerEntity expose :pipeline, using: PipelineEntity - expose :metadata, using: BuildMetadataEntity + expose :metadata, using: BuildMetadataEntity do |build| + build.ensure_metadata + end expose :erased_by, if: -> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 6affd05d8aa..b209d53ae08 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2052,11 +2052,11 @@ describe Ci::Build do shared_examples 'saves data on transition' do it 'saves timeout' do - expect { job.run! }.to change { job.reload.metadata.timeout }.from(nil).to(expected_timeout) + expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout) end it 'saves timeout_source' do - expect { job.run! }.to change { job.reload.metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) + expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) end end diff --git a/spec/services/ci/retry_build_service_spec.rb b/spec/services/ci/retry_build_service_spec.rb index 415302e84f2..8de0bdf92e2 100644 --- a/spec/services/ci/retry_build_service_spec.rb +++ b/spec/services/ci/retry_build_service_spec.rb @@ -30,7 +30,7 @@ describe Ci::RetryBuildService do runner_id tag_taggings taggings tags trigger_request_id user_id auto_canceled_by_id retried failure_reason artifacts_file_store artifacts_metadata_store - build_metadata].freeze + metadata].freeze shared_examples 'build duplication' do let(:another_pipeline) { create(:ci_empty_pipeline, project: project) } From a1cd3390e550c0008e83146e8627fdee3f58e422 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 01:20:15 +0100 Subject: [PATCH 38/48] Add project_id column to Ci::BuildMetadata --- app/models/ci/build_metadata.rb | 11 +++++++++++ .../20180301010859_create_ci_builds_metadata_table.rb | 4 ++++ db/schema.rb | 4 ++++ 3 files changed, 19 insertions(+) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 424b6da1014..c221f43384b 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -9,9 +9,12 @@ module Ci self.table_name = 'ci_builds_metadata' belongs_to :build, class_name: 'Ci::Build' + belongs_to :project chronic_duration_attr_reader :timeout_human_readable, :timeout + after_initialize :set_project_id + enum timeout_source: { unknown_timeout_source: 1, project_timeout_source: 2, @@ -27,5 +30,13 @@ module Ci update_attributes(timeout: timeout, timeout_source: timeout_source) end + + private + + def set_project_id + return unless self.project_id.nil? + + self.project_id = build&.project&.id + end end end diff --git a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb index 72c204026d8..650bf43835d 100644 --- a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb +++ b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb @@ -6,11 +6,15 @@ class CreateCiBuildsMetadataTable < ActiveRecord::Migration def change create_table :ci_builds_metadata, id: false do |t| t.integer :build_id, null: false + t.integer :project_id, null: false t.integer :timeout t.integer :timeout_source, null: false, default: 1 t.primary_key :build_id t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade + t.foreign_key :projects, column: :project_id, on_delete: :cascade + + t.index :project_id end end end diff --git a/db/schema.rb b/db/schema.rb index a14823dfa15..56541aa4ecd 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -330,10 +330,13 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree create_table "ci_builds_metadata", primary_key: "build_id", force: :cascade do |t| + t.integer "project_id", null: false t.integer "timeout" t.integer "timeout_source", default: 1, null: false end + add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree + create_table "ci_group_variables", force: :cascade do |t| t.string "key", null: false t.text "value" @@ -2035,6 +2038,7 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_foreign_key "ci_builds", "ci_stages", column: "stage_id", name: "fk_3a9eaa254d", on_delete: :cascade add_foreign_key "ci_builds", "projects", name: "fk_befce0568a", on_delete: :cascade add_foreign_key "ci_builds_metadata", "ci_builds", column: "build_id", on_delete: :cascade + add_foreign_key "ci_builds_metadata", "projects", on_delete: :cascade add_foreign_key "ci_group_variables", "namespaces", column: "group_id", name: "fk_33ae4d58d8", on_delete: :cascade add_foreign_key "ci_job_artifacts", "ci_builds", column: "job_id", on_delete: :cascade add_foreign_key "ci_job_artifacts", "projects", on_delete: :cascade From 9ccde9cc1f1ccb70cc455686a5c7aed94adb8876 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 01:23:59 +0100 Subject: [PATCH 39/48] Fix style problem in spec/requests/api/runners_spec.rb --- spec/requests/api/runners_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/requests/api/runners_spec.rb b/spec/requests/api/runners_spec.rb index 247e8795ed4..d30f0cf36e2 100644 --- a/spec/requests/api/runners_spec.rb +++ b/spec/requests/api/runners_spec.rb @@ -194,7 +194,7 @@ describe API::Runners do run_untagged: 'false', locked: 'true', access_level: 'ref_protected', - maximum_timeout: 1234 ) + maximum_timeout: 1234) shared_runner.reload expect(response).to have_gitlab_http_status(200) From 557c85a79f141e435c56d971d813f492978bad61 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 02:02:38 +0100 Subject: [PATCH 40/48] Use raw value of maximum_timeout for Runner registration API --- lib/api/runner.rb | 5 ++--- spec/requests/api/runner_spec.rb | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/api/runner.rb b/lib/api/runner.rb index 95f57e6402d..57c0a729535 100644 --- a/lib/api/runner.rb +++ b/lib/api/runner.rb @@ -14,12 +14,11 @@ module API optional :locked, type: Boolean, desc: 'Should Runner be locked for current project' optional :run_untagged, type: Boolean, desc: 'Should Runner handle untagged jobs' optional :tag_list, type: Array[String], desc: %q(List of Runner's tags) - optional :maximum_timeout, type: String, desc: 'Maximum timeout set when this Runner will handle the job' + optional :maximum_timeout, type: Integer, desc: 'Maximum timeout set when this Runner will handle the job' end post '/' do - attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list]) + attributes = attributes_for_keys([:description, :locked, :run_untagged, :tag_list, :maximum_timeout]) .merge(get_runner_details_from_request) - .merge(maximum_timeout_human_readable: params[:maximum_timeout]) runner = if runner_registration_token_valid? diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 6676ceaab66..4703c64c534 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -112,7 +112,7 @@ describe API::Runner do context 'when maximum job timeout is specified' do it 'creates runner' do post api('/runners'), token: registration_token, - maximum_timeout: '2h 30m' + maximum_timeout: 9000 expect(response).to have_gitlab_http_status 201 expect(Ci::Runner.first.maximum_timeout).to eq(9000) From ed86344f4941ce8b30546260b8437451026c8d97 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 02:58:43 +0100 Subject: [PATCH 41/48] Add tests for Ci::BuildMetadata --- spec/models/ci/build_metadata_spec.rb | 69 +++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) create mode 100644 spec/models/ci/build_metadata_spec.rb diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb new file mode 100644 index 00000000000..4758738cdd0 --- /dev/null +++ b/spec/models/ci/build_metadata_spec.rb @@ -0,0 +1,69 @@ +require 'spec_helper' + +describe Ci::BuildMetadata do + set(:user) { create(:user) } + set(:group) { create(:group, :access_requestable) } + set(:project) { create(:project, :repository, group: group, build_timeout: 2000) } + + set(:pipeline) do + create(:ci_pipeline, project: project, + sha: project.commit.id, + ref: project.default_branch, + status: 'success') + end + + let(:build) { create(:ci_build, pipeline: pipeline) } + let(:build_metadata) { described_class.create(build: build) } + + context 'when creating' do + subject { build_metadata.project_id } + + it 'saves project_id' do + is_expected.to eq(project.id) + end + end + + describe '#save_timeout_state!' do + subject { build_metadata } + + context 'when runner is not assigned to the job' do + it "doesn't change timeout value" do + expect { subject.save_timeout_state! }.not_to change { subject.reload.timeout } + end + + it "doesn't change timeout_source value" do + expect { subject.save_timeout_state! }.not_to change { subject.reload.timeout_source } + end + end + + context 'when runner is assigned to the job' do + before do + build.update_attributes(runner: runner) + end + + context 'when runner timeout is lower than project timeout' do + let(:runner) { create(:ci_runner, maximum_timeout: 1900) } + + it 'sets runner timeout' do + expect { subject.save_timeout_state! }.to change { subject.reload.timeout }.to(1900) + end + + it 'sets runner_timeout_source' do + expect { subject.save_timeout_state! }.to change { subject.reload.timeout_source }.to('runner_timeout_source') + end + end + + context 'when runner timeout is higher than project timeout' do + let(:runner) { create(:ci_runner, maximum_timeout: 2100) } + + it 'sets project timeout' do + expect { subject.save_timeout_state! }.to change { subject.reload.timeout }.to(2000) + end + + it 'sets project_timeout_source' do + expect { subject.save_timeout_state! }.to change { subject.reload.timeout_source }.to('project_timeout_source') + end + end + end + end +end From c747d9bc0b35d2982f45e2e8a0bdaa305f504f38 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 17:22:52 +0100 Subject: [PATCH 42/48] Add validation for chronic_duration_attr_writer --- .../concerns/chronic_duration_attribute.rb | 27 ++++++-- .../chronic_duration_attribute_spec.rb | 66 ++++++++++++++----- 2 files changed, 74 insertions(+), 19 deletions(-) diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index 15095d0b758..ad8934f58d8 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -18,13 +18,32 @@ module ChronicDurationAttribute end def chronic_duration_attr_writer(virtual_attribute, source_attribute) + virtual_attribute_validator = "#{virtual_attribute}_validator".to_sym + validation_error = "#{virtual_attribute}_error".to_sym + + validate virtual_attribute_validator + attr_accessor validation_error + define_method("#{virtual_attribute}=") do |value| - new_value = ChronicDuration.parse(value).to_i unless value.nil? - new_value = nil if !new_value.nil? && new_value <= 0 + begin + self.send("#{validation_error}=", '') # rubocop:disable GitlabSecurity/PublicSend - self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend + new_value = + if value.blank? + nil + else + ChronicDuration.parse(value).to_i + end - new_value + self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend + rescue ChronicDuration::DurationParseError => ex + self.send("#{validation_error}=", ex.message) # rubocop:disable GitlabSecurity/PublicSend + end + end + + define_method(virtual_attribute_validator) do + error = self.send(validation_error) # rubocop:disable GitlabSecurity/PublicSend + self.send('errors').add(source_attribute, error) unless error.blank? # rubocop:disable GitlabSecurity/PublicSend end end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index cfbf83dab54..fea3e752ab5 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -11,10 +11,12 @@ shared_examples 'ChronicDurationAttribute reader' do expect(subject.send(virtual_field)).to eq('2m') end - it 'outputs empty string when value set to nil' do - subject.send("#{source_field}=", nil) + context 'when value is set to nil' do + it 'outputs empty string' do + subject.send("#{source_field}=", nil) - expect(subject.send(virtual_field)).to be_empty + expect(subject.send(virtual_field)).to be_empty + end end end @@ -29,28 +31,62 @@ shared_examples 'ChronicDurationAttribute writer' do expect(subject.send(source_field)).to eq(600) end - it 'writes nil when empty input is used' do - subject.send("#{virtual_field}=", '') + it 'passes validation' do + subject.send("#{virtual_field}=", '10m') - expect(subject.send(source_field)).to be_nil + expect(subject.valid?).to be_truthy end - it 'writes nil when negative input is used' do - allow(ChronicDuration).to receive(:parse).and_return(-10) + context 'when negative input is used' do + before do + subject.send("#{source_field}=", 3600) + end - subject.send("#{virtual_field}=", '-10m') + it "doesn't raise exception" do + expect { subject.send("#{virtual_field}=", '-10m') }.not_to raise_error(ChronicDuration::DurationParseError) + end - expect(subject.send(source_field)).to be_nil + it "doesn't change value" do + expect { subject.send("#{virtual_field}=", '-10m') }.not_to change { subject.send(source_field) } + end + + it "doesn't pass validation" do + subject.send("#{virtual_field}=", '-10m') + + expect(subject.valid?).to be_falsey + end end - it 'writes nil when nil input is used' do - subject.send("#{virtual_field}=", nil) + context 'when empty input is used' do + it 'writes nil' do + subject.send("#{virtual_field}=", '') - expect(subject.send(source_field)).to be_nil + expect(subject.send(source_field)).to be_nil + end + + it 'passes validation' do + subject.send("#{virtual_field}=", '') + + expect(subject.valid?).to be_truthy + end end - it "doesn't raise exception when nil input is used" do - expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError) + context 'when nil input is used' do + it 'writes nil' do + subject.send("#{virtual_field}=", nil) + + expect(subject.send(source_field)).to be_nil + end + + it 'passes validation' do + subject.send("#{virtual_field}=", nil) + + expect(subject.valid?).to be_truthy + end + + it "doesn't raise exception" do + expect { subject.send("#{virtual_field}=", nil) }.not_to raise_error(NoMethodError) + end end end From 7d7b0688b846e346a0799340875d459d26c1718d Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Wed, 21 Mar 2018 17:34:55 +0100 Subject: [PATCH 43/48] Add validation for max_timeout in Ci::Runner --- app/models/ci/runner.rb | 4 ++++ spec/models/ci/build_spec.rb | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/models/ci/runner.rb b/app/models/ci/runner.rb index 62cc636db22..5a4c56ec0dc 100644 --- a/app/models/ci/runner.rb +++ b/app/models/ci/runner.rb @@ -54,6 +54,10 @@ module Ci chronic_duration_attr :maximum_timeout_human_readable, :maximum_timeout + validates :maximum_timeout, allow_nil: true, + numericality: { greater_than_or_equal_to: 600, + message: 'needs to be at least 10 minutes' } + # Searches for runners matching the given query. # # This method uses ILIKE on PostgreSQL and LIKE on MySQL. diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index b209d53ae08..14dec2d4fc9 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1299,7 +1299,7 @@ describe Ci::Build do end context 'when runner sets timeout to smaller value' do - let(:runner2) { create(:ci_runner, maximum_timeout: 500) } + let(:runner2) { create(:ci_runner, maximum_timeout: 600) } let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } it 'returns project timeout configuration' do From 973e4030b13adbcc4eb7fad347b928a5164a04ff Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Thu, 22 Mar 2018 17:52:28 +0100 Subject: [PATCH 44/48] Refactor build_metadata --- .../jobs/components/sidebar_details_block.vue | 12 +++++++++--- app/models/ci/build.rb | 2 +- app/models/ci/build_metadata.rb | 16 ++++------------ app/serializers/build_details_entity.rb | 4 +--- spec/factories/ci/build_metadata.rb | 9 +++++++++ spec/models/ci/build_metadata_spec.rb | 10 +--------- 6 files changed, 25 insertions(+), 28 deletions(-) create mode 100644 spec/factories/ci/build_metadata.rb diff --git a/app/assets/javascripts/jobs/components/sidebar_details_block.vue b/app/assets/javascripts/jobs/components/sidebar_details_block.vue index 6ff3fa6e099..172de6b3679 100644 --- a/app/assets/javascripts/jobs/components/sidebar_details_block.vue +++ b/app/assets/javascripts/jobs/components/sidebar_details_block.vue @@ -44,10 +44,16 @@ runnerId() { return `#${this.job.runner.id}`; }, + hasTimeout() { + return this.job.metadata != null && this.job.metadata.timeout_human_readable !== ''; + }, timeout() { - let t = `${this.job.metadata.timeout_human_readable}`; + if (this.job.metadata == null) { + return ''; + } - if (this.job.metadata.timeout_source != null) { + let t = this.job.metadata.timeout_human_readable; + if (this.job.metadata.timeout_source !== '') { t += ` (from ${this.job.metadata.timeout_source})`; } @@ -130,7 +136,7 @@ /> (*) { build.erased? }, using: UserEntity expose :erase_path, if: -> (*) { build.erasable? && can?(current_user, :erase_build, build) } do |build| diff --git a/spec/factories/ci/build_metadata.rb b/spec/factories/ci/build_metadata.rb new file mode 100644 index 00000000000..66bbd977b88 --- /dev/null +++ b/spec/factories/ci/build_metadata.rb @@ -0,0 +1,9 @@ +FactoryBot.define do + factory :ci_build_metadata, class: Ci::BuildMetadata do + build factory: :ci_build + + after(:build) do |build_metadata, _| + build_metadata.project ||= build_metadata.build.project + end + end +end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index 4758738cdd0..d21e9600e42 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -13,15 +13,7 @@ describe Ci::BuildMetadata do end let(:build) { create(:ci_build, pipeline: pipeline) } - let(:build_metadata) { described_class.create(build: build) } - - context 'when creating' do - subject { build_metadata.project_id } - - it 'saves project_id' do - is_expected.to eq(project.id) - end - end + let(:build_metadata) { create(:ci_build_metadata, build: build) } describe '#save_timeout_state!' do subject { build_metadata } From c2bc153314f14566abfdcbf92020cff41fc05a7e Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Fri, 23 Mar 2018 00:12:15 +0100 Subject: [PATCH 45/48] Refactorize ChronicDurationAttribute concern --- .../concerns/chronic_duration_attribute.rb | 51 ++++++++----------- .../chronic_duration_attribute_spec.rb | 24 +++++---- 2 files changed, 33 insertions(+), 42 deletions(-) diff --git a/app/models/concerns/chronic_duration_attribute.rb b/app/models/concerns/chronic_duration_attribute.rb index ad8934f58d8..fa1eafb1d7a 100644 --- a/app/models/concerns/chronic_duration_attribute.rb +++ b/app/models/concerns/chronic_duration_attribute.rb @@ -2,49 +2,38 @@ module ChronicDurationAttribute extend ActiveSupport::Concern class_methods do - def chronic_duration_attr(virtual_attribute, source_attribute) - chronic_duration_attr_reader(virtual_attribute, source_attribute) - chronic_duration_attr_writer(virtual_attribute, source_attribute) - end - def chronic_duration_attr_reader(virtual_attribute, source_attribute) define_method(virtual_attribute) do - value = self.send(source_attribute) # rubocop:disable GitlabSecurity/PublicSend - - return '' if value.nil? - - ChronicDuration.output(value, format: :short) + chronic_duration_attributes[virtual_attribute] || output_chronic_duration_attribute(source_attribute) end end def chronic_duration_attr_writer(virtual_attribute, source_attribute) - virtual_attribute_validator = "#{virtual_attribute}_validator".to_sym - validation_error = "#{virtual_attribute}_error".to_sym - - validate virtual_attribute_validator - attr_accessor validation_error + chronic_duration_attr_reader(virtual_attribute, source_attribute) define_method("#{virtual_attribute}=") do |value| + chronic_duration_attributes[virtual_attribute] = value.presence || '' + begin - self.send("#{validation_error}=", '') # rubocop:disable GitlabSecurity/PublicSend - - new_value = - if value.blank? - nil - else - ChronicDuration.parse(value).to_i - end - - self.send("#{source_attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend - rescue ChronicDuration::DurationParseError => ex - self.send("#{validation_error}=", ex.message) # rubocop:disable GitlabSecurity/PublicSend + new_value = ChronicDuration.parse(value).to_i if value.present? + assign_attributes(source_attribute => new_value) + rescue ChronicDuration::DurationParseError + # ignore error as it will be caught by validation end end - define_method(virtual_attribute_validator) do - error = self.send(validation_error) # rubocop:disable GitlabSecurity/PublicSend - self.send('errors').add(source_attribute, error) unless error.blank? # rubocop:disable GitlabSecurity/PublicSend - end + validates virtual_attribute, allow_nil: true, duration: true end + + alias_method :chronic_duration_attr, :chronic_duration_attr_writer + end + + def chronic_duration_attributes + @chronic_duration_attributes ||= {} + end + + def output_chronic_duration_attribute(source_attribute) + value = attributes[source_attribute.to_s] + ChronicDuration.output(value, format: :short) if value end end diff --git a/spec/models/concerns/chronic_duration_attribute_spec.rb b/spec/models/concerns/chronic_duration_attribute_spec.rb index fea3e752ab5..27c86e60e60 100644 --- a/spec/models/concerns/chronic_duration_attribute_spec.rb +++ b/spec/models/concerns/chronic_duration_attribute_spec.rb @@ -12,10 +12,10 @@ shared_examples 'ChronicDurationAttribute reader' do end context 'when value is set to nil' do - it 'outputs empty string' do + it 'outputs nil' do subject.send("#{source_field}=", nil) - expect(subject.send(virtual_field)).to be_empty + expect(subject.send(virtual_field)).to be_nil end end end @@ -25,15 +25,15 @@ shared_examples 'ChronicDurationAttribute writer' do expect(subject.class).to be_public_method_defined("#{virtual_field}=") end - it 'parses chronic duration input' do + before do subject.send("#{virtual_field}=", '10m') + end + it 'parses chronic duration input' do expect(subject.send(source_field)).to eq(600) end it 'passes validation' do - subject.send("#{virtual_field}=", '10m') - expect(subject.valid?).to be_truthy end @@ -54,33 +54,34 @@ shared_examples 'ChronicDurationAttribute writer' do subject.send("#{virtual_field}=", '-10m') expect(subject.valid?).to be_falsey + expect(subject.errors&.messages).to include(virtual_field => ['is not a correct duration']) end end context 'when empty input is used' do - it 'writes nil' do + before do subject.send("#{virtual_field}=", '') + end + it 'writes nil' do expect(subject.send(source_field)).to be_nil end it 'passes validation' do - subject.send("#{virtual_field}=", '') - expect(subject.valid?).to be_truthy end end context 'when nil input is used' do - it 'writes nil' do + before do subject.send("#{virtual_field}=", nil) + end + it 'writes nil' do expect(subject.send(source_field)).to be_nil end it 'passes validation' do - subject.send("#{virtual_field}=", nil) - expect(subject.valid?).to be_truthy end @@ -103,6 +104,7 @@ end describe 'ChronicDurationAttribute - reader' do let(:source_field) {:timeout} let(:virtual_field) {:timeout_human_readable} + subject {Ci::BuildMetadata.new} it "doesn't contain dynamically created writer method" do From 7008ed1e33bff510126a0eb0e4f7bf1a7adb02bd Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Mar 2018 17:47:46 +0200 Subject: [PATCH 46/48] Change and rename behavior of save_timeout_state! --- app/models/ci/build.rb | 2 +- app/models/ci/build_metadata.rb | 4 ++-- spec/models/ci/build_metadata_spec.rb | 14 +++++++------- spec/models/ci/build_spec.rb | 23 +++++++++++++++++++++++ 4 files changed, 33 insertions(+), 10 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 355d1c0523f..073d73f0426 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -157,7 +157,7 @@ module Ci end before_transition pending: :running do |build| - build.ensure_metadata.save_timeout_state! + build.ensure_metadata.update_timeout_state end end diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index 6d8a895d509..de5b4170201 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -21,14 +21,14 @@ module Ci runner_timeout_source: 3 } - def save_timeout_state! + def update_timeout_state return unless build.runner.present? project_timeout = project&.build_timeout timeout = [project_timeout, build.runner.maximum_timeout].compact.min timeout_source = timeout < project_timeout ? :runner_timeout_source : :project_timeout_source - update!(timeout: timeout, timeout_source: timeout_source) + update(timeout: timeout, timeout_source: timeout_source) end end end diff --git a/spec/models/ci/build_metadata_spec.rb b/spec/models/ci/build_metadata_spec.rb index d21e9600e42..268561ee941 100644 --- a/spec/models/ci/build_metadata_spec.rb +++ b/spec/models/ci/build_metadata_spec.rb @@ -15,16 +15,16 @@ describe Ci::BuildMetadata do let(:build) { create(:ci_build, pipeline: pipeline) } let(:build_metadata) { create(:ci_build_metadata, build: build) } - describe '#save_timeout_state!' do + describe '#update_timeout_state' do subject { build_metadata } context 'when runner is not assigned to the job' do it "doesn't change timeout value" do - expect { subject.save_timeout_state! }.not_to change { subject.reload.timeout } + expect { subject.update_timeout_state }.not_to change { subject.reload.timeout } end it "doesn't change timeout_source value" do - expect { subject.save_timeout_state! }.not_to change { subject.reload.timeout_source } + expect { subject.update_timeout_state }.not_to change { subject.reload.timeout_source } end end @@ -37,11 +37,11 @@ describe Ci::BuildMetadata do let(:runner) { create(:ci_runner, maximum_timeout: 1900) } it 'sets runner timeout' do - expect { subject.save_timeout_state! }.to change { subject.reload.timeout }.to(1900) + expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(1900) end it 'sets runner_timeout_source' do - expect { subject.save_timeout_state! }.to change { subject.reload.timeout_source }.to('runner_timeout_source') + expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('runner_timeout_source') end end @@ -49,11 +49,11 @@ describe Ci::BuildMetadata do let(:runner) { create(:ci_runner, maximum_timeout: 2100) } it 'sets project timeout' do - expect { subject.save_timeout_state! }.to change { subject.reload.timeout }.to(2000) + expect { subject.update_timeout_state }.to change { subject.reload.timeout }.to(2000) end it 'sets project_timeout_source' do - expect { subject.save_timeout_state! }.to change { subject.reload.timeout_source }.to('project_timeout_source') + expect { subject.update_timeout_state }.to change { subject.reload.timeout_source }.to('project_timeout_source') end end end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 14dec2d4fc9..920b2284eb1 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2050,6 +2050,11 @@ describe Ci::Build do job.project.update_attribute(:build_timeout, 1800) end + def run_job_without_exception + job.run! + rescue StateMachines::InvalidTransition + end + shared_examples 'saves data on transition' do it 'saves timeout' do expect { job.run! }.to change { job.reload.ensure_metadata.timeout }.from(nil).to(expected_timeout) @@ -2058,6 +2063,24 @@ describe Ci::Build do it 'saves timeout_source' do expect { job.run! }.to change { job.reload.ensure_metadata.timeout_source }.from('unknown_timeout_source').to(expected_timeout_source) end + + context 'when Ci::BuildMetadata#update_timeout_state fails update' do + before do + allow_any_instance_of(Ci::BuildMetadata).to receive(:update_timeout_state).and_return(false) + end + + it "doesn't save timeout" do + expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source } + end + + it "doesn't save timeout_source" do + expect { run_job_without_exception }.not_to change { job.reload.ensure_metadata.timeout_source } + end + + it 'raises an exception' do + expect { job.run! }.to raise_error(StateMachines::InvalidTransition) + end + end end context 'when runner timeout overrides project timeout' do From 403decbbad26bea620125aed34b440a9b6611172 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Mar 2018 18:58:35 +0200 Subject: [PATCH 47/48] Add explicit primary key for ci_builds_metadata table --- app/models/ci/build_metadata.rb | 1 + db/migrate/20180301010859_create_ci_builds_metadata_table.rb | 4 ++-- db/schema.rb | 4 +++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/app/models/ci/build_metadata.rb b/app/models/ci/build_metadata.rb index de5b4170201..96762f8845c 100644 --- a/app/models/ci/build_metadata.rb +++ b/app/models/ci/build_metadata.rb @@ -11,6 +11,7 @@ module Ci belongs_to :build, class_name: 'Ci::Build' belongs_to :project + validates :build, presence: true validates :project, presence: true chronic_duration_attr_reader :timeout_human_readable, :timeout diff --git a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb index 650bf43835d..ce737444092 100644 --- a/db/migrate/20180301010859_create_ci_builds_metadata_table.rb +++ b/db/migrate/20180301010859_create_ci_builds_metadata_table.rb @@ -4,16 +4,16 @@ class CreateCiBuildsMetadataTable < ActiveRecord::Migration DOWNTIME = false def change - create_table :ci_builds_metadata, id: false do |t| + create_table :ci_builds_metadata do |t| t.integer :build_id, null: false t.integer :project_id, null: false t.integer :timeout t.integer :timeout_source, null: false, default: 1 - t.primary_key :build_id t.foreign_key :ci_builds, column: :build_id, on_delete: :cascade t.foreign_key :projects, column: :project_id, on_delete: :cascade + t.index :build_id, unique: true t.index :project_id end end diff --git a/db/schema.rb b/db/schema.rb index 56541aa4ecd..3d3a4c0ce4a 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -329,12 +329,14 @@ ActiveRecord::Schema.define(version: 20180327101207) do add_index "ci_builds", ["updated_at"], name: "index_ci_builds_on_updated_at", using: :btree add_index "ci_builds", ["user_id"], name: "index_ci_builds_on_user_id", using: :btree - create_table "ci_builds_metadata", primary_key: "build_id", force: :cascade do |t| + create_table "ci_builds_metadata", force: :cascade do |t| + t.integer "build_id", null: false t.integer "project_id", null: false t.integer "timeout" t.integer "timeout_source", default: 1, null: false end + add_index "ci_builds_metadata", ["build_id"], name: "index_ci_builds_metadata_on_build_id", unique: true, using: :btree add_index "ci_builds_metadata", ["project_id"], name: "index_ci_builds_metadata_on_project_id", using: :btree create_table "ci_group_variables", force: :cascade do |t| From 6ecde0076afa83e30608ea9caba924bbab66a123 Mon Sep 17 00:00:00 2001 From: Tomasz Maczukin Date: Mon, 26 Mar 2018 19:26:52 +0200 Subject: [PATCH 48/48] Remove Ci::Build#timeout --- app/models/ci/build.rb | 6 ++--- lib/api/entities.rb | 2 +- lib/gitlab/ci/build/step.rb | 4 +-- spec/lib/gitlab/ci/build/step_spec.rb | 12 +++++++-- spec/models/ci/build_spec.rb | 37 --------------------------- spec/requests/api/runner_spec.rb | 4 +-- 6 files changed, 17 insertions(+), 48 deletions(-) diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb index 073d73f0426..ed02af05e3d 100644 --- a/app/models/ci/build.rb +++ b/app/models/ci/build.rb @@ -26,6 +26,8 @@ module Ci has_one :metadata, class_name: 'Ci::BuildMetadata' + delegate :timeout, to: :metadata, prefix: true, allow_nil: true + # The "environment" field for builds is a String, and is the unexpanded name def persisted_environment @persisted_environment ||= Environment.find_by( @@ -241,10 +243,6 @@ module Ci latest_builds.where('stage_idx < ?', stage_idx) end - def timeout - ensure_metadata.timeout - end - def triggered_by?(current_user) user == current_user end diff --git a/lib/api/entities.rb b/lib/api/entities.rb index 324e14f5654..38161d1f127 100644 --- a/lib/api/entities.rb +++ b/lib/api/entities.rb @@ -1120,7 +1120,7 @@ module API end class RunnerInfo < Grape::Entity - expose :timeout + expose :metadata_timeout, as: :timeout end class Step < Grape::Entity diff --git a/lib/gitlab/ci/build/step.rb b/lib/gitlab/ci/build/step.rb index 411f67f8ce7..0b1ebe4e048 100644 --- a/lib/gitlab/ci/build/step.rb +++ b/lib/gitlab/ci/build/step.rb @@ -14,7 +14,7 @@ module Gitlab self.new(:script).tap do |step| step.script = job.options[:before_script].to_a + job.options[:script].to_a step.script = job.commands.split("\n") if step.script.empty? - step.timeout = job.timeout + step.timeout = job.metadata_timeout step.when = WHEN_ON_SUCCESS end end @@ -25,7 +25,7 @@ module Gitlab self.new(:after_script).tap do |step| step.script = after_script - step.timeout = job.timeout + step.timeout = job.metadata_timeout step.when = WHEN_ALWAYS step.allow_failure = true end diff --git a/spec/lib/gitlab/ci/build/step_spec.rb b/spec/lib/gitlab/ci/build/step_spec.rb index 5a21282712a..cce4efaa069 100644 --- a/spec/lib/gitlab/ci/build/step_spec.rb +++ b/spec/lib/gitlab/ci/build/step_spec.rb @@ -5,10 +5,14 @@ describe Gitlab::Ci::Build::Step do shared_examples 'has correct script' do subject { described_class.from_commands(job) } + before do + job.run! + end + it 'fabricates an object' do expect(subject.name).to eq(:script) expect(subject.script).to eq(script) - expect(subject.timeout).to eq(job.timeout) + expect(subject.timeout).to eq(job.metadata_timeout) expect(subject.when).to eq('on_success') expect(subject.allow_failure).to be_falsey end @@ -47,6 +51,10 @@ describe Gitlab::Ci::Build::Step do subject { described_class.from_after_script(job) } + before do + job.run! + end + context 'when after_script is empty' do it 'doesn not fabricate an object' do is_expected.to be_nil @@ -59,7 +67,7 @@ describe Gitlab::Ci::Build::Step do it 'fabricates an object' do expect(subject.name).to eq(:after_script) expect(subject.script).to eq(['ls -la', 'date']) - expect(subject.timeout).to eq(job.timeout) + expect(subject.timeout).to eq(job.metadata_timeout) expect(subject.when).to eq('always') expect(subject.allow_failure).to be_truthy end diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 920b2284eb1..f5534d22a54 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -1271,43 +1271,6 @@ describe Ci::Build do end describe 'project settings' do - describe '#timeout' do - set(:project2) { create(:project, :repository, group: group, build_timeout: 1000) } - set(:pipeline2) { create(:ci_pipeline, project: project2, sha: project2.commit.id, ref: project2.default_branch, status: 'success') } - - let(:build) { create(:ci_build, :pending, pipeline: pipeline2) } - - subject { build.timeout } - - before do - build.run! - end - - context 'when runner is not assigned' do - it 'returns project timeout configuration' do - is_expected.to be_nil - end - end - - context 'when runner sets timeout to bigger value' do - let(:runner2) { create(:ci_runner, maximum_timeout: 2000) } - let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } - - it 'returns project timeout configuration' do - is_expected.to eq(project2.build_timeout) - end - end - - context 'when runner sets timeout to smaller value' do - let(:runner2) { create(:ci_runner, maximum_timeout: 600) } - let(:build) { create(:ci_build, :pending, pipeline: pipeline2, runner: runner2) } - - it 'returns project timeout configuration' do - is_expected.to eq(runner2.maximum_timeout) - end - end - end - describe '#allow_git_fetch' do it 'return project allow_git_fetch configuration' do expect(build.allow_git_fetch).to eq(project.build_allow_git_fetch) diff --git a/spec/requests/api/runner_spec.rb b/spec/requests/api/runner_spec.rb index 4703c64c534..5084b36c761 100644 --- a/spec/requests/api/runner_spec.rb +++ b/spec/requests/api/runner_spec.rb @@ -360,12 +360,12 @@ describe API::Runner do let(:expected_steps) do [{ 'name' => 'script', 'script' => %w(ls date), - 'timeout' => job.timeout, + 'timeout' => job.metadata_timeout, 'when' => 'on_success', 'allow_failure' => false }, { 'name' => 'after_script', 'script' => %w(ls date), - 'timeout' => job.timeout, + 'timeout' => job.metadata_timeout, 'when' => 'always', 'allow_failure' => true }] end