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 68ce71f4af..4aa03facdd 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 */ 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-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/_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 27e6c4ff4b..a9aaef1c0a 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,8 @@ package org.cloudfoundry.operations.applications; 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; @@ -115,13 +117,22 @@ public interface Applications { Flux listTasks(ListApplicationTasksRequest request); /** - * List the applications logs - * + * 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 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); + /** * Push a specific application * 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 03ddf9527c..1c9a82779a 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 @@ -125,8 +125,6 @@ import org.cloudfoundry.client.v3.builds.CreateBuildResponse; import org.cloudfoundry.client.v3.builds.GetBuildRequest; import org.cloudfoundry.client.v3.builds.GetBuildResponse; -import org.cloudfoundry.client.v3.domains.DomainResource; -import org.cloudfoundry.client.v3.domains.ListDomainsRequest; import org.cloudfoundry.client.v3.packages.BitsData; import org.cloudfoundry.client.v3.packages.CreatePackageRequest; import org.cloudfoundry.client.v3.packages.CreatePackageResponse; @@ -154,6 +152,10 @@ 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.Log; +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; @@ -198,6 +200,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; @@ -212,24 +218,37 @@ public final class DefaultApplications implements Applications { private final Mono dopplerClient; + private final Mono logCacheClient; + private final RandomWords randomWords; private final Mono spaceId; + @Deprecated + public DefaultApplications( + Mono cloudFoundryClient, + Mono dopplerClient, + Mono spaceId) { + this(cloudFoundryClient, dopplerClient, null, new WordListRandomWords(), spaceId); + } + 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; } @@ -527,6 +546,7 @@ public Flux listTasks(ListApplicationTasksRequest request) { .checkpoint(); } + @Deprecated @Override public Flux logs(LogsRequest request) { return Mono.zip(this.cloudFoundryClient, this.spaceId) @@ -542,6 +562,13 @@ public Flux logs(LogsRequest request) { .checkpoint(); } + @Override + public Flux logsRecent(ReadRequest request) { + return getRecentLogsLogCache(this.logCacheClient, request) + .transform(OperationsLogging.log("Get Application Logs")) + .checkpoint(); + } + @Override @SuppressWarnings("deprecation") public Mono push(PushApplicationRequest request) { @@ -651,7 +678,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( @@ -1586,6 +1612,17 @@ private static Flux getLogs( } } + 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); + } + @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = @@ -2382,15 +2419,6 @@ private static Mono requestGetApplication( .cast(AbstractApplicationResource.class); } - private static Flux requestListDomains( - CloudFoundryClient cloudFoundryClient, String organizationId) { - return PaginationUtils.requestClientV3Resources( - page -> - cloudFoundryClient - .domainsV3() - .list(ListDomainsRequest.builder().page(page).build())); - } - private static Flux requestListPrivateDomains( CloudFoundryClient cloudFoundryClient, String organizationId) { return PaginationUtils.requestClientV2Resources( @@ -2470,6 +2498,7 @@ private static Flux requestListTasks( .build())); } + @Deprecated private static Flux requestLogsRecent( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2478,6 +2507,14 @@ private static Flux requestLogsRecent( RecentLogsRequest.builder().applicationId(applicationId).build())); } + private static Mono requestLogsRecentLogCache( + Mono logCacheClient, ReadRequest readRequest) { + return logCacheClient.flatMap( + client -> + client.recentLogs(readRequest) + .flatMap(response -> Mono.justOrEmpty(response.getEnvelopes()))); + } + private static Flux requestLogsStream( Mono dopplerClient, String applicationId) { return dopplerClient.flatMapMany( @@ -2736,12 +2773,6 @@ private static Mono requestUpdateApplicationScale( builder -> builder.diskQuota(disk).instances(instances).memory(memory)); } - private static Mono requestUpdateApplicationSsh( - CloudFoundryClient cloudFoundryClient, String applicationId, Boolean enabled) { - return requestUpdateApplication( - cloudFoundryClient, applicationId, builder -> builder.enableSsh(enabled)); - } - private static Mono requestUpdateApplicationState( CloudFoundryClient cloudFoundryClient, String applicationId, String state) { return requestUpdateApplication( 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 325957e50b..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 @@ -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; @@ -139,11 +140,16 @@ 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.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 +169,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,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"); - 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) - .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 logsNoApp_doppler() { requestApplicationsEmpty(this.cloudFoundryClient, "test-application-name", TEST_SPACE_ID); this.applications @@ -1339,25 +1347,42 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsRecent() { + void logs_doppler() { 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(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 - .logs(LogsRequest.builder().name("test-application-name").recent(true).build()) + .logsRecent(ReadRequest.builder().sourceId("test-application-name").build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNext(fill(Log.builder()).type(LogType.OUT).build()) .expectComplete() .verify(Duration.ofSeconds(5)); } + @SuppressWarnings("deprecation") @Test - void logsRecentNotSet() { + void logsRecentNotSet_doppler() { requestApplications( this.cloudFoundryClient, "test-application-name", @@ -1532,6 +1557,12 @@ void pushExistingApplication() throws IOException { requestSpace(this.cloudFoundryClient, TEST_SPACE_ID, TEST_ORGANIZATION_ID); requestApplications( this.cloudFoundryClient, "test-name", TEST_SPACE_ID, "test-application-id"); + requestCreateApplication( + cloudFoundryClient, + ApplicationManifest.builder().name("test-name").build(), + TEST_SPACE_ID, + null, + "test-application-id"); requestUpdateApplication( this.cloudFoundryClient, "test-application-id", @@ -1599,6 +1630,15 @@ void pushExistingApplicationWithEnvironmentVariables() throws IOException { TEST_SPACE_ID, "test-application-id", Collections.singletonMap("test-key-1", "test-value-1")); + requestCreateApplication( + cloudFoundryClient, + ApplicationManifest.builder() + .name("test-name") + .environmentVariable("test-key-2", "test-value-2") + .build(), + TEST_SPACE_ID, + null, + "test-application-id"); requestUpdateApplication( this.cloudFoundryClient, "test-application-id", @@ -1669,6 +1709,16 @@ void pushExistingApplicationWithNullEnvironment() throws IOException { requestSpace(this.cloudFoundryClient, TEST_SPACE_ID, TEST_ORGANIZATION_ID); requestApplications( this.cloudFoundryClient, "test-name", TEST_SPACE_ID, "test-application-id", null); + requestCreateApplication( + cloudFoundryClient, + ApplicationManifest.builder() + .name("test-name") + .environmentVariable("test-key-1", "test-value-1") + .environmentVariable("test-key-2", "test-value-2") + .build(), + TEST_SPACE_ID, + null, + "test-application-id"); requestUpdateApplication( this.cloudFoundryClient, "test-application-id", @@ -1756,11 +1806,20 @@ void pushExistingRouteWithHost() throws IOException { requestSharedDomains( this.cloudFoundryClient, "test-shared-domain", "test-shared-domain-id"); requestApplicationRoutes(this.cloudFoundryClient, "test-application-id", "test-route-id"); + requestCreateRoute( + this.cloudFoundryClient, + "test-shared-domain-id", + "test-host", + null, + null, + "test-space-id", + "test-route-id"); requestRoutes( this.cloudFoundryClient, "test-shared-domain-id", "test-host", null, + null, "test-route-id"); requestListMatchingResources( this.cloudFoundryClient, @@ -1817,7 +1876,16 @@ void pushExistingRouteWithNoHost() throws IOException { requestSharedDomains( this.cloudFoundryClient, "test-shared-domain", "test-shared-domain-id"); requestApplicationRoutes(this.cloudFoundryClient, "test-application-id", "test-route-id"); - requestRoutes(this.cloudFoundryClient, "test-shared-domain-id", "", null, "test-route-id"); + requestRoutes( + this.cloudFoundryClient, "test-shared-domain-id", "", null, null, "test-route-id"); + requestCreateRoute( + this.cloudFoundryClient, + "test-shared-domain-id", + "", + null, + null, + "test-space-id", + "test-route-id"); requestListMatchingResources( this.cloudFoundryClient, Arrays.asList( @@ -2952,6 +3020,7 @@ void pushStartFailsStaging() throws IOException { requestUpdateApplicationState(this.cloudFoundryClient, "test-application-id", "STOPPED"); requestUpdateApplicationState(this.cloudFoundryClient, "test-application-id", "STARTED"); requestGetApplicationFailing(this.cloudFoundryClient, "test-application-id"); + requestInstancesApplicationFailing(this.cloudFoundryClient, "test-application-id"); StepVerifier.withVirtualTime( () -> @@ -3244,6 +3313,7 @@ void restageStagingFailure() { "test-metadata-id"); requestRestageApplication(this.cloudFoundryClient, "test-metadata-id"); requestGetApplicationFailing(this.cloudFoundryClient, "test-metadata-id"); + requestInstancesApplicationFailing(this.cloudFoundryClient, "test-metadata-id"); this.applications .restage(RestageApplicationRequest.builder().name("test-application-name").build()) @@ -3317,6 +3387,7 @@ void restageTimeout() { "test-metadata-id"); requestRestageApplication(this.cloudFoundryClient, "test-metadata-id"); requestGetApplicationTimeout(this.cloudFoundryClient, "test-metadata-id"); + requestInstancesApplicationFailing(this.cloudFoundryClient, "test-metadata-id"); this.applications .restage( @@ -4983,6 +5054,29 @@ private static void requestGetApplicationFailing( .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-") + .instance( + "instance-0", + fill( + ApplicationInstanceInfo.builder(), + "application-instance-info-") + .state("FAILED") + .build()) + .build())); + } + private static void requestGetApplicationTimeout( CloudFoundryClient cloudFoundryClient, String applicationId) { when(cloudFoundryClient @@ -5248,12 +5342,39 @@ 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 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()) @@ -5261,11 +5382,13 @@ private static void requestLogsRecent(DopplerClient dopplerClient, String applic .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( - Envelope.builder() + org.cloudfoundry.doppler.Envelope.builder() .eventType(EventType.LOG_MESSAGE) .logMessage( fill(LogMessage.builder(), "log-message-").build()) @@ -5452,11 +5575,13 @@ private static void requestRoutes( CloudFoundryClient cloudFoundryClient, String domainId, String host, + Integer port, String routePath, String routeId) { ListRoutesRequest.Builder requestBuilder = ListRoutesRequest.builder(); Optional.ofNullable(host).ifPresent(requestBuilder::host); + Optional.ofNullable(port).ifPresent(requestBuilder::port); Optional.ofNullable(routePath).ifPresent(requestBuilder::path); when(cloudFoundryClient.routes().list(requestBuilder.domainId(domainId).page(1).build())) @@ -5472,6 +5597,7 @@ private static void requestRoutes( .entity( RouteEntity.builder() .host(host) + .port(port) .path( routePath == null ? "" diff --git a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java index 5aed533645..78ad9d57d2 100644 --- a/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java +++ b/integration-test/src/test/java/org/cloudfoundry/IntegrationTestConfiguration.java @@ -46,6 +46,7 @@ import org.cloudfoundry.client.v2.stacks.ListStacksRequest; 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; @@ -254,6 +255,7 @@ ReactorCloudFoundryClient cloudFoundryClient( DefaultCloudFoundryOperations cloudFoundryOperations( CloudFoundryClient cloudFoundryClient, DopplerClient dopplerClient, + LogCacheClient logCacheClient, NetworkingClient networkingClient, RoutingClient routingClient, UaaClient uaaClient, @@ -263,6 +265,7 @@ DefaultCloudFoundryOperations cloudFoundryOperations( .cloudFoundryClient(cloudFoundryClient) .dopplerClient(dopplerClient) .networkingClient(networkingClient) + .logCacheClient(logCacheClient) .routingClient(routingClient) .uaaClient(uaaClient) .organization(organizationName) @@ -547,7 +550,7 @@ Mono stackId(CloudFoundryClient cloudFoundryClient, String stackName) { @Bean String stackName() { - return "cflinuxfs3"; + return "cflinuxfs4"; } @Bean(initMethod = "block") 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 e04c488b0c..614f6bd623 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -25,11 +25,15 @@ import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.logging.Level; import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.MessageType; +import org.cloudfoundry.logcache.v1.Log; +import org.cloudfoundry.logcache.v1.LogType; +import org.cloudfoundry.logcache.v1.ReadRequest; import org.cloudfoundry.operations.applications.ApplicationDetail; import org.cloudfoundry.operations.applications.ApplicationEnvironments; import org.cloudfoundry.operations.applications.ApplicationEvent; @@ -76,12 +80,14 @@ 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; 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; public final class ApplicationsTest extends AbstractIntegrationTest { @@ -486,6 +492,7 @@ public void listTasks() throws IOException { .verify(Duration.ofMinutes(5)); } + @Deprecated @Test public void logs() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -511,6 +518,32 @@ public void logs() throws IOException { .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)); + } + @Test public void pushBindServices() throws IOException { String applicationName = this.nameFactory.getApplicationName(); @@ -2035,4 +2068,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; + } }