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..b339af20b0 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,10 @@ package org.cloudfoundry.operations.applications; 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; @@ -120,7 +124,7 @@ public interface Applications { * @param request the application logs request * @return the applications logs */ - Flux logs(LogsRequest request); + Flux logs(LogsRequest 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..75fe741b63 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,11 @@ 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; +import org.cloudfoundry.logcache.v1.ReadResponse; import org.cloudfoundry.operations.util.OperationsLogging; import org.cloudfoundry.util.DateUtils; import org.cloudfoundry.util.DelayTimeoutException; @@ -192,12 +198,15 @@ public final class DefaultApplications implements Applications { private static final String[] ENTRY_FIELDS_CRASH = {"index", "reason", "exit_description"}; private static final String[] ENTRY_FIELDS_NORMAL = { - "instances", "memory", "state", "environment_json" + "instances", "memory", "state", "environment_json" }; 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,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; @@ -219,22 +230,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( @@ -256,10 +270,10 @@ public Mono copySource(CopySourceApplicationRequest request) { function( (cloudFoundryClient, sourceApplicationId, targetApplicationId) -> copyBits( - cloudFoundryClient, - request.getStagingTimeout(), - sourceApplicationId, - targetApplicationId) + cloudFoundryClient, + request.getStagingTimeout(), + sourceApplicationId, + targetApplicationId) .thenReturn( Tuples.of( cloudFoundryClient, @@ -288,12 +302,12 @@ public Mono delete(DeleteApplicationRequest request) { function( (cloudFoundryClient, spaceId) -> getRoutesAndApplicationId( - cloudFoundryClient, - request, - spaceId, - Optional.ofNullable( - request.getDeleteRoutes()) - .orElse(false)) + cloudFoundryClient, + request, + spaceId, + Optional.ofNullable( + request.getDeleteRoutes()) + .orElse(false)) .map( function( (routes, applicationId) -> @@ -305,9 +319,9 @@ public Mono delete(DeleteApplicationRequest request) { function( (cloudFoundryClient, routes, applicationId) -> deleteRoutes( - cloudFoundryClient, - request.getCompletionTimeout(), - routes) + cloudFoundryClient, + request.getCompletionTimeout(), + routes) .thenReturn( Tuples.of( cloudFoundryClient, @@ -528,7 +542,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( @@ -537,7 +551,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(); } @@ -576,14 +590,14 @@ public Mono push(PushApplicationRequest request) { Optional.ofNullable(request.getHost()).ifPresent(builder::host); return pushManifest( - PushApplicationManifestRequest.builder() - .manifest(builder.build()) - .dockerPassword(request.getDockerPassword()) - .dockerUsername(request.getDockerUsername()) - .noStart(request.getNoStart()) - .stagingTimeout(request.getStagingTimeout()) - .startupTimeout(request.getStartupTimeout()) - .build()) + PushApplicationManifestRequest.builder() + .manifest(builder.build()) + .dockerPassword(request.getDockerPassword()) + .dockerUsername(request.getDockerUsername()) + .noStart(request.getNoStart()) + .stagingTimeout(request.getStagingTimeout()) + .startupTimeout(request.getStartupTimeout()) + .build()) .transform(OperationsLogging.log("Push")) .checkpoint(); } @@ -633,8 +647,8 @@ public Mono pushManifest(PushApplicationManifestRequest request) { } else { throw new IllegalStateException( "One of application or" - + " dockerImage must be" - + " supplied"); + + " dockerImage must be" + + " supplied"); } }))) .then() @@ -651,15 +665,14 @@ 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( (cloudFoundryClient, spaceId) -> applyManifestAndWaitForCompletion( - cloudFoundryClient, - spaceId, - manifestSerialized) + cloudFoundryClient, + spaceId, + manifestSerialized) .then( Mono.just( Tuples.of( @@ -679,9 +692,9 @@ public Mono pushManifestV3(PushManifestV3Request request) { function( (cloudFoundryClient, spaceId, manifestApp) -> getApplicationIdV3( - cloudFoundryClient, - manifestApp.getName(), - spaceId) + cloudFoundryClient, + manifestApp.getName(), + spaceId) .flatMap( appId -> Mono.zip( @@ -694,9 +707,9 @@ public Mono pushManifestV3(PushManifestV3Request request) { function( (appId, packageId) -> buildAndStage( - cloudFoundryClient, - manifestApp, - packageId) + cloudFoundryClient, + manifestApp, + packageId) .flatMap( dropletId -> applyDropletAndWaitForRunning( @@ -1148,9 +1161,9 @@ private static Mono bindServices( .flatMap( serviceInstanceId -> requestCreateServiceBinding( - cloudFoundryClient, - applicationId, - serviceInstanceId) + cloudFoundryClient, + applicationId, + serviceInstanceId) .onErrorResume( ExceptionUtils.statusCode(CF_SERVICE_ALREADY_BOUND), t -> Mono.empty())) @@ -1414,11 +1427,11 @@ private static Mono getApplicationId( .ifPresent(merge::putAll); return requestUpdateApplication( - cloudFoundryClient, - ResourceUtils.getId(application), - merge, - manifest, - stackId) + cloudFoundryClient, + ResourceUtils.getId(application), + merge, + manifest, + stackId) .map(ResourceUtils::getId); }) .switchIfEmpty( @@ -1500,15 +1513,15 @@ private static Mono getApplicationV3( } private static Mono< - Tuple5< - List, - SummaryApplicationResponse, - GetStackResponse, - List, - List>> - getAuxiliaryContent( - CloudFoundryClient cloudFoundryClient, - AbstractApplicationResource applicationResource) { + Tuple5< + List, + SummaryApplicationResponse, + GetStackResponse, + List, + List>> + getAuxiliaryContent( + CloudFoundryClient cloudFoundryClient, + AbstractApplicationResource applicationResource) { String applicationId = ResourceUtils.getId(applicationResource); String stackId = ResourceUtils.getEntity(applicationResource).getStackId(); @@ -1519,8 +1532,8 @@ private static Mono getApplicationV3( .flatMap( function( (applicationStatisticsResponse, - summaryApplicationResponse, - applicationInstancesResponse) -> + summaryApplicationResponse, + applicationInstancesResponse) -> Mono.zip( getApplicationBuildpacks( cloudFoundryClient, applicationId), @@ -1586,6 +1599,12 @@ private static Flux getLogs( } } + private static Flux getRecentLogs(Mono logCacheClient, String applicationId) { + return requestLogsRecentLogCache(logCacheClient, applicationId) + .sort(LOG_MESSAGE_COMPARATOR_LOG_CACHE) + .map(org.cloudfoundry.logcache.v1.Envelope::getLog); + } + @SuppressWarnings("unchecked") private static Map getMetadataRequest(EventEntity entity) { Map> metadata = @@ -1685,17 +1704,17 @@ private static Flux getPushRouteIdFromDomain( .flatMap( host -> getRouteId( - cloudFoundryClient, - domainId, - host, - manifest.getRoutePath()) + cloudFoundryClient, + domainId, + host, + manifest.getRoutePath()) .switchIfEmpty( requestCreateRoute( - cloudFoundryClient, - domainId, - host, - manifest.getRoutePath(), - spaceId) + cloudFoundryClient, + domainId, + host, + manifest.getRoutePath(), + spaceId) .map(ResourceUtils::getId))); } @@ -1759,11 +1778,11 @@ private static Mono getRouteIdForHttpRoute( return getRouteId(cloudFoundryClient, domainId, derivedHost, decomposedRoute.getPath()) .switchIfEmpty( requestCreateRoute( - cloudFoundryClient, - domainId, - derivedHost, - decomposedRoute.getPath(), - spaceId) + cloudFoundryClient, + domainId, + derivedHost, + decomposedRoute.getPath(), + spaceId) .map(ResourceUtils::getId)); } @@ -1781,10 +1800,10 @@ private static Mono getRouteIdForTcpRoute( return getTcpRouteId(cloudFoundryClient, domainId, decomposedRoute.getPort()) .switchIfEmpty( requestCreateTcpRoute( - cloudFoundryClient, - domainId, - decomposedRoute.getPort(), - spaceId) + cloudFoundryClient, + domainId, + decomposedRoute.getPort(), + spaceId) .map(ResourceUtils::getId)); } @@ -1795,11 +1814,11 @@ private static Mono> getRoutes( } private static Mono>, String>> - getRoutesAndApplicationId( - CloudFoundryClient cloudFoundryClient, - DeleteApplicationRequest request, - String spaceId, - boolean deleteRoutes) { + getRoutesAndApplicationId( + CloudFoundryClient cloudFoundryClient, + DeleteApplicationRequest request, + String spaceId, + boolean deleteRoutes) { return getApplicationId(cloudFoundryClient, request.getName(), spaceId) .flatMap( applicationId -> @@ -1954,12 +1973,12 @@ private static Mono prepareDomainsAndRoutes( if (manifest.getDomains() == null) { if (existingRoutes.isEmpty()) { return associateDefaultDomain( - cloudFoundryClient, - applicationId, - availableDomains, - manifest, - randomWords, - spaceId) + cloudFoundryClient, + applicationId, + availableDomains, + manifest, + randomWords, + spaceId) .then(); } return Mono.empty(); // A route already exists for the application, do nothing @@ -1968,12 +1987,12 @@ private static Mono prepareDomainsAndRoutes( .flatMap( domain -> getPushRouteIdFromDomain( - cloudFoundryClient, - availableDomains, - getDomainId(availableDomains, domain), - manifest, - randomWords, - spaceId) + cloudFoundryClient, + availableDomains, + getDomainId(availableDomains, domain), + manifest, + randomWords, + spaceId) .flatMap( routeId -> requestAssociateRoute( @@ -1987,7 +2006,7 @@ private static Mono prepareDomainsAndRoutes( existingRoutes.stream().map(ResourceUtils::getId).collect(Collectors.toList()); return getPushRouteIdFromRoute( - cloudFoundryClient, availableDomains, manifest, randomWords, spaceId) + cloudFoundryClient, availableDomains, manifest, randomWords, spaceId) .filter(routeId -> !existingRouteIds.contains(routeId)) .flatMapSequential( routeId -> @@ -2023,13 +2042,13 @@ private static Flux pushApplication( function( (applicationId, existingRoutes, matchedResources) -> prepareDomainsAndRoutes( - cloudFoundryClient, - applicationId, - availableDomains, - manifest, - existingRoutes, - randomWords, - spaceId) + cloudFoundryClient, + applicationId, + availableDomains, + manifest, + existingRoutes, + randomWords, + spaceId) .thenReturn( Tuples.of( applicationId, matchedResources)))) @@ -2087,13 +2106,13 @@ private static Flux pushDocker( function( (applicationId, existingRoutes) -> prepareDomainsAndRoutes( - cloudFoundryClient, - applicationId, - availableDomains, - manifest, - existingRoutes, - randomWords, - spaceId) + cloudFoundryClient, + applicationId, + availableDomains, + manifest, + existingRoutes, + randomWords, + spaceId) .thenReturn(applicationId))) .delayUntil( applicationId -> @@ -2478,6 +2497,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( @@ -3138,10 +3183,10 @@ private static Mono uploadApplicationAndWait( .flatMap( filteredApplication -> requestUploadApplication( - cloudFoundryClient, - applicationId, - filteredApplication, - matchedResources) + cloudFoundryClient, + applicationId, + filteredApplication, + matchedResources) .doOnTerminate( () -> { try { @@ -3184,10 +3229,10 @@ private static Mono uploadPackageBitsAndWait( .flatMap( filteredApplication -> requestUploadPackage( - cloudFoundryClient, - packageId, - filteredApplication, - matchedResources) + cloudFoundryClient, + packageId, + filteredApplication, + matchedResources) .doOnTerminate( () -> { try { @@ -3297,7 +3342,7 @@ private static Mono waitForRunningV3( .reduce( (totalState, instanceState) -> totalState.ordinal() - < instanceState.ordinal() + < instanceState.ordinal() ? totalState : instanceState) // CRASHED takes // precedence over 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..455d2ff66b 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; @@ -144,10 +145,18 @@ 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; 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; @@ -163,6 +172,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)); @@ -1313,12 +1323,12 @@ 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(LogsRequest.builder().name("test-application-name").recent(true).build()) .as(StepVerifier::create) - .expectNext(fill(LogMessage.builder(), "log-message-").build()) + .expectNextMatches(log -> log.getPayload().equals("test-payload")) .expectComplete() .verify(Duration.ofSeconds(5)); } @@ -1339,39 +1349,40 @@ void logsNoApp() { .verify(Duration.ofSeconds(5)); } - @Test - void logsRecent() { - 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) - .expectNext(fill(LogMessage.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(LogsRequest.builder().name("test-application-name").build()) - .as(StepVerifier::create) - .expectNext(fill(LogMessage.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() { @@ -5248,17 +5259,21 @@ private static void requestListTasksEmpty( .build())); } - private static void requestLogsRecent(DopplerClient dopplerClient, String applicationId) { - when(dopplerClient.recentLogs( - RecentLogsRequest.builder().applicationId(applicationId).build())) - .thenReturn( - Flux.just( - Envelope.builder() - .eventType(EventType.LOG_MESSAGE) - .logMessage( - fill(LogMessage.builder(), "log-message-").build()) - .origin("rsp") - .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 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 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..40f9044c06 100644 --- a/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java +++ b/integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java @@ -28,8 +28,11 @@ import org.cloudfoundry.AbstractIntegrationTest; import org.cloudfoundry.CloudFoundryVersion; import org.cloudfoundry.IfCloudFoundryVersion; +import org.cloudfoundry.doppler.Envelope; import org.cloudfoundry.doppler.LogMessage; import org.cloudfoundry.doppler.MessageType; +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; @@ -494,19 +497,15 @@ public void logs() throws IOException { this.cloudFoundryOperations, new ClassPathResource("test-application.zip").getFile().toPath(), applicationName, - false) - .thenMany( - this.cloudFoundryOperations + false) + .thenMany(this.cloudFoundryOperations .applications() - .logs( - LogsRequest.builder() - .name(applicationName) - .recent(true) + .logs(ReadRequest.builder() + .sourceId(applicationName) .build())) - .map(LogMessage::getMessageType) - .next() + .map(org.cloudfoundry.logcache.v1.Log::getType) .as(StepVerifier::create) - .expectNext(MessageType.OUT) + .expectNext(org.cloudfoundry.logcache.v1.LogType.OUT) .expectComplete() .verify(Duration.ofMinutes(5)); }