Commit d8db5e74 authored by Tyler Amos's avatar Tyler Amos Committed by Adam Hegyi

Add more storage methods to project and namespace

Add methods to namespace and project related to excess storage. These
methods are a building block to displaying necessary data in
getStorageCounter graphql query.
parent 8897ad74
...@@ -197,23 +197,36 @@ module EE ...@@ -197,23 +197,36 @@ module EE
def total_repository_size_excess def total_repository_size_excess
strong_memoize(:total_repository_size_excess) do strong_memoize(:total_repository_size_excess) do
namespace_size_limit = actual_size_limit namespace_size_limit = actual_size_limit
namespace_limit_arel = Arel::Nodes::SqlLiteral.new(namespace_size_limit.to_s.presence || 'NULL') namespace_limit_arel = Arel::Nodes::SqlLiteral.new(namespace_size_limit.to_s.presence || 'NULL')
condition = 'projects.repository_size_limit != 0 AND project_statistics.repository_size > projects.repository_size_limit'
total_excess = total_repository_size_excess_calculation(condition, ::Project.arel_table[:repository_size_limit]) total_excess = total_repository_size_excess_calculation(::Project.arel_table[:repository_size_limit])
total_excess += total_repository_size_excess_calculation(namespace_limit_arel, project_level: false) if namespace_size_limit.to_i > 0
total_excess
end
end
if namespace_size_limit.to_i > 0 def repository_size_excess_project_count
condition = 'projects.repository_size_limit IS NULL AND project_statistics.repository_size > :namespace_size_limit' strong_memoize(:repository_size_excess_project_count) do
sanitized_condition = self.class.sanitize_sql_array([condition, namespace_size_limit: namespace_size_limit]) namespace_size_limit = actual_size_limit
total_excess += total_repository_size_excess_calculation(sanitized_condition, namespace_limit_arel) count = projects_for_repository_size_excess.count
count += projects_for_repository_size_excess(namespace_size_limit).count if namespace_size_limit.to_i > 0
count
end
end end
total_excess def total_repository_size
strong_memoize(:total_repository_size) do
all_projects
.joins(:statistics)
.pluck(total_repository_size_arel.sum).first || 0 # rubocop:disable Rails/Pick
end end
end end
def contains_locked_projects?
total_repository_size_excess > additional_purchased_storage_size.megabytes
end
def actual_size_limit def actual_size_limit
::Gitlab::CurrentSettings.repository_size_limit ::Gitlab::CurrentSettings.repository_size_limit
end end
...@@ -432,14 +445,27 @@ module EE ...@@ -432,14 +445,27 @@ module EE
[actual_shared_runners_minutes_limit.to_f - shared_runners_minutes.to_f, 0].max [actual_shared_runners_minutes_limit.to_f - shared_runners_minutes.to_f, 0].max
end end
def total_repository_size_excess_calculation(condition, limit) def total_repository_size_excess_calculation(repository_size_limit, project_level: true)
select_sql = Arel::Nodes::NamedFunction.new('SUM', [::ProjectStatistics.arel_table[:repository_size] - limit]).to_sql total_excess = (total_repository_size_arel - repository_size_limit).sum
relation = projects_for_repository_size_excess((repository_size_limit unless project_level))
relation.pluck(total_excess).first || 0 # rubocop:disable Rails/Pick
end
def total_repository_size_arel
arel_table = ::ProjectStatistics.arel_table
arel_table[:repository_size] + arel_table[:lfs_objects_size]
end
def projects_for_repository_size_excess(limit = nil)
if limit
all_projects all_projects
.joins(:statistics) .with_total_repository_size_greater_than(limit)
.where(condition) .without_repository_size_limit
.pluck(Arel.sql(select_sql)) # rubocop:disable Rails/Pick else
.first || 0 all_projects
.with_total_repository_size_greater_than(::Project.arel_table[:repository_size_limit])
.without_unlimited_repository_size_limit
end
end end
end end
end end
...@@ -155,6 +155,15 @@ module EE ...@@ -155,6 +155,15 @@ module EE
scope :with_group_saml_provider, -> { preload(group: :saml_provider) } scope :with_group_saml_provider, -> { preload(group: :saml_provider) }
scope :with_total_repository_size_greater_than, -> (value) do
statistics = ::ProjectStatistics.arel_table
joins(:statistics)
.where((statistics[:repository_size] + statistics[:lfs_objects_size]).gt(value))
end
scope :without_unlimited_repository_size_limit, -> { where.not(repository_size_limit: 0) }
scope :without_repository_size_limit, -> { where(repository_size_limit: nil) }
delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset, delegate :shared_runners_minutes, :shared_runners_seconds, :shared_runners_seconds_last_reset,
to: :statistics, allow_nil: true to: :statistics, allow_nil: true
...@@ -507,16 +516,28 @@ module EE ...@@ -507,16 +516,28 @@ module EE
::Gitlab::UrlSanitizer.new(bare_url, credentials: { user: import_data&.user }).full_url ::Gitlab::UrlSanitizer.new(bare_url, credentials: { user: import_data&.user }).full_url
end end
def actual_size_limit
strong_memoize(:actual_size_limit) do
repository_size_limit || namespace.actual_size_limit
end
end
def repository_size_checker def repository_size_checker
strong_memoize(:repository_size_checker) do strong_memoize(:repository_size_checker) do
::Gitlab::RepositorySizeChecker.new( ::Gitlab::RepositorySizeChecker.new(
current_size_proc: -> { statistics.total_repository_size }, current_size_proc: -> { statistics.total_repository_size },
limit: (repository_size_limit || namespace.actual_size_limit), limit: actual_size_limit,
enabled: License.feature_available?(:repository_size_limit) enabled: License.feature_available?(:repository_size_limit)
) )
end end
end end
def repository_size_excess
return 0 unless actual_size_limit.to_i > 0
[statistics.total_repository_size - actual_size_limit, 0].max
end
def username_only_import_url=(value) def username_only_import_url=(value)
unless ::Gitlab::UrlSanitizer.valid?(value) unless ::Gitlab::UrlSanitizer.valid?(value)
self.import_url = value self.import_url = value
......
...@@ -1434,36 +1434,20 @@ RSpec.describe Namespace do ...@@ -1434,36 +1434,20 @@ RSpec.describe Namespace do
describe '#total_repository_size_excess' do describe '#total_repository_size_excess' do
let_it_be(:namespace) { create(:namespace) } let_it_be(:namespace) { create(:namespace) }
def create_project(repository_size:, repository_size_limit:)
create(:project, namespace: namespace, repository_size_limit: repository_size_limit).tap do |project|
create(:project_statistics, project: project, repository_size: repository_size)
end
end
before do before do
namespace.clear_memoization(:total_repository_size_excess) namespace.clear_memoization(:total_repository_size_excess)
end end
context 'projects with a variety of repository sizes and limits' do context 'projects with a variety of repository sizes and limits' do
before_all do before_all do
create_project(repository_size: 100, repository_size_limit: nil) create_storage_excess_example_projects
create_project(repository_size: 150, repository_size_limit: nil)
create_project(repository_size: 200, repository_size_limit: nil)
create_project(repository_size: 100, repository_size_limit: 0)
create_project(repository_size: 150, repository_size_limit: 0)
create_project(repository_size: 200, repository_size_limit: 0)
create_project(repository_size: 300, repository_size_limit: 400)
create_project(repository_size: 400, repository_size_limit: 400)
create_project(repository_size: 500, repository_size_limit: 300)
end end
context 'when namespace-level repository_size_limit is not set' do context 'when namespace-level repository_size_limit is not set' do
it 'returns the total excess size of projects with repositories that exceed the size limit' do it 'returns the total excess size of projects with repositories that exceed the size limit' do
allow(namespace).to receive(:actual_size_limit).and_return(nil) allow(namespace).to receive(:actual_size_limit).and_return(nil)
expect(namespace.total_repository_size_excess).to eq(200) expect(namespace.total_repository_size_excess).to eq(400)
end end
end end
...@@ -1471,7 +1455,7 @@ RSpec.describe Namespace do ...@@ -1471,7 +1455,7 @@ RSpec.describe Namespace do
it 'returns the total excess size of projects with repositories that exceed the size limit' do it 'returns the total excess size of projects with repositories that exceed the size limit' do
allow(namespace).to receive(:actual_size_limit).and_return(0) allow(namespace).to receive(:actual_size_limit).and_return(0)
expect(namespace.total_repository_size_excess).to eq(200) expect(namespace.total_repository_size_excess).to eq(400)
end end
end end
...@@ -1479,16 +1463,16 @@ RSpec.describe Namespace do ...@@ -1479,16 +1463,16 @@ RSpec.describe Namespace do
it 'returns the total excess size of projects with repositories that exceed the size limit' do it 'returns the total excess size of projects with repositories that exceed the size limit' do
allow(namespace).to receive(:actual_size_limit).and_return(150) allow(namespace).to receive(:actual_size_limit).and_return(150)
expect(namespace.total_repository_size_excess).to eq(250) expect(namespace.total_repository_size_excess).to eq(560)
end end
end end
end end
context 'when all projects have repository_size_limit of 0 (unlimited)' do context 'when all projects have repository_size_limit of 0 (unlimited)' do
before do before do
create_project(repository_size: 100, repository_size_limit: 0) create_project(repository_size: 100, lfs_objects_size: 0, repository_size_limit: 0)
create_project(repository_size: 150, repository_size_limit: 0) create_project(repository_size: 150, lfs_objects_size: 0, repository_size_limit: 0)
create_project(repository_size: 200, repository_size_limit: 0) create_project(repository_size: 200, lfs_objects_size: 100, repository_size_limit: 0)
allow(namespace).to receive(:actual_size_limit).and_return(150) allow(namespace).to receive(:actual_size_limit).and_return(150)
end end
...@@ -1499,6 +1483,104 @@ RSpec.describe Namespace do ...@@ -1499,6 +1483,104 @@ RSpec.describe Namespace do
end end
end end
describe '#repository_size_excess_project_count' do
let_it_be(:namespace) { create(:namespace) }
before do
namespace.clear_memoization(:repository_size_excess_project_count)
end
context 'projects with a variety of repository sizes and limits' do
before_all do
create_storage_excess_example_projects
end
context 'when namespace-level repository_size_limit is not set' do
before do
allow(namespace).to receive(:actual_size_limit).and_return(nil)
end
it 'returns the count of projects with repositories that exceed the size limit' do
expect(namespace.repository_size_excess_project_count).to eq(2)
end
end
context 'when namespace-level repository_size_limit is 0 (unlimited)' do
before do
allow(namespace).to receive(:actual_size_limit).and_return(0)
end
it 'returns the count of projects with repositories that exceed the size limit' do
expect(namespace.repository_size_excess_project_count).to eq(2)
end
end
context 'when namespace-level repository_size_limit is a positive number' do
before do
allow(namespace).to receive(:actual_size_limit).and_return(150)
end
it 'returns the count of projects with repositories that exceed the size limit' do
expect(namespace.repository_size_excess_project_count).to eq(4)
end
end
end
context 'when all projects have repository_size_limit of 0 (unlimited)' do
before do
create_project(repository_size: 100, lfs_objects_size: 0, repository_size_limit: 0)
create_project(repository_size: 150, lfs_objects_size: 0, repository_size_limit: 0)
create_project(repository_size: 200, lfs_objects_size: 100, repository_size_limit: 0)
allow(namespace).to receive(:actual_size_limit).and_return(150)
end
it 'returns zero regardless of the namespace or instance-level repository_size_limit' do
expect(namespace.repository_size_excess_project_count).to eq(0)
end
end
end
describe '#total_repository_size' do
let(:namespace) { create(:namespace) }
before do
create_project(repository_size: 100, lfs_objects_size: 0, repository_size_limit: nil)
create_project(repository_size: 150, lfs_objects_size: 100, repository_size_limit: 0)
create_project(repository_size: 325, lfs_objects_size: 200, repository_size_limit: 400)
end
it 'returns the total size of all project repositories' do
expect(namespace.total_repository_size).to eq(875)
end
end
describe '#contains_locked_projects?' do
using RSpec::Parameterized::TableSyntax
let_it_be(:namespace) { create(:namespace) }
before_all do
create(:namespace_limit, namespace: namespace, additional_purchased_storage_size: 10)
end
where(:total_excess, :result) do
5.megabytes | false
10.megabytes | false
15.megabytes | true
end
with_them do
before do
allow(namespace).to receive(:total_repository_size_excess).and_return(total_excess)
end
it 'returns a boolean indicating whether the root namespace contains locked projects' do
expect(namespace.contains_locked_projects?).to be result
end
end
end
describe '#actual_size_limit' do describe '#actual_size_limit' do
let(:namespace) { build(:namespace) } let(:namespace) { build(:namespace) }
...@@ -1687,4 +1769,28 @@ RSpec.describe Namespace do ...@@ -1687,4 +1769,28 @@ RSpec.describe Namespace do
expect(namespace.errors[:"namespace_limit.temporary_storage_increase_ends_on"]).to be_present expect(namespace.errors[:"namespace_limit.temporary_storage_increase_ends_on"]).to be_present
end end
end end
def create_project(repository_size:, lfs_objects_size:, repository_size_limit:)
create(:project, namespace: namespace, repository_size_limit: repository_size_limit).tap do |project|
create(:project_statistics, project: project, repository_size: repository_size, lfs_objects_size: lfs_objects_size)
end
end
def create_storage_excess_example_projects
[
{ repository_size: 100, lfs_objects_size: 0, repository_size_limit: nil },
{ repository_size: 150, lfs_objects_size: 0, repository_size_limit: nil },
{ repository_size: 140, lfs_objects_size: 10, repository_size_limit: nil },
{ repository_size: 150, lfs_objects_size: 10, repository_size_limit: nil },
{ repository_size: 200, lfs_objects_size: 100, repository_size_limit: nil },
{ repository_size: 100, lfs_objects_size: 0, repository_size_limit: 0 },
{ repository_size: 150, lfs_objects_size: 10, repository_size_limit: 0 },
{ repository_size: 200, lfs_objects_size: 100, repository_size_limit: 0 },
{ repository_size: 300, lfs_objects_size: 0, repository_size_limit: 400 },
{ repository_size: 400, lfs_objects_size: 0, repository_size_limit: 400 },
{ repository_size: 300, lfs_objects_size: 100, repository_size_limit: 400 },
{ repository_size: 400, lfs_objects_size: 100, repository_size_limit: 400 },
{ repository_size: 500, lfs_objects_size: 100, repository_size_limit: 300 }
].map { |attrs| create_project(**attrs) }
end
end end
...@@ -2188,6 +2188,25 @@ RSpec.describe Project do ...@@ -2188,6 +2188,25 @@ RSpec.describe Project do
end end
end end
describe '#actual_size_limit' do
context 'when repository_size_limit is set on the project' do
it 'returns the repository_size_limit' do
project = build(:project, repository_size_limit: 10)
expect(project.actual_size_limit).to eq(10)
end
end
context 'when repository_size_limit is not set on the project' do
it 'returns the actual_size_limit of the namespace' do
group = build(:group, repository_size_limit: 20)
project = build(:project, namespace: group, repository_size_limit: nil)
expect(project.actual_size_limit).to eq(20)
end
end
end
describe '#repository_size_checker' do describe '#repository_size_checker' do
let(:project) { build(:project) } let(:project) { build(:project) }
let(:checker) { project.repository_size_checker } let(:checker) { project.repository_size_checker }
...@@ -2257,6 +2276,32 @@ RSpec.describe Project do ...@@ -2257,6 +2276,32 @@ RSpec.describe Project do
end end
end end
describe '#repository_size_excess' do
using RSpec::Parameterized::TableSyntax
subject { project.repository_size_excess }
let_it_be(:statistics) { create(:project_statistics) }
let_it_be(:project) { statistics.project }
where(:total_repository_size, :size_limit, :result) do
50 | nil | 0
50 | 0 | 0
50 | 60 | 0
50 | 50 | 0
50 | 10 | 40
end
with_them do
before do
allow(project).to receive(:actual_size_limit).and_return(size_limit)
allow(statistics).to receive(:total_repository_size).and_return(total_repository_size)
end
it { is_expected.to eq(result) }
end
end
describe '#repository_size_limit column' do describe '#repository_size_limit column' do
it 'support values up to 8 exabytes' do it 'support values up to 8 exabytes' do
project = create(:project) project = 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