Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions .rubocop_todo.yml
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,6 @@ Lint/UnusedMethodArgument:
Lint/UselessMethodDefinition:
Exclude:
- 'app/messages/route_destination_update_message.rb'
- 'app/presenters/v3/buildpack_presenter.rb'
- 'app/presenters/v3/process_presenter.rb'
- 'spec/support/fake_front_controller.rb'

# Offense count: 791
Expand Down
7 changes: 7 additions & 0 deletions app/controllers/v3/processes_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'presenters/v3/paginated_list_presenter'
require 'presenters/v3/process_presenter'
require 'presenters/v3/process_stats_presenter'
require 'presenters/v3/process_instances_presenter'
require 'cloud_controller/paging/pagination_options'
require 'actions/process_delete'
require 'fetchers/process_list_fetcher'
Expand Down Expand Up @@ -106,6 +107,12 @@ def stats
render status: :ok, json: Presenters::V3::ProcessStatsPresenter.new(@process.type, process_stats)
end

def instances
instances = instances_reporters.instances_for_processes([@process])

render status: :ok, json: Presenters::V3::ProcessInstancesPresenter.new(@process, instances[@process.guid])
end

private

def find_process_and_space
Expand Down
2 changes: 2 additions & 0 deletions app/messages/processes_list_message.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ class ProcessesListMessage < MetadataListMessage

validates_with NoAdditionalParamsValidator # from BaseMessage

# validates :space_guids, array: true, allow_nil: true
# validates :organization_guids, array: true, allow_nil: true
validates :app_guids, array: true, allow_nil: true
validate :app_nested_request, if: -> { app_guid.present? }

Expand Down
7 changes: 0 additions & 7 deletions app/presenters/v3/buildpack_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,6 @@ def to_hash
}
end

class << self
# :labels and :annotations come from MetadataPresentationHelpers
def associated_resources
super
end
end

private

def buildpack
Expand Down
47 changes: 47 additions & 0 deletions app/presenters/v3/process_instances_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
require 'presenters/v3/base_presenter'
require 'presenters/mixins/metadata_presentation_helpers'

module VCAP::CloudController
module Presenters
module V3
class ProcessInstancesPresenter < BasePresenter
attr_reader :instances

def initialize(process, instances)
super(process)
@instances = instances
end

def to_hash
{
resources: build_instances,
links: build_links
}
end

private

def process
@resource
end

def build_instances
instances.map do |index, instance|
{
index: index,
state: instance[:state],
since: instance[:since]
}
end
end

def build_links
{
self: { href: url_builder.build_url(path: "/v3/processes/#{process.guid}/instances") },
process: { href: url_builder.build_url(path: "/v3/processes/#{process.guid}") }
}
end
end
end
end
end
7 changes: 0 additions & 7 deletions app/presenters/v3/process_presenter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,6 @@ module V3
class ProcessPresenter < BasePresenter
include VCAP::CloudController::Presenters::Mixins::MetadataPresentationHelpers

class << self
# :labels and :annotations come from MetadataPresentationHelpers
def associated_resources
super
end
end

def to_hash
health_check_data = { timeout: process.health_check_timeout, invocation_timeout: process.health_check_invocation_timeout, interval: process.health_check_interval }
health_check_data[:endpoint] = process.health_check_http_endpoint if process.health_check_type == HealthCheckTypes::HTTP
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
get '/processes', to: 'processes#index'
get '/processes/:process_guid', to: 'processes#show'
patch '/processes/:process_guid', to: 'processes#update'
get '/processes/:process_guid/instances', to: 'processes#instances'
delete '/processes/:process_guid/instances/:index', to: 'processes#terminate'
post '/processes/:process_guid/actions/scale', to: 'processes#scale'
get '/processes/:process_guid/stats', to: 'processes#stats'
Expand Down
1 change: 1 addition & 0 deletions lib/cloud_controller/backends/instances_reporters.rb
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def stats_for_app(app)
end

delegate :number_of_starting_and_running_instances_for_processes, :instance_count_summary, to: :diego_reporter
delegate :instances_for_processes, to: :diego_stats_reporter

private

Expand Down
22 changes: 17 additions & 5 deletions lib/cloud_controller/diego/bbs_instances_client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def lrp_instances(process)
process_guid = ProcessGuid.from_process(process)
logger.info('lrp.instances.request', process_guid:)

actual_lrps_response = handle_diego_errors(process_guid) do
actual_lrps_response = handle_diego_errors do
response = @client.actual_lrps_by_process_guid(process_guid)
logger.info('lrp.instances.response', process_guid: process_guid, error: response.error)
response
Expand All @@ -20,30 +20,42 @@ def lrp_instances(process)
actual_lrps_response.actual_lrps
end

