Skip to content

Commit 1664cbf

Browse files
authored
Deprecate doppler-based ApplicationsTests#logs, restrict tests to to CF 2.x line (#1265)
* Restrict ApplicationsTests#logs to CF 2.x line - The DopplerClient does not work with Loggregator >= 107.0.0, and we cannot obtain "recent" logs. Until this is fixed, we cannot cut a release. That is the only blocking test. - See gh-1181, gh-1230, gh-1237 * Introduce Applications#logs API that will replace the doppler-based implementation Signed-off-by: Daniel Garnier-Moiroux <[email protected]> --------- Signed-off-by: Daniel Garnier-Moiroux <[email protected]>
1 parent ae64ebd commit 1664cbf

File tree

6 files changed

+230
-7
lines changed

6 files changed

+230
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.operations.applications;
18+
19+
import com.fasterxml.jackson.annotation.JsonCreator;
20+
import com.fasterxml.jackson.annotation.JsonValue;
21+
22+
public enum ApplicationLogType {
23+
/**
24+
* {@code STDERR}
25+
*/
26+
ERR("ERR"),
27+
28+
/**
29+
* {@code STDOUT}
30+
*/
31+
OUT("OUT");
32+
33+
private final String value;
34+
35+
ApplicationLogType(String value) {
36+
this.value = value;
37+
}
38+
39+
@JsonCreator
40+
public static ApplicationLogType from(String s) {
41+
switch (s.toLowerCase()) {
42+
case "err":
43+
return ERR;
44+
case "out":
45+
return OUT;
46+
default:
47+
throw new IllegalArgumentException(String.format("Unknown log type: %s", s));
48+
}
49+
}
50+
51+
@JsonValue
52+
public String getValue() {
53+
return this.value;
54+
}
55+
56+
@Override
57+
public String toString() {
58+
return getValue();
59+
}
60+
}

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/Applications.java

