Commit 6ef0fa86 authored by Avielle Wolfe's avatar Avielle Wolfe Committed by Fabio Pitino

Add a runner cost factor for new public projects

This commit adds a runner minutes cost factor for public projects in new
namespaces. "New" is defined as "after July 17, 2021".

* Renames Ci::Runner#minutes_cost_factor to #cost_factor_for_project and
  updates it to take a project
* Adds Ci::Minutes::CostFactor#for_project

https://gitlab.com/gitlab-org/gitlab/-/issues/330581

Changelog: changed
EE: true
parent 89ce649a
......@@ -272,14 +272,15 @@ If you add a member to a group by using the [share a group with another group](.
## CI pipeline minutes
CI pipeline minutes are the execution time for your
[pipelines](../../ci/pipelines/index.md) on GitLab shared runners. Each
[GitLab SaaS tier](https://about.gitlab.com/pricing/) includes a monthly quota
of CI pipeline minutes:
- Free: 400 minutes
- Premium: 10,000 minutes
- Ultimate: 50,000 minutes
CI pipeline minutes are the execution time for your [pipelines](../../ci/pipelines/index.md)
on GitLab shared runners. Each [GitLab SaaS tier](https://about.gitlab.com/pricing/)
includes a monthly quota of CI pipeline minutes for private and public projects:
| Plan | Private projects | Public projects |
|----------|------------------|-----------------|
| Free | 400 | 50,000 |
| Premium | 10,000 | 1,250,000 |
| Ultimate | 50,000 | 6,250,000 |
Quotas apply to:
......
......@@ -10,7 +10,7 @@ class Groups::UsageQuotasController < Groups::ApplicationController
feature_category :purchase
def index
@projects = @group.all_projects.with_shared_runners_limit_enabled.page(params[:page])
@projects = @group.all_projects.with_shared_runners.page(params[:page])
end
private
......
......@@ -7,7 +7,7 @@ class Profiles::UsageQuotasController < Profiles::ApplicationController
def index
@namespace = current_user.namespace
@projects = @namespace.projects.with_shared_runners_limit_enabled.page(params[:page])
@projects = @namespace.projects.with_shared_runners.page(params[:page])
end
private
......
......@@ -73,7 +73,7 @@ module EE
end
def shared_runners_minutes_limit_enabled?
project.shared_runners_minutes_limit_enabled? && runner&.minutes_cost_factor(project.visibility_level)&.positive?
project.shared_runners_minutes_limit_enabled? && runner&.cost_factor_enabled?(project)
end
def log_geo_deleted_event
......
......@@ -5,19 +5,25 @@ module EE
module Runner
extend ActiveSupport::Concern
def minutes_cost_factor(visibility_level)
::Gitlab::Ci::Minutes::CostFactor.new(runner_matcher).for_visibility(visibility_level)
def cost_factor_for_project(project)
cost_factor.for_project(project)
end
def cost_factor_enabled?(project)
cost_factor.enabled?(project)
end
def visibility_levels_without_minutes_quota
::Gitlab::VisibilityLevel.options.values.reject do |visibility_level|
minutes_cost_factor(visibility_level) > 0
cost_factor.for_visibility(visibility_level) > 0
end
end
class_methods do
def has_shared_runners_with_non_zero_public_cost?
::Ci::Runner.instance_type.where('public_projects_minutes_cost_factor > 0').exists?
private
def cost_factor
strong_memoize(:cost_factor) do
::Gitlab::Ci::Minutes::CostFactor.new(runner_matcher)
end
end
end
......
......@@ -114,14 +114,6 @@ module EE
elastic_index_dependant_association :merge_requests, on_change: :visibility_level
elastic_index_dependant_association :notes, on_change: :visibility_level
scope :with_shared_runners_limit_enabled, -> do
if ::Ci::Runner.has_shared_runners_with_non_zero_public_cost?
with_shared_runners
else
with_shared_runners.non_public_only
end
end
scope :mirror, -> { where(mirror: true) }
scope :mirrors_to_sync, ->(freeze_at, limit: nil) do
......
......@@ -14,7 +14,7 @@ module EE
private
def cost_factor_disabled?(build_matcher)
cost_factor.disabled?(build_matcher.project.visibility_level)
cost_factor.disabled?(build_matcher.project)
end
def cost_factor
......
......@@ -20,11 +20,7 @@ module Gitlab
private
def cost_factor
@build.runner.minutes_cost_factor(project_visibility_level)
end
def project_visibility_level
@build.project.visibility_level
@build.runner.cost_factor_for_project(@build.project)
end
end
end
......
......@@ -4,20 +4,38 @@ module Gitlab
module Ci
module Minutes
class CostFactor
NEW_NAMESPACE_PUBLIC_PROJECT_COST_FACTOR = 0.008
def initialize(runner_matcher)
ensure_runner_matcher_instance(runner_matcher)
@runner_matcher = runner_matcher
end
def enabled?(visibility_level)
for_visibility(visibility_level) > 0
def enabled?(project)
for_project(project) > 0
end
def disabled?(project)
!enabled?(project)
end
def disabled?(visibility_level)
!enabled?(visibility_level)
def for_project(project)
return 0.0 unless @runner_matcher.instance_type?
cost_factor = for_visibility(project.visibility_level)
if cost_factor == 0 && project.force_cost_factor?
NEW_NAMESPACE_PUBLIC_PROJECT_COST_FACTOR
else
cost_factor
end
end
# This method SHOULD NOT BE USED by new code. It is currently depended
# on by `BuildQueueService`. That dependency will be removed by
# https://gitlab.com/groups/gitlab-org/-/epics/5909, and this method
# should be made private at that time. Please use #for_project instead.
def for_visibility(visibility_level)
return 0.0 unless @runner_matcher.instance_type?
......
......@@ -22,7 +22,9 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
end
describe '#enabled?' do
subject { described_class.new(runner.runner_matcher).enabled?(visibility_level) }
let(:project) { build_stubbed(:project, visibility_level: visibility_level) }
subject { described_class.new(runner.runner_matcher).enabled?(project) }
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | false
......@@ -45,7 +47,9 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
end
describe '#disabled?' do
subject { described_class.new(runner.runner_matcher).disabled?(visibility_level) }
let(:project) { build_stubbed(:project, visibility_level: visibility_level) }
subject { described_class.new(runner.runner_matcher).disabled?(project) }
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | true
......@@ -67,6 +71,72 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
end
end
describe '#for_project' do
let(:project) { build_stubbed(:project, namespace: namespace, visibility_level: visibility_level) }
subject { described_class.new(runner.runner_matcher).for_project(project) }
context 'before the public project cost factor release date' do
let_it_be(:namespace) do
create(:group, created_at: Date.new(2021, 7, 16))
end
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | 0
:project | Gitlab::VisibilityLevel::INTERNAL | 1 | 1 | 0
:project | Gitlab::VisibilityLevel::PUBLIC | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::INTERNAL | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::PUBLIC | 1 | 1 | 0
:instance | Gitlab::VisibilityLevel::PUBLIC | 0 | 5 | 0
:instance | Gitlab::VisibilityLevel::INTERNAL | 0 | 5 | 5
:instance | Gitlab::VisibilityLevel::PRIVATE | 0 | 5 | 5
end
with_them do
it { is_expected.to eq(result) }
end
end
context 'after the public project cost factor release date' do
let_it_be(:namespace) do
create(:group, created_at: Date.new(2021, 7, 17))
end
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | 0
:project | Gitlab::VisibilityLevel::INTERNAL | 1 | 1 | 0
:project | Gitlab::VisibilityLevel::PUBLIC | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::INTERNAL | 1 | 1 | 0
:group | Gitlab::VisibilityLevel::PUBLIC | 1 | 1 | 0
:instance | Gitlab::VisibilityLevel::PUBLIC | 0 | 5 | 0.008
:instance | Gitlab::VisibilityLevel::INTERNAL | 0 | 5 | 5
:instance | Gitlab::VisibilityLevel::PRIVATE | 0 | 5 | 5
end
with_them do
it { is_expected.to eq(result) }
end
end
context 'when the project has an invalid visibility level' do
let(:namespace) { nil }
let(:private_cost_factor) { 1 }
let(:public_cost_factor) { 1 }
let(:runner_type) { :instance }
let(:visibility_level) { 123 }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError)
end
end
end
describe '#for_visibility' do
subject { described_class.new(runner.runner_matcher).for_visibility(visibility_level) }
......
......@@ -3,15 +3,15 @@
require 'spec_helper'
RSpec.describe EE::Ci::Runner do
describe '#minutes_cost_factor' do
subject { runner.minutes_cost_factor(visibility_level) }
describe '#cost_factor_for_project' do
subject { runner.cost_factor_for_project(project) }
context 'with group type runner' do
let(:runner) { create(:ci_runner, :group) }
::Gitlab::VisibilityLevel.options.each do |level_name, level_value|
context "with #{level_name}" do
let(:visibility_level) {level_value}
let(:project) { create(:project, visibility_level: level_value) }
it { is_expected.to eq(0.0) }
end
......@@ -23,7 +23,7 @@ RSpec.describe EE::Ci::Runner do
::Gitlab::VisibilityLevel.options.each do |level_name, level_value|
context "with #{level_name}" do
let(:visibility_level) {level_value}
let(:project) { create(:project, visibility_level: level_value) }
it { is_expected.to eq(0.0) }
end
......@@ -35,29 +35,47 @@ RSpec.describe EE::Ci::Runner do
create(:ci_runner,
:instance,
private_projects_minutes_cost_factor: 1.1,
public_projects_minutes_cost_factor: 2.2)
public_projects_minutes_cost_factor: 0)
end
context 'with private visibility level' do
let(:visibility_level) { ::Gitlab::VisibilityLevel::PRIVATE }
let(:project) { create(:project, visibility_level: ::Gitlab::VisibilityLevel::PRIVATE) }
it { is_expected.to eq(1.1) }
end
context 'with public visibility level' do
let(:visibility_level) { ::Gitlab::VisibilityLevel::PUBLIC }
let(:project) { create(:project, namespace: namespace, visibility_level: ::Gitlab::VisibilityLevel::PUBLIC) }
it { is_expected.to eq(2.2) }
context 'after the release date for public project cost factors' do
let(:namespace) do
create(:group, created_at: Date.new(2021, 7, 17))
end
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
it { is_expected.to eq(0.008) }
end
context 'before the release date for public project cost factors' do
let(:namespace) do
create(:group, created_at: Date.new(2021, 7, 16))
end
it { is_expected.to eq(0.0) }
end
end
context 'with internal visibility level' do
let(:visibility_level) { ::Gitlab::VisibilityLevel::INTERNAL }
let(:project) { create(:project, visibility_level: ::Gitlab::VisibilityLevel::INTERNAL) }
it { is_expected.to eq(1.1) }
end
context 'with invalid visibility level' do
let(:visibility_level) { 123 }
let(:project) { create(:project, visibility_level: 123) }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError)
......@@ -66,6 +84,33 @@ RSpec.describe EE::Ci::Runner do
end
end
describe '#cost_factor_enabled?' do
let_it_be(:project) do
namespace = create(:group, created_at: Date.new(2021, 7, 16))
create(:project, namespace: namespace)
end
context 'when the project has any cost factor' do
it 'returns true' do
runner = create(:ci_runner, :instance,
private_projects_minutes_cost_factor: 1,
public_projects_minutes_cost_factor: 0)
expect(runner.cost_factor_enabled?(project)).to be_truthy
end
end
context 'when the project has no cost factor' do
it 'returns false' do
runner = create(:ci_runner, :instance,
private_projects_minutes_cost_factor: 0,
public_projects_minutes_cost_factor: 0)
expect(runner.cost_factor_enabled?(project)).to be_falsy
end
end
end
describe '#visibility_levels_without_minutes_quota' do
subject { runner.visibility_levels_without_minutes_quota }
......
......@@ -343,46 +343,6 @@ RSpec.describe Project do
end
end
describe '.with_shared_runners_limit_enabled' do
let(:public_cost_factor) { 1.0 }
before do
create(:ci_runner, :instance, public_projects_minutes_cost_factor: public_cost_factor)
end
it 'does not return projects without shared runners' do
project_with_shared_runners = create(:project, shared_runners_enabled: true)
project_without_shared_runners = create(:project, shared_runners_enabled: false)
expect(described_class.with_shared_runners_limit_enabled).to include(project_with_shared_runners)
expect(described_class.with_shared_runners_limit_enabled).not_to include(project_without_shared_runners)
end
it 'return projects with shared runners with positive public cost factor with any visibility levels' do
public_project_with_shared_runners = create(:project, :public, shared_runners_enabled: true)
internal_project_with_shared_runners = create(:project, :internal, shared_runners_enabled: true)
private_project_with_shared_runners = create(:project, :private, shared_runners_enabled: true)
expect(described_class.with_shared_runners_limit_enabled).to include(public_project_with_shared_runners)
expect(described_class.with_shared_runners_limit_enabled).to include(internal_project_with_shared_runners)
expect(described_class.with_shared_runners_limit_enabled).to include(private_project_with_shared_runners)
end
context 'and shared runners public cost factors set to 0' do
let(:public_cost_factor) { 0.0 }
it 'return projects with any visibility levels except public' do
public_project_with_shared_runners = create(:project, :public, shared_runners_enabled: true)
internal_project_with_shared_runners = create(:project, :internal, shared_runners_enabled: true)
private_project_with_shared_runners = create(:project, :private, shared_runners_enabled: true)
expect(described_class.with_shared_runners_limit_enabled).not_to include(public_project_with_shared_runners)
expect(described_class.with_shared_runners_limit_enabled).to include(internal_project_with_shared_runners)
expect(described_class.with_shared_runners_limit_enabled).to include(private_project_with_shared_runners)
end
end
end
describe '.has_vulnerabilities' do
let_it_be(:project_1) { create(:project) }
let_it_be(:project_2) { create(:project) }
......
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