Skip to content

Commit fcb98f7

Browse files
authored
[main] Use embedded process instances for "cf apps" summary (#3726)
* Use embedded process instances for "cf apps" summary * use "/v3/processes?space_guids=:guid&embed=process_instances" to get processes and process instances in one request * see cloudfoundry/cloud_controller_ng#4796 * Fix instance.Uptime time conversion * and remove unnecessary pointer for EmbeddedProcessInstances slice
1 parent 3d8c0b8 commit fcb98f7

File tree

7 files changed

+259
-26
lines changed

7 files changed

+259
-26
lines changed

actor/v7action/application_summary.go

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,14 @@ package v7action
22

33
import (
44
"errors"
5+
"time"
56

67
"code.cloudfoundry.org/cli/v9/actor/actionerror"
8+
"code.cloudfoundry.org/cli/v9/actor/versioncheck"
79
"code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccerror"
810
"code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3"
11+
"code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccv3/constant"
12+
"code.cloudfoundry.org/cli/v9/api/cloudcontroller/ccversion"
913
"code.cloudfoundry.org/cli/v9/resources"
1014
"code.cloudfoundry.org/cli/v9/util/batcher"
1115
)
@@ -58,7 +62,15 @@ func (actor Actor) GetAppSummariesForSpace(spaceGUID string, labelSelector strin
5862
var warnings Warnings
5963

6064
if !omitStats {
61-
processSummariesByAppGUID, warnings, err = actor.getProcessSummariesForApps(apps)
65+
embeddedProcessInstancesAvailable, versionErr := versioncheck.IsMinimumAPIVersionMet(actor.Config.APIVersion(), ccversion.MinVersionEmbeddedProcessInstances)
66+
if versionErr != nil {
67+
return nil, allWarnings, versionErr
68+
}
69+
if embeddedProcessInstancesAvailable {
70+
processSummariesByAppGUID, warnings, err = actor.getProcessSummariesForSpace(spaceGUID)
71+
} else {
72+
processSummariesByAppGUID, warnings, err = actor.getProcessSummariesForApps(apps)
73+
}
6274
allWarnings = append(allWarnings, warnings...)
6375
if err != nil {
6476
return nil, allWarnings, err
@@ -174,6 +186,42 @@ func (actor Actor) getProcessSummariesForApps(apps []resources.Application) (map
174186

175187
processSummariesByAppGUID[process.AppGUID] = append(processSummariesByAppGUID[process.AppGUID], processSummary)
176188
}
189+
190+
return processSummariesByAppGUID, allWarnings, nil
191+
}
192+
193+
func (actor Actor) getProcessSummariesForSpace(spaceGUID string) (map[string]ProcessSummaries, Warnings, error) {
194+
processSummariesByAppGUID := make(map[string]ProcessSummaries)
195+
var allWarnings Warnings
196+
var processes []resources.Process
197+
198+
// use "/v3/processes?space_guids=:guid&embed=process_instances" to get processes and process instances in one request
199+
processes, warnings, err := actor.CloudControllerClient.GetProcesses(
200+
ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{spaceGUID}},
201+
ccv3.Query{Key: ccv3.Embed, Values: []string{"process_instances"}},
202+
)
203+
allWarnings = append(allWarnings, warnings...)
204+
if err != nil {
205+
return nil, allWarnings, err
206+
}
207+
208+
for _, process := range processes {
209+
var instanceDetails []ProcessInstance
210+
for _, instance := range process.EmbeddedProcessInstances {
211+
instanceDetails = append(instanceDetails, NewProcessInstance(
212+
instance.Index,
213+
constant.ProcessInstanceState(instance.State),
214+
time.Duration(instance.Since)*time.Second,
215+
))
216+
}
217+
processSummary := ProcessSummary{
218+
Process: process,
219+
InstanceDetails: instanceDetails,
220+
}
221+
222+
processSummariesByAppGUID[process.AppGUID] = append(processSummariesByAppGUID[process.AppGUID], processSummary)
223+
224+
}
177225
return processSummariesByAppGUID, allWarnings, nil
178226
}
179227

actor/v7action/application_summary_test.go

Lines changed: 151 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package v7action_test
33
import (
44
"errors"
55
"fmt"
6+
"time"
67

78
. "code.cloudfoundry.org/cli/v9/actor/v7action"
89
"code.cloudfoundry.org/cli/v9/actor/v7action/v7actionfakes"
@@ -23,11 +24,14 @@ var _ = Describe("Application Summary Actions", func() {
2324
var (
2425
actor *Actor
2526
fakeCloudControllerClient *v7actionfakes.FakeCloudControllerClient
27+
fakeConfig *v7actionfakes.FakeConfig
2628
)
2729

2830
BeforeEach(func() {
2931
fakeCloudControllerClient = new(v7actionfakes.FakeCloudControllerClient)
30-
actor = NewActor(fakeCloudControllerClient, nil, nil, nil, nil, clock.NewClock())
32+
fakeConfig = new(v7actionfakes.FakeConfig)
33+
fakeConfig.APIVersionReturns("3.210.0")
34+
actor = NewActor(fakeCloudControllerClient, fakeConfig, nil, nil, nil, clock.NewClock())
3135
})
3236

3337
Describe("ApplicationSummary", func() {
@@ -287,6 +291,152 @@ var _ = Describe("Application Summary Actions", func() {
287291
Expect(fakeCloudControllerClient.GetProcessInstancesArgsForCall(0)).To(Equal("some-process-guid"))
288292
})
289293

294+
Context("the cloud controller supports embedded process instances", func() {
295+
BeforeEach(func() {
296+
fakeConfig.APIVersionReturns("3.211.0")
297+
298+
listedProcesses := []resources.Process{
299+
{
300+
GUID: "some-process-guid",
301+
Type: "some-type",
302+
Command: *types.NewFilteredString("[Redacted Value]"),
303+
MemoryInMB: types.NullUint64{Value: 32, IsSet: true},
304+
AppGUID: "some-app-guid",
305+
EmbeddedProcessInstances: []resources.EmbeddedProcessInstance{
306+
{Index: 0, State: "RUNNING", Since: 300},
307+
{Index: 1, State: "CRASHED", Since: 0},
308+
},
309+
},
310+
{
311+
GUID: "some-process-web-guid",
312+
Type: "web",
313+
Command: *types.NewFilteredString("[Redacted Value]"),
314+
MemoryInMB: types.NullUint64{Value: 64, IsSet: true},
315+
AppGUID: "some-app-guid",
316+
EmbeddedProcessInstances: []resources.EmbeddedProcessInstance{
317+
{Index: 0, State: "RUNNING", Since: 500},
318+
{Index: 1, State: "RUNNING", Since: 600},
319+
},
320+
},
321+
}
322+
323+
fakeCloudControllerClient.GetProcessesReturns(
324+
listedProcesses,
325+
ccv3.Warnings{"get-space-processes-warning"},
326+
nil,
327+
)
328+
})
329+
330+
It("uses the embedded process instances", func() {
331+
Expect(executeErr).ToNot(HaveOccurred())
332+
Expect(summaries).To(Equal([]ApplicationSummary{
333+
{
334+
Application: resources.Application{
335+
Name: "some-app-name",
336+
GUID: "some-app-guid",
337+
State: constant.ApplicationStarted,
338+
},
339+
ProcessSummaries: []ProcessSummary{
340+
{
341+
Process: resources.Process{
342+
GUID: "some-process-web-guid",
343+
Type: "web",
344+
Command: *types.NewFilteredString("[Redacted Value]"),
345+
MemoryInMB: types.NullUint64{Value: 64, IsSet: true},
346+
AppGUID: "some-app-guid",
347+
EmbeddedProcessInstances: []resources.EmbeddedProcessInstance{
348+
{Index: 0, State: "RUNNING", Since: 500},
349+
{Index: 1, State: "RUNNING", Since: 600},
350+
},
351+
},
352+
InstanceDetails: []ProcessInstance{
353+
{
354+
Index: 0,
355+
State: "RUNNING",
356+
Uptime: 500 * time.Second,
357+
},
358+
{
359+
Index: 1,
360+
State: "RUNNING",
361+
Uptime: 600 * time.Second,
362+
},
363+
},
364+
},
365+
{
366+
Process: resources.Process{
367+
GUID: "some-process-guid",
368+
MemoryInMB: types.NullUint64{Value: 32, IsSet: true},
369+
Type: "some-type",
370+
Command: *types.NewFilteredString("[Redacted Value]"),
371+
AppGUID: "some-app-guid",
372+
EmbeddedProcessInstances: []resources.EmbeddedProcessInstance{
373+
{Index: 0, State: "RUNNING", Since: 300},
374+
{Index: 1, State: "CRASHED", Since: 0},
375+
},
376+
},
377+
InstanceDetails: []ProcessInstance{
378+
{
379+
Index: 0,
380+
State: "RUNNING",
381+
Uptime: 300 * time.Second,
382+
},
383+
{
384+
Index: 1,
385+
State: "CRASHED",
386+
Uptime: 0 * time.Second,
387+
},
388+
},
389+
},
390+
},
391+
Routes: []resources.Route{
392+
{
393+
GUID: "some-route-guid",
394+
Destinations: []resources.RouteDestination{
395+
{
396+
App: resources.RouteDestinationApp{
397+
GUID: "some-app-guid",
398+
},
399+
},
400+
},
401+
},
402+
{
403+
GUID: "some-other-route-guid",
404+
Destinations: []resources.RouteDestination{
405+
{
406+
App: resources.RouteDestinationApp{
407+
GUID: "some-app-guid",
408+
},
409+
},
410+
},
411+
},
412+
},
413+
},
414+
}))
415+
416+
Expect(warnings).To(ConsistOf(
417+
"get-apps-warning",
418+
"get-space-processes-warning",
419+
"get-routes-warning",
420+
))
421+
422+
Expect(fakeCloudControllerClient.GetApplicationsCallCount()).To(Equal(1))
423+
Expect(fakeCloudControllerClient.GetApplicationsArgsForCall(0)).To(ConsistOf(
424+
ccv3.Query{Key: ccv3.OrderBy, Values: []string{"name"}},
425+
ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"some-space-guid"}},
426+
ccv3.Query{Key: ccv3.LabelSelectorFilter, Values: []string{"some-key=some-value"}},
427+
ccv3.Query{Key: ccv3.PerPage, Values: []string{ccv3.MaxPerPage}},
428+
))
429+
430+
Expect(fakeCloudControllerClient.GetProcessesCallCount()).To(Equal(1))
431+
Expect(fakeCloudControllerClient.GetProcessesArgsForCall(0)).To(ConsistOf(
432+
ccv3.Query{Key: ccv3.SpaceGUIDFilter, Values: []string{"some-space-guid"}},
433+
ccv3.Query{Key: ccv3.Embed, Values: []string{"process_instances"}},
434+
))
435+
436+
Expect(fakeCloudControllerClient.GetProcessInstancesCallCount()).To(Equal(0))
437+
})
438+
})
439+
290440
When("there is no label selector", func() {
291441
BeforeEach(func() {
292442
labelSelector = ""

actor/v7action/process_instance.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,14 @@ import (
1212

1313
type ProcessInstance ccv3.ProcessInstance
1414

15+
func NewProcessInstance(index int64, state constant.ProcessInstanceState, uptime time.Duration) ProcessInstance {
16+
return ProcessInstance(ccv3.ProcessInstance{
17+
Index: index,
18+
State: state,
19+
Uptime: uptime,
20+
})
21+
}
22+
1523
// Running will return true if the instance is running.
1624
func (instance ProcessInstance) Running() bool {
1725
return instance.State == constant.ProcessInstanceRunning

api/cloudcontroller/ccv3/process_test.go

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,7 @@ var _ = Describe("Process", func() {
9494
"ReadinessHealthCheckEndpoint": Equal("/foo"),
9595
"ReadinessHealthCheckInvocationTimeout": BeEquivalentTo(2),
9696
"ReadinessHealthCheckInterval": BeEquivalentTo(9),
97+
"EmbeddedProcessInstances": BeNil(),
9798
}))
9899
})
99100
})
@@ -367,6 +368,7 @@ var _ = Describe("Process", func() {
367368
"ReadinessHealthCheckEndpoint": Equal("/foo"),
368369
"ReadinessHealthCheckInvocationTimeout": BeEquivalentTo(2),
369370
"ReadinessHealthCheckInterval": BeEquivalentTo(9),
371+
"EmbeddedProcessInstances": BeNil(),
370372
}))
371373
})
372374
})
@@ -524,32 +526,35 @@ var _ = Describe("Process", func() {
524526

525527
Expect(processes).To(ConsistOf(
526528
resources.Process{
527-
GUID: "process-1-guid",
528-
Type: constant.ProcessTypeWeb,
529-
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
530-
MemoryInMB: types.NullUint64{Value: 32, IsSet: true},
531-
LogRateLimitInBPS: types.NullInt{Value: 64, IsSet: true},
532-
HealthCheckType: constant.Port,
533-
HealthCheckTimeout: 0,
529+
GUID: "process-1-guid",
530+
Type: constant.ProcessTypeWeb,
531+
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
532+
MemoryInMB: types.NullUint64{Value: 32, IsSet: true},
533+
LogRateLimitInBPS: types.NullInt{Value: 64, IsSet: true},
534+
HealthCheckType: constant.Port,
535+
HealthCheckTimeout: 0,
536+
EmbeddedProcessInstances: nil,
534537
},
535538
resources.Process{
536-
GUID: "process-2-guid",
537-
Type: "worker",
538-
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
539-
MemoryInMB: types.NullUint64{Value: 64, IsSet: true},
540-
LogRateLimitInBPS: types.NullInt{Value: 128, IsSet: true},
541-
HealthCheckType: constant.HTTP,
542-
HealthCheckEndpoint: "/health",
543-
HealthCheckTimeout: 60,
539+
GUID: "process-2-guid",
540+
Type: "worker",
541+
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
542+
MemoryInMB: types.NullUint64{Value: 64, IsSet: true},
543+
LogRateLimitInBPS: types.NullInt{Value: 128, IsSet: true},
544+
HealthCheckType: constant.HTTP,
545+
HealthCheckEndpoint: "/health",
546+
HealthCheckTimeout: 60,
547+
EmbeddedProcessInstances: nil,
544548
},
545549
resources.Process{
546-
GUID: "process-3-guid",
547-
Type: "console",
548-
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
549-
MemoryInMB: types.NullUint64{Value: 128, IsSet: true},
550-
LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true},
551-
HealthCheckType: constant.Process,
552-
HealthCheckTimeout: 90,
550+
GUID: "process-3-guid",
551+
Type: "console",
552+
Command: types.FilteredString{IsSet: true, Value: "[PRIVATE DATA HIDDEN IN LISTS]"},
553+
MemoryInMB: types.NullUint64{Value: 128, IsSet: true},
554+
LogRateLimitInBPS: types.NullInt{Value: 256, IsSet: true},
555+
HealthCheckType: constant.Process,
556+
HealthCheckTimeout: 90,
557+
EmbeddedProcessInstances: nil,
553558
},
554559
))
555560
Expect(warnings).To(ConsistOf("warning-1", "warning-2"))

api/cloudcontroller/ccv3/query.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ const (
101101
// Include is a query parameter for specifying other resources associated with the
102102
// resource returned by the endpoint
103103
Include QueryKey = "include"
104+
// see https://v3-apidocs.cloudfoundry.org/version/3.212.0/index.html#embed
105+
Embed QueryKey = "embed"
104106

105107
// GloballyEnabledStaging is the query parameter for getting only security groups that are globally enabled for staging
106108
GloballyEnabledStaging QueryKey = "globally_enabled_staging"

api/cloudcontroller/ccversion/minimum_version.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,7 @@ const (
1919

2020
MinVersionServiceBindingStrategy = "3.205.0"
2121

22+
MinVersionEmbeddedProcessInstances = "3.211.0"
23+
2224
MinVersionUpdateStack = "3.211.0"
2325
)

0 commit comments

Comments
 (0)