Commit 38bd2deb authored by Allison Browne's avatar Allison Browne

Code review feedback

- Add unit test for sending an email
- Use ApplicationRecord instead of ActiveRecord::Base
- Add a test that email is not sent with 0 consumption
- Fix name of new worker to be added in comment
parent c5722c8d
# frozen_string_literal: true
# -----------------------------------------------------------------------
# This file is used by the GDK to generate a default config/puma_actioncable.rb file
# Note that `/Users/allisonbrowne/gitlab/gdk` will be substituted for the actual GDK root
# directory when this file is generated
# -----------------------------------------------------------------------
# Load "path" as a rackup file.
#
# The default is "cable/config.ru".
#
rackup 'cable/config.ru'
pidfile '/Users/allisonbrowne/gitlab/gdk/gitlab/tmp/pids/puma_actioncable.pid'
state_path '/Users/allisonbrowne/gitlab/gdk/gitlab/tmp/pids/puma_actioncable.state'
## Uncomment the lines if you would like to write puma stdout & stderr streams
## to a different location than rails logs.
## When using GitLab Development Kit, by default, these logs will be consumed
## by runit and can be accessed using `gdk tail rails-actioncable`
# stdout_redirect '/Users/allisonbrowne/gitlab/gdk/gitlab/log/puma_actioncable.stdout.log',
# '/Users/allisonbrowne/gitlab/gdk/gitlab/log/puma_actioncable.stderr.log',
# true
# Configure "min" to be the minimum number of threads to use to answer
# requests and "max" the maximum.
#
# The default is "0, 16".
#
threads 1, 4
# By default, workers accept all requests and queue them to pass to handlers.
# When false, workers accept the number of simultaneous requests configured.
#
# Queueing requests generally improves performance, but can cause deadlocks if
# the app is waiting on a request to itself. See https://github.com/puma/puma/issues/612
#
# When set to false this may require a reverse proxy to handle slow clients and
# queue requests before they reach puma. This is due to disabling HTTP keepalive
queue_requests false
# Bind the server to "url". "tcp://", "unix://" and "ssl://" are the only
# accepted protocols.
bind 'unix:///Users/allisonbrowne/gitlab/gdk/gitlab_actioncable.socket'
workers 1
require_relative "/Users/allisonbrowne/gitlab/gdk/gitlab/lib/gitlab/cluster/lifecycle_events"
on_restart do
# Signal application hooks that we're about to restart
Gitlab::Cluster::LifecycleEvents.do_before_master_restart
end
before_fork do
# Signal to the puma killer
Gitlab::Cluster::PumaWorkerKillerInitializer.start @config.options unless ENV['DISABLE_PUMA_WORKER_KILLER']
# Signal application hooks that we're about to fork
Gitlab::Cluster::LifecycleEvents.do_before_fork
end
Gitlab::Cluster::LifecycleEvents.set_puma_options @config.options
on_worker_boot do
# Signal application hooks of worker start
Gitlab::Cluster::LifecycleEvents.do_worker_start
end
# Preload the application before starting the workers; this conflicts with
# phased restart feature. (off by default)
preload_app!
tag 'gitlab-actioncable-puma-worker'
# Verifies that all workers have checked in to the master process within
# the given timeout. If not the worker process will be restarted. Default
# value is 60 seconds.
#
worker_timeout 60
# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#lower-latency-better-throughput
wait_for_less_busy_worker ENV.fetch('PUMA_WAIT_FOR_LESS_BUSY_WORKER', 0.001).to_f
# https://github.com/puma/puma/blob/master/5.0-Upgrade.md#nakayoshi_fork
nakayoshi_fork unless ENV['DISABLE_PUMA_NAKAYOSHI_FORK'] == 'true'
# Use json formatter
require_relative "/Users/allisonbrowne/gitlab/gdk/gitlab/lib/gitlab/puma_logging/json_formatter"
json_formatter = Gitlab::PumaLogging::JSONFormatter.new
log_formatter do |str|
json_formatter.call(str)
end
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
module Ci module Ci
module Minutes module Minutes
class UpdateBuildMinutesService < BaseService class UpdateBuildMinutesService < BaseService
# Calculates consumption and updates the project and namespace minutes based on the passed build # Calculates consumption and updates the project and namespace statistics(legacy)
# Calculating the consumption syncronously before triggering an async job to update # or ProjectMonthlyUsage and NamespaceMonthlyUsage(not legacy) based on the passed build.
# ensures the pipeline data still exists in the case where minutes are updated prior to pipeline deletion
def execute(build) def execute(build)
return unless build.shared_runners_minutes_limit_enabled? return unless build.shared_runners_minutes_limit_enabled?
return unless build.complete? return unless build.complete?
...@@ -13,13 +12,10 @@ module Ci ...@@ -13,13 +12,10 @@ module Ci
consumption = ::Gitlab::Ci::Minutes::BuildConsumption.new(build, build.duration).amount consumption = ::Gitlab::Ci::Minutes::BuildConsumption.new(build, build.duration).amount
Ci::Minutes::UpdateMinutesByConsumptionService.new(project, namespace).execute(consumption) return unless consumption > 0
# TODO(Issue #335338): Introduce async worker UpdateMinutesByConsumptionWorker, example:
# if ::Feature.enabled?(:cancel_pipelines_prior_to_destroy, default_enabled: :yaml) # TODO(Issue #335338): Introduce async worker UpdateProjectAndNamespaceUsageWorker
# Ci::Minutes::UpdateMinutesByConsumptionService.new(project, namespace).execute_async(consumption) Ci::Minutes::UpdateProjectAndNamespaceUsageService.new(project, namespace).execute(consumption)
# else
# Ci::Minutes::UpdateMinutesByConsumptionService.new(project, namespace).execute(consumption)
# end
compare_with_live_consumption(build, consumption) compare_with_live_consumption(build, consumption)
end end
......
...@@ -2,26 +2,24 @@ ...@@ -2,26 +2,24 @@
module Ci module Ci
module Minutes module Minutes
class UpdateMinutesByConsumptionService class UpdateProjectAndNamespaceUsageService
def initialize(project, namespace) def initialize(project, namespace)
@project = project @project = project
@namespace = namespace @namespace = namespace
end end
# Updates the project and namespace minutes based on the passed consumption amount # Updates the project and namespace usage based on the passed consumption amount
def execute(consumption) def execute(consumption)
return unless consumption > 0
consumption_in_seconds = consumption.minutes.to_i consumption_in_seconds = consumption.minutes.to_i
legacy_track_usage_of_monthly_minutes(consumption_in_seconds) legacy_track_usage_of_monthly_minutes(consumption_in_seconds)
track_usage_of_monthly_minutes(consumption) track_usage_of_monthly_minutes(consumption)
send_email_notification send_minutes_email_notification
end end
private private
def send_email_notification def send_minutes_email_notification
# `perform reset` on `project` because `Namespace#namespace_statistics` will otherwise return stale data. # `perform reset` on `project` because `Namespace#namespace_statistics` will otherwise return stale data.
::Ci::Minutes::EmailNotificationService.new(@project.reset).execute if ::Gitlab.com? ::Ci::Minutes::EmailNotificationService.new(@project.reset).execute if ::Gitlab.com?
end end
...@@ -40,7 +38,7 @@ module Ci ...@@ -40,7 +38,7 @@ module Ci
namespace_usage = ::Ci::Minutes::NamespaceMonthlyUsage.find_or_create_current(@namespace) namespace_usage = ::Ci::Minutes::NamespaceMonthlyUsage.find_or_create_current(@namespace)
project_usage = ::Ci::Minutes::ProjectMonthlyUsage.find_or_create_current(@project) project_usage = ::Ci::Minutes::ProjectMonthlyUsage.find_or_create_current(@project)
ActiveRecord::Base.transaction do ApplicationRecord.transaction do
::Ci::Minutes::NamespaceMonthlyUsage.increase_usage(namespace_usage, consumption) ::Ci::Minutes::NamespaceMonthlyUsage.increase_usage(namespace_usage, consumption)
::Ci::Minutes::ProjectMonthlyUsage.increase_usage(project_usage, consumption) ::Ci::Minutes::ProjectMonthlyUsage.increase_usage(project_usage, consumption)
end end
......
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
private private
def cost_factor def cost_factor
@build&.runner&.minutes_cost_factor(project_visibility_level) @build.runner.minutes_cost_factor(project_visibility_level)
end end
def project_visibility_level def project_visibility_level
......
...@@ -31,6 +31,38 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do ...@@ -31,6 +31,38 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do
end end
end end
shared_examples 'does nothing' do
it 'does not update legacy statistics' do
subject
expect(project.statistics.reload.shared_runners_seconds).to eq(0)
expect(namespace.namespace_statistics).to be_nil
end
it 'does not update namespace monthly usage' do
expect { subject }.not_to change { Ci::Minutes::NamespaceMonthlyUsage.count }
end
it 'does not update project monthly usage' do
expect { subject }.not_to change { Ci::Minutes::ProjectMonthlyUsage.count }
end
it 'does not observe the difference between actual vs live consumption' do
expect(::Gitlab::Ci::Pipeline::Metrics)
.not_to receive(:gitlab_ci_difference_live_vs_actual_minutes)
subject
end
it 'does not send an email' do
allow(Gitlab).to receive(:com?).and_return(true)
expect(Ci::Minutes::EmailNotificationService).not_to receive(:new)
subject
end
end
context 'with shared runner' do context 'with shared runner' do
let(:cost_factor) { 2.0 } let(:cost_factor) { 2.0 }
let(:runner) { create(:ci_runner, :instance, private_projects_minutes_cost_factor: cost_factor) } let(:runner) { create(:ci_runner, :instance, private_projects_minutes_cost_factor: cost_factor) }
...@@ -91,7 +123,17 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do ...@@ -91,7 +123,17 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do
end end
end end
context 'when statistics are created' do context 'when consumption is 0' do
let(:build) do
create(:ci_build, :success,
runner: runner, pipeline: pipeline,
started_at: Time.current, finished_at: Time.current)
end
it_behaves_like 'does nothing'
end
context 'when statistics and usage have existing amounts' do
let(:usage_in_seconds) { 100 } let(:usage_in_seconds) { 100 }
let(:usage_in_minutes) { (100.to_f / 60).round(2) } let(:usage_in_minutes) { (100.to_f / 60).round(2) }
...@@ -208,19 +250,7 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do ...@@ -208,19 +250,7 @@ RSpec.describe Ci::Minutes::UpdateBuildMinutesService do
context 'for specific runner' do context 'for specific runner' do
let(:runner) { create(:ci_runner, :project) } let(:runner) { create(:ci_runner, :project) }
it 'does not create statistics' do it_behaves_like 'does nothing'
subject
expect(namespace.namespace_statistics).to be_nil
end
it 'does not track namespace monthly usage' do
expect { subject }.not_to change { Ci::Minutes::NamespaceMonthlyUsage.count }
end
it 'does not track project monthly usage' do
expect { subject }.not_to change { Ci::Minutes::ProjectMonthlyUsage.count }
end
end end
end end
end end
...@@ -2,16 +2,16 @@ ...@@ -2,16 +2,16 @@
require 'spec_helper' require 'spec_helper'
RSpec.describe Ci::Minutes::UpdateMinutesByConsumptionService do RSpec.describe Ci::Minutes::UpdateProjectAndNamespaceUsageService do
describe '#execute' do let_it_be(:namespace) { create(:namespace, shared_runners_minutes_limit: 100) }
let_it_be(:namespace) { create(:namespace, shared_runners_minutes_limit: 100) } let_it_be(:project) { create(:project, :private, namespace: namespace) }
let_it_be(:project) { create(:project, :private, namespace: namespace) }
let(:consumption_minutes) { 120 } let(:consumption_minutes) { 120 }
let(:consumption_seconds) { 120 * 60 } let(:consumption_seconds) { consumption_minutes * 60 }
let(:namespace_amount_used) { Ci::Minutes::NamespaceMonthlyUsage.find_or_create_current(namespace).amount_used } let(:namespace_amount_used) { Ci::Minutes::NamespaceMonthlyUsage.find_or_create_current(namespace).amount_used }
let(:project_amount_used) { Ci::Minutes::ProjectMonthlyUsage.find_or_create_current(project).amount_used } let(:project_amount_used) { Ci::Minutes::ProjectMonthlyUsage.find_or_create_current(project).amount_used }
describe '#execute' do
subject { described_class.new(project, namespace).execute(consumption_minutes) } subject { described_class.new(project, namespace).execute(consumption_minutes) }
context 'with shared runner' do context 'with shared runner' do
...@@ -45,6 +45,32 @@ RSpec.describe Ci::Minutes::UpdateMinutesByConsumptionService do ...@@ -45,6 +45,32 @@ RSpec.describe Ci::Minutes::UpdateMinutesByConsumptionService do
expect(project_amount_used).to eq(0) expect(project_amount_used).to eq(0)
end end
end end
context 'when on .com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
it 'sends a minute notification email' do
expect_next_instance_of(Ci::Minutes::EmailNotificationService) do |service|
expect(service).to receive(:execute)
end
subject
end
end
context 'when not on .com' do
before do
allow(Gitlab).to receive(:com?).and_return(false)
end
it 'does not send a minute notification email' do
expect(Ci::Minutes::EmailNotificationService).not_to receive(:new)
subject
end
end
end end
context 'when statistics and usage have existing values' do context 'when statistics and usage have existing values' do
......
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