Skip to content

Commit 45828f0

Browse files
Add support for CaaS (#326)
Configure otlp exporter with CaaS credentials when found. Fall back to Cloud Logging credentials when found and CaaS is unavailble. Download the CaaS server certificate if mTLS client cert and key are found. Signed-off-by: Karsten Schnitter <k.schnitter@sap.com>
1 parent a25167f commit 45828f0

15 files changed

+860
-99
lines changed

cf-java-logging-support-opentelemetry-agent-extension/README.md

Lines changed: 53 additions & 65 deletions
Large diffs are not rendered by default.

cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/CloudLoggingConfigurationCustomizerProvider.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,32 @@
11
package com.sap.hcf.cf.logging.opentelemetry.agent.ext;
22

3+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CaasBindingPropertiesSupplier;
34
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.CloudLoggingBindingPropertiesSupplier;
5+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.DefaultOtelBackendPropertiesSupplier;
46
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.exporter.SanitizeSpanExporterCustomizer;
57
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizer;
68
import io.opentelemetry.sdk.autoconfigure.spi.AutoConfigurationCustomizerProvider;
79

810
import java.util.logging.Logger;
911

12+
import static com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding.DefaultOtelBackendPropertiesSupplier.builder;
13+
1014
public class CloudLoggingConfigurationCustomizerProvider implements AutoConfigurationCustomizerProvider {
1115

1216
private static final Logger LOG = Logger.getLogger(CloudLoggingConfigurationCustomizerProvider.class.getName());
1317
private static final String VERSION = "4.0.0";
1418

19+
private static DefaultOtelBackendPropertiesSupplier getDefaultOtelBackendPropertiesSupplier() {
20+
return builder() //
21+
.add(new CaasBindingPropertiesSupplier()) // this has priority
22+
.add(new CloudLoggingBindingPropertiesSupplier()) // look for Cloud Logging as fallback and backward compatibility
23+
.build();
24+
}
25+
1526
@Override
1627
public void customize(AutoConfigurationCustomizer autoConfiguration) {
1728
LOG.info("Initializing SAP BTP Observability extension " + VERSION);
18-
autoConfiguration.addPropertiesSupplier(new CloudLoggingBindingPropertiesSupplier());
29+
autoConfiguration.addPropertiesSupplier(getDefaultOtelBackendPropertiesSupplier());
1930
autoConfiguration.addSpanExporterCustomizer(new SanitizeSpanExporterCustomizer());
2031
}
2132

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;
2+
3+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.PemFileCreator;
4+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.ServerCertificateDownloader;
5+
import io.opentelemetry.common.ComponentLoader;
6+
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
7+
8+
import java.io.File;
9+
import java.io.IOException;
10+
import java.util.HashMap;
11+
import java.util.Map;
12+
import java.util.function.Supplier;
13+
import java.util.logging.Logger;
14+
15+
import static java.util.Collections.emptyMap;
16+
17+
public class CaasBindingPropertiesSupplier implements Supplier<Map<String, String>> {
18+
19+
private static final String CAAS_CLIENT_KEY = "tls.key";
20+
private static final String CAAS_CLIENT_CERT = "tls.crt";
21+
private static final String CAAS_ENDPOINT = "http-url";
22+
private static final String CAAS_PORT_PLACEHOLDER = "<http-receiver-port>";
23+
private static final String PORT_OTLP_HTTP = "4318";
24+
25+
private static final Logger LOG = Logger.getLogger(CaasBindingPropertiesSupplier.class.getName());
26+
27+
private final CaasServiceProvider serviceProvider;
28+
private final PemFileCreator pemFileCreator;
29+
private final ServerCertificateDownloader serverCertificateDownloader;
30+
31+
public CaasBindingPropertiesSupplier() {
32+
this(new CaasServiceProvider(getDefaultConfigProperties()), new PemFileCreator(),
33+
new ServerCertificateDownloader());
34+
}
35+
36+
CaasBindingPropertiesSupplier(CaasServiceProvider serviceProvider, PemFileCreator pemFileCreator,
37+
ServerCertificateDownloader serverCertificateDownloader) {
38+
this.serviceProvider = serviceProvider;
39+
this.pemFileCreator = pemFileCreator;
40+
this.serverCertificateDownloader = serverCertificateDownloader;
41+
}
42+
43+
private static DefaultConfigProperties getDefaultConfigProperties() {
44+
ComponentLoader componentLoader =
45+
ComponentLoader.forClassLoader(DefaultConfigProperties.class.getClassLoader());
46+
return DefaultConfigProperties.create(emptyMap(), componentLoader);
47+
}
48+
49+
private static void putCaasDefaultProperties(Map<String, String> properties) {
50+
properties.put("otel.exporter.otlp.protocol", "http/protobuf");
51+
properties.put("otel.exporter.otlp.compression", "gzip");
52+
}
53+
54+
@Override
55+
public Map<String, String> get() {
56+
CloudFoundryServiceInstance serviceInstance = serviceProvider.get();
57+
if (serviceInstance == null) {
58+
LOG.config("No CaaS service instance found.");
59+
return emptyMap();
60+
}
61+
CloudFoundryCredentials credentials = serviceInstance.getCredentials();
62+
if (credentials == null) {
63+
LOG.warning(() -> "CaaS service instance '" + serviceInstance.getName() + "' has no credentials.");
64+
return emptyMap();
65+
}
66+
String endpointUrl = credentials.getString(CAAS_ENDPOINT);
67+
if (endpointUrl == null || endpointUrl.isBlank()) {
68+
LOG.warning(() -> "CaaS service instance '" + serviceInstance.getName() + "' has no endpoint URL.");
69+
return emptyMap();
70+
}
71+
72+
Map<String, String> properties = new HashMap<>();
73+
endpointUrl = endpointUrl.replace(CAAS_PORT_PLACEHOLDER, PORT_OTLP_HTTP);
74+
LOG.config("Using CaaS OTLP endpoint URL: " + endpointUrl);
75+
properties.put("otel.exporter.otlp.endpoint", endpointUrl);
76+
77+
putCaasDefaultProperties(properties);
78+
79+
String clientCert = credentials.getString(CAAS_CLIENT_CERT);
80+
String clientKey = credentials.getString(CAAS_CLIENT_KEY);
81+
if (clientCert != null && clientKey != null) {
82+
try {
83+
String serverCert = serverCertificateDownloader.download(endpointUrl);
84+
if (serverCert == null || serverCert.isBlank()) {
85+
return properties;
86+
}
87+
File serverCertFile = pemFileCreator.writeFile("caas-server-cert-", ".crt", serverCert);
88+
File clientCertFile = pemFileCreator.writeFile("caas-client-cert-", ".crt", clientCert);
89+
File clientKeyFile = pemFileCreator.writeFile("caas-client-key-", ".key", clientKey);
90+
91+
properties.put("otel.exporter.otlp.certificate", serverCertFile.getAbsolutePath());
92+
properties.put("otel.exporter.otlp.client.certificate", clientCertFile.getAbsolutePath());
93+
properties.put("otel.exporter.otlp.client.key", clientKeyFile.getAbsolutePath());
94+
95+
} catch (IOException e) {
96+
LOG.warning(
97+
() -> "Failed to create PEM files for CaaS service instance '" + serviceInstance.getName() + "': " + e.getMessage());
98+
}
99+
} else {
100+
LOG.warning(
101+
() -> "CaaS service instance '" + serviceInstance.getName() + "' is missing client certificate or key.");
102+
}
103+
return properties;
104+
}
105+
106+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;
2+
3+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations;
4+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
5+
6+
import java.util.Collections;
7+
import java.util.function.Supplier;
8+
9+
public class CaasServiceProvider implements Supplier<CloudFoundryServiceInstance> {
10+
11+
private final CloudFoundryServiceInstance service;
12+
13+
public CaasServiceProvider(ConfigProperties config) {
14+
this(config, new CloudFoundryServicesAdapter());
15+
}
16+
17+
CaasServiceProvider(ConfigProperties config, CloudFoundryServicesAdapter adapter) {
18+
String label = ExtensionConfigurations.RUNTIME.CLOUD_FOUNDRY.SERVICE.CAAS.LABEL.getValue(config);
19+
this.service =
20+
adapter.stream(Collections.singletonList(label), Collections.emptyList()).findFirst().orElse(null);
21+
}
22+
23+
@Override
24+
public CloudFoundryServiceInstance get() {
25+
return service;
26+
}
27+
}

cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/binding/CloudLoggingBindingPropertiesSupplier.java

Lines changed: 10 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;
22

33
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.config.ExtensionConfigurations.DEPRECATED;
4+
import com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls.PemFileCreator;
45
import io.opentelemetry.common.ComponentLoader;
56
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
67
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
78

89
import java.io.File;
9-
import java.io.FileWriter;
1010
import java.io.IOException;
1111
import java.util.Collections;
1212
import java.util.HashMap;
@@ -24,13 +24,17 @@ public class CloudLoggingBindingPropertiesSupplier implements Supplier<Map<Strin
2424
private static final String OTLP_SERVER_CERT = "server-ca";
2525

2626
private final CloudLoggingServicesProvider cloudLoggingServicesProvider;
27+
private final PemFileCreator pemFileCreator;
2728

2829
public CloudLoggingBindingPropertiesSupplier() {
29-
this(new CloudLoggingServicesProvider(getDefaultProperties(), new CloudFoundryServicesAdapter()));
30+
this(new CloudLoggingServicesProvider(getDefaultProperties(), new CloudFoundryServicesAdapter()),
31+
new PemFileCreator());
3032
}
3133

32-
CloudLoggingBindingPropertiesSupplier(CloudLoggingServicesProvider cloudLoggingServicesProvider) {
34+
CloudLoggingBindingPropertiesSupplier(CloudLoggingServicesProvider cloudLoggingServicesProvider,
35+
PemFileCreator pemFileCreator) {
3336
this.cloudLoggingServicesProvider = cloudLoggingServicesProvider;
37+
this.pemFileCreator = pemFileCreator;
3438
}
3539

3640
private static ConfigProperties getDefaultProperties() {
@@ -47,16 +51,6 @@ private static boolean isBlank(String text) {
4751
return text == null || text.trim().isEmpty();
4852
}
4953

50-
private static File writeFile(String prefix, String suffix, String content) throws IOException {
51-
File file = File.createTempFile(prefix, suffix);
52-
file.deleteOnExit();
53-
try (FileWriter writer = new FileWriter(file)) {
54-
writer.append(content);
55-
LOG.fine("Created temporary file " + file.getAbsolutePath());
56-
}
57-
return file;
58-
}
59-
6054
/**
6155
* Scans service bindings, both managed and user-provided for Cloud Logging. Managed services require the label
6256
* "cloud-logging" to be considered. Services will be selected by the tag "Cloud Logging". User-provided services
@@ -95,9 +89,9 @@ private Map<String, String> createEndpointConfiguration(CloudFoundryServiceInsta
9589
}
9690

9791
try {
98-
File clientKeyFile = writeFile("cloud-logging-client", ".key", clientKey);
99-
File clientCertFile = writeFile("cloud-logging-client", ".cert", clientCert);
100-
File serverCertFile = writeFile("cloud-logging-server", ".cert", serverCert);
92+
File clientKeyFile = pemFileCreator.writeFile("cloud-logging-client", ".key", clientKey);
93+
File clientCertFile = pemFileCreator.writeFile("cloud-logging-client", ".cert", clientCert);
94+
File serverCertFile = pemFileCreator.writeFile("cloud-logging-server", ".cert", serverCert);
10195

10296
HashMap<String, String> properties = new HashMap<>();
10397
properties.put("otel.exporter.otlp.endpoint", "https://" + endpoint);
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.binding;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.Map;
6+
import java.util.function.Supplier;
7+
import java.util.logging.Logger;
8+
9+
import static java.util.Collections.emptyMap;
10+
import static java.util.function.Predicate.not;
11+
12+
public class DefaultOtelBackendPropertiesSupplier implements Supplier<Map<String, String>> {
13+
14+
private static final Logger LOG = Logger.getLogger(DefaultOtelBackendPropertiesSupplier.class.getName());
15+
16+
private final List<Supplier<Map<String, String>>> suppliers;
17+
18+
private DefaultOtelBackendPropertiesSupplier(Builder builder) {
19+
this.suppliers = builder.suppliers;
20+
}
21+
22+
@Override
23+
public Map<String, String> get() {
24+
if (suppliers.isEmpty()) {
25+
LOG.config("No OpenTelemetry backend properties suppliers configured.");
26+
return emptyMap();
27+
}
28+
return suppliers.stream().map(Supplier::get).filter(not(Map::isEmpty)).findFirst().orElse(emptyMap());
29+
}
30+
31+
public static Builder builder() {
32+
return new Builder();
33+
}
34+
35+
public static class Builder {
36+
37+
private final List<Supplier<Map<String, String>>> suppliers = new ArrayList<>();
38+
39+
public Builder add(Supplier<Map<String, String>> supplier) {
40+
suppliers.add(supplier);
41+
return this;
42+
}
43+
44+
public DefaultOtelBackendPropertiesSupplier build() {
45+
return new DefaultOtelBackendPropertiesSupplier(this);
46+
}
47+
}
48+
}

cf-java-logging-support-opentelemetry-agent-extension/src/main/java/com/sap/hcf/cf/logging/opentelemetry/agent/ext/config/ExtensionConfigurations.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,16 @@ interface CLOUD_FOUNDRY {
213213
interface RUNTIME {
214214
interface CLOUD_FOUNDRY {
215215
interface SERVICE {
216+
interface CAAS {
217+
/**
218+
* <p>Parses {@code sap.caas.cf.binding.label.value}.</p>
219+
* <p>The label value used to identify managed CaaS service bindings. Default is
220+
* {@code "caas-service"}.</p>
221+
*/
222+
ConfigProperty<String> LABEL =
223+
stringValued("sap.caas.cf.binding.label.value").withDefaultValue("caas-service").build();
224+
}
225+
216226
interface CLOUD_LOGGING {
217227
/**
218228
* <p>Parses {@code sap.cloud-logging.cf.binding.label.value}.</p>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.sap.hcf.cf.logging.opentelemetry.agent.ext.tls;
2+
3+
import java.io.File;
4+
import java.io.FileWriter;
5+
import java.io.IOException;
6+
import java.util.logging.Logger;
7+
8+
public class PemFileCreator {
9+
10+
private static final Logger LOG = Logger.getLogger(PemFileCreator.class.getName());
11+
12+
public File writeFile(String prefix, String suffix, String content) throws IOException {
13+
File file = File.createTempFile(prefix, suffix);
14+
file.deleteOnExit();
15+
try (FileWriter writer = new FileWriter(file)) {
16+
writer.append(content);
17+
LOG.fine("Created temporary file " + file.getAbsolutePath());
18+
}
19+
return file;
20+
}
21+
}

0 commit comments

Comments
 (0)