Commit 771646f4 authored by Fabio Pitino's avatar Fabio Pitino Committed by Doug Stull

Reset notification level when CI minutes limit change

Reset notification level on new monthly tracking when
either the monthly limit or the additional minutes limit
changes.

Changelog: changed
EE: true
parent 39e940d8
......@@ -9,7 +9,7 @@ module EE
def reset_runners_minutes
group
if ClearNamespaceSharedRunnersMinutesService.new(@group).execute
if ::Ci::Minutes::ResetUsageService.new(@group).execute
redirect_to [:admin, @group], notice: _('Group pipeline minutes were successfully reset.')
else
flash.now[:error] = _('There was an error resetting group pipeline minutes.')
......
......@@ -9,7 +9,7 @@ module EE
def reset_runners_minutes
user
if ClearNamespaceSharedRunnersMinutesService.new(@user.namespace).execute
if ::Ci::Minutes::ResetUsageService.new(@user.namespace).execute
redirect_to [:admin, @user], notice: _('User pipeline minutes were successfully reset.')
else
flash.now[:error] = _('There was an error resetting user pipeline minutes.')
......
......@@ -36,8 +36,21 @@ module Ci
update_counters(usage, amount_used: amount)
end
def self.reset_current_usage(namespace)
update_current(namespace, amount_used: 0, notification_level: Notification::PERCENTAGES.fetch(:not_set))
end
def self.reset_current_notification_level(namespace)
update_current(namespace, notification_level: Notification::PERCENTAGES.fetch(:not_set))
end
def self.update_current(namespace, attributes)
current_month.for_namespace(namespace).update_all(attributes)
end
private_class_method :update_current
def total_usage_notified?
usage_notified?(0)
usage_notified?(Notification::PERCENTAGES.fetch(:exceeded))
end
# Notification_level is set to 100 (meaning 100% remaining minutes) by default.
......
......@@ -4,6 +4,7 @@ module Ci
module Minutes
class Notification
PERCENTAGES = {
not_set: 100,
warning: 30,
danger: 5,
exceeded: 0
......
# frozen_string_literal: true
module Ci
module Minutes
class ResetUsageService < BaseService
def initialize(namespace)
@namespace = namespace
end
def execute
Ci::Minutes::NamespaceMonthlyUsage.reset_current_usage(@namespace)
reset_legacy_usage
::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
true
end
private
# rubocop: disable CodeReuse/ActiveRecord
def reset_legacy_usage
NamespaceStatistics.where(namespace: @namespace).update_all(
shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current)
end
# rubocop: enable CodeReuse/ActiveRecord
end
end
end
# frozen_string_literal: true
class ClearNamespaceSharedRunnersMinutesService < BaseService
def initialize(namespace)
@namespace = namespace
end
# rubocop: disable CodeReuse/ActiveRecord
def execute
NamespaceStatistics.where(namespace: @namespace).update_all(
shared_runners_seconds: 0,
shared_runners_seconds_last_reset: Time.current
).tap do
::Ci::Minutes::RefreshCachedDataService.new(@namespace).execute
end
end
# rubocop: enable CodeReuse/ActiveRecord
end
......@@ -26,12 +26,14 @@ module EE
if params[:extra_shared_runners_minutes_limit].present?
update_attrs[:last_ci_minutes_notification_at] = nil
update_attrs[:last_ci_minutes_usage_notification_level] = nil
::Ci::Runner.instance_type.each(&:tick_runner_queue)
end
namespace.update(update_attrs).tap do
if update_attrs[:extra_shared_runners_minutes_limit].present? || update_attrs[:shared_runners_minutes_limit].present?
::Ci::Minutes::RefreshCachedDataService.new(namespace).execute
::Ci::Minutes::NamespaceMonthlyUsage.reset_current_notification_level(namespace)
end
end
end
......
......@@ -14,7 +14,7 @@ RSpec.describe Admin::GroupsController do
subject { post :reset_runners_minutes, params: { id: group } }
before do
allow_next_instance_of(ClearNamespaceSharedRunnersMinutesService) do |instance|
allow_next_instance_of(Ci::Minutes::ResetUsageService) do |instance|
allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result)
end
end
......
......@@ -64,7 +64,7 @@ RSpec.describe Admin::UsersController do
subject { post :reset_runners_minutes, params: { id: user } }
before do
allow_next_instance_of(ClearNamespaceSharedRunnersMinutesService) do |instance|
allow_next_instance_of(Ci::Minutes::ResetUsageService) do |instance|
allow(instance).to receive(:execute).and_return(clear_runners_minutes_service_result)
end
end
......
......@@ -6,4 +6,8 @@ FactoryBot.define do
namespace factory: :namespace
date { Time.current.utc.beginning_of_month }
end
trait :with_warning_notification_level do
notification_level { ::Ci::Minutes::Notification::PERCENTAGES.fetch(:warning) }
end
end
......@@ -35,7 +35,7 @@ RSpec.describe 'Reset namespace pipeline minutes', :js do
shared_examples 'rendering error' do
context 'when resetting pipeline minutes fails' do
before do
allow_any_instance_of(ClearNamespaceSharedRunnersMinutesService).to receive(:execute).and_return(false)
allow_any_instance_of(Ci::Minutes::ResetUsageService).to receive(:execute).and_return(false)
end
it 'renders edit page with an error' do
......
......@@ -5,11 +5,14 @@ require 'spec_helper'
RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
let_it_be(:namespace) { create(:namespace) }
describe 'unique index' do
before_all do
create(:ci_namespace_monthly_usage, namespace: namespace)
end
let_it_be_with_refind(:current_usage) do
create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
amount_used: 100)
end
describe 'unique index' do
it 'raises unique index violation' do
expect { create(:ci_namespace_monthly_usage, namespace: namespace) }
.to raise_error { ActiveRecord::RecordNotUnique }
......@@ -36,39 +39,39 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
end
end
context 'when namespace usage does not exist' do
it_behaves_like 'creates usage record'
end
context 'when namespace usage exists for previous months' do
context 'when namespace usage does not exist for current month' do
before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
current_usage.destroy!
end
it_behaves_like 'creates usage record'
context 'when namespace usage exists for previous months' do
before do
create(:ci_namespace_monthly_usage, namespace: namespace, date: described_class.beginning_of_month(2.months.ago))
end
it_behaves_like 'creates usage record'
end
context 'when a usage for another namespace exists for the current month' do
let!(:usage) { create(:ci_namespace_monthly_usage) }
it_behaves_like 'creates usage record'
end
end
context 'when namespace usage exists for the current month' do
it 'returns the existing usage' do
freeze_time do
usage = create(:ci_namespace_monthly_usage, namespace: namespace)
expect(subject).to eq(usage)
expect(subject).to eq(current_usage)
end
end
end
context 'when a usage for another namespace exists for the current month' do
let!(:usage) { create(:ci_namespace_monthly_usage) }
it_behaves_like 'creates usage record'
end
end
describe '.increase_usage' do
subject { described_class.increase_usage(usage, amount) }
let(:usage) { create(:ci_namespace_monthly_usage, namespace: namespace, amount_used: 100.0) }
subject { described_class.increase_usage(current_usage, amount) }
context 'when amount is greater than 0' do
let(:amount) { 10.5 }
......@@ -76,7 +79,7 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'updates the current month usage' do
subject
expect(usage.reload.amount_used).to eq(110.5)
expect(current_usage.reload.amount_used).to eq(110.5)
end
end
......@@ -86,45 +89,109 @@ RSpec.describe Ci::Minutes::NamespaceMonthlyUsage do
it 'does not update the current month usage' do
subject
expect(usage.reload.amount_used).to eq(100.0)
expect(current_usage.reload.amount_used).to eq(100.0)
end
end
end
describe '.for_namespace' do
it 'returns usages for the namespace' do
matching_usage = create(:ci_namespace_monthly_usage, namespace: namespace)
create(:ci_namespace_monthly_usage, namespace: create(:namespace))
usages = described_class.for_namespace(namespace)
expect(usages).to contain_exactly(matching_usage)
expect(usages).to contain_exactly(current_usage)
end
end
describe '.reset_current_usage', :aggregate_failures do
subject { described_class.reset_current_usage(namespace) }
it 'resets current usage and notification level' do
subject
current_usage.reload
expect(current_usage.amount_used).to eq(0)
expect(current_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'does not reset data from previous months' do
previous_usage = create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
date: 1.month.ago.beginning_of_month.to_date)
subject
previous_usage.reload
expect(previous_usage.amount_used).to eq(100)
expect(previous_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:warning))
end
it 'does not reset data from other namespaces' do
another_usage = create(:ci_namespace_monthly_usage, :with_warning_notification_level)
subject
another_usage.reload
expect(another_usage.amount_used).to eq(100)
expect(another_usage.notification_level).to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:warning))
end
end
describe '.reset_current_notification_level' do
subject { described_class.reset_current_notification_level(namespace) }
it 'resets current notification level' do
expect { subject }
.to change { current_usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'does not reset notification level from previous months' do
previous_usage = create(:ci_namespace_monthly_usage,
:with_warning_notification_level,
namespace: namespace,
date: 1.month.ago.beginning_of_month.to_date)
expect { subject }
.not_to change { previous_usage.reload.notification_level }
end
it 'does not reset notification level from other namespaces' do
another_usage = create(:ci_namespace_monthly_usage, :with_warning_notification_level)
expect { subject }
.not_to change { another_usage.reload.notification_level }
end
end
describe '#usage_notified?' do
let(:usage) { build(:ci_namespace_monthly_usage, notification_level: notification_level) }
let(:notification_level) { 100 }
subject { current_usage.usage_notified?(remaining_percentage) }
subject { usage.usage_notified?(remaining_percentage) }
before do
current_usage.update!(notification_level: 30)
end
context 'when parameter is different than notification level' do
let(:remaining_percentage) { 30 }
let(:remaining_percentage) { 5 }
it { is_expected.to be_falsey }
end
context 'when parameter is same as the notification level' do
let(:remaining_percentage) { notification_level }
let(:remaining_percentage) { 30 }
it { is_expected.to be_truthy }
end
end
describe '#total_usage_notified?' do
let(:usage) { build(:ci_namespace_monthly_usage, notification_level: notification_level) }
before do
current_usage.update!(notification_level: notification_level)
end
subject { usage.total_usage_notified? }
subject { current_usage.total_usage_notified? }
context 'notification level is higher than zero' do
let(:notification_level) { 30 }
......
......@@ -234,6 +234,17 @@ RSpec.describe API::Namespaces do
subject
end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'resets the current CI minutes notification level' do
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.to change { usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
end
context 'when request has extra_shared_runners_minutes_limit param' do
before do
params[:extra_shared_runners_minutes_limit] = 1000
......@@ -264,6 +275,17 @@ RSpec.describe API::Namespaces do
expect(pending_build.reload.minutes_exceeded).to eq(false)
end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'resets the current CI minutes notification level' do
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.to change { usage.reload.notification_level }
.to(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
end
end
context 'when neither minutes limit params is provided' do
......@@ -273,6 +295,18 @@ RSpec.describe API::Namespaces do
subject
end
context 'when current CI minutes notification level is set' do
let!(:usage) { create(:ci_namespace_monthly_usage, :with_warning_notification_level, namespace: group1) }
it 'does not reset the current CI minutes notification level' do
params.delete(:shared_runners_minutes_limit)
expect do
put api("/namespaces/#{group1.id}", admin), params: params
end.not_to change { usage.reload.notification_level }
end
end
end
end
......
......@@ -2,22 +2,37 @@
require 'spec_helper'
RSpec.describe ClearNamespaceSharedRunnersMinutesService do
RSpec.describe Ci::Minutes::ResetUsageService do
include AfterNextHelpers
describe '#execute' do
subject { described_class.new(namespace).execute }
context 'when project has namespace_statistics' do
let(:namespace) { create(:namespace, :with_used_build_minutes_limit) }
let_it_be(:namespace) { create(:namespace, :with_used_build_minutes_limit) }
it 'clears counters' do
let_it_be(:namespace_usage) do
create(:ci_namespace_monthly_usage, :with_warning_notification_level,
namespace: namespace,
amount_used: 100)
end
it 'clears the amount used and notification levels', :aggregate_failures do
subject
namespace_usage.reload
expect(namespace_usage.amount_used).to eq(0)
expect(namespace_usage.notification_level)
.to eq(Ci::Minutes::Notification::PERCENTAGES.fetch(:not_set))
end
it 'clears legacy counters' do
subject
expect(namespace.namespace_statistics.reload.shared_runners_seconds).to eq(0)
end
it 'resets timer' do
it 'resets legacy timer' do
subject
expect(namespace.namespace_statistics.reload.shared_runners_seconds_last_reset).to be_like_time(Time.current)
......
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