diff --git a/opamp/build.gradle.kts b/opamp/build.gradle.kts index 644bf49ae..55acde369 100644 --- a/opamp/build.gradle.kts +++ b/opamp/build.gradle.kts @@ -1,3 +1,7 @@ +plugins { + id("splunk.java-conventions") +} + dependencies { compileOnly(project(":custom")) compileOnly(project(":profiler")) diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java index 959430a69..7d9c23571 100644 --- a/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampActivator.java @@ -37,7 +37,6 @@ import java.io.IOException; import java.time.Duration; import java.util.HashMap; -import java.util.List; import java.util.Map; import java.util.logging.Logger; import opamp.proto.AgentConfigFile; @@ -120,74 +119,15 @@ static OpampClient startOpampClient( HttpRequestService.create(okhttp, pollingDelay, DEFAULT_DELAY_BETWEEN_RETRIES); builder.setRequestService(httpSender); } - addIdentifyingAttributes(builder, resource); + OpampAgentAttributes agentAttributes = new OpampAgentAttributes(resource); + agentAttributes.addIdentifyingAttributes(builder); + agentAttributes.addNonIdentifyingAttributes(builder); State.EffectiveConfig effectiveConfig = buildEffectiveConfig(config); builder.setEffectiveConfigState(effectiveConfig); return builder.build(callbacks); } - static void addIdentifyingAttributes(OpampClientBuilder builder, Resource res) { - res.getAttributes() - .forEach( - (key, value) -> { - switch (key.getType()) { - case STRING: - builder.putIdentifyingAttribute(key.getKey(), (String) value); - break; - case LONG: - builder.putIdentifyingAttribute(key.getKey(), (long) value); - break; - case DOUBLE: - builder.putIdentifyingAttribute(key.getKey(), (double) value); - break; - case BOOLEAN: - builder.putIdentifyingAttribute(key.getKey(), (boolean) value); - break; - case VALUE: - builder.putIdentifyingAttribute(key.getKey(), value.toString()); - break; - case STRING_ARRAY: - { - List typedValueList = (List) value; - String[] array = typedValueList.toArray(new String[] {}); - builder.putIdentifyingAttribute(key.getKey(), array); - break; - } - case LONG_ARRAY: - { - List typedValueList = (List) value; - long[] primitiveArray = new long[typedValueList.size()]; - for (int i = 0; i < typedValueList.size(); i++) { - primitiveArray[i] = typedValueList.get(i); - } - builder.putIdentifyingAttribute(key.getKey(), primitiveArray); - break; - } - case DOUBLE_ARRAY: - { - List typedValueList = (List) value; - double[] primitiveArray = new double[typedValueList.size()]; - for (int i = 0; i < typedValueList.size(); i++) { - primitiveArray[i] = typedValueList.get(i); - } - builder.putIdentifyingAttribute(key.getKey(), primitiveArray); - break; - } - case BOOLEAN_ARRAY: - { - List typedValueList = (List) value; - boolean[] primitiveArray = new boolean[typedValueList.size()]; - for (int i = 0; i < typedValueList.size(); i++) { - primitiveArray[i] = typedValueList.get(i); - } - builder.putIdentifyingAttribute(key.getKey(), primitiveArray); - break; - } - } - }); - } - private static State.EffectiveConfig buildEffectiveConfig(ConfigProperties config) { // TODO: This probably doesn't handle declarative config (yaml) correctly. Ho hum. return new State.EffectiveConfig() { diff --git a/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampAgentAttributes.java b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampAgentAttributes.java new file mode 100644 index 000000000..4abb516e9 --- /dev/null +++ b/opamp/src/main/java/com/splunk/opentelemetry/opamp/OpampAgentAttributes.java @@ -0,0 +1,163 @@ +/* + * Copyright Splunk Inc. + * + * 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.splunk.opentelemetry.opamp; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.AttributeType; +import io.opentelemetry.opamp.client.OpampClientBuilder; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.function.Consumer; + +class OpampAgentAttributes { + private static final List IDENTIFYING_ATTRIBUTES = + Arrays.asList("service.name", "service.namespace", "service.instance.id"); + + private final Resource resource; + + OpampAgentAttributes(Resource resource) { + this.resource = resource; + } + + void addIdentifyingAttributes(OpampClientBuilder builder) { + resource.getAttributes().asMap().entrySet().stream() + .filter(entry -> IDENTIFYING_ATTRIBUTES.contains(entry.getKey().getKey())) + .forEach(putIdentifyingAttribute(builder)); + } + + void addNonIdentifyingAttributes(OpampClientBuilder builder) { + resource.getAttributes().asMap().entrySet().stream() + .filter(entry -> !IDENTIFYING_ATTRIBUTES.contains(entry.getKey().getKey())) + .forEach(putNonIdentifyingAttribute(builder)); + } + + private Consumer, Object>> putIdentifyingAttribute( + OpampClientBuilder builder) { + return entry -> { + AttributeKey key = entry.getKey(); + Object value = entry.getValue(); + AttributeType type = key.getType(); + + // The java type system is truly insufferable. + switch (type) { + case STRING: + case VALUE: + builder.putIdentifyingAttribute(key.getKey(), (String) makeValue(type, value)); + break; + case LONG: + builder.putIdentifyingAttribute(key.getKey(), (long) makeValue(type, value)); + break; + case DOUBLE: + builder.putIdentifyingAttribute(key.getKey(), (double) makeValue(type, value)); + break; + case BOOLEAN: + builder.putIdentifyingAttribute(key.getKey(), (boolean) makeValue(type, value)); + break; + case STRING_ARRAY: + builder.putIdentifyingAttribute(key.getKey(), (String[]) makeValue(type, value)); + break; + case LONG_ARRAY: + builder.putIdentifyingAttribute(key.getKey(), (long[]) makeValue(type, value)); + break; + case DOUBLE_ARRAY: + builder.putIdentifyingAttribute(key.getKey(), (double[]) makeValue(type, value)); + break; + case BOOLEAN_ARRAY: + builder.putIdentifyingAttribute(key.getKey(), (boolean[]) makeValue(type, value)); + break; + } + }; + } + + private Consumer, Object>> putNonIdentifyingAttribute( + OpampClientBuilder builder) { + return entry -> { + AttributeKey key = entry.getKey(); + Object value = entry.getValue(); + AttributeType type = key.getType(); + + // The java type system is truly insufferable. + switch (type) { + case STRING: + case VALUE: + builder.putNonIdentifyingAttribute(key.getKey(), (String) makeValue(type, value)); + break; + case LONG: + builder.putNonIdentifyingAttribute(key.getKey(), (long) makeValue(type, value)); + break; + case DOUBLE: + builder.putNonIdentifyingAttribute(key.getKey(), (double) makeValue(type, value)); + break; + case BOOLEAN: + builder.putNonIdentifyingAttribute(key.getKey(), (boolean) makeValue(type, value)); + break; + case STRING_ARRAY: + builder.putNonIdentifyingAttribute(key.getKey(), (String[]) makeValue(type, value)); + break; + case LONG_ARRAY: + builder.putNonIdentifyingAttribute(key.getKey(), (long[]) makeValue(type, value)); + break; + case DOUBLE_ARRAY: + builder.putNonIdentifyingAttribute(key.getKey(), (double[]) makeValue(type, value)); + break; + case BOOLEAN_ARRAY: + builder.putNonIdentifyingAttribute(key.getKey(), (boolean[]) makeValue(type, value)); + break; + } + }; + } + + private Object makeValue(AttributeType attrType, Object value) { + // More java type insanity + switch (attrType) { + case STRING: + case LONG: + case DOUBLE: + case BOOLEAN: + return value; + case VALUE: + return value.toString(); + case STRING_ARRAY: + List typedValueList = (List) value; + return typedValueList.toArray(new String[] {}); + case LONG_ARRAY: + List longList = (List) value; + long[] longArray = new long[longList.size()]; + for (int i = 0; i < longList.size(); i++) { + longArray[i] = longList.get(i); + } + return longArray; + case DOUBLE_ARRAY: + List doubleList = (List) value; + double[] doubleArray = new double[doubleList.size()]; + for (int i = 0; i < doubleList.size(); i++) { + doubleArray[i] = doubleList.get(i); + } + return doubleArray; + case BOOLEAN_ARRAY: + List booleanList = (List) value; + boolean[] booleanArray = new boolean[booleanList.size()]; + for (int i = 0; i < booleanList.size(); i++) { + booleanArray[i] = booleanList.get(i); + } + return booleanArray; + } + return null; + } +} diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java index d545d971b..e6d4080f1 100644 --- a/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampActivatorTest.java @@ -20,9 +20,9 @@ import static io.opentelemetry.api.common.AttributeKey.doubleKey; import static io.opentelemetry.api.common.AttributeKey.longKey; import static io.opentelemetry.api.common.AttributeKey.valueKey; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_INSTANCE_ID; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAMESPACE; -import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION; import static io.opentelemetry.semconv.incubating.DeploymentIncubatingAttributes.DEPLOYMENT_ENVIRONMENT_NAME; import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_NAME; import static io.opentelemetry.semconv.incubating.OsIncubatingAttributes.OS_TYPE; @@ -97,8 +97,8 @@ void testOpamp() throws Exception { "test-deployment-env", SERVICE_NAME, "test-service", - SERVICE_VERSION, - "test-ver", + SERVICE_INSTANCE_ID, + "test-instance", SERVICE_NAMESPACE, "test-ns") .toBuilder() @@ -177,61 +177,69 @@ public void onMessage(OpampClient opampClient, MessageData messageData) { byte[] body = recordedRequest.request().content().array(); AgentToServer agentToServer = AgentToServer.ADAPTER.decode(body); - assertIdentifyingString(agentToServer, DEPLOYMENT_ENVIRONMENT_NAME, "test-deployment-env"); assertIdentifyingString(agentToServer, SERVICE_NAME, "test-service"); - assertIdentifyingString(agentToServer, SERVICE_VERSION, "test-ver"); + assertIdentifyingString(agentToServer, SERVICE_INSTANCE_ID, "test-instance"); assertIdentifyingString(agentToServer, SERVICE_NAMESPACE, "test-ns"); List identifyingAttributes = agentToServer.agent_description.identifying_attributes; - assertThat(identifyingAttributes) + assertThat(identifyingAttributes).hasSize(3); + + List nonIdentifyingAttributes = + agentToServer.agent_description.non_identifying_attributes; + assertThat(nonIdentifyingAttributes) + .anyMatch( + kv -> + kv.key.equals(DEPLOYMENT_ENVIRONMENT_NAME.getKey()) + && kv.value.string_value.equals("test-deployment-env")); + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("long") && kv.value.int_value.equals(12L)); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("double") && kv.value.double_value.equals(99.0)); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("bool") && kv.value.bool_value.equals(true)); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("val") && kv.value.string_value.equals("vvv")); AnyValue longsArray = createArrayAttribute( new AnyValue.Builder().int_value(2L).build(), new AnyValue.Builder().int_value(3L).build(), new AnyValue.Builder().int_value(5L).build()); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("longarr") && kv.value.equals(longsArray)); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch(kv -> kv.key.equals("longobjarr") && kv.value.equals(longsArray)); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "doublearr", new AnyValue.Builder().double_value(2.0).build(), new AnyValue.Builder().double_value(3.0).build())); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "doubleobjarr", new AnyValue.Builder().double_value(5.0).build(), new AnyValue.Builder().double_value(6.0).build())); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "stringarr", new AnyValue.Builder().string_value("foo").build(), new AnyValue.Builder().string_value("flimflam").build())); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "stringobjarr", new AnyValue.Builder().string_value("flim").build(), new AnyValue.Builder().string_value("jibberjo").build())); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "boolarr", new AnyValue.Builder().bool_value(true).build(), new AnyValue.Builder().bool_value(false).build())); - assertThat(identifyingAttributes) + assertThat(nonIdentifyingAttributes) .anyMatch( matching( "boolobjarr", @@ -239,8 +247,16 @@ public void onMessage(OpampClient opampClient, MessageData messageData) { new AnyValue.Builder().bool_value(true).build(), new AnyValue.Builder().bool_value(false).build(), new AnyValue.Builder().bool_value(true).build())); - - assertThat(agentToServer.agent_description.non_identifying_attributes).isEmpty(); + assertThat(nonIdentifyingAttributes) + .anyMatch( + kv -> kv.key.equals(OS_NAME.getKey()) && kv.value.string_value.equals("test-os-name")); + assertThat(nonIdentifyingAttributes) + .anyMatch( + kv -> kv.key.equals(OS_TYPE.getKey()) && kv.value.string_value.equals("test-os-type")); + assertThat(nonIdentifyingAttributes) + .anyMatch( + kv -> + kv.key.equals(OS_VERSION.getKey()) && kv.value.string_value.equals("test-os-ver")); } private static Predicate matching(String key, AnyValue... values) { diff --git a/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampAgentAttributesTest.java b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampAgentAttributesTest.java new file mode 100644 index 000000000..bb883925f --- /dev/null +++ b/opamp/src/test/java/com/splunk/opentelemetry/opamp/OpampAgentAttributesTest.java @@ -0,0 +1,104 @@ +/* + * Copyright Splunk Inc. + * + * 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.splunk.opentelemetry.opamp; + +import static io.opentelemetry.api.common.AttributeKey.booleanKey; +import static io.opentelemetry.api.common.AttributeKey.doubleKey; +import static io.opentelemetry.api.common.AttributeKey.longKey; +import static io.opentelemetry.api.common.AttributeKey.valueKey; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_INSTANCE_ID; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAME; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_NAMESPACE; +import static io.opentelemetry.semconv.ServiceAttributes.SERVICE_VERSION; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyNoMoreInteractions; + +import io.opentelemetry.api.common.AttributeKey; +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.Value; +import io.opentelemetry.opamp.client.OpampClientBuilder; +import io.opentelemetry.sdk.resources.Resource; +import java.util.Arrays; +import org.junit.jupiter.api.Test; + +class OpampAgentAttributesTest { + + private static final Attributes resourceAttributes = + Attributes.of( + SERVICE_NAME, + "test-service", + SERVICE_NAMESPACE, + "test-namespace", + SERVICE_INSTANCE_ID, + "test-instance", + SERVICE_VERSION, + "test-version") + .toBuilder() + .put(longKey("long"), 12L) + .put(doubleKey("double"), 99.0) + .put(booleanKey("bool"), true) + .put(valueKey("val"), Value.of("vvv")) + .put("longarr", new long[] {2L, 3L, 5L}) + .put(AttributeKey.longArrayKey("longobjarr"), Arrays.asList(2L, 3L, 5L)) + .put("doublearr", new double[] {2.0, 3.0}) + .put(AttributeKey.doubleArrayKey("doubleobjarr"), Arrays.asList(5.0, 6.0)) + .put("stringarr", new String[] {"foo", "flimflam"}) + .put(AttributeKey.stringArrayKey("stringobjarr"), Arrays.asList("flim", "jibberjo")) + .put("boolarr", new boolean[] {true, false}) + .put(AttributeKey.booleanArrayKey("boolobjarr"), Arrays.asList(true, true, false, true)) + .build(); + private static final Resource resource = Resource.create(resourceAttributes); + + @Test + void addsIdentifyingAttributes() { + OpampClientBuilder builder = mock(OpampClientBuilder.class); + + OpampAgentAttributes testClass = new OpampAgentAttributes(resource); + testClass.addIdentifyingAttributes(builder); + + verify(builder).putIdentifyingAttribute(SERVICE_NAME.getKey(), "test-service"); + verify(builder).putIdentifyingAttribute(SERVICE_NAMESPACE.getKey(), "test-namespace"); + verify(builder).putIdentifyingAttribute(SERVICE_INSTANCE_ID.getKey(), "test-instance"); + verifyNoMoreInteractions(builder); + } + + @Test + void addsNonIdentifyingAttributes() { + OpampClientBuilder builder = mock(); + + OpampAgentAttributes testClass = new OpampAgentAttributes(resource); + + testClass.addNonIdentifyingAttributes(builder); + + verify(builder).putNonIdentifyingAttribute(SERVICE_VERSION.getKey(), "test-version"); + verify(builder).putNonIdentifyingAttribute("long", 12L); + verify(builder).putNonIdentifyingAttribute("double", 99.0); + verify(builder).putNonIdentifyingAttribute("bool", true); + verify(builder).putNonIdentifyingAttribute("val", "vvv"); + + verify(builder).putNonIdentifyingAttribute("longarr", 2L, 3L, 5L); + verify(builder).putNonIdentifyingAttribute("longobjarr", 2L, 3L, 5L); + verify(builder).putNonIdentifyingAttribute("doublearr", 2.0, 3.0); + verify(builder).putNonIdentifyingAttribute("doubleobjarr", 5.0, 6.0); + verify(builder).putNonIdentifyingAttribute("stringarr", "foo", "flimflam"); + verify(builder).putNonIdentifyingAttribute("stringobjarr", "flim", "jibberjo"); + verify(builder).putNonIdentifyingAttribute("boolarr", true, false); + verify(builder).putNonIdentifyingAttribute("boolobjarr", true, true, false, true); + verifyNoMoreInteractions(builder); + } +}