Add support for fetching xray sampling rules. (#3331)

* Add support for fetching xray sampling rules.

* Small tweak

* Remove unused

* Cleanup

* Creators
This commit is contained in:
Anuraag Agrawal 2021-06-21 13:26:42 +09:00 committed by GitHub
parent df1f47d929
commit c3614b6b23
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 390 additions and 4 deletions

View File

@ -14,6 +14,8 @@ dependencies {
compileOnly(project(":sdk-extensions:autoconfigure"))
annotationProcessor("com.google.auto.value:auto-value")
implementation(project(":semconv"))
implementation("com.fasterxml.jackson.core:jackson-core")
@ -23,4 +25,5 @@ dependencies {
testImplementation("com.linecorp.armeria:armeria-junit5")
testImplementation("com.google.guava:guava")
testImplementation("org.slf4j:slf4j-simple")
}

View File

@ -3,12 +3,13 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.resource;
package io.opentelemetry.sdk.extension.aws.internal;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
@ -26,17 +27,32 @@ import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
class JdkHttpClient {
/**
* A simple HTTP client based on the standard JDK {@link HttpURLConnection}. Not meant for high
* throughput.
*/
public final class JdkHttpClient {
private static final Logger logger = Logger.getLogger(JdkHttpClient.class.getName());
private static final int TIMEOUT_MILLIS = 2000;
String fetchString(
/** Fetch a string from a remote server. */
public String fetchString(
String httpMethod,
String urlStr,
Map<String, String> requestPropertyMap,
@Nullable String certPath) {
return fetchString(httpMethod, urlStr, requestPropertyMap, certPath, null);
}
/** Fetch a string from a remote server with a request body. */
public String fetchString(
String httpMethod,
String urlStr,
Map<String, String> requestPropertyMap,
@Nullable String certPath,
@Nullable byte[] requestBody) {
final HttpURLConnection connection;
try {
@ -58,6 +74,13 @@ class JdkHttpClient {
connection.setRequestProperty(requestProperty.getKey(), requestProperty.getValue());
}
if (requestBody != null) {
connection.setDoOutput(true);
try (OutputStream outputStream = connection.getOutputStream()) {
outputStream.write(requestBody);
}
}
int responseCode = connection.getResponseCode();
if (responseCode != 200) {
logger.log(

View File

@ -10,6 +10,7 @@ import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.sdk.extension.aws.internal.JdkHttpClient;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.IOException;

View File

@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.api.common.AttributesBuilder;
import io.opentelemetry.sdk.extension.aws.internal.JdkHttpClient;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.File;

View File

@ -0,0 +1,24 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.google.auto.value.AutoValue;
import javax.annotation.Nullable;
@AutoValue
@JsonSerialize(as = GetSamplingRulesRequest.class)
abstract class GetSamplingRulesRequest {
static GetSamplingRulesRequest create(@Nullable String nextToken) {
return new AutoValue_GetSamplingRulesRequest(nextToken);
}
@JsonProperty("NextToken")
@Nullable
abstract String getNextToken();
}

View File

@ -0,0 +1,110 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.google.auto.value.AutoValue;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
@AutoValue
abstract class GetSamplingRulesResponse {
@JsonCreator
static GetSamplingRulesResponse create(
@JsonProperty("NextToken") String nextToken,
@JsonProperty("SamplingRuleRecords") List<SamplingRuleRecord> samplingRules) {
return new AutoValue_GetSamplingRulesResponse(nextToken, samplingRules);
}
@Nullable
abstract String getNextToken();
abstract List<SamplingRuleRecord> getSamplingRules();
@AutoValue
abstract static class SamplingRuleRecord {
@JsonCreator
static SamplingRuleRecord create(
@JsonProperty("CreatedAt") String createdAt,
@JsonProperty("ModifiedAt") String modifiedAt,
@JsonProperty("SamplingRule") SamplingRule rule) {
return new AutoValue_GetSamplingRulesResponse_SamplingRuleRecord(createdAt, modifiedAt, rule);
}
abstract String getCreatedAt();
abstract String getModifiedAt();
abstract SamplingRule getRule();
}
@AutoValue
abstract static class SamplingRule {
@JsonCreator
static SamplingRule create(
@JsonProperty("Attributes") Map<String, String> attributes,
@JsonProperty("FixedRate") double fixedRate,
@JsonProperty("Host") String host,
@JsonProperty("HTTPMethod") String httpMethod,
@JsonProperty("Priority") int priority,
@JsonProperty("ReservoirSize") int reservoirSize,
@JsonProperty("ResourceARN") String resourceArn,
@JsonProperty("RuleARN") @Nullable String ruleArn,
@JsonProperty("RuleName") @Nullable String ruleName,
@JsonProperty("ServiceName") String serviceName,
@JsonProperty("ServiceType") String serviceType,
@JsonProperty("URLPath") String urlPath,
@JsonProperty("Version") int version) {
return new AutoValue_GetSamplingRulesResponse_SamplingRule(
attributes,
fixedRate,
host,
httpMethod,
priority,
reservoirSize,
resourceArn,
ruleArn,
ruleName,
serviceName,
serviceType,
urlPath,
version);
}
abstract Map<String, String> getAttributes();
abstract double getFixedRate();
abstract String getHost();
abstract String getHttpMethod();
abstract int getPriority();
abstract int getReservoirSize();
abstract String getResourceArn();
@Nullable
abstract String getRuleArn();
@Nullable
abstract String getRuleName();
abstract String getServiceName();
abstract String getServiceType();
abstract String getUrlPath();
abstract int getVersion();
}
}

View File

@ -0,0 +1,54 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.opentelemetry.sdk.extension.aws.internal.JdkHttpClient;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.Map;
final class XraySamplerClient {
private static final ObjectMapper OBJECT_MAPPER =
new ObjectMapper()
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
// In case API is extended with new fields.
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, /* state= */ false);
private static final Map<String, String> JSON_CONTENT_TYPE =
Collections.singletonMap("Content-Type", "application/json");
private final String getSamplingRulesEndpoint;
private final JdkHttpClient httpClient;
XraySamplerClient(String host) {
this.getSamplingRulesEndpoint = host + "/GetSamplingRules";
httpClient = new JdkHttpClient();
}
GetSamplingRulesResponse getSamplingRules(GetSamplingRulesRequest request) {
final byte[] requestBody;
try {
requestBody = OBJECT_MAPPER.writeValueAsBytes(request);
} catch (JsonProcessingException e) {
throw new UncheckedIOException("Failed to serialize request.", e);
}
String response =
httpClient.fetchString(
"POST", getSamplingRulesEndpoint, JSON_CONTENT_TYPE, null, requestBody);
try {
return OBJECT_MAPPER.readValue(response, GetSamplingRulesResponse.class);
} catch (JsonProcessingException e) {
throw new UncheckedIOException("Failed to deserialize response.", e);
}
}
}

View File

@ -3,7 +3,7 @@
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.resource;
package io.opentelemetry.sdk.extension.aws.internal;
import static org.assertj.core.api.Assertions.assertThat;
@ -54,6 +54,23 @@ class JdkHttpClientTest {
assertThat(result).isEmpty();
}
@Test
void requestBody() {
server.enqueue(HttpResponse.of("expected result"));
String urlStr = String.format("http://localhost:%s%s", server.httpPort(), "/path");
JdkHttpClient jdkHttpClient = new JdkHttpClient();
String result =
jdkHttpClient.fetchString(
"POST", urlStr, Collections.emptyMap(), null, "body".getBytes(StandardCharsets.UTF_8));
assertThat(result).isEqualTo("expected result");
AggregatedHttpRequest request1 = server.takeRequest().request();
assertThat(request1.path()).isEqualTo("/path");
assertThat(request1.contentUtf8()).isEqualTo("body");
}
static class HttpsServerTest {
@RegisterExtension
@Order(1)

View File

@ -16,6 +16,7 @@ import com.google.common.base.Charsets;
import com.google.common.io.Files;
import io.opentelemetry.api.common.Attributes;
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
import io.opentelemetry.sdk.extension.aws.internal.JdkHttpClient;
import io.opentelemetry.sdk.resources.Resource;
import io.opentelemetry.semconv.resource.attributes.ResourceAttributes;
import java.io.File;

View File

@ -0,0 +1,106 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package io.opentelemetry.sdk.extension.aws.trace;
import static java.util.Objects.requireNonNull;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
import com.google.common.io.ByteStreams;
import com.linecorp.armeria.common.HttpResponse;
import com.linecorp.armeria.common.HttpStatus;
import com.linecorp.armeria.common.MediaType;
import com.linecorp.armeria.server.ServerBuilder;
import com.linecorp.armeria.server.logging.LoggingService;
import com.linecorp.armeria.testing.junit5.server.ServerExtension;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
class XraySamplerClientTest {
@RegisterExtension
public static ServerExtension server =
new ServerExtension() {
@Override
protected void configure(ServerBuilder sb) throws Exception {
byte[] getSamplingRulesResponse =
ByteStreams.toByteArray(
requireNonNull(
XraySamplerClientTest.class.getResourceAsStream(
"/get-sampling-rules-response.json")));
sb.decorator(LoggingService.newDecorator());
sb.service(
"/GetSamplingRules",
(ctx, req) ->
HttpResponse.from(
req.aggregate()
.thenApply(
aggregatedReq -> {
assertThat(aggregatedReq.contentUtf8())
.isEqualTo("{\"NextToken\":\"token\"}");
return HttpResponse.of(
HttpStatus.OK, MediaType.JSON_UTF_8, getSamplingRulesResponse);
})));
}
};
private XraySamplerClient client;
@BeforeEach
void setUp() {
client = new XraySamplerClient(server.httpUri().toString());
}
@Test
void getSamplingRules() {
GetSamplingRulesResponse response =
client.getSamplingRules(GetSamplingRulesRequest.create("token"));
assertThat(response.getNextToken()).isNull();
assertThat(response.getSamplingRules())
.satisfiesExactly(
rule -> {
assertThat(rule.getCreatedAt()).isEqualTo("2021-06-18T17:28:15+09:00");
assertThat(rule.getModifiedAt()).isEqualTo("2021-06-18T17:28:15+09:00");
assertThat(rule.getRule().getRuleName()).isEqualTo("Test");
assertThat(rule.getRule().getRuleArn())
.isEqualTo("arn:aws:xray:us-east-1:595986152929:sampling-rule/Test");
assertThat(rule.getRule().getResourceArn()).isEqualTo("*");
assertThat(rule.getRule().getPriority()).isEqualTo(1);
assertThat(rule.getRule().getFixedRate()).isEqualTo(0.9);
assertThat(rule.getRule().getReservoirSize()).isEqualTo(1000);
assertThat(rule.getRule().getServiceName()).isEqualTo("test-service-foo-bar");
assertThat(rule.getRule().getServiceType()).isEqualTo("*");
assertThat(rule.getRule().getHost()).isEqualTo("*");
assertThat(rule.getRule().getHttpMethod()).isEqualTo("*");
assertThat(rule.getRule().getUrlPath()).isEqualTo("*");
assertThat(rule.getRule().getVersion()).isEqualTo(1);
assertThat(rule.getRule().getAttributes())
.containsExactly(entry("animal", "cat"), entry("speed", "10"));
},
rule -> {
assertThat(rule.getCreatedAt()).isEqualTo("1970-01-01T09:00:00+09:00");
assertThat(rule.getModifiedAt()).isEqualTo("1970-01-01T09:00:00+09:00");
assertThat(rule.getRule().getRuleName()).isEqualTo("Default");
assertThat(rule.getRule().getRuleArn())
.isEqualTo("arn:aws:xray:us-east-1:595986152929:sampling-rule/Default");
assertThat(rule.getRule().getResourceArn()).isEqualTo("*");
assertThat(rule.getRule().getPriority()).isEqualTo(10000);
assertThat(rule.getRule().getFixedRate()).isEqualTo(0.05);
assertThat(rule.getRule().getReservoirSize()).isEqualTo(1);
assertThat(rule.getRule().getServiceName()).isEqualTo("*");
assertThat(rule.getRule().getServiceType()).isEqualTo("*");
assertThat(rule.getRule().getHost()).isEqualTo("*");
assertThat(rule.getRule().getHttpMethod()).isEqualTo("*");
assertThat(rule.getRule().getUrlPath()).isEqualTo("*");
assertThat(rule.getRule().getVersion()).isEqualTo(1);
assertThat(rule.getRule().getAttributes()).isEmpty();
});
}
}

View File

@ -0,0 +1,45 @@
{
"SamplingRuleRecords": [
{
"SamplingRule": {
"RuleName": "Test",
"RuleARN": "arn:aws:xray:us-east-1:595986152929:sampling-rule/Test",
"ResourceARN": "*",
"Priority": 1,
"FixedRate": 0.9,
"ReservoirSize": 1000,
"ServiceName": "test-service-foo-bar",
"ServiceType": "*",
"Host": "*",
"HTTPMethod": "*",
"URLPath": "*",
"Version": 1,
"Attributes": {
"animal": "cat",
"speed": "10"
}
},
"CreatedAt": "2021-06-18T17:28:15+09:00",
"ModifiedAt": "2021-06-18T17:28:15+09:00"
},
{
"SamplingRule": {
"RuleName": "Default",
"RuleARN": "arn:aws:xray:us-east-1:595986152929:sampling-rule/Default",
"ResourceARN": "*",
"Priority": 10000,
"FixedRate": 0.05,
"ReservoirSize": 1,
"ServiceName": "*",
"ServiceType": "*",
"Host": "*",
"HTTPMethod": "*",
"URLPath": "*",
"Version": 1,
"Attributes": {}
},
"CreatedAt": "1970-01-01T09:00:00+09:00",
"ModifiedAt": "1970-01-01T09:00:00+09:00"
}
]
}

View File

@ -0,0 +1 @@
mock-maker-inline