diff --git a/app/models/runtime/organization.rb b/app/models/runtime/organization.rb index c19c35c8ca7..1062d020e4c 100644 --- a/app/models/runtime/organization.rb +++ b/app/models/runtime/organization.rb @@ -274,6 +274,14 @@ def members User.where(id: Role.where(organization_id: id).distinct.select(:user_id)) end + def running_and_pending_tasks_count + VCAP::CloudController::TaskModel.dataset.where(state: [TaskModel::PENDING_STATE, TaskModel::RUNNING_STATE]). + join(:apps, guid: :app_guid). + join(:spaces, guid: :space_guid). + where(spaces__organization_id: id). + count + end + private def validate_default_isolation_segment @@ -334,9 +342,5 @@ def running_task_log_rate_limit def started_app_log_rate_limit processes_dataset.where(state: ProcessModel::STARTED).sum(Sequel.*(:log_rate_limit, :instances)) || 0 end - - def running_and_pending_tasks_count - tasks_dataset.where(state: [TaskModel::PENDING_STATE, TaskModel::RUNNING_STATE]).count - end end end diff --git a/app/presenters/v3/organization_usage_summary_presenter.rb b/app/presenters/v3/organization_usage_summary_presenter.rb index a8684af2228..fe0dc3d7630 100644 --- a/app/presenters/v3/organization_usage_summary_presenter.rb +++ b/app/presenters/v3/organization_usage_summary_presenter.rb @@ -3,10 +3,17 @@ module VCAP::CloudController::Presenters::V3 class OrganizationUsageSummaryPresenter < BasePresenter def to_hash + org_usage = VCAP::CloudController::OrganizationQuotaUsage.new(org) { usage_summary: { started_instances: VCAP::CloudController::OrganizationInstanceUsageCalculator.get_instance_usage(org), - memory_in_mb: org.memory_used + memory_in_mb: org.memory_used, + routes: org_usage.routes, + service_instances: org_usage.service_instances, + reserved_ports: org_usage.reserved_route_ports, + domains: org_usage.private_domains, + per_app_tasks: org_usage.app_tasks, + service_keys: org_usage.service_keys }, links: build_links } diff --git a/docs/v3/source/includes/api_resources/_organizations.erb b/docs/v3/source/includes/api_resources/_organizations.erb index 41d9cc6fb9a..e2664fb7a4d 100644 --- a/docs/v3/source/includes/api_resources/_organizations.erb +++ b/docs/v3/source/includes/api_resources/_organizations.erb @@ -219,7 +219,13 @@ { "usage_summary": { "started_instances": 3, - "memory_in_mb": 50 + "memory_in_mb": 50, + "routes": 4, + "service_instances": 2, + "reserved_ports": 1, + "domains": 4, + "per_app_tasks": 2, + "service_keys": 1 }, "links": { "self": { diff --git a/lib/cloud_controller.rb b/lib/cloud_controller.rb index 1a7f5c5e9ba..6bf61f25d8d 100644 --- a/lib/cloud_controller.rb +++ b/lib/cloud_controller.rb @@ -79,6 +79,7 @@ module VCAP::CloudController; end require 'cloud_controller/controller_factory' require 'cloud_controller/egress_network_rules_presenter' require 'cloud_controller/organization_instance_usage_calculator' +require 'cloud_controller/organization_quota_usage' require 'cloud_controller/url_secret_obfuscator' require 'cloud_controller/legacy_api/legacy_api_base' diff --git a/lib/cloud_controller/organization_quota_usage.rb b/lib/cloud_controller/organization_quota_usage.rb new file mode 100644 index 00000000000..eb9751278b1 --- /dev/null +++ b/lib/cloud_controller/organization_quota_usage.rb @@ -0,0 +1,34 @@ +module VCAP::CloudController + class OrganizationQuotaUsage + def initialize(organization) + @organization = organization + end + + def routes + OrganizationRoutes.new(@organization).count + end + + def service_instances + @organization.managed_service_instances_dataset.count + end + + def private_domains + @organization.owned_private_domains_dataset.count + end + + def service_keys + VCAP::CloudController::ServiceKey.dataset.join(:service_instances, id: :service_instance_id). + join(:spaces, id: :space_id). + where(spaces__organization_id: @organization.id). + count + end + + def reserved_route_ports + OrganizationReservedRoutePorts.new(@organization).count + end + + def app_tasks + @organization.running_and_pending_tasks_count + end + end +end diff --git a/spec/request/organizations_spec.rb b/spec/request/organizations_spec.rb index c7f3dae1a24..ea399694578 100644 --- a/spec/request/organizations_spec.rb +++ b/spec/request/organizations_spec.rb @@ -994,7 +994,13 @@ module VCAP::CloudController { usage_summary: { started_instances: 5, - memory_in_mb: 705 # (tasks: 200 * 2) + (processes: 101 + 2 * 102) + memory_in_mb: 705, # (tasks: 200 * 2) + (processes: 101 + 2 * 102) + routes: 0, + service_instances: 0, + reserved_ports: 0, + domains: 0, + per_app_tasks: 0, + service_keys: 0 }, links: { self: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{org.guid}/usage_summary} }, diff --git a/spec/unit/lib/cloud_controller/organization_quota_usage_spec.rb b/spec/unit/lib/cloud_controller/organization_quota_usage_spec.rb new file mode 100644 index 00000000000..0407d7e828d --- /dev/null +++ b/spec/unit/lib/cloud_controller/organization_quota_usage_spec.rb @@ -0,0 +1,80 @@ +require 'spec_helper' + +module VCAP::CloudController + RSpec.describe OrganizationQuotaUsage do + let(:org) { Organization.make } + let(:space1) { Space.make(organization: org) } + let(:space2) { Space.make } + let(:space3) { Space.make(organization: org) } + + subject(:org_usage) { OrganizationQuotaUsage.new(org) } + + describe '#routes' do + before do + [space1, space1, space2, space3].each { |s| Route.make(space: s) } + end + + it 'returns the number of routes in all spaces under the org' do + expect(org_usage.routes).to eq(3) + end + end + + describe '#service_instances' do + before do + [space1, space1, space2, space3].each { |s| ManagedServiceInstance.make(space: s) } + UserProvidedServiceInstance.make(space: space1) + end + + it 'returns the number of service instances in all spaces under the org' do + expect(org_usage.service_instances).to eq(3) + end + end + + describe '#private_domains' do + before do + [space1, space1, space2, space3].each { |s| Domain.make(owning_organization: s.organization) } + end + + it 'returns the number of private domains in all spaces under the org' do + expect(org_usage.private_domains).to eq(3) + end + end + + describe '#service_keys' do + before do + [space1, space1, space2, space3].each { |s| ServiceKey.make(service_instance: ServiceInstance.make(space: s)) } + end + + it 'returns the number of service keys in all spaces under the org' do + expect(org_usage.service_keys).to eq(3) + end + end + + describe '#reserved_route_ports' do + before do + reservable_ports = [1234, 2, 2345, 3] + router_group = instance_double(RoutingApi::RouterGroup, type: 'tcp', reservable_ports: reservable_ports) + routing_api_client = instance_double(RoutingApi::Client, router_group: router_group, enabled?: true) + allow(CloudController::DependencyLocator.instance).to receive(:routing_api_client).and_return(routing_api_client) + domain = SharedDomain.make(router_group_guid: 'some-router-group') + [space1, space1, space2, space3].each_with_index { |s, i| Route.make(space: s, domain: domain, host: '', port: reservable_ports[i]) } + end + + it 'returns the number of reserved route ports in all spaces under the org' do + expect(org_usage.reserved_route_ports).to eq(3) + end + end + + describe '#app_tasks' do + before do + [space1, space1, space2, space3].each { |s| TaskModel.make(app: AppModel.make(space: s), state: TaskModel::RUNNING_STATE) } + TaskModel.make(app: AppModel.make(space: space1), state: TaskModel::PENDING_STATE) + TaskModel.make(app: AppModel.make(space: space1), state: TaskModel::CANCELING_STATE) + end + + it 'returns the number of app tasks in all spaces under the org' do + expect(org_usage.app_tasks).to eq(4) + end + end + end +end