Commit ad394fc6 authored by Kamil Trzcinski's avatar Kamil Trzcinski

Add namespace_metrics and count number of build minutes on namespace basis

parent 7b9dc100
...@@ -872,8 +872,8 @@ DEPENDENCIES ...@@ -872,8 +872,8 @@ DEPENDENCIES
gollum-rugged_adapter (~> 0.4.2) gollum-rugged_adapter (~> 0.4.2)
gon (~> 6.1.0) gon (~> 6.1.0)
grape (~> 0.15.0) grape (~> 0.15.0)
gssapi
grape-entity (~> 0.6.0) grape-entity (~> 0.6.0)
gssapi
haml_lint (~> 0.18.2) haml_lint (~> 0.18.2)
hamlit (~> 2.6.1) hamlit (~> 2.6.1)
health_check (~> 2.2.0) health_check (~> 2.2.0)
......
...@@ -4,11 +4,14 @@ class Namespace < ActiveRecord::Base ...@@ -4,11 +4,14 @@ class Namespace < ActiveRecord::Base
include CacheMarkdownField include CacheMarkdownField
include Sortable include Sortable
include Gitlab::ShellAdapter include Gitlab::ShellAdapter
include Gitlab::CurrentSettings
include Routable include Routable
cache_markdown_field :description, pipeline: :description cache_markdown_field :description, pipeline: :description
has_many :projects, dependent: :destroy has_many :projects, dependent: :destroy
has_one :namespace_metrics, dependent: :destroy
belongs_to :owner, class_name: "User" belongs_to :owner, class_name: "User"
belongs_to :parent, class_name: "Namespace" belongs_to :parent, class_name: "Namespace"
...@@ -39,6 +42,8 @@ class Namespace < ActiveRecord::Base ...@@ -39,6 +42,8 @@ class Namespace < ActiveRecord::Base
scope :root, -> { where('type IS NULL') } scope :root, -> { where('type IS NULL') }
delegate :shared_runners_minutes, to: :namespace_metrics, allow_nil: true
class << self class << self
def by_path(path) def by_path(path)
find_by('lower(path) = :value', value: path.downcase) find_by('lower(path) = :value', value: path.downcase)
...@@ -203,7 +208,23 @@ class Namespace < ActiveRecord::Base ...@@ -203,7 +208,23 @@ class Namespace < ActiveRecord::Base
find_each(&:refresh_members_authorized_projects) find_each(&:refresh_members_authorized_projects)
end end
<<<<<<< HEAD
def full_path_changed? def full_path_changed?
path_changed? || parent_id_changed? path_changed? || parent_id_changed?
=======
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
>>>>>>> Add namespace_metrics and count number of build minutes on namespace basis
end end
end end
class NamespaceMetrics < ActiveRecord::Base
belongs_to :namespace
validates :namespace, presence: true
end
...@@ -28,7 +28,9 @@ class Project < ActiveRecord::Base ...@@ -28,7 +28,9 @@ class Project < ActiveRecord::Base
:merge_requests_enabled?, :issues_enabled?, to: :project_feature, :merge_requests_enabled?, :issues_enabled?, to: :project_feature,
allow_nil: true allow_nil: true
delegate :shared_runners_minutes, to: :project_metrics, allow_nil: true delegate :shared_runners_minutes, :shared_runners_minutes_limit,
:shared_runners_minutes_used?, :shared_runners_minutes_limit_enabled?,
to: :namespace, allow_nil: true
default_value_for :archived, false default_value_for :archived, false
default_value_for :visibility_level, gitlab_config_features.visibility_level default_value_for :visibility_level, gitlab_config_features.visibility_level
...@@ -152,7 +154,6 @@ class Project < ActiveRecord::Base ...@@ -152,7 +154,6 @@ class Project < ActiveRecord::Base
has_one :import_data, dependent: :destroy, class_name: "ProjectImportData" has_one :import_data, dependent: :destroy, class_name: "ProjectImportData"
has_one :project_feature, dependent: :destroy 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 :commit_statuses, dependent: :destroy, foreign_key: :gl_project_id
has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id has_many :pipelines, dependent: :destroy, class_name: 'Ci::Pipeline', foreign_key: :gl_project_id
...@@ -1546,20 +1547,6 @@ class Project < ActiveRecord::Base ...@@ -1546,20 +1547,6 @@ class Project < ActiveRecord::Base
end end
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 private
# Check if a reference is being done cross-project # Check if a reference is being done cross-project
......
class ProjectMetrics < ActiveRecord::Base
belongs_to :project
validates :project, presence: true
end
...@@ -9,7 +9,11 @@ module Ci ...@@ -9,7 +9,11 @@ module Ci
builds = builds =
if current_runner.shared? if current_runner.shared?
builds_for_shared_runner_with_build_minutes if current_runner.limit_build_minutes?
builds_for_shared_runners_with_build_minutes_limit
else
builds_for_shared_runners
end
else else
builds_for_specific_runner builds_for_specific_runner
end end
...@@ -40,18 +44,23 @@ module Ci ...@@ -40,18 +44,23 @@ module Ci
joins('LEFT JOIN project_features ON ci_builds.gl_project_id = project_features.project_id'). 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'). 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 # this returns builds that are ordered by number of running builds
# we prefer projects that don't use shared runners at all # 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"). 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') order('COALESCE(project_builds.running_builds, 0) ASC', 'ci_builds.id ASC')
end end
def builds_for_shared_runners_with_build_minutes_limit
builds_for_shared_runner.
# select projects with allowed number of shared runner minutes
joins('LEFT JOIN namespaces ON ci_builds.gl_project_id = namespaces.project_id').
joins('LEFT JOIN namespace_metrics ON namespaces.id = namespace_metrics.namespace_id').
where('COALESCE(namespaces.shared_runner_minutes_limit, ?, 0) > 0 AND ' \
'COALESCE(namespace_metrics.shared_runner_minutes, 0) < COALESCE(namespaces.shared_runner_minutes_limit, ?, 0)',
current_application_settings.shared_runners_minutes,
current_application_settings.shared_runners_minutes)
end
def builds_for_specific_runner def builds_for_specific_runner
new_builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC') new_builds.where(project: current_runner.projects.with_builds_enabled).order('created_at ASC')
end end
......
...@@ -2,9 +2,16 @@ class UpdateBuildMinutesService < BaseService ...@@ -2,9 +2,16 @@ class UpdateBuildMinutesService < BaseService
def execute(build) def execute(build)
return unless build.runner return unless build.runner
return unless build.runner.shared? return unless build.runner.shared?
return unless build.runner.limit_build_minutes?
return unless build.duration return unless build.duration
project.find_or_create_project_metrics. project = build.project
return unless project
namespace = project.namespace
return unless namespace
namespace.find_or_create_project_metrics.
update_all('shared_runners_minutes = shared_runners_minutes + ?', build.duration) update_all('shared_runners_minutes = shared_runners_minutes + ?', build.duration)
end end
end end
...@@ -18,6 +18,13 @@ ...@@ -18,6 +18,13 @@
.checkbox .checkbox
= f.check_box :locked = f.check_box :locked
%span.light When a runner is locked, it cannot be assigned to other projects %span.light When a runner is locked, it cannot be assigned to other projects
- if runner.shared?
.form-group
= label :limit_build_minutes, 'Limit build minutes', class: 'control-label'
.col-sm-10
.checkbox
= f.check_box :limit_build_minutes
%span.light When limiting is enabled, only namespaces with build minutes allowance will be picked.
.form-group .form-group
= label_tag :token, class: 'control-label' do = label_tag :token, class: 'control-label' do
Token Token
......
...@@ -3,6 +3,6 @@ class ClearSharedRunnerMinutesWorker ...@@ -3,6 +3,6 @@ class ClearSharedRunnerMinutesWorker
include DedicatedSidekiqQueue include DedicatedSidekiqQueue
def perform def perform
ProjectMetrics.update_all(shared_runner_minutes: 0) NamespaceMetrics.update_all(shared_runner_minutes: 0)
end end
end end
class AddSharedRunnersMinutesLimitToProjects < ActiveRecord::Migration class AddSharedRunnersMinutesLimitToNamespace < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
DOWNTIME = false DOWNTIME = false
def change def change
add_column :projects, :shared_runners_minutes_limit, :integer add_column :namespaces, :shared_runners_minutes_limit, :integer
end end
end end
class CreateTableNamespaceMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
create_table :namespace_metrics do |t|
t.integer :namespace_id, null: false
t.integer :shared_runners_minutes, default: 0, null: false
end
add_foreign_key :namespace_metrics, :projects, column: :namespace_id, on_delete: :cascade
end
end
# 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
class AddIndexToProjectMetrics < ActiveRecord::Migration class AddIndexToNamespaceMetrics < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers include Gitlab::Database::MigrationHelpers
DOWNTIME = false DOWNTIME = false
...@@ -6,6 +6,6 @@ class AddIndexToProjectMetrics < ActiveRecord::Migration ...@@ -6,6 +6,6 @@ class AddIndexToProjectMetrics < ActiveRecord::Migration
disable_ddl_transaction! disable_ddl_transaction!
def change def change
add_concurrent_index :project_metrics, [:project_id], { unique: true } add_concurrent_index :namespace_metrics, [:namespace_id], { unique: true }
end end
end end
class AddLimitBuildMinutesToRunners < ActiveRecord::Migration
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
def change
add_column :runners, :limit_build_minutes, :boolean
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment