Commit caec1166 authored by Corinna Wiesner's avatar Corinna Wiesner Committed by Douglas Barbosa Alexandre

Fix previous license period

parent df5a6a0b
...@@ -8,7 +8,6 @@ module Admin ...@@ -8,7 +8,6 @@ module Admin
@license ||= begin @license ||= begin
License.reset_current License.reset_current
License.reset_future_dated License.reset_future_dated
License.reset_previous
License.current License.current
end end
end end
......
...@@ -258,16 +258,15 @@ class License < ApplicationRecord ...@@ -258,16 +258,15 @@ class License < ApplicationRecord
validate :valid_license validate :valid_license
validate :check_users_limit, if: :new_record?, unless: [:validate_with_trueup?, :reconciliation_completed?] validate :check_users_limit, if: :new_record?, unless: [:validate_with_trueup?, :reconciliation_completed?]
validate :check_trueup, unless: [:persisted?, :reconciliation_completed?], if: :validate_with_trueup? validate :check_trueup, unless: :reconciliation_completed?, if: [:new_record?, :validate_with_trueup?]
validate :check_restricted_user_count, if: :reconciliation_completed? validate :check_restricted_user_count, if: :reconciliation_completed?
validate :not_expired, unless: :persisted? validate :not_expired, if: :new_record?
before_validation :reset_license, if: :data_changed? before_validation :reset_license, if: :data_changed?
after_create :update_trial_setting after_create :update_trial_setting
after_commit :reset_current after_commit :reset_current
after_commit :reset_future_dated, on: [:create, :destroy] after_commit :reset_future_dated, on: [:create, :destroy]
after_commit :reset_previous, on: [:create, :destroy]
scope :cloud, -> { where(cloud: true) } scope :cloud, -> { where(cloud: true) }
scope :recent, -> { reorder(id: :desc) } scope :recent, -> { reorder(id: :desc) }
...@@ -331,14 +330,6 @@ class License < ApplicationRecord ...@@ -331,14 +330,6 @@ class License < ApplicationRecord
Gitlab::SafeRequestStore.delete(:future_dated_license) Gitlab::SafeRequestStore.delete(:future_dated_license)
end end
def previous
Gitlab::SafeRequestStore.fetch(:previous_license) { load_previous }
end
def reset_previous
Gitlab::SafeRequestStore.delete(:previous_license)
end
def global_feature?(feature) def global_feature?(feature)
GLOBAL_FEATURES.include?(feature) GLOBAL_FEATURES.include?(feature)
end end
...@@ -377,10 +368,6 @@ class License < ApplicationRecord ...@@ -377,10 +368,6 @@ class License < ApplicationRecord
def load_future_dated def load_future_dated
self.last_hundred.find { |license| license.valid? && license.future_dated? } self.last_hundred.find { |license| license.valid? && license.future_dated? }
end end
def load_previous
self.last_hundred.find { |license| license.valid? && !license.future_dated? && license != License.current }
end
end end
def data_filename def data_filename
...@@ -641,10 +628,6 @@ class License < ApplicationRecord ...@@ -641,10 +628,6 @@ class License < ApplicationRecord
self.class.reset_future_dated self.class.reset_future_dated
end end
def reset_previous
self.class.reset_previous
end
def reset_license def reset_license
@license = nil @license = nil
end end
...@@ -655,12 +638,27 @@ class License < ApplicationRecord ...@@ -655,12 +638,27 @@ class License < ApplicationRecord
self.errors.add(:base, _('The license key is invalid. Make sure it is exactly as you received it from GitLab Inc.')) self.errors.add(:base, _('The license key is invalid. Make sure it is exactly as you received it from GitLab Inc.'))
end end
# This method, `previous_started_at` and `previous_expired_at` are
# only used in the validation methods `check_users_limit` and check_trueup
# which are only used when uploading/creating a new license.
# The method will not work in other workflows since it has a dependency to
# use the current license as the previous in the system.
def prior_historical_max def prior_historical_max
@prior_historical_max ||= begin strong_memoize(:prior_historical_max) do
historical_max(from: previous_started_at, to: previous_expired_at) historical_max(from: previous_started_at, to: previous_expired_at)
end end
end end
# See comment for `prior_historical_max`.
def previous_started_at
(License.current&.starts_at || starts_at - 1.year).beginning_of_day
end
# See comment for `prior_historical_max`.
def previous_expired_at
(License.current&.expires_at || expires_at && expires_at - 1.year || starts_at).end_of_day
end
def restricted_user_count_with_threshold def restricted_user_count_with_threshold
(restricted_user_count * (1 + ALLOWED_PERCENTAGE_OF_USERS_OVERAGE)).to_i (restricted_user_count * (1 + ALLOWED_PERCENTAGE_OF_USERS_OVERAGE)).to_i
end end
...@@ -669,15 +667,19 @@ class License < ApplicationRecord ...@@ -669,15 +667,19 @@ class License < ApplicationRecord
return if cloud_license? return if cloud_license?
return unless restricted_user_count return unless restricted_user_count
user_count = daily_billable_users_count
current_period = true
if previous_user_count && (prior_historical_max <= previous_user_count) if previous_user_count && (prior_historical_max <= previous_user_count)
return if restricted_user_count_with_threshold >= daily_billable_users_count return if restricted_user_count_with_threshold >= daily_billable_users_count
else else
return if restricted_user_count_with_threshold >= prior_historical_max return if restricted_user_count_with_threshold >= prior_historical_max
end
user_count = prior_historical_max == 0 ? daily_billable_users_count : prior_historical_max user_count = prior_historical_max
current_period = false
end
add_limit_error(current_period: prior_historical_max == 0, user_count: user_count) add_limit_error(current_period: current_period, user_count: user_count)
end end
def check_trueup def check_trueup
...@@ -731,14 +733,6 @@ class License < ApplicationRecord ...@@ -731,14 +733,6 @@ class License < ApplicationRecord
self.errors.add(:base, _('This license has already expired.')) self.errors.add(:base, _('This license has already expired.'))
end end
def previous_started_at
(License.previous&.starts_at || starts_at - 1.year).beginning_of_day
end
def previous_expired_at
(License.previous&.expires_at || starts_at).end_of_day
end
def starts_at_for_historical_data def starts_at_for_historical_data
(starts_at || Time.current - 1.year).beginning_of_day (starts_at || Time.current - 1.year).beginning_of_day
end end
......
...@@ -185,6 +185,30 @@ RSpec.describe License do ...@@ -185,6 +185,30 @@ RSpec.describe License do
end end
describe '#check_users_limit' do describe '#check_users_limit' do
let(:expires_at) { 11.months.from_now.to_date }
let(:restrictions) { { active_user_count: 9 } }
let(:gl_license) do
build(
:gitlab_license,
starts_at: 1.month.ago.to_date,
expires_at: expires_at,
restrictions: restrictions
)
end
def create_historical_data(recorded_at, prior_active_user_count)
create(
:historical_data,
recorded_at: recorded_at + 1.day,
active_user_count: 1
)
create(
:historical_data,
recorded_at: recorded_at,
active_user_count: prior_active_user_count
)
end
context 'for each plan' do context 'for each plan' do
before do before do
create(:group_member, :guest) create(:group_member, :guest)
...@@ -223,164 +247,155 @@ RSpec.describe License do ...@@ -223,164 +247,155 @@ RSpec.describe License do
end end
end end
context 'threshold for users overage' do context 'when license is a cloud license' do
let(:current_active_users_count) { 0 }
let(:new_license) { build(:license, data: gitlab_license.export) }
let(:gitlab_license) do let(:gitlab_license) do
build( build(
:gitlab_license, :gitlab_license,
cloud_licensing_enabled: true,
starts_at: Date.current, starts_at: Date.current,
restrictions: { active_user_count: 10, previous_user_count: previous_user_count } restrictions: { active_user_count: 10 }
) )
end end
context 'when current active users count is above the limit set by the license' do it { is_expected.to be_valid }
before do
create_list(:user, current_active_users_count)
HistoricalData.track!
end end
shared_examples 'current active user count within threshold' do context 'when no restriction is set' do
context 'when current active users count is under the threshold' do let(:restrictions) { {} }
let(:current_active_users_count) { 10 }
it 'accepts the license' do it { is_expected.to be_valid }
expect(new_license).to be_valid
end
end end
context 'when current active users count is equal to the threshold' do context 'without historical data' do
let(:current_active_users_count) { 11 } let(:active_user_count) { 9 }
it 'accepts the license' do before do
expect(new_license).to be_valid create_list(:user, billable_users_count)
end
end
end end
context 'when license is from a fresh subscription' do context 'with previous user count' do
let(:previous_user_count) { nil } let(:prior_active_user_count) { 0 }
let(:restrictions) { { active_user_count: active_user_count, previous_user_count: previous_user_count } }
include_examples 'current active user count within threshold' context 'when prior historical max is less than previous user count' do
let(:previous_user_count) { 1 }
context 'when current active users count is above the threshold' do
let(:current_active_users_count) { 12 }
it 'does not accept the license' do include_examples 'valid daily billable users count compared to limit set by license checks'
expect(new_license).not_to be_valid include_examples 'invalid daily billable users count compared to limit set by license checks'
end end
context 'when license is a cloud license' do context 'when prior historical max is equal to previous user count' do
let(:gitlab_license) do let(:previous_user_count) { 0 }
build(
:gitlab_license,
cloud_licensing_enabled: true,
starts_at: Date.current,
restrictions: { active_user_count: 10, previous_user_count: previous_user_count }
)
end
it 'accepts the license' do include_examples 'valid daily billable users count compared to limit set by license checks'
expect(new_license).to be_valid include_examples 'invalid daily billable users count compared to limit set by license checks'
end end
end end
context 'without previous user count' do
let(:restrictions) { { active_user_count: active_user_count } }
include_examples 'valid prior historical max compared to limit set by license checks'
end end
end end
context 'when license is from a renewal' do context 'with historical data in the term of an existing current license' do
let(:previous_user_count) { 1 } let(:active_user_count) { 9 }
include_examples 'current active user count within threshold' before do
create_list(:user, billable_users_count)
create_historical_data(License.current.expires_at, prior_active_user_count)
end
context 'when current active users count is over the threshold' do context 'with previous user count' do
let(:current_active_users_count) { 12 } let(:previous_user_count) { 7 }
let(:restrictions) { { active_user_count: active_user_count, previous_user_count: previous_user_count } }
it 'does not accept the license' do include_examples 'with previous user count checks'
expect(new_license).not_to be_valid
end
end
end
end
end end
describe 'Historical active user count' do context 'without previous user count' do
let(:active_user_count) { described_class.current.daily_billable_users_count + 10 } let(:restrictions) { { active_user_count: active_user_count } }
let(:date) { described_class.current.starts_at }
let!(:historical_data) { create(:historical_data, recorded_at: date, active_user_count: active_user_count) }
context 'when there is no active user count restriction' do include_examples 'valid prior historical max compared to limit set by license checks'
it { is_expected.to be_valid } include_examples 'invalid prior historical max compared to limit set by license checks'
end
end end
context 'without historical data' do context 'with historical data in the term of the new license (no current license exists)' do
let(:active_user_count) { 9 }
let(:restrictions) { { active_user_count: active_user_count } }
before do before do
create_list(:user, 2) create_list(:user, billable_users_count)
gl_license.restrictions = { allow(License).to receive(:current).and_return(nil)
previous_user_count: 1, end
active_user_count: described_class.current.daily_billable_users_count - 1
}
HistoricalData.delete_all context 'when new license has an expiration date' do
before do
create_historical_data(license.starts_at - 1.year, prior_active_user_count)
end end
context 'with previous_user_count and active users above of license limit' do context 'with previous user count' do
it { is_expected.not_to be_valid } let(:previous_user_count) { 7 }
let(:restrictions) { { active_user_count: active_user_count, previous_user_count: previous_user_count } }
it 'shows the proper error message' do include_examples 'with previous user count checks'
license.valid? end
error_msg = "This GitLab installation currently has 2 active users, exceeding this license's limit of 1 by 1 user. " \ context 'without previous user count' do
"Please upload a license for at least 2 users or contact sales at https://about.gitlab.com/sales/" let(:restrictions) { { active_user_count: active_user_count } }
expect(license.errors[:base].first).to eq(error_msg) include_examples 'valid prior historical max compared to limit set by license checks'
end include_examples 'invalid prior historical max compared to limit set by license checks'
end end
end end
context 'when the active user count restriction is exceeded' do context 'when new license has no expiration' do
before do let(:expires_at) { nil }
gl_license.restrictions = { active_user_count: active_user_count - 1 }
end
context 'when the license started' do before do
it { is_expected.not_to be_valid } create_historical_data(license.starts_at, prior_active_user_count)
end end
context 'after the license started' do context 'with previous user count' do
let(:date) { Date.current } let(:previous_user_count) { 7 }
let(:restrictions) { { active_user_count: active_user_count, previous_user_count: previous_user_count } }
it { is_expected.to be_valid } include_examples 'with previous user count checks'
end end
context 'in the year before the license started' do context 'without previous user count' do
let(:date) { described_class.current.starts_at - 6.months } let(:restrictions) { { active_user_count: active_user_count } }
it { is_expected.not_to be_valid } include_examples 'valid prior historical max compared to limit set by license checks'
include_examples 'invalid prior historical max compared to limit set by license checks'
end end
context 'earlier than a year before the license started' do
let(:date) { described_class.current.starts_at - 2.years }
it { is_expected.to be_valid }
end end
end end
context 'when the active user count restriction is not exceeded' do context 'downgrade' do
context 'when more users were added in previous period' do
before do before do
gl_license.restrictions = { active_user_count: active_user_count + 1 } create(:historical_data, recorded_at: described_class.current.starts_at + 1.month, active_user_count: 15)
set_restrictions(restricted_user_count: 5, previous_user_count: 10)
end end
it { is_expected.to be_valid } it 'is invalid without a true-up' do
expect(license).not_to be_valid
end
end end
context 'when the active user count is met exactly' do context 'when no users were added in the previous period' do
it 'is valid' do before do
active_user_count = 100 create(:historical_data, recorded_at: 6.months.ago, active_user_count: 15)
gl_license.restrictions = { active_user_count: active_user_count }
expect(license).to be_valid set_restrictions(restricted_user_count: 10, previous_user_count: 15)
end end
it { is_expected.to be_valid }
end end
end end
end end
...@@ -406,30 +421,6 @@ RSpec.describe License do ...@@ -406,30 +421,6 @@ RSpec.describe License do
it { is_expected.to be_valid } it { is_expected.to be_valid }
end end
end end
describe 'downgrade' do
context 'when more users were added in previous period' do
before do
create(:historical_data, recorded_at: described_class.current.starts_at - 6.months, active_user_count: 15)
set_restrictions(restricted_user_count: 5, previous_user_count: 10)
end
it 'is invalid without a true-up' do
expect(license).not_to be_valid
end
end
context 'when no users were added in the previous period' do
before do
create(:historical_data, recorded_at: 6.months.ago, active_user_count: 15)
set_restrictions(restricted_user_count: 10, previous_user_count: 15)
end
it { is_expected.to be_valid }
end
end
end end
describe 'Callbacks' do describe 'Callbacks' do
...@@ -498,36 +489,6 @@ RSpec.describe License do ...@@ -498,36 +489,6 @@ RSpec.describe License do
end end
end end
end end
describe '#reset_previous', :request_store do
let!(:previous_license) do
create(
:license,
data: create(:gitlab_license, starts_at: Date.new(1969, 1, 1), expires_at: Date.new(1969, 12, 31)).export)
end
before do
described_class.previous
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_present
end
context 'when a license is created' do
it 'deletes the previous_license value in Gitlab::SafeRequestStore' do
create(:license)
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_nil
end
end
context 'when a license is destroyed' do
it 'deletes the previous_license value in Gitlab::SafeRequestStore' do
previous_license.destroy
expect(Gitlab::SafeRequestStore.read(:previous_license)).to be_nil
end
end
end
end end
describe 'Scopes' do describe 'Scopes' do
...@@ -689,62 +650,6 @@ RSpec.describe License do ...@@ -689,62 +650,6 @@ RSpec.describe License do
end end
end end
describe '.previous' do
before do
described_class.reset_previous
end
context 'when there is no license' do
it 'returns nil' do
allow(described_class).to receive(:last_hundred).and_return([])
expect(described_class.previous).to be_nil
end
end
context 'when the license is invalid' do
it 'returns nil' do
license = build(
:license,
data: build(:gitlab_license, starts_at: Date.new(1969, 1, 1), expires_at: Date.new(1969, 12, 31)).export
)
allow(described_class).to receive(:last_hundred).and_return([license])
allow(license).to receive(:valid?).and_return(false)
expect(described_class.previous).to be_nil
end
end
context 'when the license is valid' do
context 'when only a current and a future dated license exist' do
before do
create(:license, data: create(:gitlab_license, starts_at: Date.current + 1.month).export)
end
it 'returns nil' do
expect(described_class.previous).to be_nil
end
end
context 'when license is not a future dated or the current one' do
it 'returns the the previous license' do
previous_license = create(
:license,
data: create(:gitlab_license, starts_at: Date.new(2000, 1, 1), expires_at: Date.new(2000, 12, 31)).export
)
# create another license since the last uploaded license is considered the current one
create(
:license,
data: create(:gitlab_license, starts_at: Date.new(2001, 1, 1), expires_at: Date.new(2001, 12, 31)).export
)
expect(described_class.previous).to eq(previous_license)
end
end
end
end
describe ".block_changes?" do describe ".block_changes?" do
before do before do
allow(License).to receive(:current).and_return(license) allow(License).to receive(:current).and_return(license)
......
# frozen_string_literal: true
RSpec.shared_examples 'valid daily billable users count compared to limit set by license checks' do
context 'when daily billable users count is less than the restricted user count' do
let(:billable_users_count) { active_user_count - 5 }
it { is_expected.to be_valid }
end
context 'when daily billable users count is equal to the restricted user count' do
let(:billable_users_count) { active_user_count }
it { is_expected.to be_valid }
end
context 'when daily billable users count is equal to the restricted user count with threshold' do
let(:active_user_count) { 10 }
let(:billable_users_count) { 11 }
it { is_expected.to be_valid }
end
end
RSpec.shared_examples 'invalid daily billable users count compared to limit set by license checks' do
context 'when daily billable users count is greater than the restricted user count' do
let(:billable_users_count) { active_user_count + 5 }
it { is_expected.not_to be_valid }
it 'includes the correct error message' do
license.valid?
overage = billable_users_count - active_user_count
error_message = "This GitLab installation currently has #{billable_users_count} active users, " \
"exceeding this license's limit of #{active_user_count} by #{overage} users. " \
"Please upload a license for at least #{billable_users_count} users"
expect(license.errors.full_messages.to_sentence).to include(error_message)
end
end
end
RSpec.shared_examples 'valid prior historical max compared to limit set by license checks' do
context 'when prior historical max is less than the restricted user count' do
let(:billable_users_count) { active_user_count }
let(:prior_active_user_count) { active_user_count - 1 }
it { is_expected.to be_valid }
end
context 'when prior historical max is equal to the restricted user count' do
let(:billable_users_count) { active_user_count }
let(:prior_active_user_count) { active_user_count }
it { is_expected.to be_valid }
end
context 'when prior historical max is equal to the restricted user count with threshold' do
let(:active_user_count) { 10 }
let(:billable_users_count) { active_user_count }
let(:prior_active_user_count) { 11 }
it { is_expected.to be_valid }
end
end
RSpec.shared_examples 'invalid prior historical max compared to limit set by license checks' do
context 'when prior historical max is greater than the restricted user count' do
let(:billable_users_count) { active_user_count }
let(:prior_active_user_count) { active_user_count + 1 }
it { is_expected.not_to be_valid }
it 'includes the correct error message' do
license.valid?
overage = prior_active_user_count - active_user_count
error_message = "During the year before this license started, " \
"this GitLab installation had #{prior_active_user_count} active users, " \
"exceeding this license's limit of #{active_user_count} by #{overage} user. " \
"Please upload a license for at least #{prior_active_user_count} users"
expect(license.errors.full_messages.to_sentence).to include(error_message)
end
end
end
RSpec.shared_examples 'with previous user count checks' do
context 'when prior historical max is less than previous user count' do
let(:prior_active_user_count) { previous_user_count - 5 }
include_examples 'valid daily billable users count compared to limit set by license checks'
include_examples 'invalid daily billable users count compared to limit set by license checks'
end
context 'when prior historical max is equal to previous user count' do
let(:prior_active_user_count) { previous_user_count }
include_examples 'valid daily billable users count compared to limit set by license checks'
include_examples 'invalid daily billable users count compared to limit set by license checks'
end
context 'when prior historical max is greater than previous user count' do
include_examples 'valid prior historical max compared to limit set by license checks'
include_examples 'invalid prior historical max compared to limit set by license checks'
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