summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKamil Trzcinski <ayufan@ayufan.eu>2016-11-29 18:47:18 +0100
committerKamil Trzcinski <ayufan@ayufan.eu>2016-11-29 18:47:18 +0100
commit54ccd409c200b13f7de4d349a5b4f05912e90167 (patch)
tree1a0218e2188118db99ca2aaf33a80f818377a1f8
parent1e62a13968cc4351684f919630cd426e20fc022a (diff)
downloadgitlab-ce-introduce-build-minutes.tar.gz
Rough implementation of build minutes for shared runners [ci skip]introduce-build-minutes
-rw-r--r--app/controllers/admin/application_settings_controller.rb1
-rw-r--r--app/models/application_setting.rb3
-rw-r--r--app/models/ci/build.rb6
-rw-r--r--app/models/project.rb21
-rw-r--r--app/models/project_metrics.rb5
-rw-r--r--app/services/ci/register_build_service.rb43
-rw-r--r--app/services/update_build_minutes_service.rb10
-rw-r--r--app/views/admin/application_settings/_form.html.haml7
-rw-r--r--app/views/admin/projects/show.html.haml14
-rw-r--r--app/views/projects/builds/show.html.haml13
-rw-r--r--app/workers/clear_shared_runner_minutes_worker.rb8
-rw-r--r--config/initializers/1_settings.rb4
-rw-r--r--db/migrate/20161129161815_add_shared_runners_minutes_to_application_settings.rb9
-rw-r--r--db/migrate/20161129161913_add_shared_runners_minutes_limit_to_projects.rb9
-rw-r--r--db/migrate/20161129161957_create_table_project_metrics.rb18
-rw-r--r--db/migrate/20161129162216_add_index_to_project_metrics.rb11
-rw-r--r--lib/api/entities.rb1
17 files changed, 170 insertions, 13 deletions
diff --git a/app/controllers/admin/application_settings_controller.rb b/app/controllers/admin/application_settings_controller.rb
index b81842e319b..b31eb49185d 100644
--- a/app/controllers/admin/application_settings_controller.rb
+++ b/app/controllers/admin/application_settings_controller.rb
@@ -93,6 +93,7 @@ class Admin::ApplicationSettingsController < Admin::ApplicationController
:user_oauth_applications,
:user_default_external,
:shared_runners_enabled,
+ :shared_runners_minutes,
:shared_runners_text,
:max_artifacts_size,
:metrics_enabled,
diff --git a/app/models/application_setting.rb b/app/models/application_setting.rb
index bf463a3b6bb..312280c3b32 100644
--- a/app/models/application_setting.rb
+++ b/app/models/application_setting.rb
@@ -107,6 +107,9 @@ class ApplicationSetting < ActiveRecord::Base
presence: true,
numericality: { only_integer: true, greater_than: :housekeeping_full_repack_period }
+ validates :shared_runners_minutes,
+ numericality: { greater_than_or_equal_to: 0 }
+
validates_each :restricted_visibility_levels do |record, attr, value|
unless value.nil?
value.each do |level|
diff --git a/app/models/ci/build.rb b/app/models/ci/build.rb
index e7d33bd26db..ef63501d1ed 100644
--- a/app/models/ci/build.rb
+++ b/app/models/ci/build.rb
@@ -93,6 +93,12 @@ module Ci
end
end
+ after_transition any => [:success, :failed, :canceled] do |build|
+ build.run_after_commit do
+ UpdateBuildMinutesService.new(project, nil).execute(build)
+ end
+ end
+
after_transition any => [:success] do |build|
build.run_after_commit do
BuildSuccessWorker.perform_async(id)
diff --git a/app/models/project.rb b/app/models/project.rb
index f8a54324341..d4cd398c934 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -28,6 +28,8 @@ class Project < ActiveRecord::Base
:merge_requests_enabled?, :issues_enabled?, to: :project_feature,
allow_nil: true
+ delegate :shared_runners_minutes, to: :project_metrics, allow_nil: true
+
default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level
default_value_for :container_registry_enabled, gitlab_config_features.container_registry
@@ -146,6 +148,7 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy
+ has_one :project_metrics, dependent: :destroy
has_many :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
@@ -1165,7 +1168,9 @@ class Project < ActiveRecord::Base
return true
end
- shared_runners_enabled? && Ci::Runner.shared.active.any?(&block)
+ shared_runners_enabled? &&
+ !shared_runners_minutes_used? &&
+ Ci::Runner.shared.active.any?(&block)
end
def valid_runners_token?(token)
@@ -1346,6 +1351,20 @@ class Project < ActiveRecord::Base
end
end
+ def shared_runners_minutes_limit
+ read_attribute(:shared_runners_minutes_limit) || current_application_settings.shared_runners_minutes
+ end
+
+ def shared_runners_minutes_limit_enabled?
+ shared_runners_minutes_limit.nonzero?
+ end
+
+ def shared_runners_minutes_used?
+ shared_runners_enabled? &&
+ shared_runners_minutes_limit_enabled? &&
+ shared_runners_minutes.to_i < shared_runners_minutes_limit
+ end
+
private
def pushes_since_gc_redis_key
diff --git a/app/models/project_metrics.rb b/app/models/project_metrics.rb
new file mode 100644
index 00000000000..35f3c713de2
--- /dev/null
+++ b/app/models/project_metrics.rb
@@ -0,0 +1,5 @@
+class ProjectMetrics < ActiveRecord::Base
+ belongs_to :project
+
+ validates :project, presence: true
+end
diff --git a/app/services/ci/register_build_service.rb b/app/services/ci/register_build_service.rb
index 74b5ebf372b..a9a663f528a 100644
--- a/app/services/ci/register_build_service.rb
+++ b/app/services/ci/register_build_service.rb
@@ -2,24 +2,16 @@ module Ci
# This class responsible for assigning
# proper pending build to runner on runner API request
class RegisterBuildService
+ include CurrentSettings
+
def execute(current_runner)
builds = Ci::Build.pending.unstarted
builds =
if current_runner.shared?
- builds.
- # don't run projects which have not enabled shared runners and builds
- joins(:project).where(projects: { shared_runners_enabled: true }).
- joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
-
- # this returns builds that are ordered by number of running builds
- # we prefer projects that don't use shared runners at all
- joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
- where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
- order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+ builds_for_shared_runner_with_build_minutes
else
- # do run projects which are only assigned to this runner (FIFO)
- builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
+ builds_for_specific_runner
end
build = builds.find do |build|
@@ -41,9 +33,36 @@ module Ci
private
+ def builds_for_shared_runner
+ new_builds.
+ # don't run projects which have not enabled shared runners and builds
+ joins(:project).where(projects: { shared_runners_enabled: true }).
+ joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id').
+ where('project_features.builds_access_level IS NULL or project_features.builds_access_level > 0').
+
+ # select projects with allowed number of shared runner minutes
+ joins('LEFT JOIN project_metrics ON ci_builds.gl_project_id = project_metrics.project_id').
+ where('COALESCE(projects.shared_runner_minutes_limit, ?, 0) > 0 AND ' \
+ 'COALESCE(project_metrics.shared_runner_minutes, 0) < COALESCE(projects.shared_runner_minutes_limit, ?, 0)',
+ current_application_settings.shared_runners_minutes)
+
+ # this returns builds that are ordered by number of running builds
+ # we prefer projects that don't use shared runners at all
+ joins("LEFT JOIN (#{running_builds_for_shared_runners.to_sql}) AS project_builds ON ci_builds.gl_project_id=project_builds.gl_project_id").
+ order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
+ end
+
+ def builds_for_specific_runner
+ new_builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
+ end
+
def running_builds_for_shared_runners
Ci::Build.running.where(runner: Ci::Runner.shared).
group(:gl_project_id).select(:gl_project_id, 'count(*) AS running_builds')
end
+
+ def new_builds
+ Ci::Build.pending.unstarted
+ end
end
end
diff --git a/app/services/update_build_minutes_service.rb b/app/services/update_build_minutes_service.rb
new file mode 100644
index 00000000000..47293cbf6e9
--- /dev/null
+++ b/app/services/update_build_minutes_service.rb
@@ -0,0 +1,10 @@
+class UpdateBuildMinutesService < BaseService
+ def execute(build)
+ return unless build.runner
+ return unless build.runner.shared?
+ return unless build.duration
+
+ project.find_or_create_project_metrics.
+ update_all('shared_runners_minutes = shared_runners_minutes + ?', build.duration)
+ end
+end
diff --git a/app/views/admin/application_settings/_form.html.haml b/app/views/admin/application_settings/_form.html.haml
index 95cae5ea24b..b475e784605 100644
--- a/app/views/admin/application_settings/_form.html.haml
+++ b/app/views/admin/application_settings/_form.html.haml
@@ -197,6 +197,13 @@
= f.check_box :shared_runners_enabled
Enable shared runners for new projects
.form-group
+ = f.label :shared_runners_minutes, 'Shared runners minutes', class: 'control-label col-sm-2'
+ .col-sm-10
+ = f.number_field :shared_runners_minutes, class: 'form-control'
+ .help-block
+ Set the maximum amount of minutes that project can use shared runners in period of time
+ = link_to "(?)", help_page_path("user/admin_area/settings/continuous_integration", anchor: "shared-runners-minutes")
+ .form-group
= f.label :shared_runners_text, class: 'control-label col-sm-2'
.col-sm-10
= f.text_area :shared_runners_text, class: 'form-control', rows: 4
diff --git a/app/views/admin/projects/show.html.haml b/app/views/admin/projects/show.html.haml
index 6c7c3c48604..b1952a2562b 100644
--- a/app/views/admin/projects/show.html.haml
+++ b/app/views/admin/projects/show.html.haml
@@ -90,6 +90,20 @@
%span.light archived:
%strong repository is read-only
+ - if @project.builds_enabled?
+ %li
+ %span.light Shared Runners:
+ %strong
+ - if @project.shared_runners_enabled?
+ Enabled
+ - if @project.shared_runner_minutes_limit.nonzero?
+ = @project.shared_runner_minutes_limit
+ total minutes
+ - elsif current_application_settings.shared_runners_minutes
+ Unlimited
+ - else
+ Disabled
+
%li
%span.light access:
%strong
diff --git a/app/views/projects/builds/show.html.haml b/app/views/projects/builds/show.html.haml
index 108674dbba6..661a392a048 100644
--- a/app/views/projects/builds/show.html.haml
+++ b/app/views/projects/builds/show.html.haml
@@ -26,6 +26,19 @@
= link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
Runners page
+ - if @build.project.shared_runners_minutes_used?
+ .bs-callout.bs-callout-warning
+ %p
+ You did use all your allowed shared runners minutes:
+ = @build.project.shared_runners_minutes.to_i
+ of
+ = @build.project.shared_runners_minutes_limit
+ .
+ Consider looking at
+ = link_to namespace_project_runners_path(@build.project.namespace, @build.project) do
+ Runners page
+ .
+
- if @build.starts_environment?
.prepend-top-default
.environment-information
diff --git a/app/workers/clear_shared_runner_minutes_worker.rb b/app/workers/clear_shared_runner_minutes_worker.rb
new file mode 100644
index 00000000000..a62babc19d0
--- /dev/null
+++ b/app/workers/clear_shared_runner_minutes_worker.rb
@@ -0,0 +1,8 @@
+class ClearSharedRunnerMinutesWorker
+ include Sidekiq::Worker
+ include DedicatedSidekiqQueue
+
+ def perform
+ ProjectMetrics.update_all(shared_runner_minutes: 0)
+ end
+end
diff --git a/config/initializers/1_settings.rb b/config/initializers/1_settings.rb
index 9ddd1554811..3ccecc48d50 100644
--- a/config/initializers/1_settings.rb
+++ b/config/initializers/1_settings.rb
@@ -312,6 +312,10 @@ Settings.cron_jobs['remove_unreferenced_lfs_objects_worker'] ||= Settingslogic.n
Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['cron'] ||= '20 0 * * *'
Settings.cron_jobs['remove_unreferenced_lfs_objects_worker']['job_class'] = 'RemoveUnreferencedLfsObjectsWorker'
+Settings.cron_jobs['clear_shared_runner_minutes_worker'] ||= Settingslogic.new({})
+Settings.cron_jobs['clear_shared_runner_minutes_worker']['cron'] ||= '0 0 0 * *'
+Settings.cron_jobs['clear_shared_runner_minutes_worker']['job_class'] = 'ClearSharedRunnerMinutesWorker'
+
#
# GitLab Shell
#
diff --git a/db/migrate/20161129161815_add_shared_runners_minutes_to_application_settings.rb b/db/migrate/20161129161815_add_shared_runners_minutes_to_application_settings.rb
new file mode 100644
index 00000000000..7d14f3a5ff7
--- /dev/null
+++ b/db/migrate/20161129161815_add_shared_runners_minutes_to_application_settings.rb
@@ -0,0 +1,9 @@
+class AddSharedRunnersMinutesToApplicationSettings < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :application_settings, :shared_runners_minutes, :integer, null: false, default: 0
+ end
+end
diff --git a/db/migrate/20161129161913_add_shared_runners_minutes_limit_to_projects.rb b/db/migrate/20161129161913_add_shared_runners_minutes_limit_to_projects.rb
new file mode 100644
index 00000000000..b33c5618b91
--- /dev/null
+++ b/db/migrate/20161129161913_add_shared_runners_minutes_limit_to_projects.rb
@@ -0,0 +1,9 @@
+class AddSharedRunnersMinutesLimitToProjects < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ def change
+ add_column :projects, :shared_runners_minutes_limit, :integer
+ end
+end
diff --git a/db/migrate/20161129161957_create_table_project_metrics.rb b/db/migrate/20161129161957_create_table_project_metrics.rb
new file mode 100644
index 00000000000..21aeec74fe9
--- /dev/null
+++ b/db/migrate/20161129161957_create_table_project_metrics.rb
@@ -0,0 +1,18 @@
+# See http://doc.gitlab.com/ce/development/migration_style_guide.html
+# for more information on how to write migrations for GitLab.
+
+class CreateTableProjectMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ # Set this constant to true if this migration requires downtime.
+ DOWNTIME = false
+
+ def change
+ create_table :project_metrics do |t|
+ t.integer :project_id, null: false
+ t.integer :shared_runners_minutes, default: 0, null: false
+ end
+
+ add_foreign_key :project_metrics, :projects, column: :project_id, on_delete: :cascade
+ end
+end
diff --git a/db/migrate/20161129162216_add_index_to_project_metrics.rb b/db/migrate/20161129162216_add_index_to_project_metrics.rb
new file mode 100644
index 00000000000..87ce5e334cf
--- /dev/null
+++ b/db/migrate/20161129162216_add_index_to_project_metrics.rb
@@ -0,0 +1,11 @@
+class AddIndexToProjectMetrics < ActiveRecord::Migration
+ include Gitlab::Database::MigrationHelpers
+
+ DOWNTIME = false
+
+ disable_ddl_transaction!
+
+ def change
+ add_concurrent_index :project_metrics, [:project_id], { unique: true }
+ end
+end
diff --git a/lib/api/entities.rb b/lib/api/entities.rb
index 33cb6fd3704..70667c7bb60 100644
--- a/lib/api/entities.rb
+++ b/lib/api/entities.rb
@@ -101,6 +101,7 @@ module API
expose :only_allow_merge_if_build_succeeds
expose :request_access_enabled
expose :only_allow_merge_if_all_discussions_are_resolved
+ expose :shared_runners_minutes_limit
end
class Member < UserBasic