Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ public void afterAgent(AutoConfiguredOpenTelemetrySdk autoConfiguredOpenTelemetr
"Successfully submitted SolarwindsAPM OpenTelemetry extensions initialization tasks");
} else {
autoConfiguredOpenTelemetrySdk.getOpenTelemetrySdk().shutdown();
SettingsManager.reset();
Comment thread
cleverchuk marked this conversation as resolved.
Resolved
}
}

Expand Down Expand Up @@ -90,11 +91,14 @@ private void executeStartupTasks() {
+ " ip addresses: "
+ ipAddresses);

CountDownLatch settingsLatch =
SettingsManager.initialize(
new HttpSettingsFetcher(
new HttpSettingsReader(new HttpSettingsReaderDelegate()), 60),
SamplingConfigProvider.getSamplingConfiguration());
if (!SettingsManager.isInitialized()) {
SettingsManager.initialize(
new HttpSettingsFetcher(
HttpSettingsReader.forSystemConfig(new HttpSettingsReaderDelegate()), 60),
SamplingConfigProvider.getSamplingConfiguration());
Comment thread
cleverchuk marked this conversation as resolved.
Resolved
}

CountDownLatch settingsLatch = SettingsManager.getSettingsAvailableLatch();
logger.debug("Initialized HostUtils");

ProfilerSetting profilerSetting =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import com.solarwinds.joboe.logging.Logger;
import com.solarwinds.joboe.logging.LoggerFactory;
import com.solarwinds.joboe.sampling.Settings;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

