Define dedicated file configuration SPI ComponentProvider (#6457)
This commit is contained in:
parent
809457d2b0
commit
d9cef81e29
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.exporter.prometheus.internal;
|
||||
|
||||
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
|
||||
import io.opentelemetry.exporter.prometheus.PrometheusHttpServerBuilder;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.metrics.export.MetricReader;
|
||||
|
||||
/**
|
||||
* File configuration SPI implementation for {@link PrometheusHttpServer}.
|
||||
*
|
||||
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
|
||||
* at any time.
|
||||
*/
|
||||
public class PrometheusComponentProvider implements ComponentProvider<MetricReader> {
|
||||
|
||||
@Override
|
||||
public Class<MetricReader> getType() {
|
||||
return MetricReader.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "prometheus";
|
||||
}
|
||||
|
||||
@Override
|
||||
public MetricReader create(StructuredConfigProperties config) {
|
||||
PrometheusHttpServerBuilder prometheusBuilder = PrometheusHttpServer.builder();
|
||||
|
||||
Integer port = config.getInt("port");
|
||||
if (port != null) {
|
||||
prometheusBuilder.setPort(port);
|
||||
}
|
||||
String host = config.getString("host");
|
||||
if (host != null) {
|
||||
prometheusBuilder.setHost(host);
|
||||
}
|
||||
|
||||
return prometheusBuilder.build();
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
io.opentelemetry.exporter.prometheus.internal.PrometheusComponentProvider
|
|
@ -0,0 +1,45 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure.spi.internal;
|
||||
|
||||
import io.opentelemetry.sdk.trace.export.SpanExporter;
|
||||
|
||||
/**
|
||||
* Provides configured instances of SDK extension components. {@link ComponentProvider} allows SDK
|
||||
* extension components which are not part of the core SDK to be referenced in file based
|
||||
* configuration.
|
||||
*
|
||||
* @param <T> the type of the SDK extension component. See {@link #getType()}.
|
||||
*/
|
||||
// TODO (jack-berg): list the specific types which are supported in file configuration
|
||||
public interface ComponentProvider<T> {
|
||||
|
||||
/**
|
||||
* The type of SDK extension component. For example, if providing instances of a custom span
|
||||
* exporter, the type would be {@link SpanExporter}.
|
||||
*/
|
||||
Class<T> getType();
|
||||
|
||||
/**
|
||||
* The name of the exporter, to be referenced in configuration files. For example, if providing
|
||||
* instances of a custom span exporter for the "acme" protocol, the name might be "acme".
|
||||
*
|
||||
* <p>This name MUST not be the same as any other component provider name which returns components
|
||||
* of the same {@link #getType() type}.
|
||||
*/
|
||||
String getName();
|
||||
|
||||
/**
|
||||
* Configure an instance of the SDK extension component according to the {@code config}.
|
||||
*
|
||||
* @param config the configuration provided where the component is referenced in a configuration
|
||||
* file.
|
||||
* @return an instance the SDK extension component
|
||||
*/
|
||||
// TODO (jack-berg): consider dynamic configuration use case before stabilizing in case that
|
||||
// affects any API decisions
|
||||
T create(StructuredConfigProperties config);
|
||||
}
|
|
@ -0,0 +1,187 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.autoconfigure.spi.internal;
|
||||
|
||||
import static io.opentelemetry.api.internal.ConfigUtil.defaultIfNull;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* An interface for accessing structured configuration data.
|
||||
*
|
||||
* <p>An instance of {@link StructuredConfigProperties} is equivalent to a <a
|
||||
* href="https://yaml.org/spec/1.2.2/#3211-nodes">YAML mapping node</a>. It has accessors for
|
||||
* reading scalar properties, {@link #getStructured(String)} for reading children which are
|
||||
* themselves mappings, and {@link #getStructuredList(String)} for reading children which are
|
||||
* sequences of mappings.
|
||||
*/
|
||||
public interface StructuredConfigProperties {
|
||||
|
||||
/**
|
||||
* Returns a {@link String} configuration property.
|
||||
*
|
||||
* @return null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar string
|
||||
*/
|
||||
@Nullable
|
||||
String getString(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link String} configuration property.
|
||||
*
|
||||
* @return a {@link String} configuration property or {@code defaultValue} if a property with
|
||||
* {@code name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar string
|
||||
*/
|
||||
default String getString(String name, String defaultValue) {
|
||||
return defaultIfNull(getString(name), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Boolean} configuration property. Implementations should use the same rules as
|
||||
* {@link Boolean#parseBoolean(String)} for handling the values.
|
||||
*
|
||||
* @return null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar boolean
|
||||
*/
|
||||
@Nullable
|
||||
Boolean getBoolean(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link Boolean} configuration property.
|
||||
*
|
||||
* @return a {@link Boolean} configuration property or {@code defaultValue} if a property with
|
||||
* {@code name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar boolean
|
||||
*/
|
||||
default boolean getBoolean(String name, boolean defaultValue) {
|
||||
return defaultIfNull(getBoolean(name), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Integer} configuration property.
|
||||
*
|
||||
* <p>If the underlying config property is {@link Long}, it is converted to {@link Integer} with
|
||||
* {@link Long#intValue()} which may result in loss of precision.
|
||||
*
|
||||
* @return null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar integer
|
||||
*/
|
||||
@Nullable
|
||||
Integer getInt(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link Integer} configuration property.
|
||||
*
|
||||
* <p>If the underlying config property is {@link Long}, it is converted to {@link Integer} with
|
||||
* {@link Long#intValue()} which may result in loss of precision.
|
||||
*
|
||||
* @return a {@link Integer} configuration property or {@code defaultValue} if a property with
|
||||
* {@code name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar integer
|
||||
*/
|
||||
default int getInt(String name, int defaultValue) {
|
||||
return defaultIfNull(getInt(name), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Long} configuration property.
|
||||
*
|
||||
* @return null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar long
|
||||
*/
|
||||
@Nullable
|
||||
Long getLong(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link Long} configuration property.
|
||||
*
|
||||
* @return a {@link Long} configuration property or {@code defaultValue} if a property with {@code
|
||||
* name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar long
|
||||
*/
|
||||
default long getLong(String name, long defaultValue) {
|
||||
return defaultIfNull(getLong(name), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link Double} configuration property.
|
||||
*
|
||||
* @return null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar double
|
||||
*/
|
||||
@Nullable
|
||||
Double getDouble(String name);
|
||||
|
||||
/**
|
||||
* Returns a {@link Double} configuration property.
|
||||
*
|
||||
* @return a {@link Double} configuration property or {@code defaultValue} if a property with
|
||||
* {@code name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid scalar double
|
||||
*/
|
||||
default double getDouble(String name, double defaultValue) {
|
||||
return defaultIfNull(getDouble(name), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link List} configuration property. Empty values and values which do not map to the
|
||||
* {@code scalarType} will be removed.
|
||||
*
|
||||
* @param name the property name
|
||||
* @param scalarType the scalar type, one of {@link String}, {@link Boolean}, {@link Long} or
|
||||
* {@link Double}
|
||||
* @return a {@link List} configuration property, or null if the property has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid sequence of scalars, or if {@code
|
||||
* scalarType} is not supported
|
||||
*/
|
||||
@Nullable
|
||||
<T> List<T> getScalarList(String name, Class<T> scalarType);
|
||||
|
||||
/**
|
||||
* Returns a {@link List} configuration property. Entries which are not strings are converted to
|
||||
* their string representation.
|
||||
*
|
||||
* @see ConfigProperties#getList(String name)
|
||||
* @return a {@link List} configuration property or {@code defaultValue} if a property with {@code
|
||||
* name} has not been configured
|
||||
* @throws ConfigurationException if the property is not a valid sequence of scalars
|
||||
*/
|
||||
default <T> List<T> getScalarList(String name, Class<T> scalarType, List<T> defaultValue) {
|
||||
return defaultIfNull(getScalarList(name, scalarType), defaultValue);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link StructuredConfigProperties} configuration property.
|
||||
*
|
||||
* @return a map-valued configuration property, or {@code null} if {@code name} has not been
|
||||
* configured
|
||||
* @throws ConfigurationException if the property is not a mapping
|
||||
*/
|
||||
@Nullable
|
||||
StructuredConfigProperties getStructured(String name);
|
||||
|
||||
/**
|
||||
* Returns a list of {@link StructuredConfigProperties} configuration property.
|
||||
*
|
||||
* @return a list of map-valued configuration property, or {@code null} if {@code name} has not
|
||||
* been configured
|
||||
* @throws ConfigurationException if the property is not a sequence of mappings
|
||||
*/
|
||||
@Nullable
|
||||
List<StructuredConfigProperties> getStructuredList(String name);
|
||||
|
||||
/**
|
||||
* Returns a set of all configuration property keys.
|
||||
*
|
||||
* @return the configuration property keys
|
||||
*/
|
||||
Set<String> getPropertyKeys();
|
||||
}
|
|
@ -10,7 +10,9 @@ import io.opentelemetry.api.GlobalOpenTelemetry;
|
|||
import io.opentelemetry.api.OpenTelemetry;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import javax.annotation.Nullable;
|
||||
import javax.annotation.concurrent.Immutable;
|
||||
|
||||
/**
|
||||
|
@ -43,8 +45,12 @@ public abstract class AutoConfiguredOpenTelemetrySdk {
|
|||
}
|
||||
|
||||
static AutoConfiguredOpenTelemetrySdk create(
|
||||
OpenTelemetrySdk sdk, Resource resource, ConfigProperties config) {
|
||||
return new AutoValue_AutoConfiguredOpenTelemetrySdk(sdk, resource, config);
|
||||
OpenTelemetrySdk sdk,
|
||||
Resource resource,
|
||||
@Nullable ConfigProperties config,
|
||||
@Nullable StructuredConfigProperties structuredConfigProperties) {
|
||||
return new AutoValue_AutoConfiguredOpenTelemetrySdk(
|
||||
sdk, resource, config, structuredConfigProperties);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -60,8 +66,23 @@ public abstract class AutoConfiguredOpenTelemetrySdk {
|
|||
/** Returns the {@link Resource} that was auto-configured. */
|
||||
abstract Resource getResource();
|
||||
|
||||
/** Returns the {@link ConfigProperties} used for auto-configuration. */
|
||||
/**
|
||||
* Returns the {@link ConfigProperties} used for auto-configuration, or {@code null} if file
|
||||
* configuration was used.
|
||||
*
|
||||
* @see #getStructuredConfig()
|
||||
*/
|
||||
@Nullable
|
||||
abstract ConfigProperties getConfig();
|
||||
|
||||
/**
|
||||
* Returns the {@link StructuredConfigProperties} used for auto-configuration, or {@code null} if
|
||||
* file configuration was not used.
|
||||
*
|
||||
* @see #getConfig()
|
||||
*/
|
||||
@Nullable
|
||||
abstract StructuredConfigProperties getStructuredConfig();
|
||||
|
||||
AutoConfiguredOpenTelemetrySdk() {}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
|||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.AutoConfigureListener;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.logs.LogRecordProcessor;
|
||||
import io.opentelemetry.sdk.logs.SdkLoggerProvider;
|
||||
import io.opentelemetry.sdk.logs.SdkLoggerProviderBuilder;
|
||||
|
@ -505,7 +506,7 @@ public final class AutoConfiguredOpenTelemetrySdkBuilder implements AutoConfigur
|
|||
maybeSetAsGlobal(openTelemetrySdk);
|
||||
callAutoConfigureListeners(spiHelper, openTelemetrySdk);
|
||||
|
||||
return AutoConfiguredOpenTelemetrySdk.create(openTelemetrySdk, resource, config);
|
||||
return AutoConfiguredOpenTelemetrySdk.create(openTelemetrySdk, resource, config, null);
|
||||
} catch (RuntimeException e) {
|
||||
logger.info(
|
||||
"Error encountered during autoconfiguration. Closing partially configured components.");
|
||||
|
@ -546,11 +547,21 @@ public final class AutoConfiguredOpenTelemetrySdkBuilder implements AutoConfigur
|
|||
try {
|
||||
Class<?> configurationFactory =
|
||||
Class.forName("io.opentelemetry.sdk.extension.incubator.fileconfig.FileConfiguration");
|
||||
Method parseAndCreate = configurationFactory.getMethod("parseAndCreate", InputStream.class);
|
||||
OpenTelemetrySdk sdk = (OpenTelemetrySdk) parseAndCreate.invoke(null, fis);
|
||||
Method parse = configurationFactory.getMethod("parse", InputStream.class);
|
||||
Object model = parse.invoke(null, fis);
|
||||
Class<?> openTelemetryConfiguration =
|
||||
Class.forName(
|
||||
"io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration");
|
||||
Method create = configurationFactory.getMethod("create", openTelemetryConfiguration);
|
||||
OpenTelemetrySdk sdk = (OpenTelemetrySdk) create.invoke(null, model);
|
||||
Method toConfigProperties =
|
||||
configurationFactory.getMethod("toConfigProperties", openTelemetryConfiguration);
|
||||
StructuredConfigProperties structuredConfigProperties =
|
||||
(StructuredConfigProperties) toConfigProperties.invoke(null, model);
|
||||
// Note: can't access file configuration resource without reflection so setting a dummy
|
||||
// resource
|
||||
return AutoConfiguredOpenTelemetrySdk.create(sdk, Resource.getDefault(), config);
|
||||
return AutoConfiguredOpenTelemetrySdk.create(
|
||||
sdk, Resource.getDefault(), null, structuredConfigProperties);
|
||||
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException e) {
|
||||
throw new ConfigurationException(
|
||||
"Error configuring from file. Is opentelemetry-sdk-extension-incubator on the classpath?",
|
||||
|
|
|
@ -8,9 +8,11 @@ package io.opentelemetry.sdk.autoconfigure.internal;
|
|||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdkBuilder;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.function.Function;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
|
||||
|
@ -20,7 +22,12 @@ public final class AutoConfigureUtil {
|
|||
|
||||
private AutoConfigureUtil() {}
|
||||
|
||||
/** Returns the {@link ConfigProperties} used for auto-configuration. */
|
||||
/**
|
||||
* Returns the {@link ConfigProperties} used for auto-configuration.
|
||||
*
|
||||
* @return the config properties, or {@code null} if file based configuration is used
|
||||
*/
|
||||
@Nullable
|
||||
public static ConfigProperties getConfig(
|
||||
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
|
||||
try {
|
||||
|
@ -33,6 +40,25 @@ public final class AutoConfigureUtil {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link StructuredConfigProperties} used for auto-configuration when file based
|
||||
* configuration is used.
|
||||
*
|
||||
* @return the config properties, or {@code null} if file based configuration is NOT used
|
||||
*/
|
||||
@Nullable
|
||||
public static StructuredConfigProperties getStructuredConfig(
|
||||
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk) {
|
||||
try {
|
||||
Method method = AutoConfiguredOpenTelemetrySdk.class.getDeclaredMethod("getStructuredConfig");
|
||||
method.setAccessible(true);
|
||||
return (StructuredConfigProperties) method.invoke(autoConfiguredOpenTelemetrySdk);
|
||||
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
|
||||
throw new IllegalStateException(
|
||||
"Error calling getStructuredConfig on AutoConfiguredOpenTelemetrySdk", e);
|
||||
}
|
||||
}
|
||||
|
||||
/** Sets the {@link ComponentLoader} to be used in the auto-configuration process. */
|
||||
public static AutoConfiguredOpenTelemetrySdkBuilder setComponentLoader(
|
||||
AutoConfiguredOpenTelemetrySdkBuilder builder, ComponentLoader componentLoader) {
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
package io.opentelemetry.sdk.autoconfigure;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
|
@ -29,6 +28,7 @@ import io.opentelemetry.sdk.OpenTelemetrySdk;
|
|||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.logs.internal.SdkEventLoggerProvider;
|
||||
import io.opentelemetry.sdk.resources.Resource;
|
||||
import io.opentelemetry.sdk.trace.SdkTracerProvider;
|
||||
|
@ -67,7 +67,11 @@ class FileConfigurationTest {
|
|||
+ " processors:\n"
|
||||
+ " - simple:\n"
|
||||
+ " exporter:\n"
|
||||
+ " console: {}\n";
|
||||
+ " console: {}\n"
|
||||
+ "other:\n"
|
||||
+ " str_key: str_value\n"
|
||||
+ " map_key:\n"
|
||||
+ " str_key1: str_value1\n";
|
||||
configFilePath = tempDir.resolve("otel-config.yaml");
|
||||
Files.write(configFilePath, yaml.getBytes(StandardCharsets.UTF_8));
|
||||
GlobalOpenTelemetry.resetForTest();
|
||||
|
@ -183,4 +187,27 @@ class FileConfigurationTest {
|
|||
.isInstanceOf(ConfigurationException.class)
|
||||
.hasMessage("Unrecognized span exporter(s): [foo]");
|
||||
}
|
||||
|
||||
@Test
|
||||
void configFile_StructuredConfigProperties() {
|
||||
ConfigProperties config =
|
||||
DefaultConfigProperties.createFromMap(
|
||||
Collections.singletonMap("otel.experimental.config.file", configFilePath.toString()));
|
||||
|
||||
AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetrySdk =
|
||||
AutoConfiguredOpenTelemetrySdk.builder().setConfig(config).setResultAsGlobal().build();
|
||||
OpenTelemetrySdk openTelemetrySdk = autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk();
|
||||
cleanup.addCloseable(openTelemetrySdk);
|
||||
|
||||
// getConfig() should return ExtendedConfigProperties generic representation of the config file
|
||||
StructuredConfigProperties structuredConfigProps =
|
||||
autoConfiguredOpenTelemetrySdk.getStructuredConfig();
|
||||
assertThat(structuredConfigProps).isNotNull();
|
||||
StructuredConfigProperties otherProps = structuredConfigProps.getStructured("other");
|
||||
assertThat(otherProps).isNotNull();
|
||||
assertThat(otherProps.getString("str_key")).isEqualTo("str_value");
|
||||
StructuredConfigProperties otherMapKeyProps = otherProps.getStructured("map_key");
|
||||
assertThat(otherMapKeyProps).isNotNull();
|
||||
assertThat(otherMapKeyProps.getString("str_key1")).isEqualTo("str_value1");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,8 +5,14 @@
|
|||
|
||||
package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import java.io.Closeable;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
final class FileConfigUtil {
|
||||
|
@ -27,4 +33,54 @@ final class FileConfigUtil {
|
|||
}
|
||||
return object;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find a registered {@link ComponentProvider} which {@link ComponentProvider#getType()} matching
|
||||
* {@code type}, {@link ComponentProvider#getName()} matching {@code name}, and call {@link
|
||||
* ComponentProvider#create(StructuredConfigProperties)} with the given {@code model}.
|
||||
*
|
||||
* @throws ConfigurationException if no matching providers are found, or if multiple are found
|
||||
* (i.e. conflict), or if {@link ComponentProvider#create(StructuredConfigProperties)} throws
|
||||
*/
|
||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||
static <T> T loadComponent(SpiHelper spiHelper, Class<T> type, String name, Object model) {
|
||||
// TODO(jack-berg): cache loaded component providers
|
||||
List<ComponentProvider> componentProviders = spiHelper.load(ComponentProvider.class);
|
||||
List<ComponentProvider<?>> matchedProviders =
|
||||
componentProviders.stream()
|
||||
.map(
|
||||
(Function<ComponentProvider, ComponentProvider<?>>)
|
||||
componentProvider -> componentProvider)
|
||||
.filter(
|
||||
componentProvider ->
|
||||
componentProvider.getType() == type && name.equals(componentProvider.getName()))
|
||||
.collect(Collectors.toList());
|
||||
if (matchedProviders.isEmpty()) {
|
||||
throw new ConfigurationException(
|
||||
"No component provider detected for " + type.getName() + " with name \"" + name + "\".");
|
||||
}
|
||||
if (matchedProviders.size() > 1) {
|
||||
throw new ConfigurationException(
|
||||
"Component provider conflict. Multiple providers detected for "
|
||||
+ type.getName()
|
||||
+ " with name \""
|
||||
+ name
|
||||
+ "\": "
|
||||
+ componentProviders.stream()
|
||||
.map(provider -> provider.getClass().getName())
|
||||
.collect(Collectors.joining(",", "[", "]")));
|
||||
}
|
||||
// Exactly one matching component provider
|
||||
ComponentProvider<T> provider = (ComponentProvider<T>) matchedProviders.get(0);
|
||||
|
||||
// Map model to generic structured config properties
|
||||
StructuredConfigProperties config = FileConfiguration.toConfigProperties(model);
|
||||
|
||||
try {
|
||||
return provider.create(config);
|
||||
} catch (Throwable throwable) {
|
||||
throw new ConfigurationException(
|
||||
"Error configuring " + type.getName() + " with name \"" + name + "\"", throwable);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,10 +7,12 @@ package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
|||
|
||||
import com.fasterxml.jackson.annotation.JsonSetter;
|
||||
import com.fasterxml.jackson.annotation.Nulls;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import io.opentelemetry.sdk.OpenTelemetrySdk;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
|
@ -138,6 +140,23 @@ public final class FileConfiguration {
|
|||
return yaml.loadFromInputStream(inputStream);
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the {@code model} to a generic {@link StructuredConfigProperties}, which can be used to
|
||||
* read configuration not part of the model.
|
||||
*
|
||||
* @param model the configuration model
|
||||
* @return a generic {@link StructuredConfigProperties} representation of the model
|
||||
*/
|
||||
public static StructuredConfigProperties toConfigProperties(OpenTelemetryConfiguration model) {
|
||||
return toConfigProperties((Object) model);
|
||||
}
|
||||
|
||||
static StructuredConfigProperties toConfigProperties(Object model) {
|
||||
Map<String, Object> configurationMap =
|
||||
MAPPER.convertValue(model, new TypeReference<Map<String, Object>>() {});
|
||||
return YamlStructuredConfigProperties.create(configurationMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link StandardConstructor} which substitutes environment variables.
|
||||
*
|
||||
|
|
|
@ -5,12 +5,8 @@
|
|||
|
||||
package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.NamedSpiManager;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ConfigurableMetricReaderProvider;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.DefaultConfigProperties;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.PeriodicMetricReader;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.Prometheus;
|
||||
|
@ -19,9 +15,7 @@ import io.opentelemetry.sdk.metrics.export.MetricReader;
|
|||
import io.opentelemetry.sdk.metrics.export.PeriodicMetricReaderBuilder;
|
||||
import java.io.Closeable;
|
||||
import java.time.Duration;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
final class MetricReaderFactory
|
||||
|
@ -77,25 +71,10 @@ final class MetricReaderFactory
|
|||
}
|
||||
Prometheus prometheusModel = exporterModel.getPrometheus();
|
||||
if (prometheusModel != null) {
|
||||
// Translate from file configuration scheme to environment variable scheme. This is
|
||||
// ultimately
|
||||
// interpreted by PrometheusMetricReaderProvider, but we want to avoid the dependency on
|
||||
// opentelemetry-exporter-prometheus
|
||||
Map<String, String> properties = new HashMap<>();
|
||||
if (prometheusModel.getHost() != null) {
|
||||
properties.put("otel.exporter.prometheus.host", prometheusModel.getHost());
|
||||
}
|
||||
if (prometheusModel.getPort() != null) {
|
||||
properties.put(
|
||||
"otel.exporter.prometheus.port", String.valueOf(prometheusModel.getPort()));
|
||||
}
|
||||
|
||||
ConfigProperties configProperties = DefaultConfigProperties.createFromMap(properties);
|
||||
return FileConfigUtil.addAndReturn(
|
||||
closeables,
|
||||
FileConfigUtil.assertNotNull(
|
||||
metricReaderSpiManager(configProperties, spiHelper).getByName("prometheus"),
|
||||
"prometheus reader"));
|
||||
MetricReader metricReader =
|
||||
FileConfigUtil.loadComponent(
|
||||
spiHelper, MetricReader.class, "prometheus", prometheusModel);
|
||||
return FileConfigUtil.addAndReturn(closeables, metricReader);
|
||||
}
|
||||
|
||||
throw new ConfigurationException("prometheus is the only currently supported pull reader");
|
||||
|
@ -103,13 +82,4 @@ final class MetricReaderFactory
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static NamedSpiManager<io.opentelemetry.sdk.metrics.export.MetricReader>
|
||||
metricReaderSpiManager(ConfigProperties config, SpiHelper spiHelper) {
|
||||
return spiHelper.loadConfigurable(
|
||||
ConfigurableMetricReaderProvider.class,
|
||||
ConfigurableMetricReaderProvider::getName,
|
||||
ConfigurableMetricReaderProvider::createMetricReader,
|
||||
config);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,280 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
||||
|
||||
import static java.util.stream.Collectors.joining;
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.StringJoiner;
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Implementation of {@link StructuredConfigProperties} which uses a file configuration model as a
|
||||
* source.
|
||||
*
|
||||
* @see #getStructured(String) Accessing nested maps
|
||||
* @see #getStructuredList(String) Accessing lists of maps
|
||||
* @see FileConfiguration#toConfigProperties(Object) Converting configuration model to properties
|
||||
*/
|
||||
final class YamlStructuredConfigProperties implements StructuredConfigProperties {
|
||||
|
||||
/** Values are {@link #isPrimitive(Object)}, {@link List} of scalars. */
|
||||
private final Map<String, Object> simpleEntries;
|
||||
|
||||
private final Map<String, List<StructuredConfigProperties>> listEntries;
|
||||
private final Map<String, StructuredConfigProperties> mapEntries;
|
||||
|
||||
private YamlStructuredConfigProperties(
|
||||
Map<String, Object> simpleEntries,
|
||||
Map<String, List<StructuredConfigProperties>> listEntries,
|
||||
Map<String, StructuredConfigProperties> mapEntries) {
|
||||
this.simpleEntries = simpleEntries;
|
||||
this.listEntries = listEntries;
|
||||
this.mapEntries = mapEntries;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link YamlStructuredConfigProperties} from the {@code properties} map.
|
||||
*
|
||||
* <p>{@code properties} is expected to be the output of YAML parsing (i.e. with Jackson {@link
|
||||
* com.fasterxml.jackson.databind.ObjectMapper}), and have values which are scalars, lists of
|
||||
* scalars, lists of maps, and maps.
|
||||
*
|
||||
* @see FileConfiguration#toConfigProperties(OpenTelemetryConfiguration)
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
static YamlStructuredConfigProperties create(Map<String, Object> properties) {
|
||||
Map<String, Object> simpleEntries = new HashMap<>();
|
||||
Map<String, List<StructuredConfigProperties>> listEntries = new HashMap<>();
|
||||
Map<String, StructuredConfigProperties> mapEntries = new HashMap<>();
|
||||
for (Map.Entry<String, Object> entry : properties.entrySet()) {
|
||||
String key = entry.getKey();
|
||||
Object value = entry.getValue();
|
||||
if (isPrimitive(value)) {
|
||||
simpleEntries.put(key, value);
|
||||
continue;
|
||||
}
|
||||
if (isPrimitiveList(value)) {
|
||||
simpleEntries.put(key, value);
|
||||
continue;
|
||||
}
|
||||
if (isListOfMaps(value)) {
|
||||
List<StructuredConfigProperties> list =
|
||||
((List<Map<String, Object>>) value)
|
||||
.stream().map(YamlStructuredConfigProperties::create).collect(toList());
|
||||
listEntries.put(key, list);
|
||||
continue;
|
||||
}
|
||||
if (isMap(value)) {
|
||||
YamlStructuredConfigProperties configProperties =
|
||||
YamlStructuredConfigProperties.create((Map<String, Object>) value);
|
||||
mapEntries.put(key, configProperties);
|
||||
continue;
|
||||
}
|
||||
throw new ConfigurationException(
|
||||
"Unable to initialize ExtendedConfigProperties. Key \""
|
||||
+ key
|
||||
+ "\" has unrecognized object type "
|
||||
+ value.getClass().getName());
|
||||
}
|
||||
return new YamlStructuredConfigProperties(simpleEntries, listEntries, mapEntries);
|
||||
}
|
||||
|
||||
private static boolean isPrimitiveList(Object object) {
|
||||
if (object instanceof List) {
|
||||
List<?> list = (List<?>) object;
|
||||
return list.stream().allMatch(YamlStructuredConfigProperties::isPrimitive);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isPrimitive(Object object) {
|
||||
return object instanceof String
|
||||
|| object instanceof Integer
|
||||
|| object instanceof Long
|
||||
|| object instanceof Float
|
||||
|| object instanceof Double
|
||||
|| object instanceof Boolean;
|
||||
}
|
||||
|
||||
private static boolean isListOfMaps(Object object) {
|
||||
if (object instanceof List) {
|
||||
List<?> list = (List<?>) object;
|
||||
return list.stream()
|
||||
.allMatch(
|
||||
entry ->
|
||||
entry instanceof Map
|
||||
&& ((Map<?, ?>) entry)
|
||||
.keySet().stream().allMatch(key -> key instanceof String));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean isMap(Object object) {
|
||||
if (object instanceof Map) {
|
||||
Map<?, ?> map = (Map<?, ?>) object;
|
||||
return map.keySet().stream().allMatch(entry -> entry instanceof String);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public String getString(String name) {
|
||||
return stringOrNull(simpleEntries.get(name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Boolean getBoolean(String name) {
|
||||
return booleanOrNull(simpleEntries.get(name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Integer getInt(String name) {
|
||||
Object value = simpleEntries.get(name);
|
||||
if (value instanceof Integer) {
|
||||
return (Integer) value;
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return ((Long) value).intValue();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Long getLong(String name) {
|
||||
return longOrNull(simpleEntries.get(name));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Double getDouble(String name) {
|
||||
return doubleOrNull(simpleEntries.get(name));
|
||||
}
|
||||
|
||||
private static final Set<Class<?>> SUPPORTED_SCALAR_TYPES =
|
||||
Collections.unmodifiableSet(
|
||||
new HashSet<>(Arrays.asList(String.class, Boolean.class, Long.class, Double.class)));
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> List<T> getScalarList(String name, Class<T> scalarType) {
|
||||
if (!SUPPORTED_SCALAR_TYPES.contains(scalarType)) {
|
||||
throw new ConfigurationException(
|
||||
"Unsupported scalar type "
|
||||
+ scalarType.getName()
|
||||
+ ". Supported types include "
|
||||
+ SUPPORTED_SCALAR_TYPES.stream()
|
||||
.map(Class::getName)
|
||||
.collect(joining(",", "[", "]")));
|
||||
}
|
||||
Object value = simpleEntries.get(name);
|
||||
if (value instanceof List) {
|
||||
return (List<T>)
|
||||
((List<Object>) value)
|
||||
.stream()
|
||||
.map(
|
||||
entry -> {
|
||||
if (scalarType == String.class) {
|
||||
return stringOrNull(entry);
|
||||
} else if (scalarType == Boolean.class) {
|
||||
return booleanOrNull(entry);
|
||||
} else if (scalarType == Long.class) {
|
||||
return longOrNull(entry);
|
||||
} else if (scalarType == Double.class) {
|
||||
return doubleOrNull(entry);
|
||||
}
|
||||
return null;
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(toList());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static String stringOrNull(@Nullable Object value) {
|
||||
if (value instanceof String) {
|
||||
return (String) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Boolean booleanOrNull(@Nullable Object value) {
|
||||
if (value instanceof Boolean) {
|
||||
return (Boolean) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Long longOrNull(@Nullable Object value) {
|
||||
if (value instanceof Integer) {
|
||||
return ((Integer) value).longValue();
|
||||
}
|
||||
if (value instanceof Long) {
|
||||
return (Long) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static Double doubleOrNull(@Nullable Object value) {
|
||||
if (value instanceof Float) {
|
||||
return ((Float) value).doubleValue();
|
||||
}
|
||||
if (value instanceof Double) {
|
||||
return (Double) value;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public StructuredConfigProperties getStructured(String name) {
|
||||
return mapEntries.get(name);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public List<StructuredConfigProperties> getStructuredList(String name) {
|
||||
return listEntries.get(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Set<String> getPropertyKeys() {
|
||||
Set<String> keys = new HashSet<>();
|
||||
keys.addAll(simpleEntries.keySet());
|
||||
keys.addAll(listEntries.keySet());
|
||||
keys.addAll(mapEntries.keySet());
|
||||
return Collections.unmodifiableSet(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringJoiner joiner = new StringJoiner(", ", "YamlStructuredConfigProperties{", "}");
|
||||
simpleEntries.forEach((key, value) -> joiner.add(key + "=" + value));
|
||||
listEntries.forEach((key, value) -> joiner.add(key + "=" + value));
|
||||
mapEntries.forEach((key, value) -> joiner.add(key + "=" + value));
|
||||
return joiner.toString();
|
||||
}
|
||||
}
|
|
@ -6,10 +6,7 @@
|
|||
package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
||||
|
||||
import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
|
@ -18,9 +15,8 @@ import io.opentelemetry.exporter.otlp.metrics.OtlpGrpcMetricExporter;
|
|||
import io.opentelemetry.exporter.prometheus.PrometheusHttpServer;
|
||||
import io.opentelemetry.internal.testing.CleanupExtension;
|
||||
import io.opentelemetry.sdk.autoconfigure.internal.SpiHelper;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.ConfigurationException;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ConfigurableMetricReaderProvider;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricExporter;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.MetricReader;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OtlpMetric;
|
||||
|
@ -36,7 +32,6 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.RegisterExtension;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
class MetricReaderFactoryTest {
|
||||
|
||||
|
@ -141,14 +136,8 @@ class MetricReaderFactoryTest {
|
|||
cleanup.addCloseables(closeables);
|
||||
|
||||
assertThat(reader.toString()).isEqualTo(expectedReader.toString());
|
||||
|
||||
ArgumentCaptor<ConfigProperties> configCaptor = ArgumentCaptor.forClass(ConfigProperties.class);
|
||||
verify(spiHelper)
|
||||
.loadConfigurable(
|
||||
eq(ConfigurableMetricReaderProvider.class), any(), any(), configCaptor.capture());
|
||||
ConfigProperties configProperties = configCaptor.getValue();
|
||||
assertThat(configProperties.getString("otel.exporter.prometheus.host")).isNull();
|
||||
assertThat(configProperties.getInt("otel.exporter.prometheus.port")).isEqualTo(port);
|
||||
// TODO(jack-berg): validate prometheus component provider was invoked with correct arguments
|
||||
verify(spiHelper).load(ComponentProvider.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -178,14 +167,8 @@ class MetricReaderFactoryTest {
|
|||
cleanup.addCloseables(closeables);
|
||||
|
||||
assertThat(reader.toString()).isEqualTo(expectedReader.toString());
|
||||
|
||||
ArgumentCaptor<ConfigProperties> configCaptor = ArgumentCaptor.forClass(ConfigProperties.class);
|
||||
verify(spiHelper)
|
||||
.loadConfigurable(
|
||||
eq(ConfigurableMetricReaderProvider.class), any(), any(), configCaptor.capture());
|
||||
ConfigProperties configProperties = configCaptor.getValue();
|
||||
assertThat(configProperties.getString("otel.exporter.prometheus.host")).isEqualTo("localhost");
|
||||
assertThat(configProperties.getInt("otel.exporter.prometheus.port")).isEqualTo(port);
|
||||
// TODO(jack-berg): validate prometheus component provider was invoked with correct arguments
|
||||
verify(spiHelper).load(ComponentProvider.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -0,0 +1,200 @@
|
|||
/*
|
||||
* Copyright The OpenTelemetry Authors
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
package io.opentelemetry.sdk.extension.incubator.fileconfig;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import io.opentelemetry.sdk.autoconfigure.spi.internal.StructuredConfigProperties;
|
||||
import io.opentelemetry.sdk.extension.incubator.fileconfig.internal.model.OpenTelemetryConfiguration;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class YamlStructuredConfigPropertiesTest {
|
||||
|
||||
private static final String extendedSchema =
|
||||
"file_format: \"0.1\"\n"
|
||||
+ "disabled: false\n"
|
||||
+ "\n"
|
||||
+ "resource:\n"
|
||||
+ " attributes:\n"
|
||||
+ " service.name: \"unknown_service\"\n"
|
||||
+ "\n"
|
||||
+ "other:\n"
|
||||
+ " str_key: str_value\n"
|
||||
+ " int_key: 1\n"
|
||||
+ " float_key: 1.1\n"
|
||||
+ " bool_key: true\n"
|
||||
+ " str_list_key: [val1, val2]\n"
|
||||
+ " int_list_key: [1, 2]\n"
|
||||
+ " float_list_key: [1.1, 2.2]\n"
|
||||
+ " bool_list_key: [true, false]\n"
|
||||
+ " mixed_list_key: [val1, 1, 1.1, true]\n"
|
||||
+ " map_key:\n"
|
||||
+ " str_key1: str_value1\n"
|
||||
+ " int_key1: 2\n"
|
||||
+ " map_key1:\n"
|
||||
+ " str_key2: str_value2\n"
|
||||
+ " int_key2: 3\n"
|
||||
+ " list_key:\n"
|
||||
+ " - str_key1: str_value1\n"
|
||||
+ " int_key1: 2\n"
|
||||
+ " map_key1:\n"
|
||||
+ " str_key2: str_value2\n"
|
||||
+ " int_key2: 3\n"
|
||||
+ " - str_key1: str_value1\n"
|
||||
+ " int_key1: 2";
|
||||
|
||||
private StructuredConfigProperties structuredConfigProps;
|
||||
|
||||
@BeforeEach
|
||||
void setup() {
|
||||
OpenTelemetryConfiguration configuration =
|
||||
FileConfiguration.parse(
|
||||
new ByteArrayInputStream(extendedSchema.getBytes(StandardCharsets.UTF_8)));
|
||||
structuredConfigProps = FileConfiguration.toConfigProperties(configuration);
|
||||
}
|
||||
|
||||
@Test
|
||||
void configurationSchema() {
|
||||
// Validate can read file configuration schema properties
|
||||
assertThat(structuredConfigProps.getString("file_format")).isEqualTo("0.1");
|
||||
StructuredConfigProperties resourceProps = structuredConfigProps.getStructured("resource");
|
||||
assertThat(resourceProps).isNotNull();
|
||||
StructuredConfigProperties resourceAttributesProps = resourceProps.getStructured("attributes");
|
||||
assertThat(resourceAttributesProps).isNotNull();
|
||||
assertThat(resourceAttributesProps.getString("service.name")).isEqualTo("unknown_service");
|
||||
}
|
||||
|
||||
@Test
|
||||
void additionalProperties() {
|
||||
assertThat(structuredConfigProps.getPropertyKeys())
|
||||
.isEqualTo(ImmutableSet.of("file_format", "disabled", "resource", "other"));
|
||||
|
||||
// Validate can read properties not part of configuration schema
|
||||
// .other
|
||||
StructuredConfigProperties otherProps = structuredConfigProps.getStructured("other");
|
||||
assertThat(otherProps).isNotNull();
|
||||
assertThat(otherProps.getPropertyKeys())
|
||||
.isEqualTo(
|
||||
ImmutableSet.of(
|
||||
"str_key",
|
||||
"int_key",
|
||||
"float_key",
|
||||
"bool_key",
|
||||
"str_list_key",
|
||||
"int_list_key",
|
||||
"float_list_key",
|
||||
"bool_list_key",
|
||||
"mixed_list_key",
|
||||
"map_key",
|
||||
"list_key"));
|
||||
assertThat(otherProps.getString("str_key")).isEqualTo("str_value");
|
||||
assertThat(otherProps.getInt("int_key")).isEqualTo(1);
|
||||
assertThat(otherProps.getLong("int_key")).isEqualTo(1);
|
||||
assertThat(otherProps.getDouble("float_key")).isEqualTo(1.1);
|
||||
assertThat(otherProps.getBoolean("bool_key")).isTrue();
|
||||
assertThat(otherProps.getScalarList("str_list_key", String.class))
|
||||
.isEqualTo(Arrays.asList("val1", "val2"));
|
||||
assertThat(otherProps.getScalarList("int_list_key", Long.class))
|
||||
.isEqualTo(Arrays.asList(1L, 2L));
|
||||
assertThat(otherProps.getScalarList("float_list_key", Double.class))
|
||||
.isEqualTo(Arrays.asList(1.1d, 2.2d));
|
||||
assertThat(otherProps.getScalarList("bool_list_key", Boolean.class))
|
||||
.isEqualTo(Arrays.asList(true, false));
|
||||
// If reading a scalar list which is mixed, entries which are not aligned with the requested
|
||||
// type are filtered out
|
||||
assertThat(otherProps.getScalarList("mixed_list_key", String.class))
|
||||
.isEqualTo(Collections.singletonList("val1"));
|
||||
assertThat(otherProps.getScalarList("mixed_list_key", Long.class))
|
||||
.isEqualTo(Collections.singletonList(1L));
|
||||
assertThat(otherProps.getScalarList("mixed_list_key", Double.class))
|
||||
.isEqualTo(Collections.singletonList(1.1d));
|
||||
assertThat(otherProps.getScalarList("mixed_list_key", Boolean.class))
|
||||
.isEqualTo(Collections.singletonList(true));
|
||||
|
||||
// .other.map_key
|
||||
StructuredConfigProperties otherMapKeyProps = otherProps.getStructured("map_key");
|
||||
assertThat(otherMapKeyProps).isNotNull();
|
||||
assertThat(otherMapKeyProps.getPropertyKeys())
|
||||
.isEqualTo(ImmutableSet.of("str_key1", "int_key1", "map_key1"));
|
||||
assertThat(otherMapKeyProps.getString("str_key1")).isEqualTo("str_value1");
|
||||
assertThat(otherMapKeyProps.getInt("int_key1")).isEqualTo(2);
|
||||
// other.map_key.map_key1
|
||||
StructuredConfigProperties otherMapKeyMapKey1Props = otherMapKeyProps.getStructured("map_key1");
|
||||
assertThat(otherMapKeyMapKey1Props).isNotNull();
|
||||
assertThat(otherMapKeyMapKey1Props.getPropertyKeys())
|
||||
.isEqualTo(ImmutableSet.of("str_key2", "int_key2"));
|
||||
assertThat(otherMapKeyMapKey1Props.getString("str_key2")).isEqualTo("str_value2");
|
||||
assertThat(otherMapKeyMapKey1Props.getInt("int_key2")).isEqualTo(3);
|
||||
|
||||
// .other.list_key
|
||||
List<StructuredConfigProperties> listKey = otherProps.getStructuredList("list_key");
|
||||
assertThat(listKey).hasSize(2);
|
||||
StructuredConfigProperties listKeyProps1 = listKey.get(0);
|
||||
assertThat(listKeyProps1.getPropertyKeys())
|
||||
.isEqualTo(ImmutableSet.of("str_key1", "int_key1", "map_key1"));
|
||||
assertThat(listKeyProps1.getString("str_key1")).isEqualTo("str_value1");
|
||||
assertThat(listKeyProps1.getInt("int_key1")).isEqualTo(2);
|
||||
// .other.list_key[0]
|
||||
StructuredConfigProperties listKeyProps1MapKeyProps = listKeyProps1.getStructured("map_key1");
|
||||
assertThat(listKeyProps1MapKeyProps).isNotNull();
|
||||
assertThat(listKeyProps1MapKeyProps.getPropertyKeys())
|
||||
.isEqualTo(ImmutableSet.of("str_key2", "int_key2"));
|
||||
assertThat(listKeyProps1MapKeyProps.getString("str_key2")).isEqualTo("str_value2");
|
||||
assertThat(listKeyProps1MapKeyProps.getInt("int_key2")).isEqualTo(3);
|
||||
// .other.list_key[1]
|
||||
StructuredConfigProperties listKeyProps2 = listKey.get(1);
|
||||
assertThat(listKeyProps2.getPropertyKeys()).isEqualTo(ImmutableSet.of("str_key1", "int_key1"));
|
||||
assertThat(listKeyProps2.getString("str_key1")).isEqualTo("str_value1");
|
||||
assertThat(listKeyProps2.getInt("int_key1")).isEqualTo(2);
|
||||
}
|
||||
|
||||
@Test
|
||||
void defaults() {
|
||||
assertThat(structuredConfigProps.getString("foo", "bar")).isEqualTo("bar");
|
||||
assertThat(structuredConfigProps.getInt("foo", 1)).isEqualTo(1);
|
||||
assertThat(structuredConfigProps.getLong("foo", 1)).isEqualTo(1);
|
||||
assertThat(structuredConfigProps.getDouble("foo", 1.1)).isEqualTo(1.1);
|
||||
assertThat(structuredConfigProps.getBoolean("foo", true)).isTrue();
|
||||
assertThat(
|
||||
structuredConfigProps.getScalarList(
|
||||
"foo", String.class, Collections.singletonList("bar")))
|
||||
.isEqualTo(Collections.singletonList("bar"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void missingKeys() {
|
||||
assertThat(structuredConfigProps.getString("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getInt("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getLong("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getDouble("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getBoolean("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getScalarList("foo", String.class)).isNull();
|
||||
assertThat(structuredConfigProps.getStructured("foo")).isNull();
|
||||
assertThat(structuredConfigProps.getStructuredList("foo")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
void wrongType() {
|
||||
StructuredConfigProperties otherProps = structuredConfigProps.getStructured("other");
|
||||
assertThat(otherProps).isNotNull();
|
||||
|
||||
assertThat(otherProps.getString("int_key")).isNull();
|
||||
assertThat(otherProps.getInt("str_key")).isNull();
|
||||
assertThat(otherProps.getLong("str_key")).isNull();
|
||||
assertThat(otherProps.getDouble("str_key")).isNull();
|
||||
assertThat(otherProps.getBoolean("str_key")).isNull();
|
||||
assertThat(otherProps.getScalarList("str_key", String.class)).isNull();
|
||||
assertThat(otherProps.getStructured("str_key")).isNull();
|
||||
assertThat(otherProps.getStructuredList("str_key")).isNull();
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue