Skip to content

Commit

Permalink
Enhance /v3/organizations/:guid/usage_summary
Browse files Browse the repository at this point in the history
It would be nice to get org usage info for (nearly) all resources that are restricted by an org quota plan, beside memory in mb and started instances, which are already implemented.

In the course of the change running_and_pending_tasks_count was refactored from nested selects to joins. Joins are generally more efficient than nested selects, especially when dealing with large datasets.
  • Loading branch information
kathap committed Dec 6, 2023
1 parent 64cb085 commit 5f2829e
Show file tree
Hide file tree
Showing 6 changed files with 210 additions and 6 deletions.
12 changes: 8 additions & 4 deletions app/models/runtime/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
9 changes: 8 additions & 1 deletion app/presenters/v3/organization_usage_summary_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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,
total_routes: org_usage.routes,
total_service_instances: org_usage.service_instances,
total_reserved_ports: org_usage.reserved_route_ports,
total_domains: org_usage.private_domains,
per_app_tasks: org_usage.app_tasks,
total_service_keys: org_usage.service_keys
},
links: build_links
}
Expand Down
1 change: 1 addition & 0 deletions lib/cloud_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down
34 changes: 34 additions & 0 deletions lib/cloud_controller/organization_quota_usage.rb
Original file line number Diff line number Diff line change
@@ -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 || 0
end

def reserved_route_ports
OrganizationReservedRoutePorts.new(@organization).count
end