def actual_lrps_by_processes(processes)
process_guids = processes.map { |process| ProcessGuid.from_process(process) }
logger.info('actual.lrps.by.processes.request', process_guids:)

actual_lrps_response = handle_diego_errors do
response = @client.actual_lrps_by_process_guids(process_guids)
logger.info('actual.lrps.by.processes.response', process_guids: process_guids, error: response.error)
response
end

actual_lrps_response.actual_lrps
end

def desired_lrp_instance(process)
process_guid = ProcessGuid.from_process(process)
response = handle_diego_errors(process_guid) do
response = handle_diego_errors(handle_resource_not_found: true, process_guid: process_guid) do
@client.desired_lrp_by_process_guid(process_guid)
end
response.desired_lrp
end

private

def handle_diego_errors(process_guid)
def handle_diego_errors(handle_resource_not_found: false, process_guid: nil)
begin
response = yield
rescue ::Diego::Error => e
raise CloudController::Errors::InstancesUnavailable.new(e)
end

if response.error
if response.error.type == ::Diego::Bbs::ErrorTypes::ResourceNotFound
if handle_resource_not_found && response.error.type == ::Diego::Bbs::ErrorTypes::ResourceNotFound
raise CloudController::Errors::NoRunningInstances.new("No running instances found for process guid #{process_guid}")
end

raise CloudController::Errors::InstancesUnavailable.new(response.error.message)

end

response
Expand Down
35 changes: 35 additions & 0 deletions lib/cloud_controller/diego/reporters/instances_stats_reporter.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,41 @@ def stats_for_app(process)
raise exception
end

def instances_for_processes(processes)
logger.debug('instances_for_processes.fetching_actual_lrps')

# Fetch actual_lrps for all processes
actual_lrps = bbs_instances_client.actual_lrps_by_processes(processes)

lrps_by_process_guid = actual_lrps.group_by { |lrp| (pg = lrp.actual_lrp_key&.process_guid) && ProcessGuid.cc_process_guid(pg) }

current_time_since_epoch_ns = Time.now.to_f * 1e9
results = {}
processes.each do |process|
newest_lrp_by_index = (lrps_by_process_guid[process.guid] || []).
group_by { |lrp| lrp.actual_lrp_key&.index }.
transform_values { |lrps| lrps.max_by { |lrp| lrp.since || 0 } }

instances = {}
# Fill in the instances up to the max of desired instances and actual instances
[process.instances, newest_lrp_by_index.length].max.times do |idx|
lrp = newest_lrp_by_index[idx]
instances[idx] = if lrp
{
state: LrpStateTranslator.translate_lrp_state(lrp),
since: nanoseconds_to_seconds(current_time_since_epoch_ns - lrp.since)
}
else
{ state: VCAP::CloudController::Diego::LRP_DOWN }
end
end

results[process.guid] = instances
end

results
end

private

attr_reader :bbs_instances_client
Expand Down
11 changes: 11 additions & 0 deletions lib/diego/client.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,17 @@ def actual_lrps_by_process_guid(process_guid)
protobuf_decode!(response.body, Bbs::Models::ActualLRPsResponse)
end

def actual_lrps_by_process_guids(process_guids)
request = protobuf_encode!({ process_guids: }, Bbs::Models::ActualLRPsByProcessGuidsRequest)

response = with_request_error_handling do
client.post(Routes::ACTUAL_LRPS_BY_PROCESS_GUIDS, request, headers)
end

validate_status_200!(response)
protobuf_decode!(response.body, Bbs::Models::ActualLRPsByProcessGuidsResponse)
end

def with_request_error_handling
delay = 0.25
max_delay = 5
Expand Down
1 change: 1 addition & 0 deletions lib/diego/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ module Routes
REMOVE_DESIRED_LRP = '/v1/desired_lrp/remove'.freeze
RETIRE_ACTUAL_LRP = '/v1/actual_lrps/retire'.freeze
ACTUAL_LRPS = '/v1/actual_lrps/list'.freeze
ACTUAL_LRPS_BY_PROCESS_GUIDS = '/v1/actual_lrps/list_by_process_guids'.freeze
end
end
28 changes: 28 additions & 0 deletions spec/diego/client_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -668,6 +668,34 @@ module Diego
end
end

describe '#actual_lrps_by_process_guids' do
let(:actual_lrps) { [::Diego::Bbs::Models::ActualLRP.new, ::Diego::Bbs::Models::ActualLRP.new] }
let(:response_status) { 200 }
let(:response_body) do
Bbs::Models::ActualLRPsByProcessGuidsResponse.encode(
Bbs::Models::ActualLRPsByProcessGuidsResponse.new(error: nil, actual_lrps: actual_lrps)
).to_s
end
let(:process_guids) { %w[process-guid another-process-guid] }