+15-1
Original file line numberDiff line numberDiff line change
@@ -115,13 +115,27 @@ public interface Applications {
115115
Flux<Task> listTasks(ListApplicationTasksRequest request);
116116

117117
/**
118-
* List the applications logs
118+
* List the applications logs. Uses Doppler under the hood.
119+
* Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3}
120+
* and {@code TAS < 4.0}.
119121
*
120122
* @param request the application logs request
121123
* @return the applications logs
124+
* @deprecated Use {@link #logs(ApplicationLogsRequest)} instead.
122125
*/
126+
@Deprecated
123127
Flux<LogMessage> logs(LogsRequest request);
124128

129+
/**
130+
* List the applications logs.
131+
* Only works with {@code Loggregator < 107.0}, shipped in {@code CFD < 24.3}
132+
* and {@code TAS < 4.0}.
133+
*
134+
* @param request the application logs request
135+
* @return the applications logs
136+
*/
137+
Flux<ApplicationLog> logs(ApplicationLogsRequest request);
138+
125139
/**
126140
* Push a specific application
127141
*

cloudfoundry-operations/src/main/java/org/cloudfoundry/operations/applications/DefaultApplications.java

+20
Original file line numberDiff line numberDiff line change
@@ -542,6 +542,26 @@ public Flux<LogMessage> logs(LogsRequest request) {
542542
.checkpoint();
543543
}
544544

545+
@Override
546+
public Flux<ApplicationLog> logs(ApplicationLogsRequest request) {
547+
return logs(LogsRequest.builder()
548+
.name(request.getName())
549+
.recent(request.getRecent())
550+
.build())
551+
.map(
552+
logMessage ->
553+
ApplicationLog.builder()
554+
.sourceId(logMessage.getApplicationId())
555+
.sourceType(logMessage.getSourceType())
556+
.instanceId(logMessage.getSourceInstance())
557+
.message(logMessage.getMessage())
558+
.timestamp(logMessage.getTimestamp())
559+
.logType(
560+
ApplicationLogType.from(
561+
logMessage.getMessageType().name()))
562+
.build());
563+
}
564+
545565
@Override
546566
@SuppressWarnings("deprecation")
547567
public Mono<Void> push(PushApplicationRequest request) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.operations.applications;
18+
19+
import org.immutables.value.Value;
20+
21+
/**
22+
* Represents an application log.
23+
*/
24+
@Value.Immutable
25+
abstract class _ApplicationLog {
26+
abstract String getSourceId();
27+
28+
abstract String getInstanceId();
29+
30+
abstract String getSourceType();
31+
32+
abstract String getMessage();
33+
34+
abstract ApplicationLogType getLogType();
35+
36+
abstract Long getTimestamp();
37+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Copyright 2013-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.cloudfoundry.operations.applications;
18+
19+
import org.cloudfoundry.Nullable;
20+
import org.immutables.value.Value;
21+
22+
/**
23+
* Represents a request for logs.
24+
*/
25+
@Value.Immutable
26+
abstract class _ApplicationLogsRequest {
27+
28+
/**
29+
* The name of the application
30+
*/
31+
abstract String getName();
32+
33+
/**
34+
* Whether only recent logs should be retrieved
35+
*/
36+
@Nullable
37+
abstract Boolean getRecent();
38+
}

integration-test/src/test/java/org/cloudfoundry/operations/ApplicationsTest.java

+60-6
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,21 @@
2828
import org.cloudfoundry.AbstractIntegrationTest;
2929
import org.cloudfoundry.CloudFoundryVersion;
3030
import org.cloudfoundry.IfCloudFoundryVersion;
31-
import org.cloudfoundry.doppler.LogMessage;
32-
import org.cloudfoundry.doppler.MessageType;
31+
import org.cloudfoundry.logcache.v1.Envelope;
32+
import org.cloudfoundry.logcache.v1.EnvelopeBatch;
33+
import org.cloudfoundry.logcache.v1.EnvelopeType;
34+
import org.cloudfoundry.logcache.v1.Log;
35+
import org.cloudfoundry.logcache.v1.LogCacheClient;
36+
import org.cloudfoundry.logcache.v1.LogType;
37+
import org.cloudfoundry.logcache.v1.ReadRequest;
38+
import org.cloudfoundry.logcache.v1.ReadResponse;
3339
import org.cloudfoundry.operations.applications.ApplicationDetail;
3440
import org.cloudfoundry.operations.applications.ApplicationEnvironments;
3541
import org.cloudfoundry.operations.applications.ApplicationEvent;
3642
import org.cloudfoundry.operations.applications.ApplicationHealthCheck;
43+
import org.cloudfoundry.operations.applications.ApplicationLog;
44+
import org.cloudfoundry.operations.applications.ApplicationLogType;
45+
import org.cloudfoundry.operations.applications.ApplicationLogsRequest;
3746
import org.cloudfoundry.operations.applications.ApplicationManifest;
3847
import org.cloudfoundry.operations.applications.ApplicationSshEnabledRequest;
3948
import org.cloudfoundry.operations.applications.ApplicationSummary;
@@ -47,7 +56,6 @@
4756
import org.cloudfoundry.operations.applications.GetApplicationManifestRequest;
4857
import org.cloudfoundry.operations.applications.GetApplicationRequest;
4958
import org.cloudfoundry.operations.applications.ListApplicationTasksRequest;
50-
import org.cloudfoundry.operations.applications.LogsRequest;
5159
import org.cloudfoundry.operations.applications.ManifestV3;
5260
import org.cloudfoundry.operations.applications.ManifestV3Application;
5361
import org.cloudfoundry.operations.applications.PushApplicationManifestRequest;
@@ -96,6 +104,8 @@ public final class ApplicationsTest extends AbstractIntegrationTest {
96104

97105
@Autowired private String serviceName;
98106

107+
@Autowired private LogCacheClient logCacheClient;
108+
99109
@Test
100110
public void copySource() throws IOException {
101111
String sourceName = this.nameFactory.getApplicationName();
@@ -486,7 +496,12 @@ public void listTasks() throws IOException {
486496
.verify(Duration.ofMinutes(5));
487497
}
488498

499+
/**
500+
* Doppler was dropped in PCF 4.x in favor of logcache. This test does not work
501+
* on TAS 4.x.
502+
*/
489503
@Test
504+
@IfCloudFoundryVersion(lessThan = CloudFoundryVersion.PCF_4_v2)
490505
public void logs() throws IOException {
491506
String applicationName = this.nameFactory.getApplicationName();
492507

@@ -499,14 +514,53 @@ public void logs() throws IOException {
499514
this.cloudFoundryOperations
500515
.applications()
501516
.logs(
502-
LogsRequest.builder()
517+
ApplicationLogsRequest.builder()
503518
.name(applicationName)
504519
.recent(true)
505520
.build()))
506-
.map(LogMessage::getMessageType)
521+
.map(ApplicationLog::getLogType)
522+
.next()
523+
.as(StepVerifier::create)
524+
.expectNext(ApplicationLogType.OUT)
525+
.expectComplete()
526+
.verify(Duration.ofMinutes(5));
527+
}
528+
529+
/**
530+
* Exercise the LogCache client. Serves as a reference for using the logcache client,
531+
* and will help with the transition to the new
532+
* {@link org.cloudfoundry.operations.applications.Applications#logs(ApplicationLogsRequest)}.
533+
*/
534+
@Test
535+
public void logCacheLogs() throws IOException {
536+
String applicationName = this.nameFactory.getApplicationName();
537+
538+
createApplication(
539+
this.cloudFoundryOperations,
540+
new ClassPathResource("test-application.zip").getFile().toPath(),
541+
applicationName,
542+
false)
543+
.then(
544+
this.cloudFoundryOperations
545+
.applications()
546+
.get(GetApplicationRequest.builder().name(applicationName).build()))
547+
.map(ApplicationDetail::getId)
548+
.flatMapMany(
549+
appGuid ->
550+
this.logCacheClient.read(
551+
ReadRequest.builder()
552+
.sourceId(appGuid)
553+
.envelopeType(EnvelopeType.LOG)
554+
.limit(1)
555+
.build()))
556+
.map(ReadResponse::getEnvelopes)
557+
.map(EnvelopeBatch::getBatch)
558+
.flatMap(Flux::fromIterable)
559+
.map(Envelope::getLog)
560+
.map(Log::getType)
507561
.next()
508562
.as(StepVerifier::create)
509-
.expectNext(MessageType.OUT)
563+
.expectNext(LogType.OUT)
510564
.expectComplete()
511565
.verify(Duration.ofMinutes(5));
512566
}

0 commit comments

Comments
 (0)