Skip to content

Commit

Permalink
(2.3.x): Adding logging module using LogCacheClient (#1065)
Browse files Browse the repository at this point in the history
* Adding logging module using LogCacheClient

* Documenting logging starter
  • Loading branch information
Albertoimpl authored Jul 12, 2024
1 parent a27a0fa commit ca6f427
Show file tree
Hide file tree
Showing 43 changed files with 2,236 additions and 45 deletions.
3 changes: 2 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -414,7 +414,8 @@ def getTestProjects() {

def getStarterProjects() {
[project(":spring-cloud-starter-app-broker"),
project(":spring-cloud-starter-app-broker-cloudfoundry")] as Set
project(":spring-cloud-starter-app-broker-cloudfoundry"),
project(":spring-cloud-starter-app-broker-logging")] as Set
}

def getLibraryProjects() {
Expand Down
13 changes: 4 additions & 9 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,16 +1,9 @@
pluginManagement {
resolutionStrategy {
eachPlugin {
if (requested.id.namespace?.startsWith('org.asciidoctor.jvm')) {
useVersion("4.0.1")
}
}
}
plugins {
id "io.spring.nohttp" version "0.0.11"
id 'org.springframework.boot' version "3.3.1"
id 'org.asciidoctor.jvm.pdf'
id 'org.asciidoctor.jvm.convert'
id 'org.asciidoctor.jvm.pdf' version '4.0.2'
id 'org.asciidoctor.jvm.convert' version '4.0.2'
}
repositories {
gradlePluginPortal()
Expand Down Expand Up @@ -38,5 +31,7 @@ include "spring-cloud-app-broker-core"
include "spring-cloud-app-broker-autoconfigure"
include "spring-cloud-app-broker-integration-tests"
include "spring-cloud-app-broker-acceptance-tests"
include "spring-cloud-app-broker-logging"
include "spring-cloud-starter-app-broker"
include "spring-cloud-starter-app-broker-cloudfoundry"
include "spring-cloud-starter-app-broker-logging"
1 change: 1 addition & 0 deletions spring-cloud-app-broker-acceptance-tests/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ description = "Spring Cloud App Broker Acceptance Tests"
dependencies {
api platform(SpringBootPlugin.BOM_COORDINATES)
api project(":spring-cloud-starter-app-broker-cloudfoundry")
api project(":spring-cloud-starter-app-broker-logging")
api "org.springframework.boot:spring-boot-starter-webflux"

testImplementation "org.springframework.boot:spring-boot-starter-test"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/*
* Copyright 2016-2024 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.appbroker.acceptance.logging;


import org.cloudfoundry.client.CloudFoundryClient;
import org.cloudfoundry.client.v3.applications.ApplicationResource;
import org.cloudfoundry.client.v3.applications.ListApplicationsRequest;
import org.cloudfoundry.operations.CloudFoundryOperations;
import org.cloudfoundry.operations.spaces.GetSpaceRequest;
import org.cloudfoundry.operations.spaces.SpaceDetail;
import reactor.core.publisher.Flux;

import org.springframework.cloud.appbroker.logging.ApplicationIdsProvider;
import org.springframework.stereotype.Component;

@Component
class BackingApplicationIdsProvider implements ApplicationIdsProvider {

private final CloudFoundryClient cloudFoundryClient;

private final CloudFoundryOperations cloudFoundryOperations;

public BackingApplicationIdsProvider(CloudFoundryClient cloudFoundryClient,
CloudFoundryOperations cloudFoundryOperations) {
this.cloudFoundryClient = cloudFoundryClient;
this.cloudFoundryOperations = cloudFoundryOperations;
}

@Override
public Flux<String> getApplicationIds(String serviceInstanceId) {
return cloudFoundryOperations.spaces().get(GetSpaceRequest.builder().name(serviceInstanceId).build())
.map(SpaceDetail::getId)
.flatMap(spaceId ->
cloudFoundryClient.applicationsV3()
.list(ListApplicationsRequest.builder().spaceIds(spaceId).build())
)
.flatMapMany(
listApplicationsResponse -> Flux.fromIterable(listApplicationsResponse.getResources())
.map(ApplicationResource::getId));
}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand All @@ -16,9 +16,14 @@

package org.springframework.cloud.appbroker.acceptance;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -29,6 +34,12 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

import com.jayway.jsonpath.Configuration;
import com.jayway.jsonpath.DocumentContext;
Expand Down Expand Up @@ -64,6 +75,7 @@
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryClientConfiguration;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryProperties;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.CloudFoundryService;
import org.springframework.cloud.appbroker.acceptance.fixtures.cf.UserCloudFoundryService;
import org.springframework.cloud.appbroker.acceptance.fixtures.uaa.UaaService;
Expand Down Expand Up @@ -91,6 +103,7 @@
@ExtendWith(SpringExtension.class)
@ExtendWith(BrokerPropertiesParameterResolver.class)
@EnableConfigurationProperties(AcceptanceTestProperties.class)
@SuppressWarnings("PMD.GodClass")
abstract class CloudFoundryAcceptanceTest {

private static final Logger LOG = LoggerFactory.getLogger(CloudFoundryAcceptanceTest.class);
Expand All @@ -113,12 +126,17 @@ abstract class CloudFoundryAcceptanceTest {
@Autowired
protected UserCloudFoundryService userCloudFoundryService;

@Autowired
private CloudFoundryProperties cloudFoundryProperties;

@Autowired
private UaaService uaaService;

@Autowired
private AcceptanceTestProperties acceptanceTestProperties;

private String cfHome;

private final WebClient webClient = getSslIgnoringWebClient();

protected abstract String testSuffix();
Expand All @@ -144,6 +162,7 @@ void setUp(TestInfo testInfo, BrokerProperties brokerProperties) {
List<String> appBrokerProperties = getAppBrokerProperties(brokerProperties);
blockingSubscribe(initializeUser());
blockingSubscribe(initializeBroker(appBrokerProperties));
prepareCLI();
}

void setUpForBrokerUpdate(BrokerProperties brokerProperties) {
Expand All @@ -157,6 +176,8 @@ private List<String> getAppBrokerProperties(BrokerProperties brokerProperties) {
"spring.cloud.openservicebroker.catalog.services[0].name=" + appServiceName(),
"spring.cloud.openservicebroker.catalog.services[0].description=A service that deploys a backing app",
"spring.cloud.openservicebroker.catalog.services[0].bindable=true",
"spring.cloud.openservicebroker.catalog.services[0].metadata.properties.serviceInstanceLogsEndpoint=" +
getServiceInstanceLogsEndpoint(),
"spring.cloud.openservicebroker.catalog.services[0].plans[0].id=" + PLAN_ID,
"spring.cloud.openservicebroker.catalog.services[0].plans[0].name=standard",
"spring.cloud.openservicebroker.catalog.services[0].plans[0].bindable=true",
Expand All @@ -179,6 +200,10 @@ private List<String> getAppBrokerProperties(BrokerProperties brokerProperties) {
return appBrokerProperties;
}

private String getServiceInstanceLogsEndpoint() {
return "https://" + testBrokerAppName() + "." + cloudFoundryProperties.getApiHost().substring(4) + "/logs/";
}

@BeforeEach
void configureJsonPath() {
Configuration.setDefaults(new Configuration.Defaults() {
Expand Down Expand Up @@ -332,7 +357,7 @@ protected String getServiceInstanceGuid(String serviceInstanceName) {
.block();
}

private Mono<ServiceInstance> getServiceInstanceMono(String serviceInstanceName) {
Mono<ServiceInstance> getServiceInstanceMono(String serviceInstanceName) {
return userCloudFoundryService.getServiceInstance(serviceInstanceName);
}

Expand Down Expand Up @@ -423,7 +448,8 @@ protected Mono<String> manageApps(String serviceInstanceName, String serviceName
.getApplicationRoute(testBrokerAppName())
.flatMap(appRoute ->
webClient.get()
.uri(URI.create(appRoute + "/" + operation + "/" + serviceName + "/" + planName + "/" + serviceInstanceId))
.uri(URI.create(
appRoute + "/" + operation + "/" + serviceName + "/" + planName + "/" + serviceInstanceId))
.retrieve()
.toEntity(String.class)
.map(HttpEntity::getBody)));
Expand Down Expand Up @@ -451,11 +477,81 @@ private WebClient getSslIgnoringWebClient() {

protected Mono<List<ApplicationDetail>> getApplications(String app1, String app2) {
return Flux.merge(cloudFoundryService.getApplication(app1),
cloudFoundryService.getApplication(app2))
cloudFoundryService.getApplication(app2))
.parallel()
.runOn(Schedulers.parallel())
.sequential()
.collectList();
}

private void prepareCLI() {
try {
cfHome = Files.createTempDirectory("app-broker-acceptance-tests").toString();

callCLICommand(List.of("cf", "login", "-a",
cloudFoundryProperties.getApiHost(),
"--skip-ssl-validation", "-u",
cloudFoundryProperties.getUsername(),
"-p",
cloudFoundryProperties.getPassword(),
"-o", "test-instances"))
.block(Duration.ofSeconds(60));
callCLICommand(List.of("cf", "install-plugin", "-f", "-r", "Cf-Community", "Service Instance Logging"))
.block(Duration.ofSeconds(60));
}
catch (IOException e) {
throw new RuntimeException(e);
}
}

protected Mono<String> callCLICommand(List<String> command) {
return Mono.fromCallable(() -> {
if (LOG.isDebugEnabled()) {
LOG.debug("Executing command: {}", command);
}
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.environment().put("CF_HOME", cfHome);
processBuilder.redirectErrorStream(true);
Process process = processBuilder.start();

return processOutput(process);
}).subscribeOn(Schedulers.boundedElastic());
}

private static String processOutput(Process process) throws InterruptedException, ExecutionException {
StringBuffer outputBuilder = new StringBuffer();
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Void> future = appendLines(executor, process, outputBuilder);
try {
future.get(30, TimeUnit.SECONDS);
}
catch (TimeoutException e) {
LOG.info("Process reading timed out after 30 seconds");
}
finally {
future.cancel(true);
executor.shutdownNow();
process.destroyForcibly();
}
return outputBuilder.toString();
}

private static Future<Void> appendLines(ExecutorService executor, Process process, StringBuffer outputBuilder) {
return executor.submit(() -> {
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line = "";
while (line != null) {
line = reader.readLine();
if (line != null) {
outputBuilder.append(line).append('\n');
if (LOG.isDebugEnabled()) {
LOG.debug("Read line: {}", line);
}
}
}
}
return null;
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
/*
* Copyright 2002-2020 the original author or authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.cloud.appbroker.acceptance;

import java.time.Duration;
import java.util.List;

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test;

import static org.assertj.core.api.Assertions.assertThat;

class LoggingRecentAcceptanceTest extends CloudFoundryAcceptanceTest {

private static final String APP_CREATE_1 = "app-logging-recent-1";

private static final String SI_NAME = "si-logging-recent";

private static final String SUFFIX = "logging-recent-instance";

private static final String APP_SERVICE_NAME = "app-service-" + SUFFIX;

private static final String BACKING_SERVICE_NAME = "backing-service-" + SUFFIX;

@Override
protected String testSuffix() {
return SUFFIX;
}

@Override
protected String appServiceName() {
return APP_SERVICE_NAME;
}

@Override
protected String backingServiceName() {
return BACKING_SERVICE_NAME;
}

@Test
@AppBrokerTestProperties({
"spring.cloud.appbroker.services[0].service-name=" + APP_SERVICE_NAME,
"spring.cloud.appbroker.services[0].plan-name=" + PLAN_NAME,
"spring.cloud.appbroker.services[0].apps[0].name=" + APP_CREATE_1,
"spring.cloud.appbroker.services[0].apps[0].path=" + BACKING_APP_PATH,
"spring.cloud.appbroker.services[0].target.name=SpacePerServiceInstance",
"spring.cloud.appbroker.deployer.cloudfoundry.properties.stack=cflinuxfs4"
})
void shouldReturnBackingApplicationLogs() {
createServiceInstance(SI_NAME);

String lines = callCLICommand(
List.of("cf", "service-logs", SI_NAME, "--recent", "--skip-ssl-validation"))
.block(Duration.ofSeconds(35));

assertThat(lines).isNotEmpty();

assertThat(lines).contains("Created app with guid");
assertThat(lines).contains("Updated app with guid");
assertThat(lines).contains("APP/PROC/WEB");
}

@AfterEach
void tearDown() {
deleteServiceInstance(SI_NAME);
}

}
Loading

0 comments on commit ca6f427

Please sign in to comment.