diff --git a/README.md b/README.md index ffed2f36f4..058431f921 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,44 @@ 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()); +> ``` + +> [!NOTE] +> **Operations API users:** `Applications.logs(ApplicationLogsRequest)` now uses Log Cache under the hood for recent logs (the default). No migration is needed at the Operations layer. + +[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 +114,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 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..3d61922734 100644 --- a/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java +++ b/cloudfoundry-client/src/main/java/org/cloudfoundry/doppler/DopplerClient.java @@ -42,9 +42,15 @@ public interface DopplerClient { /** * Makes the Recent Logs request * + * @deprecated Use {@link org.cloudfoundry.logcache.v1.LogCacheClient#read(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#read(org.cloudfoundry.logcache.v1.ReadRequest) */ + @Deprecated Flux recentLogs(RecentLogsRequest request); /** 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 299b4bf5e4..62e442a53d 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 @@ -185,6 +186,19 @@ 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()) + .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..56aba6af64 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 @@ -128,8 +128,9 @@ public interface Applications { /** * List the applications logs. - * Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3} - * and {@code TAS < 4.0}. + * Uses Log Cache under the hood when {@link ApplicationLogsRequest#getRecent()} is {@code true}. + * Log streaming still uses Doppler, which is not available in CF deployments following + * shared-nothing architecture. * * @param request the application logs request * @return 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 e51ddbb472..bb492fffe7 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,6 +154,9 @@ 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.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 +203,10 @@ 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; @@ -214,6 +221,8 @@ public final class DefaultApplications implements Applications { private final Mono dopplerClient; + private final Mono logCacheClient; + private final RandomWords randomWords; private final Mono spaceId; @@ -221,17 +230,20 @@ 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; } @@ -529,6 +541,7 @@ public Flux listTasks(ListApplicationTasksRequest request) { .checkpoint(); } + @Deprecated @Override public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) @@ -546,22 +559,23 @@ public Flux logs(LogsRequest request) { @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()); + if (request.getRecent() == null || request.getRecent()) { + return Mono.zip(this.cloudFoundryClient, this.spaceId) + .flatMap( + function( + (cloudFoundryClient, spaceId) -> + getApplicationId( + cloudFoundryClient, + request.getName(), + spaceId))) + .flatMapMany( + applicationId -> getLogsLogCache(this.logCacheClient, applicationId)) + .transform(OperationsLogging.log("Get Application Logs")) + .checkpoint(); + } else { + return logs(LogsRequest.builder().name(request.getName()).recent(false).build()) + .map(DefaultApplications::toApplicationLog); + } } @Override @@ -673,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( @@ -1617,6 +1630,32 @@ private static Flux getLogs( } } + private static Flux getLogsLogCache( + Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .filter(e -> e.getLog() != null) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .map( + envelope -> + ApplicationLog.builder() + .sourceId( + Optional.ofNullable(envelope.getSourceId()) + .orElse("")) + .sourceType( + envelope.getTags().getOrDefault("source_type", "")) + .instanceId( + Optional.ofNullable(envelope.getInstanceId()) + .orElse("")) + .message(envelope.getLog().getPayloadAsText()) + .timestamp( + Optional.ofNullable(envelope.getTimestamp()) + .orElse(0L)) + .logType( + ApplicationLogType.from( + envelope.getLog().getType().name())) + .build()); + } + @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = @@ -2501,6 +2540,7 @@ private static Flux requestListTasks( .build())); } + @Deprecated private static Flux requestLogsRecent( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2509,6 +2549,16 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } + private static Flux requestLogsRecentLogCache( + Mono logCacheClient, String applicationId) { + return logCacheClient + .flatMap( + client -> + client.read(ReadRequest.builder().sourceId(applicationId).build())) + .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes())) + .flatMapIterable(EnvelopeBatch::getBatch); + } + private static Flux requestLogsStream( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2914,6 +2964,17 @@ private static Mono stopApplicationIfNotStopped( : Mono.just(resource); } + private static ApplicationLog toApplicationLog(LogMessage logMessage) { + return ApplicationLog.builder() + .sourceId(logMessage.getApplicationId()) + .sourceType(logMessage.getSourceType()) + .instanceId(logMessage.getSourceInstance()) + .message(logMessage.getMessage()) + .timestamp(logMessage.getTimestamp()) + .logType(ApplicationLogType.from(logMessage.getMessageType().name())) + .build(); + } + private static ApplicationDetail toApplicationDetail( List buildpacks, SummaryApplicationResponse summaryApplicationResponse, 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 d864ed497e..5a0854f48b 100644 --- a/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java +++ b/cloudfoundry-operations/src/test/java/org/cloudfoundry/operations/AbstractOperationsTest.java @@ -51,6 +51,7 @@ import org.cloudfoundry.client.v3.spaces.SpacesV3; 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; @@ -101,6 +102,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..5d21f8e584 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 @@ -25,9 +25,11 @@ import static org.mockito.Mockito.when; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.nio.file.Path; import java.time.Duration; import java.util.Arrays; +import java.util.Base64; import java.util.Collection; import java.util.Collections; import java.util.Date; @@ -139,11 +141,17 @@ 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.Envelope; +import org.cloudfoundry.logcache.v1.EnvelopeBatch; +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; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.FluentMap; @@ -163,6 +171,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)); @@ -1306,8 +1315,9 @@ void listTasks() { .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logs() { + void logsDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1318,13 +1328,14 @@ void logs() { this.applications .logs(LogsRequest.builder().name("test-application-name").recent(false).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsNoApp() { + void logsNoAppDoppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1339,8 +1350,9 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsRecent() { + void logsRecentDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1351,13 +1363,38 @@ void logsRecent() { this.applications .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNextMatches(log -> log.getMessage().equals("test-log-message-message")) .expectComplete() .verify(Duration.ofSeconds(5)); } @Test - void logsRecentNotSet() { + void logsLogCache() { + requestApplications( + this.cloudFoundryClient, + "test-application-name", + TEST_SPACE_ID, + "test-metadata-id"); + requestLogsRecentLogCache(this.logCacheClient, "test-metadata-id"); + + this.applications + .logs(ApplicationLogsRequest.builder().name("test-application-name").build()) + .as(StepVerifier::create) + .expectNextMatches( + log -> + log.getMessage().equals("test-payload") + && log.getLogType() == ApplicationLogType.OUT + && log.getSourceId().equals("test-sourceId") + && log.getInstanceId().equals("test-instanceId") + && log.getSourceType().equals("APP/PROC/WEB") + && log.getTimestamp() == 1L) + .expectComplete() + .verify(Duration.ofSeconds(5)); + } + + @SuppressWarnings("deprecation") + @Test + void logsRecentNotSetDoppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -5317,12 +5354,13 @@ private static void requestListTasksEmpty( .build())); } + @SuppressWarnings("deprecation") private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { when(dopplerClient.recentLogs( RecentLogsRequest.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()) @@ -5330,11 +5368,42 @@ private static void requestLogsRecent(DopplerClient dopplerClient, String applic .build())); } + private static void requestLogsRecentLogCache(LogCacheClient logCacheClient, String sourceId) { + String base64Payload = + Base64.getEncoder().encodeToString("test-payload".getBytes(StandardCharsets.UTF_8)); + when(logCacheClient.read(ReadRequest.builder().sourceId(sourceId).build())) + .thenReturn( + Mono.just( + fill(ReadResponse.builder()) + .envelopes( + fill(EnvelopeBatch.builder()) + .batch( + Arrays.asList( + fill(Envelope.builder()) + .tags( + Collections + .singletonMap( + "source_type", + "APP/PROC/WEB")) + .log( + Log + .builder() + .payload( + base64Payload) + .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()) diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index c0b3f44b30..da390ac204 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; @@ -266,6 +267,7 @@ ReactorCloudFoundryClient cloudFoundryClient( DefaultCloudFoundryOperations cloudFoundryOperations( CloudFoundryClient cloudFoundryClient, DopplerClient dopplerClient, + LogCacheClient logCacheClient, NetworkingClient networkingClient, RoutingClient routingClient, UaaClient uaaClient, @@ -274,6 +276,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 36e1bd9456..37c701dbbe 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -30,14 +30,6 @@ import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.client.CloudFoundryClient; -import org.cloudfoundry.logcache.v1.Envelope; -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.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationEnvironments; import org.cloudfoundry.operations.applications.ApplicationEvent; @@ -108,7 +100,6 @@ public final class ApplicationsTest extends AbstractIntegrationTest { @Autowired private String serviceName; - @Autowired private LogCacheClient logCacheClient; @Autowired private CloudFoundryClient cloudFoundryClient; // To create a service in #pushBindService, the Service Broker must be installed first. @@ -508,11 +499,13 @@ 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. + * Exercise the LogCache client via {@code logs(ApplicationLogsRequest)}. + * LogCache has been a default cf-deployment component since v3.0.0 (July 2018), + * with the {@code /api/v1/read} endpoint available since log-cache-release v2.0.0 + * (October 2018). */ @Test - @IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2) + @IfCloudFoundryVersion(greaterThanOrEqualTo = CloudFoundryVersion.PCF_2_3) public void logs() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -537,45 +530,6 @@ public void logs() throws IOException { .verify(Duration.ofMinutes(5)); } - /** - * Exercise the LogCache client. Serves as a reference for using the logcache client, - * and will help with the transition to the new - * {@link org.cloudfoundry.operations.applications.Applications#logs(ApplicationLogsRequest)}. - */ - @Test - public void logCacheLogs() throws IOException { - String applicationName = this.nameFactory.getApplicationName(); - - createApplication( - this.cloudFoundryOperations, - new ClassPathResource("test-application.zip").getFile().toPath(), - applicationName, - false) - .then( - this.cloudFoundryOperations - .applications() - .get(GetApplicationRequest.builder().name(applicationName).build())) - .map(ApplicationDetail::getId) - .flatMapMany( - appGuid -> - this.logCacheClient.read( - ReadRequest.builder() - .sourceId(appGuid) - .envelopeType(EnvelopeType.LOG) - .limit(1) - .build())) - .map(ReadResponse::getEnvelopes) - .map(EnvelopeBatch::getBatch) - .flatMap(Flux::fromIterable) - .map(Envelope::getLog) - .map(Log::getType) - .next() - .as(StepVerifier::create) - .expectNext(LogType.OUT) - .expectComplete() - .verify(Duration.ofMinutes(5)); - } - @Test public void pushBindServices() throws IOException { String applicationName = this.nameFactory.getApplicationName();