before do
stub_request(:post, "#{bbs_url}/v1/actual_lrps/list_by_process_guids").to_return(status: response_status, body: response_body)
end

it 'returns a LRP instances by process_guids response' do
expected_request = Bbs::Models::ActualLRPsByProcessGuidsRequest.new(process_guids:)

response = client.actual_lrps_by_process_guids(process_guids)
expect(response).to be_a(Bbs::Models::ActualLRPsByProcessGuidsResponse)
expect(response.error).to be_nil
expect(response.actual_lrps).to eq(actual_lrps)
expect(a_request(:post, "#{bbs_url}/v1/actual_lrps/list_by_process_guids").with(
body: Bbs::Models::ActualLRPsByProcessGuidsRequest.encode(expected_request).to_s,
headers: { 'Content-Type' => 'application/x-protobuf', 'X-Vcap-Request-Id' => request_id }
)).to have_been_made.once
end
end

describe '#desired_lrps_scheduling_infos' do
let(:scheduling_infos) { [::Diego::Bbs::Models::DesiredLRPSchedulingInfo.new] }
let(:response_body) do
Expand Down
71 changes: 71 additions & 0 deletions spec/request/processes_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -657,6 +657,77 @@
end
end

describe 'GET /v3/processes/:guid/instances' do
let(:process) { VCAP::CloudController::ProcessModel.make(:process, app: app_model) }
let(:two_days_ago_since_epoch_ns) { 2.days.ago.to_f * 1e9 }
let(:two_days_in_seconds) { 60 * 60 * 24 * 2 }
let(:second_in_ns) { 1_000_000_000 }
let(:actual_lrp_0) do
Diego::Bbs::Models::ActualLRP.new(
actual_lrp_key: Diego::Bbs::Models::ActualLRPKey.new(process_guid: process.guid + 'version', index: 0),
actual_lrp_instance_key: Diego::Bbs::Models::ActualLRPInstanceKey.new(instance_guid: 'instance-a'),
state: Diego::ActualLRPState::RUNNING,
placement_error: '',
since: two_days_ago_since_epoch_ns
)
end
let(:actual_lrp_1) do
Diego::Bbs::Models::ActualLRP.new(
actual_lrp_key: Diego::Bbs::Models::ActualLRPKey.new(process_guid: process.guid + 'version', index: 1),
actual_lrp_instance_key: Diego::Bbs::Models::ActualLRPInstanceKey.new(instance_guid: 'instance-b'),
state: Diego::ActualLRPState::CLAIMED,
placement_error: '',
since: two_days_ago_since_epoch_ns + (1 * second_in_ns)
)
end
let(:bbs_response) { Diego::Bbs::Models::ActualLRPsByProcessGuidsResponse.new(actual_lrps: [actual_lrp_0, actual_lrp_1]) }
let(:bbs_client) { double(:bbs_client) }

let(:expected_response) do
{
'resources' => [{
'index' => 0,
'state' => 'RUNNING',
'since' => two_days_in_seconds
}, {
'index' => 1,
'state' => 'STARTING',
'since' => two_days_in_seconds - 1
}],
'links' => {
'self' => { 'href' => "#{link_prefix}/v3/processes/#{process.guid}/instances" },
'process' => { 'href' => "#{link_prefix}/v3/processes/#{process.guid}" }
}
}
end

before do
CloudController::DependencyLocator.instance.register(:bbs_instances_client, VCAP::CloudController::Diego::BbsInstancesClient.new(bbs_client))
allow(bbs_client).to receive(:actual_lrps_by_process_guids).and_return(bbs_response)
end

it 'retrieves all instances for the process' do
get "/v3/processes/#{process.guid}/instances", nil, developer_headers

parsed_response = Oj.load(last_response.body)

expect(last_response.status).to eq(200)
expect(parsed_response).to be_a_response_like(expected_response)
end

context 'permissions' do
let(:api_call) { ->(user_headers) { get "/v3/processes/#{process.guid}/instances", nil, user_headers } }

let(:expected_codes_and_responses) do
h = Hash.new({ code: 200, response_object: expected_response }.freeze)
h['org_auditor'] = h['org_billing_manager'] = h['no_role'] = { code: 404, response_object: [] }
h
end

it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
end
end

describe 'PATCH /v3/processes/:guid' do
let(:revision) { VCAP::CloudController::RevisionModel.make }
let(:process) do
Expand Down
Loading