Skip to content

Commit 47b401d

Browse files
jackshiraziCopilot
andauthored
[dynamic control] Create providers from SourceKind (#2749)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent d79c574 commit 47b401d

2 files changed

Lines changed: 160 additions & 5 deletions

File tree

dynamic-control/src/main/java/io/opentelemetry/contrib/dynamic/policy/source/SourceKind.java

Lines changed: 71 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,18 @@
55

66
package io.opentelemetry.contrib.dynamic.policy.source;
77

8+
import com.google.errorprone.annotations.Immutable;
9+
import io.opentelemetry.contrib.dynamic.policy.OpampPolicyProvider;
10+
import io.opentelemetry.contrib.dynamic.policy.PolicyProvider;
11+
import io.opentelemetry.contrib.dynamic.policy.PolicyValidator;
12+
import io.opentelemetry.contrib.dynamic.policy.registry.PolicySourceConfig;
13+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
14+
import java.util.List;
815
import java.util.Locale;
916
import java.util.Objects;
17+
import java.util.logging.Level;
18+
import java.util.logging.Logger;
19+
import javax.annotation.Nullable;
1020

1121
/**
1222
* Identifies where policy configuration is loaded from for registry initialization (e.g. local
@@ -15,21 +25,24 @@
1525
*/
1626
public enum SourceKind {
1727
/** Policies loaded from a local file (e.g. line-per-policy file). */
18-
FILE("file"),
28+
FILE("file", SourceKind::createNoProvider),
1929

2030
/** Policies delivered via OpAMP (remote management). */
21-
OPAMP("opamp"),
31+
OPAMP("opamp", SourceKind::createOpampProvider),
2232

2333
/** Policies fetched from an HTTP/HTTPS endpoint. */
24-
HTTP("http"),
34+
HTTP("http", SourceKind::createNoProvider),
2535

2636
/** User-defined or extension provider. */
27-
CUSTOM("custom");
37+
CUSTOM("custom", SourceKind::createNoProvider);
2838

2939
private final String configValue;
40+
private final ProviderCreator providerCreator;
41+
private static final Logger logger = Logger.getLogger(SourceKind.class.getName());
3042

31-
SourceKind(String configValue) {
43+
SourceKind(String configValue, ProviderCreator providerCreator) {
3244
this.configValue = configValue;
45+
this.providerCreator = providerCreator;
3346
}
3447

3548
/**
@@ -41,6 +54,59 @@ public String configValue() {
4154
return configValue;
4255
}
4356

57+
/**
58+
* Creates a runtime {@link PolicyProvider} for this source kind.
59+
*
60+
* <p>Provider creation is delegated to a per-kind method reference configured on each enum
61+
* constant.
62+
*/
63+
@Nullable
64+
public PolicyProvider createProvider(
65+
PolicySourceConfig source, ConfigProperties config, List<PolicyValidator> validators) {
66+
Objects.requireNonNull(source, "source cannot be null");
67+
Objects.requireNonNull(config, "config cannot be null");
68+
Objects.requireNonNull(validators, "validators cannot be null");
69+
SourceKind sourceKind = source.getKind();
70+
if (sourceKind != this) {
71+
throw new IllegalArgumentException(
72+
"Source kind mismatch: expected " + this + " but was " + sourceKind);
73+
}
74+
return providerCreator.create(source, config, validators);
75+
}
76+
77+
@Nullable
78+
private static PolicyProvider createNoProvider(
79+
PolicySourceConfig source, ConfigProperties config, List<PolicyValidator> validators) {
80+
return null;
81+
}
82+
83+
@Nullable
84+
private static PolicyProvider createOpampProvider(
85+
PolicySourceConfig source, ConfigProperties config, List<PolicyValidator> validators) {
86+
String location = source.getLocation();
87+
if (location == null || location.trim().isEmpty()) {
88+
return null;
89+
}
90+
try {
91+
return new OpampPolicyProvider(
92+
config, location, source.getFormat(), source.getMappings(), validators);
93+
} catch (IllegalArgumentException e) {
94+
logger.log(
95+
Level.FINE,
96+
"Skipping OpAMP provider creation due to invalid/missing OpAMP configuration: {0}",
97+
e.getMessage());
98+
return null;
99+
}
100+
}
101+
102+
@Immutable
103+
@FunctionalInterface
104+
private interface ProviderCreator {
105+
@Nullable
106+
PolicyProvider create(
107+
PolicySourceConfig source, ConfigProperties config, List<PolicyValidator> validators);
108+
}
109+
44110
/**
45111
* Parses the value used in JSON configuration. Leading and trailing whitespace is removed, then
46112
* the remainder is matched case-insensitively against {@link #configValue()} for each kind.

dynamic-control/src/test/java/io/opentelemetry/contrib/dynamic/policy/source/SourceKindTest.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,15 @@
77

88
import static org.assertj.core.api.Assertions.assertThat;
99
import static org.assertj.core.api.Assertions.assertThatThrownBy;
10+
import static org.mockito.Mockito.mock;
11+
import static org.mockito.Mockito.when;
1012

13+
import io.opentelemetry.contrib.dynamic.policy.PolicyProvider;
14+
import io.opentelemetry.contrib.dynamic.policy.PolicyValidator;
15+
import io.opentelemetry.contrib.dynamic.policy.registry.PolicySourceConfig;
16+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
17+
import java.util.Collections;
18+
import java.util.List;
1119
import org.junit.jupiter.api.Test;
1220

1321
class SourceKindTest {
@@ -44,4 +52,85 @@ void fromConfigValueRejectsUnknownValue() {
4452
.isInstanceOf(IllegalArgumentException.class)
4553
.hasMessage("Unknown source kind: unknown");
4654
}
55+
56+
@Test
57+
void createProviderReturnsNullForKindsWithoutProviderCreator() {
58+
PolicySourceConfig source = source(SourceKind.FILE, "ignored");
59+
ConfigProperties config = opampConfig();
60+
List<PolicyValidator> validators = Collections.emptyList();
61+
62+
assertThat(SourceKind.FILE.createProvider(source, config, validators)).isNull();
63+
assertThat(
64+
SourceKind.HTTP.createProvider(source(SourceKind.HTTP, "ignored"), config, validators))
65+
.isNull();
66+
assertThat(
67+
SourceKind.CUSTOM.createProvider(
68+
source(SourceKind.CUSTOM, "ignored"), config, validators))
69+
.isNull();
70+
}
71+
72+
@Test
73+
void opampCreateProviderReturnsNullWhenLocationMissing() {
74+
ConfigProperties config = opampConfig();
75+
List<PolicyValidator> validators = Collections.emptyList();
76+
77+
assertThat(SourceKind.OPAMP.createProvider(source(SourceKind.OPAMP, null), config, validators))
78+
.isNull();
79+
assertThat(SourceKind.OPAMP.createProvider(source(SourceKind.OPAMP, " "), config, validators))
80+
.isNull();
81+
}
82+
83+
@Test
84+
void opampCreateProviderReturnsProviderWhenLocationPresent() {
85+
PolicyProvider provider =
86+
SourceKind.OPAMP.createProvider(
87+
source(SourceKind.OPAMP, "vendor-specific"), opampConfig(), Collections.emptyList());
88+
89+
assertThat(provider).isNotNull();
90+
}
91+
92+
@Test
93+
void opampCreateProviderReturnsNullWhenRequiredConfigMissing() {
94+
ConfigProperties config = mock(ConfigProperties.class);
95+
when(config.getString("otel.opamp.service.url")).thenReturn(null);
96+
when(config.getString("otel.service.name")).thenReturn("test-service");
97+
when(config.getMap("otel.experimental.opamp.headers")).thenReturn(Collections.emptyMap());
98+
when(config.getMap("otel.resource.attributes")).thenReturn(Collections.emptyMap());
99+
100+
PolicyProvider provider =
101+
SourceKind.OPAMP.createProvider(
102+
source(SourceKind.OPAMP, "vendor-specific"), config, Collections.emptyList());
103+
104+
assertThat(provider).isNull();
105+
}
106+
107+
@Test
108+
void createProviderRejectsNullArguments() {
109+
ConfigProperties config = opampConfig();
110+
PolicySourceConfig source = source(SourceKind.OPAMP, "vendor-specific");
111+
List<PolicyValidator> validators = Collections.emptyList();
112+
113+
assertThatThrownBy(() -> SourceKind.OPAMP.createProvider(null, config, validators))
114+
.isInstanceOf(NullPointerException.class)
115+
.hasMessage("source cannot be null");
116+
assertThatThrownBy(() -> SourceKind.OPAMP.createProvider(source, null, validators))
117+
.isInstanceOf(NullPointerException.class)
118+
.hasMessage("config cannot be null");
119+
assertThatThrownBy(() -> SourceKind.OPAMP.createProvider(source, config, null))
120+
.isInstanceOf(NullPointerException.class)
121+
.hasMessage("validators cannot be null");
122+
}
123+
124+
private static PolicySourceConfig source(SourceKind kind, String location) {
125+
return new PolicySourceConfig(kind, SourceFormat.KEYVALUE, location, Collections.emptyList());
126+
}
127+
128+
private static ConfigProperties opampConfig() {
129+
ConfigProperties config = mock(ConfigProperties.class);
130+
when(config.getString("otel.opamp.service.url")).thenReturn("https://example.com");
131+
when(config.getString("otel.service.name")).thenReturn("test-service");
132+
when(config.getMap("otel.experimental.opamp.headers")).thenReturn(Collections.emptyMap());
133+
when(config.getMap("otel.resource.attributes")).thenReturn(Collections.emptyMap());
134+
return config;
135+
}
47136
}

0 commit comments

Comments
 (0)