From b6b5205b1a769498f9a48cb956d56760a25a39a3 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 1 Aug 2024 09:31:49 +0200 Subject: [PATCH 01/17] feat: Add RecentLogs support --- .../cloudfoundry/doppler/DopplerClient.java | 3 ++ .../logcache/v1/LogCacheClient.java | 8 ++++ .../applications/DefaultApplications.java | 41 +++++++++++++++++++ 3 files changed, 52 insertions(+) diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index a9c03441cf..4e2c869b32 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,12 +39,15 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); + //TODO Adapt the message /** * Makes the Recent Logs request * + * @deprecated Do not use this type directly, it exists only for the Jackson-binding infrastructure * @param request the Recent Logs request * @return the events from the recent logs */ + @Deprecated Flux recentLogs(RecentLogsRequest request); /** diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java index e455db220a..8a9b08505c 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/logcache/v1/LogCacheClient.java @@ -46,4 +46,12 @@ public interface LogCacheClient { * @return the read response */ Mono read(ReadRequest request); + + /** + * Makes the Log Cache RecentLogs /api/v1/read request + * + * @param request the Recent Logs request + * @return the events from the recent logs + */ + Mono recentLogs(ReadRequest request); } diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index e51ddbb472..718ab25245 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -40,6 +40,7 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; + import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.OrderDirection; import org.cloudfoundry.client.v2.applications.AbstractApplicationResource; @@ -154,6 +155,9 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.EnvelopeType; +import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -200,6 +204,9 @@ public final class DefaultApplications implements Applications { private static final Comparator LOG_MESSAGE_COMPARATOR = Comparator.comparing(LogMessage::getTimestamp); + private static final Comparator LOG_MESSAGE_COMPARATOR_LOG_CACHE = + Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); + private static final Duration LOG_MESSAGE_TIMESPAN = Duration.ofMillis(500); private static final int MAX_NUMBER_OF_RECENT_EVENTS = 50; @@ -1617,6 +1624,14 @@ private static Flux getLogs( } } + private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + .map(org.cloudfoundry.logcache.v1.Envelope::getLog) + .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .flatMapIterable(d -> d); + } + @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = @@ -2509,6 +2524,32 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } + private static Flux requestLogsRecentLogCache( + Mono logCacheClient, String applicationId) { + return logCacheClient.flatMapMany( + client -> + client.recentLogs( + ReadRequest.builder() + .sourceId(applicationId) + .envelopeType(EnvelopeType.LOG) + .limit(100) + .build() + ) + .flatMap( + response -> + Mono.justOrEmpty( + response.getEnvelopes().getBatch().stream().findFirst() + ) + ) + .repeatWhenEmpty( + exponentialBackOff( + Duration.ofSeconds(1), + Duration.ofSeconds(5), + Duration.ofMinutes(1)) + ) + ); + } + private static Flux requestLogsStream( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( From 1afe7df7bdedd834cd1414dbab051eeec30d741c Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Mon, 19 Aug 2024 12:28:00 +0200 Subject: [PATCH 02/17] fix: The Reactor part --- .../reactor/logcache/v1/ReactorLogCacheEndpoints.java | 4 ++++ .../reactor/logcache/v1/_ReactorLogCacheClient.java | 5 +++++ 2 files changed, 9 insertions(+) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java index 2e68c52538..f0b610d0c3 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java @@ -48,4 +48,8 @@ Mono meta(MetaRequest request) { Mono read(ReadRequest request) { return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); } + + Mono recentLogs(ReadRequest request) { + return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); + } } diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java index d9460476ea..286f4c787c 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/_ReactorLogCacheClient.java @@ -53,6 +53,11 @@ public Mono read(ReadRequest request) { return getReactorLogCacheEndpoints().read(request); } + @Override + public Mono recentLogs(ReadRequest request) { + return getReactorLogCacheEndpoints().recentLogs(request); + } + /** * The connection context */ From 61c556d0801af1183c11f9acea7a4b391259b676 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 4 Sep 2024 16:48:48 +0100 Subject: [PATCH 03/17] fix: Adjust the logcache --- .../_DefaultCloudFoundryOperations.java | 16 ++++++- .../operations/applications/Applications.java | 3 +- .../applications/DefaultApplications.java | 47 ++++++++++--------- .../operations/AbstractOperationsTest.java | 3 ++ .../applications/DefaultApplicationsTest.java | 27 +++++------ .../operations/ApplicationsTest.java | 4 +- 6 files changed, 61 insertions(+), 39 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java index 1d21f29b2b..7413a0a7fc 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java @@ -23,6 +23,7 @@ import org.cloudfoundry.client.v3.spaces.ListSpacesRequest; import org.cloudfoundry.client.v3.spaces.SpaceResource; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.networking.NetworkingClient; import org.cloudfoundry.operations.advanced.Advanced; import org.cloudfoundry.operations.advanced.DefaultAdvanced; @@ -79,7 +80,7 @@ public Advanced advanced() { @Override @Value.Derived public Applications applications() { - return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getSpaceId()); + return new DefaultApplications(getCloudFoundryClientPublisher(), getDopplerClientPublisher(), getLogCacheClientPublisher(), getSpaceId()); } @Override @@ -190,6 +191,12 @@ Mono getCloudFoundryClientPublisher() { @Nullable abstract DopplerClient getDopplerClient(); + /** + * The {@link LogCacheClient} to use for operations functionality + */ + @Nullable + abstract LogCacheClient getLogCacheClient(); + @Value.Derived Mono getDopplerClientPublisher() { return Optional.ofNullable(getDopplerClient()) @@ -197,6 +204,13 @@ Mono getDopplerClientPublisher() { .orElse(Mono.error(new IllegalStateException("DopplerClient must be set"))); } + @Value.Derived + Mono getLogCacheClientPublisher() { + return Optional.ofNullable(getLogCacheClient()) + .map(Mono::just) + .orElse(Mono.error(new IllegalStateException("LogCacheClient must be set"))); + } + /** * The {@link NetworkingClient} to use for operations functionality */ diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 5196fef6c8..2f0750523e 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -17,6 +17,7 @@ package org.cloudfoundry.operations.applications; import org.cloudfoundry.doppler.LogMessage; +import org.cloudfoundry.logcache.v1.Log; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -124,7 +125,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(LogsRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 718ab25245..be6f0cf1bf 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -156,6 +156,7 @@ import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; import org.cloudfoundry.logcache.v1.EnvelopeType; +import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.util.OperationsLogging; @@ -221,6 +222,8 @@ public final class DefaultApplications implements Applications { private final Mono dopplerClient; + private final Mono logCacheClient; + private final RandomWords randomWords; private final Mono spaceId; @@ -228,22 +231,25 @@ public final class DefaultApplications implements Applications { public DefaultApplications( Mono cloudFoundryClient, Mono dopplerClient, + Mono logCacheClient, Mono spaceId) { - this(cloudFoundryClient, dopplerClient, new WordListRandomWords(), spaceId); + this(cloudFoundryClient, dopplerClient, logCacheClient, new WordListRandomWords(), spaceId); } DefaultApplications( Mono cloudFoundryClient, Mono dopplerClient, + Mono logCacheClient, RandomWords randomWords, Mono spaceId) { this.cloudFoundryClient = cloudFoundryClient; this.dopplerClient = dopplerClient; + this.logCacheClient = logCacheClient; this.randomWords = randomWords; this.spaceId = spaceId; } - @Override +@Override public Mono copySource(CopySourceApplicationRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( @@ -537,7 +543,7 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(LogsRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -546,7 +552,7 @@ public Flux logs(LogsRequest request) { cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> - getLogs(this.dopplerClient, applicationId, request.getRecent())) + getRecentLogs(this.logCacheClient, applicationId)) .transform(OperationsLogging.log("Get Application Logs")) .checkpoint(); } @@ -1607,30 +1613,29 @@ private static int getInstances(AbstractApplicationResource resource) { .orElse(0); } - private static Flux getLogs( - Mono dopplerClient, String applicationId, Boolean recent) { + /* private static Flux getLogs( + Mono logCacheClient, String applicationId, Boolean recent) { if (Optional.ofNullable(recent).orElse(false)) { - return requestLogsRecent(dopplerClient, applicationId) - .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) - .map(Envelope::getLogMessage) - .collectSortedList(LOG_MESSAGE_COMPARATOR) - .flatMapIterable(d -> d); - } else { - return requestLogsStream(dopplerClient, applicationId) - .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) - .map(Envelope::getLogMessage) - .transformDeferred( - SortingUtils.timespan(LOG_MESSAGE_COMPARATOR, LOG_MESSAGE_TIMESPAN)); + return getRecentLogs(logCacheClient, applicationId); } + }*/ + + private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + // .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } - private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { +/* private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { return requestLogsRecentLogCache(logCacheClient, applicationId) .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog) - .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .flatMapIterable(d -> d); - } + .collectList() + .flatMapIterable(d1 -> d1).cast(org.cloudfoundry.logcache.v1.Log.class); + } */ @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java index ab1250658a..9dd97126e8 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java @@ -53,6 +53,7 @@ import org.cloudfoundry.client.v3.stacks.StacksV3; import org.cloudfoundry.client.v3.tasks.Tasks; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.routing.RoutingClient; import org.cloudfoundry.routing.v1.routergroups.RouterGroups; import org.cloudfoundry.uaa.UaaClient; @@ -104,6 +105,8 @@ public abstract class AbstractOperationsTest { protected final DopplerClient dopplerClient = mock(DopplerClient.class, RETURNS_SMART_NULLS); + protected final LogCacheClient logCacheClient = mock(LogCacheClient.class, RETURNS_SMART_NULLS); + protected final Events events = mock(Events.class, RETURNS_SMART_NULLS); protected final FeatureFlags featureFlags = mock(FeatureFlags.class, RETURNS_SMART_NULLS); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index cdc9619d2d..685c572717 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -144,6 +144,10 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.Log; +import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.AbstractOperationsTest; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; @@ -163,6 +167,7 @@ final class DefaultApplicationsTest extends AbstractOperationsTest { new DefaultApplications( Mono.just(this.cloudFoundryClient), Mono.just(this.dopplerClient), + Mono.just(this.logCacheClient), this.randomWords, Mono.just(TEST_SPACE_ID)); @@ -1318,7 +1323,7 @@ void logs() { this.applications .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -1346,12 +1351,12 @@ void logsRecent() { "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -1368,7 +1373,7 @@ void logsRecentNotSet() { this.applications .logs(LogsRequest.builder().name("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -5317,17 +5322,11 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.recentLogs( - RecentLogsRequest.builder().applicationId(applicationId).build())) + private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { + when(logCacheClient.recentLogs( + ReadRequest.builder().sourceId(applicationId).build())) .thenReturn( - Flux.just( - Envelope.builder() - .eventType(EventType.LOG_MESSAGE) - .logMessage( - fill(LogMessage.builder(), "log-message-").build()) - .origin("rsp") - .build())); + Mono.just(ReadResponse.builder().envelopes(fill(org.cloudfoundry.logcache.v1.EnvelopeBatch.builder()).build()).build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 36e1bd9456..cf2cde4aa6 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -529,10 +529,10 @@ public void logs() throws IOException { .name(applicationName) .recent(true) .build())) - .map(ApplicationLog::getLogType) + .map(org.cloudfoundry.logcache.v1.Log::getType) .next() .as(StepVerifier::create) - .expectNext(ApplicationLogType.OUT) + .expectNext(org.cloudfoundry.logcache.v1.LogType.OUT) .expectComplete() .verify(Duration.ofMinutes(5)); } From 7a4cb06713919194053e9b69047a7b19ce90964e Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Wed, 30 Oct 2024 16:36:05 +0000 Subject: [PATCH 04/17] fix: Adjust the logcache client implementation --- .../ReactorServiceInstancesV3Test.java | 1 + .../_DefaultCloudFoundryOperations.java | 12 +++---- .../operations/applications/Applications.java | 4 ++- .../applications/DefaultApplications.java | 35 +++++++++---------- .../applications/DefaultApplicationsTest.java | 29 ++++++++++----- .../IntegrationTestConfiguration.java | 3 ++ .../operations/ApplicationsTest.java | 3 +- 7 files changed, 51 insertions(+), 36 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java index 1e6df1f258..1745aa7285 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java @@ -62,6 +62,7 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; +import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java index 7413a0a7fc..4db0bd8489 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/_DefaultCloudFoundryOperations.java @@ -191,12 +191,6 @@ Mono getCloudFoundryClientPublisher() { @Nullable abstract DopplerClient getDopplerClient(); - /** - * The {@link LogCacheClient} to use for operations functionality - */ - @Nullable - abstract LogCacheClient getLogCacheClient(); - @Value.Derived Mono getDopplerClientPublisher() { return Optional.ofNullable(getDopplerClient()) @@ -204,6 +198,12 @@ Mono getDopplerClientPublisher() { .orElse(Mono.error(new IllegalStateException("DopplerClient must be set"))); } + /** + * The {@link LogCacheClient} to use for operations functionality + */ + @Nullable + abstract LogCacheClient getLogCacheClient(); + @Value.Derived Mono getLogCacheClientPublisher() { return Optional.ofNullable(getLogCacheClient()) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 2f0750523e..31a4ad510c 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -18,6 +18,8 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; +import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -125,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(ReadRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index be6f0cf1bf..62d76d5c08 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -159,6 +159,7 @@ import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; +import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -543,13 +544,13 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(LogsRequest request) { + public Flux logs(ReadRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( (cloudFoundryClient, spaceId) -> getApplicationId( - cloudFoundryClient, request.getName(), spaceId))) + cloudFoundryClient, request.getSourceId(), spaceId))) .flatMapMany( applicationId -> getRecentLogs(this.logCacheClient, applicationId)) @@ -686,7 +687,6 @@ public Mono pushManifestV3(PushManifestV3Request request) { } catch (IOException e) { throw new RuntimeException("Could not serialize manifest", e); } - return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -1613,30 +1613,29 @@ private static int getInstances(AbstractApplicationResource resource) { .orElse(0); } - /* private static Flux getLogs( - Mono logCacheClient, String applicationId, Boolean recent) { + private static Flux getLogs( + Mono dopplerClient, String applicationId, Boolean recent) { if (Optional.ofNullable(recent).orElse(false)) { - return getRecentLogs(logCacheClient, applicationId); + return requestLogsRecent(dopplerClient, applicationId) + .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) + .map(Envelope::getLogMessage) + .collectSortedList(LOG_MESSAGE_COMPARATOR) + .flatMapIterable(d -> d); + } else { + return requestLogsStream(dopplerClient, applicationId) + .filter(e -> EventType.LOG_MESSAGE == e.getEventType()) + .map(Envelope::getLogMessage) + .transformDeferred( + SortingUtils.timespan(LOG_MESSAGE_COMPARATOR, LOG_MESSAGE_TIMESPAN)); } - }*/ + } private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { return requestLogsRecentLogCache(logCacheClient, applicationId) - .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) - // .collectSortedList(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } -/* private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { - return requestLogsRecentLogCache(logCacheClient, applicationId) - .filter(e -> EnvelopeType.LOG.getValue().equals(e.getLog().getType().getValue())) - .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) - .map(org.cloudfoundry.logcache.v1.Envelope::getLog) - .collectList() - .flatMapIterable(d1 -> d1).cast(org.cloudfoundry.logcache.v1.Log.class); - } */ - @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 685c572717..e10e673a2d 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -144,8 +144,11 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; +import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; +import org.cloudfoundry.logcache.v1.LogType; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.AbstractOperationsTest; @@ -1318,10 +1321,10 @@ void logs() { "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -1333,7 +1336,7 @@ void logsNoApp() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .consumeErrorWith( t -> @@ -1354,7 +1357,7 @@ void logsRecent() { requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -1371,7 +1374,7 @@ void logsRecentNotSet() { requestLogsStream(this.dopplerClient, "test-metadata-id"); this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) + .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder(), "log-message-").build()) .expectComplete() @@ -5323,10 +5326,18 @@ private static void requestListTasksEmpty( } private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { - when(logCacheClient.recentLogs( - ReadRequest.builder().sourceId(applicationId).build())) - .thenReturn( - Mono.just(ReadResponse.builder().envelopes(fill(org.cloudfoundry.logcache.v1.EnvelopeBatch.builder()).build()).build())); + when(logCacheClient.recentLogs( + ReadRequest.builder().sourceId(applicationId).build())) + .thenReturn( + Mono.just(fill(ReadResponse.builder()) + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT).build()) + .build()) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 36c30c3578..58b01252f0 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -48,6 +48,7 @@ import org.cloudfoundry.client.v2.stacks.StackResource; import org.cloudfoundry.client.v2.userprovidedserviceinstances.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.TestLogCacheEndpoints; import org.cloudfoundry.networking.NetworkingClient; import org.cloudfoundry.operations.DefaultCloudFoundryOperations; @@ -273,6 +274,7 @@ ReactorCloudFoundryClient cloudFoundryClient( DefaultCloudFoundryOperations cloudFoundryOperations( CloudFoundryClient cloudFoundryClient, DopplerClient dopplerClient, + LogCacheClient logCacheClient, NetworkingClient networkingClient, RoutingClient routingClient, UaaClient uaaClient, @@ -281,6 +283,7 @@ DefaultCloudFoundryOperations cloudFoundryOperations( return DefaultCloudFoundryOperations.builder() .cloudFoundryClient(cloudFoundryClient) .dopplerClient(dopplerClient) + .logCacheClient(logCacheClient) .networkingClient(networkingClient) .routingClient(routingClient) .uaaClient(uaaClient) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index cf2cde4aa6..a70e276875 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -526,8 +526,7 @@ public void logs() throws IOException { .applications() .logs( ApplicationLogsRequest.builder() - .name(applicationName) - .recent(true) + .sourceId(applicationName) .build())) .map(org.cloudfoundry.logcache.v1.Log::getType) .next() From 9f76d44d931322122bee562870f7d3a9efa99f4b Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 31 Oct 2024 12:37:08 -0400 Subject: [PATCH 05/17] fix: Adjust the expectations * because of the findFirst() on the envelopes, it could be type OUT or ERR, so we don't really care, as long as the payload is the same. Also, we don't care about the precise argument to the recentLogs call --- .../applications/DefaultApplicationsTest.java | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index e10e673a2d..bfb4c28d39 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -20,6 +20,7 @@ import static org.cloudfoundry.client.v3.LifecycleType.BUILDPACK; import static org.cloudfoundry.client.v3.LifecycleType.DOCKER; import static org.cloudfoundry.operations.TestObjects.fill; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_SMART_NULLS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -1326,7 +1327,7 @@ void logs() { this.applications .logs(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) + .expectNextMatches(log -> log.getPayload().equals("test-payload")) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -5327,16 +5328,16 @@ private static void requestListTasksEmpty( private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { when(logCacheClient.recentLogs( - ReadRequest.builder().sourceId(applicationId).build())) + any())) .thenReturn( Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT).build()) - .build()) - .build()) + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT).build()) + .build()) + .build()) .build())); } From 03e1eb744ab58844316dae4d7c7a5a96da654832 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Fri, 1 Nov 2024 12:46:50 +0000 Subject: [PATCH 06/17] fix: Test only recent logs request --- .../operations/applications/Applications.java | 2 +- .../applications/DefaultApplications.java | 4 +- .../applications/DefaultApplicationsTest.java | 90 ++++++++++--------- 3 files changed, 50 insertions(+), 46 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 31a4ad510c..9c4c68ca3d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -127,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(ReadRequest request); + Flux logs(LogsRequest request); /** * List the applications logs. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 62d76d5c08..9e8dfc8c95 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -544,13 +544,13 @@ public Flux listTasks(ListApplicationTasksRequest request) { } @Override - public Flux logs(ReadRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( (cloudFoundryClient, spaceId) -> getApplicationId( - cloudFoundryClient, request.getSourceId(), spaceId))) + cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> getRecentLogs(this.logCacheClient, applicationId)) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index bfb4c28d39..79914016d4 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -156,6 +156,7 @@ import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; import org.cloudfoundry.util.ResourceMatchingUtils; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; @@ -1325,7 +1326,7 @@ void logs() { requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getPayload().equals("test-payload")) .expectComplete() @@ -1337,7 +1338,7 @@ void logsNoApp() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) + .logs(LogsRequest.builder().name("test-application-name").build()) .as(StepVerifier::create) .consumeErrorWith( t -> @@ -1348,39 +1349,40 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } - @Test - void logsRecent() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); - - this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @Test - void logsRecentNotSet() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } + // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient + @Test + void logsRecent() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient + @Test + void logsRecentNotSet() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } @Test void pushDocker() { @@ -5331,14 +5333,16 @@ private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, Str any())) .thenReturn( Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope.builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT).build()) - .build()) - .build()) - .build())); + .envelopes(fill(EnvelopeBatch.builder()) + .batch(fill(org.cloudfoundry.logcache.v1.Envelope + .builder()) + .log(fill(Log.builder()) + .payload("test-payload") + .type(LogType.OUT) + .build()) + .build()) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { From 5ed6c0705c300e3c7fa2167241eaf3a64006c591 Mon Sep 17 00:00:00 2001 From: Georg Lokowandt Date: Wed, 23 Apr 2025 18:29:45 +0200 Subject: [PATCH 07/17] Fix JUnit tests for logCache The old "Applications.logs" method is keept for compatibility and when a log stream is needed. Adding new "logsRecent" method to access the logCache. integration-tests "ApplicationTest.logs" and "ApplicationTest.logsRecent" work. Minor changes in DefaultApplicationsTest in other JUnit tests to make them execute in Eclipse. --- .../operations/applications/Applications.java | 11 +- .../applications/DefaultApplications.java | 130 ++++++++++++++- .../applications/DefaultApplicationsTest.java | 155 +++++++++++------- .../operations/ApplicationsTest.java | 62 ++++++- 4 files changed, 293 insertions(+), 65 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 9c4c68ca3d..6549f0a84c 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -19,7 +19,7 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; + import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; @@ -129,6 +129,15 @@ public interface Applications { @Deprecated Flux logs(LogsRequest request); + /** + * List the applications logs from logCacheClient. + * If no messages are available, an empty Flux is returned. + * + * @param request the application logs request + * @return the applications logs + */ + Flux logsRecent(ReadRequest request); + /** * List the applications logs. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 9e8dfc8c95..1121e739fc 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -150,12 +150,10 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; -import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; -import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; @@ -578,6 +576,83 @@ public Flux logs(ApplicationLogsRequest request) { .build()); } + @SuppressWarnings("deprecation") + @Test + void logsRecent_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecent(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsNoApp_doppler() { + requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); + + this.applications + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logs_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void logsRecent_LogCache() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications + .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder()).type(LogType.OUT).build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsRecentNotSet_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + @Override @SuppressWarnings("deprecation") public Mono push(PushApplicationRequest request) { @@ -2432,6 +2507,57 @@ private static Mono requestGetApplication( .cast(AbstractApplicationResource.class); } + private static void requestInstancesApplicationFailing( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) + .thenReturn( + Mono.just( + fill( + ApplicationInstancesResponse.builder(), + "application-instances-") + .instance( + "instance-0", + fill( + ApplicationInstanceInfo.builder(), + "application-instance-info-") + .state("FAILED") + .build()) + .build())); + } + + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); + } + private static Flux requestListDomains( CloudFoundryClient cloudFoundryClient, String organizationId) { return PaginationUtils.requestClientV3Resources( diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 79914016d4..08effafac7 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -140,13 +140,11 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; -import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.EnvelopeType; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.LogType; @@ -156,7 +154,6 @@ import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; import org.cloudfoundry.util.ResourceMatchingUtils; -import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; @@ -1316,25 +1313,26 @@ void listTasks() { .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logs() { + void logsRecent_doppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-application-name"); - + requestLogsRecent(this.dopplerClient, "test-metadata-id"); this.applications .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNextMatches(log -> log.getPayload().equals("test-payload")) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsNoApp() { + void logsNoApp_doppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1349,40 +1347,56 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } - // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient - @Test - void logsRecent() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - // TODO: it's not passing since recentLogs is not properly implemented yet with logcacheclient - @Test - void logsRecentNotSet() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } + @SuppressWarnings("deprecation") + @Test + void logs_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .as(StepVerifier::create) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @Test + void logsRecent_LogCache() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications + .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(Log.builder()).type(LogType.OUT).build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsRecentNotSet_doppler() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + + this.applications + .logs(LogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } @Test void pushDocker() { @@ -5328,28 +5342,53 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String applicationId) { - when(logCacheClient.recentLogs( - any())) - .thenReturn( - Mono.just(fill(ReadResponse.builder()) - .envelopes(fill(EnvelopeBatch.builder()) - .batch(fill(org.cloudfoundry.logcache.v1.Envelope - .builder()) - .log(fill(Log.builder()) - .payload("test-payload") - .type(LogType.OUT) - .build()) - .build()) - .build()) - .build())); + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); } private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( - Envelope.builder() + org.cloudfoundry.doppler.Envelope.builder() + .eventType(EventType.LOG_MESSAGE) + .logMessage( + fill(LogMessage.builder(), "log-message-").build()) + .origin("rsp") + .build())); + } + + @SuppressWarnings("deprecation") + private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.recentLogs( + RecentLogsRequest.builder().applicationId(applicationId).build())) + .thenReturn( + Flux.just( + org.cloudfoundry.doppler.Envelope.builder() .eventType(EventType.LOG_MESSAGE) .logMessage( fill(LogMessage.builder(), "log-message-").build()) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index a70e276875..19d784cb16 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -25,6 +25,7 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.logging.Level; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CleanupCloudFoundryAfterClass; import org.cloudfoundry.CloudFoundryVersion; @@ -86,6 +87,7 @@ import org.cloudfoundry.operations.services.CreateUserProvidedServiceInstanceRequest; import org.cloudfoundry.operations.services.GetServiceInstanceRequest; import org.cloudfoundry.operations.services.ServiceInstance; +import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.FluentMap; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -93,6 +95,7 @@ import org.springframework.core.io.ClassPathResource; import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; +import reactor.core.publisher.SignalType; import reactor.test.StepVerifier; @CleanupCloudFoundryAfterClass @@ -511,6 +514,7 @@ public void listTasks() throws IOException { * Doppler was dropped in PCF 4.x in favor of logcache. This test does not work * on TAS 4.x. */ + @Deprecated @Test @IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2) public void logs() throws IOException { @@ -525,13 +529,40 @@ public void logs() throws IOException { this.cloudFoundryOperations .applications() .logs( - ApplicationLogsRequest.builder() - .sourceId(applicationName) + LogsRequest.builder() + .name(applicationName) + .recent(true) .build())) - .map(org.cloudfoundry.logcache.v1.Log::getType) + .map(LogMessage::getMessageType) .next() .as(StepVerifier::create) - .expectNext(org.cloudfoundry.logcache.v1.LogType.OUT) + .expectNext(MessageType.OUT) + .expectComplete() + .verify(Duration.ofMinutes(5)); + } + + @Test + public void logsRecent() throws IOException { + String applicationName = this.nameFactory.getApplicationName(); + Mono applicationGuid = + getAppGuidFromAppName(cloudFoundryOperations, applicationName); + createApplication( + this.cloudFoundryOperations, + new ClassPathResource("test-application.zip").getFile().toPath(), + applicationName, + false) + .then( + applicationGuid + .map(ApplicationsTest::getReadRequest) + .flatMapMany( + readRequest -> + callLogsRecent( + this.cloudFoundryOperations, + readRequest) + .log(null, Level.ALL, SignalType.ON_NEXT)) + .map(ApplicationsTest::checkOneLogEntry) + .then()) + .as(StepVerifier::create) .expectComplete() .verify(Duration.ofMinutes(5)); } @@ -2155,4 +2186,27 @@ private static Mono requestSshEnabled( .applications() .sshEnabled(ApplicationSshEnabledRequest.builder().name(applicationName).build()); } + + private static ReadRequest getReadRequest(String applicationId) { + return ReadRequest.builder().sourceId(applicationId).build(); + } + + private static Flux callLogsRecent( + CloudFoundryOperations cloudFoundryOperations, ReadRequest readRequest) { + return cloudFoundryOperations.applications().logsRecent(readRequest); + } + + private static Mono getAppGuidFromAppName( + CloudFoundryOperations cloudFoundryOperations, String applicationName) { + return cloudFoundryOperations + .applications() + .get(GetApplicationRequest.builder().name(applicationName).build()) + .map(ApplicationDetail::getId); + } + + private static Log checkOneLogEntry(Log log) { + assertThat(log.getType().equals(LogType.OUT)); + OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); + return log; + } } From 13aff7b6dadb62c294763ac46bfe8f0d1c7fad7c Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 08:01:21 +0100 Subject: [PATCH 08/17] fix: Adjust the merge conflicts --- .../operations/applications/Applications.java | 8 + .../applications/DefaultApplications.java | 163 +++++------------- 2 files changed, 50 insertions(+), 121 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 6549f0a84c..b484e7f586 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -117,6 +117,14 @@ public interface Applications { */ Flux listTasks(ListApplicationTasksRequest request); + /** + * List the applications logs from dopplerClient + * @deprecated Only for compatibility. Switch to logCacheClient method below. + * @param request the application logs request + * @return the applications logs + */ + Flux logs(LogsRequest request); + /** * List the applications logs. Uses Doppler under the hood. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 1121e739fc..880bcdc339 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -158,6 +158,8 @@ import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.logcache.v1.ReadResponse; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; +import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -248,7 +250,7 @@ public DefaultApplications( this.spaceId = spaceId; } -@Override + @Override public Mono copySource(CopySourceApplicationRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( @@ -541,8 +543,9 @@ public Flux listTasks(ListApplicationTasksRequest request) { .checkpoint(); } + @Deprecated @Override - public Flux logs(LogsRequest request) { + public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) .flatMap( function( @@ -551,107 +554,37 @@ public Flux logs(LogsRequest request) { cloudFoundryClient, request.getName(), spaceId))) .flatMapMany( applicationId -> - getRecentLogs(this.logCacheClient, applicationId)) + getLogs(this.dopplerClient, applicationId, request.getRecent())) .transform(OperationsLogging.log("Get Application Logs")) .checkpoint(); } @Override - public Flux logs(ApplicationLogsRequest request) { - return logs(LogsRequest.builder() - .name(request.getName()) - .recent(request.getRecent()) - .build()) - .map( - logMessage -> - ApplicationLog.builder() - .sourceId(logMessage.getApplicationId()) - .sourceType(logMessage.getSourceType()) - .instanceId(logMessage.getSourceInstance()) - .message(logMessage.getMessage()) - .timestamp(logMessage.getTimestamp()) - .logType( - ApplicationLogType.from( - logMessage.getMessageType().name())) - .build()); - } - - @SuppressWarnings("deprecation") - @Test - void logsRecent_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); - this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) - .as(StepVerifier::create) - .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @SuppressWarnings("deprecation") - @Test - void logsNoApp_doppler() { - requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); - - this.applications - .verify(Duration.ofSeconds(5)); - } - - @SuppressWarnings("deprecation") - @Test - void logs_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) - .as(StepVerifier::create) - .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } - - @Test - void logsRecent_LogCache() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); - this.applications - .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(Log.builder()).type(LogType.OUT).build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); + public Flux logsRecent(ReadRequest request) { + return getRecentLogsLogCache(this.logCacheClient, request) + .transform(OperationsLogging.log("Get Application Logs")) + .checkpoint(); } - @SuppressWarnings("deprecation") - @Test - void logsRecentNotSet_doppler() { - requestApplications( - this.cloudFoundryClient, - "test-application-name", - TEST_SPACE_ID, - "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); - - this.applications - .logs(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) - .expectComplete() - .verify(Duration.ofSeconds(5)); - } +// @Override +// public Flux logs(ApplicationLogsRequest request) { +// return logs(LogsRequest.builder() +// .name(request.getName()) +// .recent(request.getRecent()) +// .build()) +// .map( +// logMessage -> +// ApplicationLog.builder() +// .sourceId(logMessage.getApplicationId()) +// .sourceType(logMessage.getSourceType()) +// .instanceId(logMessage.getSourceInstance()) +// .message(logMessage.getMessage()) +// .timestamp(logMessage.getTimestamp()) +// .logType( +// ApplicationLogType.from( +// logMessage.getMessageType().name())) +// .build()); +// } @Override @SuppressWarnings("deprecation") @@ -1705,8 +1638,13 @@ private static Flux getLogs( } } - private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { - return requestLogsRecentLogCache(logCacheClient, applicationId) + private static Flux getRecentLogsLogCache( + Mono logCacheClient, ReadRequest readRequest) { + return requestLogsRecentLogCache(logCacheClient, readRequest) + .map(EnvelopeBatch::getBatch) + .map(List::stream) + .flatMapIterable(envelopeStream -> envelopeStream.collect(Collectors.toList())) + .filter(e -> e.getLog() != null) .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) .map(org.cloudfoundry.logcache.v1.Envelope::getLog); } @@ -2646,6 +2584,7 @@ private static Flux requestListTasks( .build())); } + @Deprecated private static Flux requestLogsRecent( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2654,30 +2593,12 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } - private static Flux requestLogsRecentLogCache( - Mono logCacheClient, String applicationId) { - return logCacheClient.flatMapMany( + private static Mono requestLogsRecentLogCache( + Mono logCacheClient, ReadRequest readRequest) { + return logCacheClient.flatMap( client -> - client.recentLogs( - ReadRequest.builder() - .sourceId(applicationId) - .envelopeType(EnvelopeType.LOG) - .limit(100) - .build() - ) - .flatMap( - response -> - Mono.justOrEmpty( - response.getEnvelopes().getBatch().stream().findFirst() - ) - ) - .repeatWhenEmpty( - exponentialBackOff( - Duration.ofSeconds(1), - Duration.ofSeconds(5), - Duration.ofMinutes(1)) - ) - ); + client.recentLogs(readRequest) + .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))); } private static Flux requestLogsStream( From 8a8cdeefa60dc0f62e50adec510230721093c65d Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 08:52:07 +0100 Subject: [PATCH 09/17] fix: Adjust the code and tests --- .../operations/applications/Applications.java | 10 +- .../applications/DefaultApplications.java | 92 ++++-------------- .../applications/DefaultApplicationsTest.java | 96 +++++++++---------- 3 files changed, 69 insertions(+), 129 deletions(-) diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index b484e7f586..38ffb6251e 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -117,14 +117,6 @@ public interface Applications { */ Flux listTasks(ListApplicationTasksRequest request); - /** - * List the applications logs from dopplerClient - * @deprecated Only for compatibility. Switch to logCacheClient method below. - * @param request the application logs request - * @return the applications logs - */ - Flux logs(LogsRequest request); - /** * List the applications logs. Uses Doppler under the hood. * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} @@ -135,7 +127,7 @@ public interface Applications { * @deprecated Use {@link #logs(ApplicationLogsRequest)} instead. */ @Deprecated - Flux logs(LogsRequest request); + Flux logs(LogsRequest request); /** * List the applications logs from logCacheClient. diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 880bcdc339..22a923607d 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -154,12 +154,11 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.logcache.v1.EnvelopeBatch; -import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -566,25 +565,25 @@ public Flux logsRecent(ReadRequest request) { .checkpoint(); } -// @Override -// public Flux logs(ApplicationLogsRequest request) { -// return logs(LogsRequest.builder() -// .name(request.getName()) -// .recent(request.getRecent()) -// .build()) -// .map( -// logMessage -> -// ApplicationLog.builder() -// .sourceId(logMessage.getApplicationId()) -// .sourceType(logMessage.getSourceType()) -// .instanceId(logMessage.getSourceInstance()) -// .message(logMessage.getMessage()) -// .timestamp(logMessage.getTimestamp()) -// .logType( -// ApplicationLogType.from( -// logMessage.getMessageType().name())) -// .build()); -// } + @Override + public Flux logs(ApplicationLogsRequest request) { + return logs(LogsRequest.builder() + .name(request.getName()) + .recent(request.getRecent()) + .build()) + .map( + logMessage -> + ApplicationLog.builder() + .sourceId(logMessage.getApplicationId()) + .sourceType(logMessage.getSourceType()) + .instanceId(logMessage.getSourceInstance()) + .message(logMessage.getMessage()) + .timestamp(logMessage.getTimestamp()) + .logType( + ApplicationLogType.from( + logMessage.getMessageType().name())) + .build()); + } @Override @SuppressWarnings("deprecation") @@ -2445,57 +2444,6 @@ private static Mono requestGetApplication( .cast(AbstractApplicationResource.class); } - private static void requestInstancesApplicationFailing( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() - .applicationId(applicationId) - .build())) - .thenReturn( - Mono.just( - fill( - ApplicationInstancesResponse.builder(), - "application-instances-") - .instance( - "instance-0", - fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") - .state("FAILED") - .build()) - .build())); - } - - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static Flux requestListDomains( CloudFoundryClient cloudFoundryClient, String organizationId) { return PaginationUtils.requestClientV3Resources( diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 08effafac7..5594830836 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5054,44 +5054,72 @@ private static void requestGetApplicationFailing( .build())); } - private static void requestInstancesApplicationFailing( + private static void requestGetApplicationTimeout( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() .applicationId(applicationId) .build())) + .thenReturn( + Mono.just( + fill(GetApplicationResponse.builder()) + .entity( + fill(ApplicationEntity.builder()) + .packageState("STAGING") + .build()) + .build())); + } + + private static void requestInstancesApplicationFailing( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill( - ApplicationInstancesResponse.builder(), - "application-instances-") + ApplicationInstancesResponse.builder(), + "application-instances-") .instance( "instance-0", fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") + ApplicationInstanceInfo.builder(), + "application-instance-info-") .state("FAILED") .build()) .build())); } - private static void requestGetApplicationTimeout( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String applicationName, String payload) { + when(logCacheClient.recentLogs(any())) .thenReturn( Mono.just( - fill(GetApplicationResponse.builder()) - .entity( - fill(ApplicationEntity.builder()) - .packageState("STAGING") + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(org.cloudfoundry + .logcache.v1 + .Envelope + .builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) .build()) .build())); } @@ -5342,34 +5370,6 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( From 5e0d91834db64594e3dc93d0186cedbdfc885091 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 11:55:22 +0100 Subject: [PATCH 10/17] feat: Adjust the integrationtests --- .../java/org/cloudfoundry/operations/ApplicationsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 19d784cb16..e3cc443c50 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -529,14 +529,14 @@ public void logs() throws IOException { this.cloudFoundryOperations .applications() .logs( - LogsRequest.builder() + ApplicationLogsRequest.builder() .name(applicationName) .recent(true) .build())) - .map(LogMessage::getMessageType) + .map(ApplicationLog::getLogType) .next() .as(StepVerifier::create) - .expectNext(MessageType.OUT) + .expectNext(ApplicationLogType.OUT) .expectComplete() .verify(Duration.ofMinutes(5)); } From b39e6476bce144e2b7569df22b1e08e05fbaa399 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Thu, 26 Feb 2026 15:05:21 +0100 Subject: [PATCH 11/17] fix: Adjust the lint issues --- .../ReactorServiceInstancesV3Test.java | 1 - .../cloudfoundry/doppler/DopplerClient.java | 2 +- .../operations/applications/Applications.java | 1 - .../applications/DefaultApplications.java | 10 ++++---- .../applications/DefaultApplicationsTest.java | 24 +++++++++---------- .../operations/ApplicationsTest.java | 12 +++++----- 6 files changed, 24 insertions(+), 26 deletions(-) diff --git a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java index 1745aa7285..1e6df1f258 100644 --- a/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java +++ b/cloudfoundry-client-reactor/src/test/java/org/cloudfoundry/reactor/client/v3/serviceinstances/ReactorServiceInstancesV3Test.java @@ -62,7 +62,6 @@ import org.cloudfoundry.reactor.TestRequest; import org.cloudfoundry.reactor.TestResponse; import org.cloudfoundry.reactor.client.AbstractClientApiTest; -import org.cloudfoundry.reactor.client.v3.serviceinstances.ReactorServiceInstancesV3; import org.junit.jupiter.api.Test; import reactor.test.StepVerifier; diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index 4e2c869b32..44d5306036 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,7 +39,7 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); - //TODO Adapt the message + // TODO Adapt the message /** * Makes the Recent Logs request * diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java index 38ffb6251e..53317ccb63 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java @@ -19,7 +19,6 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.ReadRequest; - import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; diff --git a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java index 22a923607d..2f1bdef41a 100644 --- a/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java +++ b/cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java @@ -40,7 +40,6 @@ import java.util.function.Predicate; import java.util.function.UnaryOperator; import java.util.stream.Collectors; - import org.cloudfoundry.client.CloudFoundryClient; import org.cloudfoundry.client.v2.OrderDirection; import org.cloudfoundry.client.v2.applications.AbstractApplicationResource; @@ -150,15 +149,15 @@ import org.cloudfoundry.client.v3.tasks.CreateTaskResponse; import org.cloudfoundry.client.v3.tasks.TaskResource; import org.cloudfoundry.doppler.DopplerClient; +import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.EventType; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; -import org.cloudfoundry.doppler.Envelope; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; import org.cloudfoundry.logcache.v1.ReadRequest; -import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -205,8 +204,9 @@ public final class DefaultApplications implements Applications { private static final Comparator LOG_MESSAGE_COMPARATOR = Comparator.comparing(LogMessage::getTimestamp); - private static final Comparator LOG_MESSAGE_COMPARATOR_LOG_CACHE = - Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); + private static final Comparator + LOG_MESSAGE_COMPARATOR_LOG_CACHE = + Comparator.comparing(org.cloudfoundry.logcache.v1.Envelope::getTimestamp); private static final Duration LOG_MESSAGE_TIMESPAN = Duration.ofMillis(500); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 5594830836..5695108af7 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5076,21 +5076,21 @@ private static void requestGetApplicationTimeout( private static void requestInstancesApplicationFailing( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient - .applicationsV2() - .instances( - ApplicationInstancesRequest.builder() - .applicationId(applicationId) - .build())) + .applicationsV2() + .instances( + ApplicationInstancesRequest.builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill( - ApplicationInstancesResponse.builder(), - "application-instances-") + ApplicationInstancesResponse.builder(), + "application-instances-") .instance( "instance-0", fill( - ApplicationInstanceInfo.builder(), - "application-instance-info-") + ApplicationInstanceInfo.builder(), + "application-instance-info-") .state("FAILED") .build()) .build())); @@ -5107,9 +5107,9 @@ private static void requestLogsRecentLogCache( .batch( Arrays.asList( fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) + .logcache.v1 + .Envelope + .builder()) .log( Log .builder() diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index e3cc443c50..832afe3c07 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -547,18 +547,18 @@ public void logsRecent() throws IOException { Mono applicationGuid = getAppGuidFromAppName(cloudFoundryOperations, applicationName); createApplication( - this.cloudFoundryOperations, - new ClassPathResource("test-application.zip").getFile().toPath(), - applicationName, - false) + this.cloudFoundryOperations, + new ClassPathResource("test-application.zip").getFile().toPath(), + applicationName, + false) .then( applicationGuid .map(ApplicationsTest::getReadRequest) .flatMapMany( readRequest -> callLogsRecent( - this.cloudFoundryOperations, - readRequest) + this.cloudFoundryOperations, + readRequest) .log(null, Level.ALL, SignalType.ON_NEXT)) .map(ApplicationsTest::checkOneLogEntry) .then()) From 78d06abb1c7caa73662ae7965fb79687d72d8835 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 08:17:44 +0100 Subject: [PATCH 12/17] feat: Sanitize the code --- .../logcache/v1/ReactorLogCacheEndpoints.java | 2 +- .../cloudfoundry/doppler/DopplerClient.java | 9 ++- .../applications/DefaultApplicationsTest.java | 61 +++++++++---------- 3 files changed, 36 insertions(+), 36 deletions(-) diff --git a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java index f0b610d0c3..3d1623cef3 100644 --- a/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java +++ b/cloudfoundry-client-reactor/src/main/java/org/cloudfoundry/reactor/logcache/v1/ReactorLogCacheEndpoints.java @@ -50,6 +50,6 @@ Mono read(ReadRequest request) { } Mono recentLogs(ReadRequest request) { - return get(request, ReadResponse.class, "read", request.getSourceId()).checkpoint(); + return read(request); } } diff --git a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java index 44d5306036..74d23b1488 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -39,13 +39,16 @@ public interface DopplerClient { */ Flux firehose(FirehoseRequest request); - // TODO Adapt the message /** * Makes the Recent Logs request * - * @deprecated Do not use this type directly, it exists only for the Jackson-binding infrastructure + * @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest)} instead. + * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} and {@code TAS < 4.0}. * @param request the Recent Logs request - * @return the events from the recent logs + * @return a flux of events from the recent logs + * @see Loggregator + * @see Log Cache + * @see org.cloudfoundry.logcache.v1.LogCacheClient#recentLogs(org.cloudfoundry.logcache.v1.ReadRequest) */ @Deprecated Flux recentLogs(RecentLogsRequest request); diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 5695108af7..657fb7d561 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -20,7 +20,6 @@ import static org.cloudfoundry.client.v3.LifecycleType.BUILDPACK; import static org.cloudfoundry.client.v3.LifecycleType.DOCKER; import static org.cloudfoundry.operations.TestObjects.fill; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.RETURNS_SMART_NULLS; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -144,6 +143,7 @@ import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.RecentLogsRequest; import org.cloudfoundry.doppler.StreamRequest; +import org.cloudfoundry.logcache.v1.Envelope; import org.cloudfoundry.logcache.v1.EnvelopeBatch; import org.cloudfoundry.logcache.v1.Log; import org.cloudfoundry.logcache.v1.LogCacheClient; @@ -1315,7 +1315,7 @@ void listTasks() { @SuppressWarnings("deprecation") @Test - void logsRecent_doppler() { + void logsRecentDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1332,7 +1332,7 @@ void logsRecent_doppler() { @SuppressWarnings("deprecation") @Test - void logsNoApp_doppler() { + void logsNoAppDoppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1349,7 +1349,7 @@ void logsNoApp_doppler() { @SuppressWarnings("deprecation") @Test - void logs_doppler() { + void logsDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1365,7 +1365,7 @@ void logs_doppler() { } @Test - void logsRecent_LogCache() { + void logsRecentLogCache() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1373,7 +1373,7 @@ void logsRecent_LogCache() { "test-metadata-id"); requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); this.applications - .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) + .logsRecent(ReadRequest.builder().sourceId("test-metadata-id").build()) .as(StepVerifier::create) .expectNext(fill(Log.builder()).type(LogType.OUT).build()) .expectComplete() @@ -1382,7 +1382,7 @@ void logsRecent_LogCache() { @SuppressWarnings("deprecation") @Test - void logsRecentNotSet_doppler() { + void logsRecentNotSetDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -5054,25 +5054,6 @@ private static void requestGetApplicationFailing( .build())); } - private static void requestGetApplicationTimeout( - CloudFoundryClient cloudFoundryClient, String applicationId) { - when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) - .thenReturn( - Mono.just( - fill(GetApplicationResponse.builder()) - .entity( - fill(ApplicationEntity.builder()) - .packageState("STAGING") - .build()) - .build())); - } - private static void requestInstancesApplicationFailing( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient @@ -5096,9 +5077,28 @@ private static void requestInstancesApplicationFailing( .build())); } + private static void requestGetApplicationTimeout( + CloudFoundryClient cloudFoundryClient, String applicationId) { + when(cloudFoundryClient + .applicationsV2() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() + .applicationId(applicationId) + .build())) + .thenReturn( + Mono.just( + fill(GetApplicationResponse.builder()) + .entity( + fill(ApplicationEntity.builder()) + .packageState("STAGING") + .build()) + .build())); + } + private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String applicationName, String payload) { - when(logCacheClient.recentLogs(any())) + LogCacheClient logCacheClient, String sourceId, String payload) { + when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) .thenReturn( Mono.just( fill(ReadResponse.builder()) @@ -5106,10 +5106,7 @@ private static void requestLogsRecentLogCache( fill(EnvelopeBatch.builder()) .batch( Arrays.asList( - fill(org.cloudfoundry - .logcache.v1 - .Envelope - .builder()) + fill(Envelope.builder()) .log( Log .builder() From 5e78e832bcfd2d4f8c7d07ee056e21018b44c3ea Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 08:22:10 +0100 Subject: [PATCH 13/17] docs: Add deprecation notes --- README.md | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/README.md b/README.md index 6f6e77f4ae..617d1e1b9c 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,41 @@ The `cf-java-client` project is a Java language binding for interacting with a C ## Versions The Cloud Foundry Java Client has two active versions. The `5.x` line is compatible with Spring Boot `2.4.x - 2.6.x` just to manage its dependencies, while the `4.x` line uses Spring Boot `2.3.x`. +## Deprecations + +### `DopplerClient.recentLogs()` — Recent Logs via Doppler + +> [!WARNING] +> **Deprecated since cf-java-client `5.17.x`** +> +> The `DopplerClient.recentLogs()` endpoint (and the related `RecentLogsRequest` / `LogMessage` types from the `org.cloudfoundry.doppler` package) are **deprecated** and will be removed in a future release. +> +> This API relies on the [Loggregator][loggregator] Doppler/Traffic Controller endpoint `/apps/{id}/recentlogs`, which was removed in **Loggregator ≥ 107.0**. +> The affected platform versions are: +> +> | Platform | Last version with Doppler recent-logs support | +> | -------- | --------------------------------------------- | +> | CF Deployment (CFD) | `< 24.3` | +> | Tanzu Application Service (TAS) | `< 4.0` | +> +> **Migration:** Replace any call to `DopplerClient.recentLogs()` with [`LogCacheClient.read()`][log-cache-api] (available via `org.cloudfoundry.logcache.v1.LogCacheClient`). +> +> ```java +> // Before (deprecated) +> dopplerClient.recentLogs(RecentLogsRequest.builder() +> .applicationId(appId) +> .build()); +> +> // After +> logCacheClient.read(ReadRequest.builder() +> .sourceId(appId) +> .envelopeTypes(EnvelopeType.LOG) +> .build()); +> ``` + +[loggregator]: https://github.com/cloudfoundry/loggregator +[log-cache-api]: https://github.com/cloudfoundry/log-cache + ## Dependencies Most projects will need two dependencies; the Operations API and an implementation of the Client API. For Maven, the dependencies would be defined like this: @@ -76,6 +111,9 @@ Both the `cloudfoundry-operations` and `cloudfoundry-client` projects follow a [ ### `CloudFoundryClient`, `DopplerClient`, `UaaClient` Builders +> [!NOTE] +> **`DopplerClient` — partial deprecation:** The `recentLogs()` method on `DopplerClient` is deprecated and only works against Loggregator \< 107.0 (CFD \< 24.3 / TAS \< 4.0). See the [Deprecations](#deprecations) section above for the migration path to `LogCacheClient`. + The lowest-level building blocks of the API are `ConnectionContext` and `TokenProvider`. These types are intended to be shared between instances of the clients, and come with out of the box implementations. To instantiate them, you configure them with builders: ```java From 06d11f69f1f469309426ebe1a8fd020908b02a82 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Thu, 26 Feb 2026 11:40:17 +0100 Subject: [PATCH 14/17] feat: Update the tests --- .../test/java/org/cloudfoundry/operations/ApplicationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 832afe3c07..1fdbd023ab 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -2205,7 +2205,7 @@ private static Mono getAppGuidFromAppName( } private static Log checkOneLogEntry(Log log) { - assertThat(log.getType().equals(LogType.OUT)); + assertThat(log.getType()).isEqualTo(LogType.OUT); OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); return log; } From 4ab560d4dde43289f510d76aaf82db3ebfc8aa38 Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Fri, 27 Feb 2026 11:44:36 +0100 Subject: [PATCH 15/17] STDERR logs should not let test fail --- .../test/java/org/cloudfoundry/operations/ApplicationsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java index 1fdbd023ab..03903e028f 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -2205,8 +2205,8 @@ private static Mono getAppGuidFromAppName( } private static Log checkOneLogEntry(Log log) { - assertThat(log.getType()).isEqualTo(LogType.OUT); OperationsLogging.log("one log entry: " + log.getType() + " " + log.getPayloadAsText()); + assertThat(log.getType()).isIn(LogType.OUT, LogType.ERR); return log; } } From 7e7174beb583f5137d36595aeee6b62e3efb8e15 Mon Sep 17 00:00:00 2001 From: Pascal Zimmermann Date: Tue, 3 Mar 2026 09:24:02 +0100 Subject: [PATCH 16/17] fix: Adjust the lint issues --- .../applications/DefaultApplicationsTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index 657fb7d561..d189b2c0fe 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -5080,12 +5080,12 @@ private static void requestInstancesApplicationFailing( private static void requestGetApplicationTimeout( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient - .applicationsV2() - .get( - org.cloudfoundry.client.v2.applications.GetApplicationRequest - .builder() - .applicationId(applicationId) - .build())) + .applicationsV2() + .get( + org.cloudfoundry.client.v2.applications.GetApplicationRequest + .builder() + .applicationId(applicationId) + .build())) .thenReturn( Mono.just( fill(GetApplicationResponse.builder()) From b236ad3d311c551d8447de4c6b31010c35139cda Mon Sep 17 00:00:00 2001 From: Joris Baum Date: Thu, 26 Feb 2026 11:40:17 +0100 Subject: [PATCH 17/17] Revert some unnecessary style changes --- .../applications/DefaultApplicationsTest.java | 77 ++++++++++--------- 1 file changed, 40 insertions(+), 37 deletions(-) diff --git a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java index d189b2c0fe..6fab68c50c 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/applications/DefaultApplicationsTest.java @@ -1315,15 +1315,16 @@ void listTasks() { @SuppressWarnings("deprecation") @Test - void logsRecentDoppler() { + void logsDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsRecent(this.dopplerClient, "test-metadata-id"); + requestLogsStream(this.dopplerClient, "test-metadata-id"); + this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() @@ -1349,15 +1350,16 @@ void logsNoAppDoppler() { @SuppressWarnings("deprecation") @Test - void logsDoppler() { + void logsRecentDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID, "test-metadata-id"); - requestLogsStream(this.dopplerClient, "test-metadata-id"); + requestLogsRecent(this.dopplerClient, "test-metadata-id"); + this.applications - .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) + .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() @@ -1372,6 +1374,7 @@ void logsRecentLogCache() { TEST_SPACE_ID, "test-metadata-id"); requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id", "test-payload"); + this.applications .logsRecent(ReadRequest.builder().sourceId("test-metadata-id").build()) .as(StepVerifier::create) @@ -5096,31 +5099,6 @@ private static void requestGetApplicationTimeout( .build())); } - private static void requestLogsRecentLogCache( - LogCacheClient logCacheClient, String sourceId, String payload) { - when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) - .thenReturn( - Mono.just( - fill(ReadResponse.builder()) - .envelopes( - fill(EnvelopeBatch.builder()) - .batch( - Arrays.asList( - fill(Envelope.builder()) - .log( - Log - .builder() - .payload( - payload) - .type( - LogType - .OUT) - .build()) - .build())) - .build()) - .build())); - } - private static void requestGetApplicationV3Buildpack( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient @@ -5367,8 +5345,10 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) + @SuppressWarnings("deprecation") + private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.recentLogs( + RecentLogsRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( org.cloudfoundry.doppler.Envelope.builder() @@ -5379,10 +5359,33 @@ private static void requestLogsStream(DopplerClient dopplerClient, String applic .build())); } - @SuppressWarnings("deprecation") - private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.recentLogs( - RecentLogsRequest.builder().applicationId(applicationId).build())) + private static void requestLogsRecentLogCache( + LogCacheClient logCacheClient, String sourceId, String payload) { + when(logCacheClient.recentLogs(ReadRequest.builder().sourceId(sourceId).build())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(Envelope.builder()) + .log( + Log + .builder() + .payload( + payload) + .type( + LogType + .OUT) + .build()) + .build())) + .build()) + .build())); + } + + private static void requestLogsStream(DopplerClient dopplerClient, String applicationId) { + when(dopplerClient.stream(StreamRequest.builder().applicationId(applicationId).build())) .thenReturn( Flux.just( org.cloudfoundry.doppler.Envelope.builder()