diff --git a/app/actions/organization_quota_apply.rb b/app/actions/organization_quota_apply.rb index 8f3f8b2c530..65af7672d6d 100644 --- a/app/actions/organization_quota_apply.rb +++ b/app/actions/organization_quota_apply.rb @@ -4,8 +4,20 @@ class Error < ::StandardError end def apply(org_quota, message) + orgs = valid_orgs(message.organization_guids) + + if org_quota.log_rate_limit != QuotaDefinition::UNLIMITED + affected_processes = Organization.where(Sequel[:organizations][:id] => orgs.map(&:id)). + join(:spaces, organization_id: :id). + join(:apps, space_guid: :guid). + join(:processes, app_guid: :guid) + + unless affected_processes.where(log_rate_limit: ProcessModel::UNLIMITED_LOG_RATE).empty? + error!('Current usage exceeds new quota values. The org(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end + QuotaDefinition.db.transaction do - orgs = valid_orgs(message.organization_guids) orgs.each { |org| org_quota.add_organization(org) } end rescue Sequel::ValidationFailed => e diff --git a/app/actions/organization_quotas_update.rb b/app/actions/organization_quotas_update.rb index 12f7cfcd35c..229b57b642a 100644 --- a/app/actions/organization_quotas_update.rb +++ b/app/actions/organization_quotas_update.rb @@ -2,8 +2,18 @@ module VCAP::CloudController class OrganizationQuotasUpdate class Error < ::StandardError end + + MAX_ORGS_TO_LIST_ON_FAILURE = 2 + # rubocop:disable Metrics/CyclomaticComplexity def self.update(quota, message) + if log_rate_limit(message) != QuotaDefinition::UNLIMITED + orgs = orgs_with_unlimited_processes(quota) + if orgs.any? + unlimited_processes_exist_error!(orgs) + end + end + quota.db.transaction do quota.lock! @@ -84,5 +94,35 @@ def self.total_routes(message) def self.total_private_domains(message) default_if_nil(message.total_domains, QuotaDefinition::UNLIMITED) end + + def self.orgs_with_unlimited_processes(quota) + quota.organizations_dataset. + distinct. + select(Sequel[:organizations][:name]). + join(:spaces, organization_id: :id). + join(:apps, space_guid: :guid). + join(:processes, app_guid: :guid). + where(log_rate_limit: QuotaDefinition::UNLIMITED). + order(:name). + all + end + + def self.unlimited_processes_exist_error!(orgs) + named_orgs = orgs.take(MAX_ORGS_TO_LIST_ON_FAILURE).map(&:name).join("', '") + message = 'This quota is applied to ' + + if orgs.size == 1 + "org '#{named_orgs}' which contains" + elsif orgs.size > MAX_ORGS_TO_LIST_ON_FAILURE + "orgs '#{named_orgs}' and #{orgs.drop(MAX_ORGS_TO_LIST_ON_FAILURE).size}" \ + ' other orgs which contain' + else + "orgs '#{named_orgs}' which contain" + end + ' apps running with an unlimited log rate limit.' + + raise Error.new("Current usage exceeds new quota values. #{message}") + end + + private_class_method :orgs_with_unlimited_processes, + :unlimited_processes_exist_error! end end diff --git a/app/actions/space_quota_apply.rb b/app/actions/space_quota_apply.rb index 02b77f70667..01f00beba9d 100644 --- a/app/actions/space_quota_apply.rb +++ b/app/actions/space_quota_apply.rb @@ -6,6 +6,16 @@ class Error < ::StandardError def apply(space_quota, message, visible_space_guids: [], all_spaces_visible: false) spaces = valid_spaces(message.space_guids, visible_space_guids, all_spaces_visible, space_quota.organization_id) + if space_quota.log_rate_limit != QuotaDefinition::UNLIMITED + affected_processes = Space.where(Sequel[:spaces][:id] => spaces.map(&:id)). + join(:apps, space_guid: :guid). + join(:processes, app_guid: :guid) + + unless affected_processes.where(log_rate_limit: ProcessModel::UNLIMITED_LOG_RATE).empty? + error!('Current usage exceeds new quota values. The space(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end + SpaceQuotaDefinition.db.transaction do spaces.each { |space| space_quota.add_space(space) } end diff --git a/app/actions/space_quota_update.rb b/app/actions/space_quota_update.rb index 99c58cbee99..1b5ff6808d1 100644 --- a/app/actions/space_quota_update.rb +++ b/app/actions/space_quota_update.rb @@ -3,8 +3,17 @@ class SpaceQuotaUpdate class Error < ::StandardError end + MAX_SPACES_TO_LIST_ON_FAILURE = 2 + # rubocop:disable Metrics/CyclomaticComplexity def self.update(quota, message) + if log_rate_limit(message) != QuotaDefinition::UNLIMITED + spaces = spaces_with_unlimited_processes(quota) + if spaces.any? + unlimited_processes_exist_error!(spaces) + end + end + quota.db.transaction do quota.lock! @@ -79,5 +88,34 @@ def self.total_reserved_route_ports(message) def self.total_routes(message) default_if_nil(message.total_routes, SpaceQuotaDefinition::UNLIMITED) end + + def self.spaces_with_unlimited_processes(quota) + quota.spaces_dataset. + distinct. + select(Sequel[:spaces][:name]). + join(:apps, space_guid: :guid). + join(:processes, app_guid: :guid). + where(log_rate_limit: QuotaDefinition::UNLIMITED). + order(:name). + all + end + + def self.unlimited_processes_exist_error!(spaces) + named_spaces = spaces.take(MAX_SPACES_TO_LIST_ON_FAILURE).map(&:name).join("', '") + message = 'This quota is applied to ' + + if spaces.size == 1 + "space '#{named_spaces}' which contains" + elsif spaces.size > MAX_SPACES_TO_LIST_ON_FAILURE + "spaces '#{named_spaces}' and #{spaces.drop(MAX_SPACES_TO_LIST_ON_FAILURE).size}" \ + ' other spaces which contain' + else + "spaces '#{named_spaces}' which contain" + end + ' apps running with an unlimited log rate limit.' + + raise Error.new("Current usage exceeds new quota values. #{message}") + end + + private_class_method :spaces_with_unlimited_processes, + :unlimited_processes_exist_error! end end diff --git a/app/controllers/runtime/organizations_controller.rb b/app/controllers/runtime/organizations_controller.rb index b004a25615b..3d2ef17cfd9 100644 --- a/app/controllers/runtime/organizations_controller.rb +++ b/app/controllers/runtime/organizations_controller.rb @@ -73,6 +73,18 @@ def before_update(org) end end + if request_attrs['quota_definition_guid'] + quota = QuotaDefinition.first(guid: request_attrs['quota_definition_guid']) + if quota.log_rate_limit != QuotaDefinition::UNLIMITED + affected_processes = org.processes_dataset + unless affected_processes.where(log_rate_limit: ProcessModel::UNLIMITED_LOG_RATE).empty? + raise CloudController::Errors::ApiError.new_from_details( + 'UnprocessableEntity', + 'Current usage exceeds new quota values. This org currently contains apps running with an unlimited log rate limit.') + end + end + end + super(org) end diff --git a/app/controllers/runtime/space_quota_definitions_controller.rb b/app/controllers/runtime/space_quota_definitions_controller.rb index 306bb0ba604..90615174c63 100644 --- a/app/controllers/runtime/space_quota_definitions_controller.rb +++ b/app/controllers/runtime/space_quota_definitions_controller.rb @@ -25,6 +25,23 @@ def self.translate_validation_exception(e, attributes) end end + def before_update(quota) + if request_attrs['space'] && quota.log_rate_limit != QuotaDefinition::UNLIMITED + affected_processes = Space.dataset. + join(:apps, space_guid: :guid). + join(:processes, app_guid: :guid). + where(Sequel[:spaces][:guid] => request_attrs['space']) + + unless affected_processes.where(log_rate_limit: ProcessModel::UNLIMITED_LOG_RATE).empty? + raise CloudController::Errors::ApiError.new_from_details( + 'UnprocessableEntity', + 'Current usage exceeds new quota values. This space currently contains apps running with an unlimited log rate limit.') + end + end + + super(quota) + end + def delete(guid) do_delete(find_guid_and_validate_access(:delete, guid)) end diff --git a/app/models/runtime/process_model.rb b/app/models/runtime/process_model.rb index 097e346a25e..4184530ceea 100644 --- a/app/models/runtime/process_model.rb +++ b/app/models/runtime/process_model.rb @@ -34,6 +34,7 @@ def after_initialize NO_APP_PORT_SPECIFIED = -1 DEFAULT_HTTP_PORT = 8080 DEFAULT_PORTS = [DEFAULT_HTTP_PORT].freeze + UNLIMITED_LOG_RATE = -1 many_to_one :app, class: 'VCAP::CloudController::AppModel', key: :app_guid, primary_key: :guid, without_guid_generation: true many_to_one :revision, class: 'VCAP::CloudController::RevisionModel', key: :revision_guid, primary_key: :guid, without_guid_generation: true diff --git a/spec/request/organization_quotas_spec.rb b/spec/request/organization_quotas_spec.rb index e3dcf90c3f2..f77714ae301 100644 --- a/spec/request/organization_quotas_spec.rb +++ b/spec/request/organization_quotas_spec.rb @@ -462,6 +462,20 @@ module VCAP::CloudController expect(last_response).to include_error_message("Organization Quota '#{organization_quota.name}' already exists.") end end + + context 'when trying to set a log rate limit and there are apps with unlimited log rates' do + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + patch "/v3/organization_quotas/#{organization_quota.guid}", params.to_json, admin_header + + expect(last_response).to have_status_code(422) + expect(last_response).to include_error_message( + 'Current usage exceeds new quota values. ' \ + "This quota is applied to org '#{org.name}' which contains apps running with an unlimited log rate limit.") + end + end end describe 'POST /v3/organization_quotas/:guid/relationships/organizations' do @@ -524,6 +538,19 @@ module VCAP::CloudController expect(parsed_response['errors'][0]['detail']).to eq('Invalid data type: Data[0] guid should be a string.') end end + + context 'when the quota has a finite log rate limit and there are apps with unlimited log rates' do + let(:org_quota) { VCAP::CloudController::QuotaDefinition.make(log_rate_limit: 100) } + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + post "/v3/organization_quotas/#{org_quota.guid}/relationships/organizations", params.to_json, admin_header + expect(last_response).to have_status_code(422) + expect(last_response).to include_error_message( + 'Current usage exceeds new quota values. The org(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end end describe 'DELETE /v3/organization_quotas/:guid/' do diff --git a/spec/request/space_quotas_spec.rb b/spec/request/space_quotas_spec.rb index 67a3dfed230..c7eab4b8b6e 100644 --- a/spec/request/space_quotas_spec.rb +++ b/spec/request/space_quotas_spec.rb @@ -260,6 +260,18 @@ module VCAP::CloudController expect(last_response).to include_error_message("Unknown field(s): 'wat'") end end + context 'when trying to set a log rate limit and there are apps with unlimited log rates' do + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + patch "/v3/space_quotas/#{space_quota.guid}", params.to_json, admin_header + + expect(last_response).to have_status_code(422) + expect(last_response).to include_error_message( + "Current usage exceeds new quota values. This quota is applied to space '#{space.name}' which contains apps running with an unlimited log rate limit.") + end + end end describe 'GET /v3/space_quotas' do @@ -810,6 +822,19 @@ module VCAP::CloudController expect(parsed_response['errors'][0]['detail']).to eq('Invalid data type: Data[1] guid should be a string.') end end + context 'when the quota has a finite log rate limit and there are apps with unlimited log rates' do + let(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(guid: 'space-quota-guid', organization: org, log_rate_limit: 100) } + let!(:other_space) { VCAP::CloudController::Space.make(guid: 'other-space-guid', organization: org, space_quota_definition: space_quota) } + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: other_space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + post "/v3/space_quotas/#{space_quota.guid}/relationships/spaces", params.to_json, admin_header + expect(last_response).to have_status_code(422) + expect(last_response).to include_error_message( + 'Current usage exceeds new quota values. The space(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end end describe 'DELETE /v3/space_quotas/:guid/relationships/spaces' do diff --git a/spec/request/v2/organizations_spec.rb b/spec/request/v2/organizations_spec.rb index 4b296beb9ae..7822405b6af 100644 --- a/spec/request/v2/organizations_spec.rb +++ b/spec/request/v2/organizations_spec.rb @@ -99,4 +99,28 @@ ) end end + + describe 'PUT /v2/organizations/:guid' do + context 'when the quota has a finite log rate limit and there are apps with unlimited log rates' do + let(:admin_header) { headers_for(user, scopes: %w(cloud_controller.admin)) } + let(:org_quota) { VCAP::CloudController::QuotaDefinition.make(log_rate_limit: 100) } + + let(:params) do + { + quota_definition_guid: org_quota.guid + } + end + + let!(:space) { VCAP::CloudController::Space.make(organization: org) } + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + put "/v2/organizations/#{org.guid}", params.to_json, admin_header + expect(last_response).to have_status_code(422) + expect(decoded_response['error_code']).to eq('CF-UnprocessableEntity') + expect(decoded_response['description']).to eq('Current usage exceeds new quota values. This org currently contains apps running with an unlimited log rate limit.') + end + end + end end diff --git a/spec/request/v2/space_quota_definitions_spec.rb b/spec/request/v2/space_quota_definitions_spec.rb new file mode 100644 index 00000000000..37ca5f1f85e --- /dev/null +++ b/spec/request/v2/space_quota_definitions_spec.rb @@ -0,0 +1,24 @@ +require 'spec_helper' + +RSpec.describe 'SpaceQuotaDefinitions' do + let(:user) { VCAP::CloudController::User.make } + let(:org) { VCAP::CloudController::Organization.make } + + describe 'PUT /v2/space_quota_definitions/guid/spaces/space_guid' do + context 'when the quota has a finite log rate limit and there are apps with unlimited log rates' do + let(:admin_header) { headers_for(user, scopes: %w(cloud_controller.admin)) } + let(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, log_rate_limit: 100) } + + let!(:space) { VCAP::CloudController::Space.make(organization: org) } + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'returns 422' do + put "/v2/space_quota_definitions/#{space_quota.guid}/spaces/#{space.guid}", nil, admin_header + expect(last_response).to have_status_code(422) + expect(decoded_response['error_code']).to eq('CF-UnprocessableEntity') + expect(decoded_response['description']).to eq('Current usage exceeds new quota values. This space currently contains apps running with an unlimited log rate limit.') + end + end + end +end diff --git a/spec/unit/actions/organization_quota_apply_spec.rb b/spec/unit/actions/organization_quota_apply_spec.rb index d010b4d77b1..0e1b6c0d7f9 100644 --- a/spec/unit/actions/organization_quota_apply_spec.rb +++ b/spec/unit/actions/organization_quota_apply_spec.rb @@ -4,7 +4,7 @@ module VCAP::CloudController RSpec.describe OrganizationQuotaApply do - describe '#create' do + describe '#apply' do subject { OrganizationQuotaApply.new } let(:org) { VCAP::CloudController::Organization.make } @@ -53,6 +53,21 @@ module VCAP::CloudController }.to raise_error(OrganizationQuotaApply::Error, "Organizations with guids [\"#{invalid_org_guid}\"] do not exist") end end + + context 'when trying to set a log rate limit and there are apps with unlimited log rates' do + let(:space) { VCAP::CloudController::Space.make(guid: 'space-guid', organization: org) } + let(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + let(:org_quota) { VCAP::CloudController::QuotaDefinition.make(log_rate_limit: 2000) } + + it 'raises an error' do + expect { + subject.apply(org_quota, message) + }.to raise_error(OrganizationQuotaApply::Error, + 'Current usage exceeds new quota values. ' \ + 'The org(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end end end end diff --git a/spec/unit/actions/organization_quotas_update_spec.rb b/spec/unit/actions/organization_quotas_update_spec.rb index e75f9151bae..f1d1a891bc4 100644 --- a/spec/unit/actions/organization_quotas_update_spec.rb +++ b/spec/unit/actions/organization_quotas_update_spec.rb @@ -103,6 +103,67 @@ module VCAP::CloudController end end end + + context 'when there are affected processes that have an unlimited log rate limit' do + def create_orgs_with_unlimited_log_rate_process(count) + count.downto(1) do |i| + org = VCAP::CloudController::Organization.make(guid: "org-guid-#{i}", name: "org-name-#{i}", quota_definition: org_quota) + space = VCAP::CloudController::Space.make(guid: "space-guid-#{i}", organization: org) + app_model = VCAP::CloudController::AppModel.make(name: "app-#{i}", space: space) + VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) + end + end + + context 'and they are only in a single org' do + before do + create_orgs_with_unlimited_log_rate_process(1) + end + it 'errors with a message telling the user the affected org' do + expect do + OrganizationQuotasUpdate.update(org_quota, message) + end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to org ' \ + "'org-name-1' which contains apps running with an unlimited log rate limit.") + end + end + context 'and they are in two orgs' do + before do + create_orgs_with_unlimited_log_rate_process(2) + end + it 'errors with a message telling the user the affected orgs' do + expect do + OrganizationQuotasUpdate.update(org_quota, message) + end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to orgs ' \ + "'org-name-1', 'org-name-2' which contain apps running with an unlimited log rate limit.") + end + end + + context 'and they are spread across five orgs' do + before do + create_orgs_with_unlimited_log_rate_process(5) + end + it 'errors with a message telling the user some of the affected orgs and a total count' do + expect do + OrganizationQuotasUpdate.update(org_quota, message) + end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to orgs ' \ + "'org-name-1', 'org-name-2' and 3 other orgs which contain apps running with an unlimited log rate limit.") + end + end + + context 'and there is more than one affected process within an org' do + let(:org) { VCAP::CloudController::Organization.make(guid: 'org-guid', name: 'org-name', quota_definition: org_quota) } + let(:space) { VCAP::CloudController::Space.make(guid: 'space-guid', organization: org) } + let(:app_model) { VCAP::CloudController::AppModel.make(name: 'app', space: space) } + let!(:process_1) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + let!(:process_2) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'only names the org once in the error message' do + expect do + OrganizationQuotasUpdate.update(org_quota, message) + end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to org ' \ + "'org-name' which contains apps running with an unlimited log rate limit.") + end + end + end end end end diff --git a/spec/unit/actions/space_quota_apply_spec.rb b/spec/unit/actions/space_quota_apply_spec.rb index 561389b4790..f7d0d04598f 100644 --- a/spec/unit/actions/space_quota_apply_spec.rb +++ b/spec/unit/actions/space_quota_apply_spec.rb @@ -62,6 +62,21 @@ module VCAP::CloudController end end + context 'when trying to set a log rate limit and there are apps with unlimited log rates' do + let(:visible_space_guids) { [space.guid] } + let(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } + let!(:process_model) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + let(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org, log_rate_limit: 2000) } + + it 'raises an error' do + expect { + subject.apply(space_quota, message, visible_space_guids: visible_space_guids) + }.to raise_error(SpaceQuotaApply::Error, + 'Current usage exceeds new quota values. ' \ + 'The space(s) being assigned this quota contain apps running with an unlimited log rate limit.') + end + end + context "when the space is outside the space quota's org" do let(:other_space) { VCAP::CloudController::Space.make } let(:invalid_space_guid) { other_space.guid } diff --git a/spec/unit/actions/space_quota_update_spec.rb b/spec/unit/actions/space_quota_update_spec.rb index e98a366a899..2a8c85344b4 100644 --- a/spec/unit/actions/space_quota_update_spec.rb +++ b/spec/unit/actions/space_quota_update_spec.rb @@ -7,7 +7,7 @@ module VCAP::CloudController let(:org) { VCAP::CloudController::Organization.make } describe 'update' do - context 'when updating an organization quota' do + context 'when updating a space quota' do let!(:space_quota) do VCAP::CloudController::SpaceQuotaDefinition.make( name: 'space_quota_name', @@ -42,7 +42,7 @@ module VCAP::CloudController VCAP::CloudController::SpaceQuotaUpdateMessage.new({}) end - it 'updates an organization quota with the given values' do + it 'updates a space quota with the given values' do updated_space_quota = SpaceQuotaUpdate.update(space_quota, message) expect(updated_space_quota.name).to eq('don-quixote') @@ -61,7 +61,7 @@ module VCAP::CloudController expect(updated_space_quota.total_routes).to eq(8) end - it 'updates an organization quota with only the given values' do + it 'updates a space quota with only the given values' do updated_space_quota = SpaceQuotaUpdate.update(space_quota, minimum_message) expect(updated_space_quota.name).to eq('space_quota_name') @@ -92,6 +92,67 @@ module VCAP::CloudController end end end + + context 'when there are affected processes that have an unlimited log rate limit' do + def create_spaces_with_unlimited_log_rate_process(count) + count.downto(1) do |i| + space = VCAP::CloudController::Space.make(guid: "space-guid-#{i}", name: "space-name-#{i}", organization: org, space_quota_definition: space_quota) + app_model = VCAP::CloudController::AppModel.make(name: "app-#{i}", space: space) + VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) + end + end + + context 'and they are only in a single space' do + before do + create_spaces_with_unlimited_log_rate_process(1) + end + it 'errors with a message telling the user the affected space' do + expect do + SpaceQuotaUpdate.update(space_quota, message) + end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to space ' \ + "'space-name-1' which contains apps running with an unlimited log rate limit.") + end + end + + context 'and they are in two spaces' do + before do + create_spaces_with_unlimited_log_rate_process(2) + end + it 'errors with a message telling the user the affected spaces' do + expect do + SpaceQuotaUpdate.update(space_quota, message) + end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to spaces ' \ + "'space-name-1', 'space-name-2' which contain apps running with an unlimited log rate limit.") + end + end + + context 'and they are spread across five spaces' do + before do + create_spaces_with_unlimited_log_rate_process(5) + end + it 'errors with a message telling the user some of the affected spaces and a total count' do + expect do + SpaceQuotaUpdate.update(space_quota, message) + end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to spaces ' \ + "'space-name-1', 'space-name-2' and 3 other spaces which contain apps running with an unlimited log rate limit.") + end + end + + context 'and there is more than one affected process within a space' do + let!(:org) { VCAP::CloudController::Organization.make(guid: 'org-guid', name: 'org-name') } + let!(:space) { VCAP::CloudController::Space.make(guid: 'space-guid', name: 'space-name', organization: org, space_quota_definition: space_quota) } + let!(:app_model) { VCAP::CloudController::AppModel.make(name: 'app', space: space) } + let!(:process_1) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + let!(:process_2) { VCAP::CloudController::ProcessModel.make(app: app_model, log_rate_limit: -1) } + + it 'only names the space once in the error message' do + expect do + SpaceQuotaUpdate.update(space_quota, message) + end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to space ' \ + "'space-name' which contains apps running with an unlimited log rate limit.") + end + end + end end end end