def app_tasks
@organization.running_and_pending_tasks_count
end
end
end
8 changes: 7 additions & 1 deletion spec/request/organizations_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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)
total_routes: 0,
total_service_instances: 0,
total_reserved_ports: 0,
total_domains: 0,
per_app_tasks: 0,
total_service_keys: 0
},
links: {
self: { href: %r{#{Regexp.escape(link_prefix)}/v3/organizations/#{org.guid}/usage_summary} },
Expand Down
152 changes: 152 additions & 0 deletions spec/unit/lib/cloud_controller/organization_quota_usage_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
require 'spec_helper'

module VCAP::CloudController
RSpec.describe OrganizationQuotaUsage do
let!(:org) { Organization.make }
let!(:org_usage) { OrganizationQuotaUsage.new(org) }
let!(:space1) { Space.make(organization: org) }
let!(:space2) { Space.make }
let!(:space3) { Space.make(organization: org) }

describe '#routes' do
let!(:route) { Route.make(space: space1) }
let!(:route2) { Route.make(space: space1) }
let!(:route3) { Route.make(space: space2) }
let!(:route4) { Route.make(space: space3) }

it 'returns the number of routes in all spaces under the org' do
result_routes = org_usage.routes
expect(result_routes).to eq(3)
end
end

describe '#service_instances' do
let!(:service_instance) { ServiceInstance.make(space: space1, is_gateway_service: true) }
let!(:service_instance2) { ServiceInstance.make(space: space1, is_gateway_service: true) }
let!(:service_instance3) { ServiceInstance.make(space: space2, is_gateway_service: true) }
let!(:service_instance4) { ServiceInstance.make(space: space3, is_gateway_service: true) }

it 'returns the number of service instances for all spaces under the org' do
result_service_instances = org_usage.service_instances
expect(result_service_instances).to eq(3)
end
end

describe '#private_domains' do
let!(:org2) { Organization.make }
let!(:domain) { Domain.make(owning_organization: org) }
let!(:domain2) { Domain.make(owning_organization: org) }
let!(:domain3) { Domain.make(owning_organization: org2) }
let!(:domain4) { Domain.make(owning_organization: org) }

it 'returns the number of domains for all spaces under the org' do
result_domains = org_usage.private_domains
expect(result_domains).to eq(3)
end
end

describe '#service_keys' do
let!(:service_instance) { ServiceInstance.make(space: space1) }
let!(:service_instance2) { ServiceInstance.make(space: space1) }
let!(:service_instance3) { ServiceInstance.make(space: space2) }
let!(:service_instance4) { ServiceInstance.make(space: space3) }
let!(:service_key) { ServiceKey.make(service_instance_id: service_instance.id) }
let!(:service_key2) { ServiceKey.make(service_instance_id: service_instance2.id) }
let!(:service_key3) { ServiceKey.make(service_instance_id: service_instance3.id) }
let!(:service_key4) { ServiceKey.make(service_instance_id: service_instance4.id) }

it 'returns the number of service keys for all spaces under the org' do
result_service_keys = org_usage.service_keys
expect(result_service_keys).to eq(3)
end
end

describe '#reserved_route_ports' do
let!(:domain) { SharedDomain.make(router_group_guid: 'some-router-group') }
let!(:routing_api_client) { instance_double(RoutingApi::Client) }
let!(:router_group) { instance_double(RoutingApi::RouterGroup) }

before do
allow(CloudController::DependencyLocator.instance).to receive(:routing_api_client).and_return(routing_api_client)
allow(routing_api_client).to receive(:router_group).and_return(router_group)
allow(routing_api_client).to receive_messages(router_group: router_group, enabled?: true)
allow(router_group).to receive_messages(type: 'tcp', reservable_ports: [1234, 2, 2345, 3])
end

let!(:route) { Route.make(domain: domain, host: '', port: 1234, space: space1) }
let!(:route2) { Route.make(domain: domain, host: '', port: 2, space: space2) }
let!(:route3) { Route.make(domain: domain, host: '', port: 2345, space: space1) }
let!(:route4) { Route.make(domain: domain, host: '', port: 3, space: space3) }

it 'returns the number of ports for all spaces under the org' do
result_ports = org_usage.reserved_route_ports
expect(result_ports).to eq(3)
end
end

describe '#app_tasks' do
let!(:app) { VCAP::CloudController::AppModel.make(space: space1) }
let!(:app2) { VCAP::CloudController::AppModel.make(space: space1) }
let!(:app3) { VCAP::CloudController::AppModel.make(space: space2) }
let!(:app4) { VCAP::CloudController::AppModel.make(space: space3) }
let!(:droplet) do
VCAP::CloudController::DropletModel.make(
app_guid: app.guid,
state: VCAP::CloudController::DropletModel::STAGED_STATE
)
end

before do
VCAP::CloudController::FeatureFlag.make(name: 'task_creation', enabled: true, error_message: nil)
app.droplet = droplet
app.save
end

let!(:task) do
VCAP::CloudController::TaskModel.make(
name: 'task one',
command: 'echo task',
app_guid: app.guid,
droplet: app.droplet,
memory_in_mb: 5,
state: VCAP::CloudController::TaskModel::RUNNING_STATE
)
end
let!(:task2) do
VCAP::CloudController::TaskModel.make(
name: 'task two',
command: 'echo task',
app_guid: app2.guid,
droplet: app.droplet,
memory_in_mb: 5,
state: VCAP::CloudController::TaskModel::RUNNING_STATE
)
end
let!(:task3) do
VCAP::CloudController::TaskModel.make(
name: 'task false',
command: 'echo task',
app_guid: app3.guid,
droplet: app.droplet,
memory_in_mb: 5,
state: VCAP::CloudController::TaskModel::RUNNING_STATE
)
end
let!(:task4) do
VCAP::CloudController::TaskModel.make(
name: 'task three',
command: 'echo task',
app_guid: app4.guid,
droplet: app.droplet,
memory_in_mb: 5,
state: VCAP::CloudController::TaskModel::RUNNING_STATE
)
end

it 'returns the number of service keys for all spaces under the org' do
result_per_app_tasks = org_usage.app_tasks
expect(result_per_app_tasks).to eq(3)
end
end
end
end

0 comments on commit 5f2829e

Please sign in to comment.