Expand All @@ -32,20 +33,44 @@ public class HttpSettingsReader implements SettingsReader {

private static final Logger logger = LoggerFactory.getLogger();

private static final String DEFAULT_COLLECTOR = "apm.collector.na-01.cloud.solarwinds.com";

private static final String DEFAULT_SERVICE_KEY = ":unknown-java";

private final HttpSettingsReaderDelegate delegate;

public HttpSettingsReader(HttpSettingsReaderDelegate delegate) {
private final Supplier<String> collector;

private final Supplier<String> serviceKey;

public static HttpSettingsReader forSystemConfig(HttpSettingsReaderDelegate delegate) {
return new HttpSettingsReader(
delegate,
() -> ConfigManager.getConfigOptional(ConfigProperty.AGENT_COLLECTOR, DEFAULT_COLLECTOR),
() ->
ConfigManager.getConfigOptional(ConfigProperty.AGENT_SERVICE_KEY, DEFAULT_SERVICE_KEY));
}

public static HttpSettingsReader forFileConfig(
HttpSettingsReaderDelegate delegate, String collector, String serviceKey) {
return new HttpSettingsReader(
delegate, () -> collector != null ? collector : DEFAULT_COLLECTOR, () -> serviceKey);
}

private HttpSettingsReader(
HttpSettingsReaderDelegate delegate,
Supplier<String> collector,
Supplier<String> serviceKey) {

this.delegate = delegate;
this.collector = collector;
this.serviceKey = serviceKey;
}

@Override
public Settings getSettings() {
String collectorUrl =
constructSettingsEndpoint(
ConfigManager.getConfigOptional(
ConfigProperty.AGENT_COLLECTOR, "apm.collector.na-01.cloud.solarwinds.com"));
String serviceKey =
ConfigManager.getConfigOptional(ConfigProperty.AGENT_SERVICE_KEY, ":unknown-java");
String collectorUrl = constructSettingsEndpoint(collector.get());
String serviceKey = this.serviceKey.get();
String serviceName = ServiceKeyUtils.getServiceName(serviceKey);

String apiToken = ServiceKeyUtils.getApiKey(serviceKey);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,24 @@
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;

public class HttpSettingsReaderDelegate {

private static final Logger logger = LoggerFactory.getLogger();

private static final Gson gson = new GsonBuilder().create();

private final Supplier<ProxyConfig> proxyConfig;

public HttpSettingsReaderDelegate() {
this.proxyConfig = () -> (ProxyConfig) ConfigManager.getConfig(ConfigProperty.AGENT_PROXY);
}

public HttpSettingsReaderDelegate(ProxyConfig proxyConfig) {
this.proxyConfig = () -> proxyConfig;
}

public Settings fetchSettings(String url, String authorizationHeader) {
AtomicReference<Settings> settings = new AtomicReference<>();
InstrumentationUtil.suppressInstrumentation(
Expand Down Expand Up @@ -97,7 +108,7 @@ private String getErrorMessage(HttpURLConnection connection) {

HttpURLConnection getHttpUrlConnection(URL url, String authorizationHeader) throws IOException {
HttpURLConnection connection;
ProxyConfig proxyConfig = (ProxyConfig) ConfigManager.getConfig(ConfigProperty.AGENT_PROXY);
ProxyConfig proxyConfig = this.proxyConfig.get();
if (proxyConfig != null) {
Proxy proxy =
new Proxy(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/*
* © SolarWinds Worldwide, LLC. All rights reserved.
*
* 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
*
* http://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 com.solarwinds.opentelemetry.extensions.config.provider;

import com.google.auto.service.AutoService;
import com.solarwinds.joboe.config.InvalidConfigException;
import com.solarwinds.joboe.config.ProxyConfig;
import com.solarwinds.joboe.config.ServiceKeyUtils;
import com.solarwinds.joboe.sampling.SamplingConfiguration;
import com.solarwinds.joboe.sampling.SettingsFetcher;
import com.solarwinds.opentelemetry.extensions.SharedNames;
import com.solarwinds.opentelemetry.extensions.config.HttpSettingsFetcher;
import com.solarwinds.opentelemetry.extensions.config.HttpSettingsReader;
import com.solarwinds.opentelemetry.extensions.config.HttpSettingsReaderDelegate;
import io.opentelemetry.api.incubator.config.DeclarativeConfigProperties;
import io.opentelemetry.sdk.autoconfigure.spi.internal.ComponentProvider;
import java.util.function.Function;

@AutoService(ComponentProvider.class)
public class HttpSamplerComponentProvider extends AbstractSamplerComponentProvider {

private static final String COLLECTOR = "collector";

private static final String SERVICE_KEY = "service_key";

private static final String CONTEXT_TTL = "context_ttl";

private static final String CONTEXT_MAX_EVENTS = "context_max_events";

private static final String CONTEXT_MAX_BACKTRACES = "context_max_backtraces";

private static final String PROXY = "proxy";

private static final String PROXY_HOST = "host";

private static final String PROXY_PORT = "port";

private static final long POLLING_INTERVAL_SECONDS = 60;

private final Function<ProxyConfig, HttpSettingsReaderDelegate> delegateFactory;

public HttpSamplerComponentProvider() {
this(HttpSettingsReaderDelegate::new);
}

HttpSamplerComponentProvider(Function<ProxyConfig, HttpSettingsReaderDelegate> delegateFactory) {
this.delegateFactory = delegateFactory;
}

@Override
public String getName() {
return SharedNames.HTTP_SAMPLER_NAME;
Comment thread
cleverchuk marked this conversation as resolved.
Resolved
}

@Override
protected void parseConfig(
DeclarativeConfigProperties properties,
SamplingConfiguration.SamplingConfigurationBuilder builder)
throws InvalidConfigException {

Integer ttl = parsePositiveInt(properties, CONTEXT_TTL);
if (ttl != null) {
builder.ttl(ttl);
}

Integer maxEvents = parsePositiveInt(properties, CONTEXT_MAX_EVENTS);
if (maxEvents != null) {
builder.maxEvents(maxEvents);
}

Integer maxBacktraces = parsePositiveInt(properties, CONTEXT_MAX_BACKTRACES);
if (maxBacktraces != null) {
builder.maxBacktraces(maxBacktraces);
}
}

@Override
protected SettingsFetcher createSettingsFetcher(DeclarativeConfigProperties properties)
throws InvalidConfigException {

String collector = parseNonEmpty(properties, COLLECTOR);
String serviceKey = parseNonEmpty(properties, SERVICE_KEY);
Comment thread
cleverchuk marked this conversation as resolved.
Resolved
if (serviceKey == null) {
throw new InvalidConfigException("Missing required sampler " + SERVICE_KEY + " config");
}

serviceKey = ServiceKeyUtils.transformServiceKey(serviceKey);
return new HttpSettingsFetcher(
HttpSettingsReader.forFileConfig(
delegateFactory.apply(parseProxy(properties)), collector, serviceKey),
POLLING_INTERVAL_SECONDS);
}

private static Integer parsePositiveInt(DeclarativeConfigProperties properties, String key)
throws InvalidConfigException {
Integer value = properties.getInt(key);
if (value != null && value < 1) {
throw new InvalidConfigException(
"Invalid sampler " + key + " config, value must be a positive integer");
}
return value;
}

private static String parseNonEmpty(DeclarativeConfigProperties properties, String key)
throws InvalidConfigException {
String value = properties.getString(key);
if (value != null && value.trim().isEmpty()) {
throw new InvalidConfigException("Invalid sampler " + key + " config, value cannot be empty");
}
return value;
}

private ProxyConfig parseProxy(DeclarativeConfigProperties properties)
throws InvalidConfigException {
DeclarativeConfigProperties proxy = properties.getStructured(PROXY);
if (proxy == null) {
return null;
}

String host = proxy.getString(PROXY_HOST);
Integer port = proxy.getInt(PROXY_PORT);
if (host == null || host.isEmpty() || port == null || port < 1 || port > 65535) {
throw new InvalidConfigException(
"Invalid sampler proxy config, both proxy.host and a valid proxy.port are required");
}
return new ProxyConfig(host, port, null, null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.Proxy;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.junit.jupiter.MockitoExtension;
Expand Down Expand Up @@ -224,4 +227,28 @@ void testGetHttpUrlConnectionConfigurationProxy() throws IOException, InvalidCon
verify(mockConnection).setConnectTimeout(10000);
verify(mockConnection).setReadTimeout(10000);
}

@Test
void testGetHttpUrlConnectionUsesConfigureProxy() throws IOException {
HttpSettingsReaderDelegate samplerProxied =
new HttpSettingsReaderDelegate(new ProxyConfig("sampler.example.com", 2000, null, null));
when(mockUrl.openConnection(any())).thenReturn(mockConnection);

samplerProxied.getHttpUrlConnection(mockUrl, TEST_AUTH_HEADER);
ArgumentCaptor<Proxy> proxyCaptor = ArgumentCaptor.forClass(Proxy.class);
verify(mockUrl).openConnection(proxyCaptor.capture());

InetSocketAddress address = (InetSocketAddress) proxyCaptor.getValue().address();
assertEquals("sampler.example.com", address.getHostString());
assertEquals(2000, address.getPort());
}

@Test
void testGetHttpUrlConnectionNoProxy() throws IOException {
HttpSettingsReaderDelegate samplerScoped = new HttpSettingsReaderDelegate(null);
when(mockUrl.openConnection()).thenReturn(mockConnection);

samplerScoped.getHttpUrlConnection(mockUrl, TEST_AUTH_HEADER);
verify(mockUrl).openConnection();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

Expand All @@ -29,7 +30,6 @@
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension;

Expand All @@ -40,11 +40,12 @@ class HttpSettingsReaderTest {

@Mock private Settings mockSettings;

@InjectMocks private HttpSettingsReader tested;
private HttpSettingsReader tested;

@BeforeEach
void setUp() {
ConfigManager.reset();
tested = HttpSettingsReader.forSystemConfig(delegate);
}

@Test
Expand Down Expand Up @@ -171,4 +172,28 @@ void testGetSettings_UrlWithPortAndPath() throws InvalidConfigException {
assertEquals(mockSettings, result);
verify(delegate).fetchSettings(expectedUrl, expectedTokenHeader);
}

@Test
void testGetSettingsSamplerScopedReaderUsesOnlyItsValues() {
HttpSettingsReader samplerScoped =
HttpSettingsReader.forFileConfig(
delegate, "sampler.collector.com", "sampler-key:sampler-service");
String expectedUrl = "https://sampler.collector.com/v1/settings/sampler-service";
String expectedTokenHeader = "Bearer sampler-key";

when(delegate.fetchSettings(eq(expectedUrl), eq(expectedTokenHeader))).thenReturn(mockSettings);
assertEquals(mockSettings, samplerScoped.getSettings());
}

@Test
void testGetSettingsSamplerScopedReaderUsesDefaultConfig() {
HttpSettingsReader samplerScoped =
HttpSettingsReader.forFileConfig(delegate, null, "sampler-key:sampler-service");
String expectedUrl =
"https://apm.collector.na-01.cloud.solarwinds.com/v1/settings/sampler-service";

when(delegate.fetchSettings(eq(expectedUrl), eq("Bearer sampler-key")))
.thenReturn(mockSettings);
assertEquals(mockSettings, samplerScoped.getSettings());
}
}
Loading
Loading