Commit 6f09c2fa authored by Marius Bobin's avatar Marius Bobin

Merge branch 'add-open-source-cost-factor' into 'master'

Add subsidized cost factor for Open Source plan

See merge request gitlab-org/gitlab!77882
parents 19a7edd1 f71cc952
......@@ -454,7 +454,7 @@ class Project < ApplicationRecord
delegate :job_token_scope_enabled, :job_token_scope_enabled=, to: :ci_cd_settings, prefix: :ci, allow_nil: true
delegate :keep_latest_artifact, :keep_latest_artifact=, to: :ci_cd_settings, allow_nil: true
delegate :restrict_user_defined_variables, :restrict_user_defined_variables=, to: :ci_cd_settings, allow_nil: true
delegate :actual_limits, :actual_plan_name, to: :namespace, allow_nil: true
delegate :actual_limits, :actual_plan_name, :actual_plan, to: :namespace, allow_nil: true
delegate :allow_merge_on_skipped_pipeline, :allow_merge_on_skipped_pipeline?,
:allow_merge_on_skipped_pipeline=, :has_confluence?, :has_shimo?,
to: :project_setting
......
......@@ -180,6 +180,8 @@ The cost factor for a job running on a shared runner is:
- `0.008` for public projects on GitLab SaaS, if [created 2021-07-17 or later](https://gitlab.com/gitlab-org/gitlab/-/issues/332708).
(For every 125 minutes of job time, you accrue 1 CD/CD minute.)
- `0.008` for projects members of GitLab [Open Source program](../../subscriptions/index.md#gitlab-for-open-source).
(For every 125 minutes of job time, you accrue 1 CD/CD minute.)
- `0` for public projects on GitLab self-managed instances, and for GitLab SaaS public projects created before 2021-07-17.
- `1` for internal and private projects.
......
......@@ -14,9 +14,10 @@ module EE
ULTIMATE = 'ultimate'
ULTIMATE_TRIAL = 'ultimate_trial'
PREMIUM_TRIAL = 'premium_trial'
OPEN_SOURCE = 'opensource'
EE_DEFAULT_PLANS = (const_get(:DEFAULT_PLANS, false) + [FREE]).freeze
PAID_HOSTED_PLANS = [BRONZE, SILVER, PREMIUM, GOLD, ULTIMATE, ULTIMATE_TRIAL, PREMIUM_TRIAL].freeze
PAID_HOSTED_PLANS = [BRONZE, SILVER, PREMIUM, GOLD, ULTIMATE, ULTIMATE_TRIAL, PREMIUM_TRIAL, OPEN_SOURCE].freeze
EE_ALL_PLANS = (EE_DEFAULT_PLANS + PAID_HOSTED_PLANS).freeze
PLANS_ELIGIBLE_FOR_TRIAL = EE_DEFAULT_PLANS
......@@ -64,5 +65,9 @@ module EE
def paid?
PAID_HOSTED_PLANS.include?(name)
end
def open_source?
name == OPEN_SOURCE
end
end
end
......@@ -4,7 +4,10 @@ module Gitlab
module Ci
module Minutes
class CostFactor
NEW_NAMESPACE_PUBLIC_PROJECT_COST_FACTOR = 0.008
DISABLED = 0.0
STANDARD = 1.0
OPEN_SOURCE = 0.008
NEW_NAMESPACE_PUBLIC_PROJECT = 0.008
def initialize(runner_matcher)
ensure_runner_matcher_instance(runner_matcher)
......@@ -21,15 +24,20 @@ module Gitlab
end
def for_project(project)
return 0.0 unless @runner_matcher.instance_type?
return 0.0 unless project.ci_minutes_quota.enabled?
return DISABLED unless @runner_matcher.instance_type?
return DISABLED unless project.ci_minutes_quota.enabled?
cost_factor = for_visibility(project.visibility_level)
runner_cost_factor = for_visibility(project.visibility_level)
if cost_factor == 0 && project.force_cost_factor?
NEW_NAMESPACE_PUBLIC_PROJECT_COST_FACTOR
if runner_cost_factor == DISABLED && project.force_cost_factor?
# Once visibility level cost factors are consolidated into a single
# cost factor, this condition can be removed.
# https://gitlab.com/gitlab-org/gitlab/-/issues/243722
NEW_NAMESPACE_PUBLIC_PROJECT
elsif runner_cost_factor == STANDARD && project.actual_plan.open_source?
OPEN_SOURCE
else
cost_factor
runner_cost_factor
end
end
......
......@@ -75,5 +75,9 @@ FactoryBot.define do
trait :ultimate_trial do
association :hosted_plan, factory: :ultimate_trial_plan
end
trait :opensource do
association :hosted_plan, factory: :opensource_plan
end
end
end
......@@ -8,6 +8,7 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
let(:runner_type) {}
let(:public_cost_factor) {}
let(:private_cost_factor) {}
let(:cost_factor) { described_class.new(runner.runner_matcher) }
let(:runner) do
build_stubbed(:ci_runner,
......@@ -27,9 +28,8 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
describe '#enabled?' do
let(:project) { build_stubbed(:project) }
let(:cost_factor) { described_class.new(runner.runner_matcher) }
subject { cost_factor.enabled?(project) }
subject(:is_enabled) { cost_factor.enabled?(project) }
context 'when the cost factor is zero' do
before do
......@@ -50,9 +50,8 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
describe '#disabled?' do
let(:project) { build_stubbed(:project) }
let(:cost_factor) { described_class.new(runner.runner_matcher) }
subject { cost_factor.disabled?(project) }
subject(:is_disabled) { cost_factor.disabled?(project) }
context 'when the cost factor is zero' do
before do
......@@ -72,6 +71,8 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
end
describe '#for_project' do
subject(:for_project) { cost_factor.for_project(project) }
context 'before the public project cost factor release date' do
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :namespace_limit, :instance_limit, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | nil | 400 | 0
......@@ -123,13 +124,11 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
allow(Gitlab::CurrentSettings).to receive(:shared_runners_minutes) { instance_limit }
end
subject { described_class.new(runner.runner_matcher).for_project(project) }
it { is_expected.to eq(result) }
end
end
context 'after the public project cost factor release date' do
context 'after the public project cost factor release date', :saas do
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :namespace_limit, :instance_limit, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | nil | 400 | 0
:project | Gitlab::VisibilityLevel::INTERNAL | 1 | 1 | nil | 400 | 0
......@@ -178,30 +177,68 @@ RSpec.describe Gitlab::Ci::Minutes::CostFactor do
before do
allow(Gitlab::CurrentSettings).to receive(:shared_runners_minutes) { instance_limit }
allow(Gitlab).to receive(:com?).and_return(true)
end
subject { described_class.new(runner.runner_matcher).for_project(project) }
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 }
context 'plan based cost factor', :saas do
let(:runner_type) { :instance }
let(:visibility_level) { 123 }
let(:project) { create(:project, visibility_level: Gitlab::VisibilityLevel::PRIVATE) }
it 'raises an error' do
expect { subject }.to raise_error(ArgumentError)
before do
create(:gitlab_subscription, namespace: project.namespace, hosted_plan: plan)
allow(Gitlab::CurrentSettings).to receive(:shared_runners_minutes) { 100 }
end
context 'when project has an Open Source plan' do
let(:plan) { create(:opensource_plan) }
context 'when runner cost factor is standard' do
let(:private_cost_factor) { described_class::STANDARD }
it 'returns a lower cost factor' do
expect(subject).to eq(described_class::OPEN_SOURCE)
expect(subject).to be < private_cost_factor
expect(subject).to be > described_class::DISABLED
end
end
context 'when runner cost factor is custom' do
let(:private_cost_factor) { 2.0 }
it 'returns the runner cost factor' do
expect(subject).to eq(private_cost_factor)
end
end
end
context 'when project does not have an Open Source plan' do
let(:plan) { create(:free_plan) }
context 'when runner cost factor is standard' do
let(:private_cost_factor) { described_class::STANDARD }
it 'returns the runner cost factor' do
expect(subject).to eq(private_cost_factor)
end
end
context 'when runner cost factor is custom' do
let(:private_cost_factor) { 2.0 }
it 'returns the runner cost factor' do
expect(subject).to eq(private_cost_factor)
end
end
end
end
end
describe '#for_visibility' do
subject { described_class.new(runner.runner_matcher).for_visibility(visibility_level) }
subject(:for_visibility) { described_class.new(runner.runner_matcher).for_visibility(visibility_level) }
where(:runner_type, :visibility_level, :public_cost_factor, :private_cost_factor, :result) do
:project | Gitlab::VisibilityLevel::PRIVATE | 1 | 1 | 0
......
......@@ -28,4 +28,20 @@ RSpec.describe Plan do
it { is_expected.to eq(%w[default free]) }
end
describe '#open_source?' do
subject { plan.open_source? }
context 'when is opensource' do
let(:plan) { build(:opensource_plan) }
it { is_expected.to be_truthy }
end
context 'when is not opensource' do
let(:plan) { build(:free_plan) }
it { is_expected.to be_falsey }
end
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