Skip to content

Commit cb74286

Browse files
committed
Add more merge helper methods and tests
1 parent ce0e66d commit cb74286

6 files changed

Lines changed: 267 additions & 4 deletions

File tree

sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/Entity.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,21 +32,21 @@ public interface Entity {
3232
*
3333
* @return the entity type.
3434
*/
35-
public abstract String getType();
35+
String getType();
3636

3737
/**
3838
* Returns a map of attributes that identify the entity.
3939
*
4040
* @return a map of attributes.
4141
*/
42-
public abstract Attributes getId();
42+
Attributes getId();
4343

4444
/**
4545
* Returns a map of attributes that describe the entity.
4646
*
4747
* @return a map of attributes.
4848
*/
49-
public abstract Attributes getDescription();
49+
Attributes getDescription();
5050

5151
/**
5252
* Returns the URL of the OpenTelemetry schema used by this resource. May be null if this entity
@@ -56,7 +56,7 @@ public interface Entity {
5656
* @since 1.4.0
5757
*/
5858
@Nullable
59-
public abstract String getSchemaUrl();
59+
String getSchemaUrl();
6060

6161
/**
6262
* Returns a new {@link EntityBuilder} instance populated with the data of this {@link Entity}.

sdk/common/src/main/java/io/opentelemetry/sdk/resources/internal/EntityUtil.java

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,17 @@
55

66
package io.opentelemetry.sdk.resources.internal;
77

8+
import io.opentelemetry.api.common.AttributeKey;
9+
import io.opentelemetry.api.common.Attributes;
10+
import io.opentelemetry.api.common.AttributesBuilder;
11+
import java.util.ArrayList;
812
import java.util.Collection;
913
import java.util.HashMap;
1014
import java.util.Map;
15+
import java.util.Set;
1116
import java.util.logging.Logger;
17+
import java.util.stream.Collectors;
18+
import javax.annotation.Nullable;
1219

1320
/**
1421
* Helper class for dealing with Entities.
@@ -21,6 +28,88 @@ public final class EntityUtil {
2128

2229
private EntityUtil() {}
2330

31+
/** Returns true if any entity in the collection has the attribute key, in id or description. */
32+
static final <T> boolean hasAttributeKey(Collection<Entity> entities, AttributeKey<T> key) {
33+
return entities.stream()
34+
.anyMatch(
35+
e -> e.getId().asMap().containsKey(key) || e.getDescription().asMap().containsKey(key));
36+
}
37+
38+
/** Decides on a final SchemaURL for OTLP Resource based on entities chosen. */
39+
@Nullable
40+
static final String mergeResourceSchemaUrl(
41+
Collection<Entity> entities, @Nullable String baseUrl, @Nullable String nextUrl) {
42+
// Check if entities all share the same URL.
43+
Set<String> entitySchemas =
44+
entities.stream().map(Entity::getSchemaUrl).collect(Collectors.toSet());
45+
// If we have no entities, we preserve previous schema url behavior.
46+
String result = baseUrl;
47+
if (entitySchemas.size() == 1) {
48+
// Updated Entities use same schema, we can preserve it.
49+
result = entitySchemas.iterator().next();
50+
} else if (entitySchemas.size() > 1) {
51+
// Entities use different schemas, resource must treat this as no schema_url.
52+
result = null;
53+
}
54+
55+
// If schema url of merging resource is null, we use our current result.
56+
if (nextUrl == null) {
57+
return result;
58+
}
59+
// When there are no entities, we use old schema url merge behavior
60+
if (result == null && entities.isEmpty()) {
61+
return nextUrl;
62+
}
63+
if (!nextUrl.equals(result)) {
64+
logger.info(
65+
"Attempting to merge Resources with different schemaUrls. "
66+
+ "The resulting Resource will have no schemaUrl assigned. Schema 1: "
67+
+ baseUrl
68+
+ " Schema 2: "
69+
+ nextUrl);
70+
return null;
71+
}
72+
return result;
73+
}
74+
75+
/**
76+
* Merges "loose" attributes on resource, removing those which conflict with the set of entities.
77+
*
78+
* @param base loose attributes from base resource
79+
* @param additional additional attributes to add to the resource.
80+
* @param entities the set of entites on the resource.
81+
* @return the new set of raw attributes for Resource and the set of conflicting entities that
82+
* MUST NOT be reported on OTLP resource.
83+
*/
84+
@SuppressWarnings("unchecked")
85+
static final RawAttributeMergeResult mergeRawAttributes(
86+
Attributes base, Attributes additional, Collection<Entity> entities) {
87+
AttributesBuilder result = base.toBuilder();
88+
// We know attribute conflicts were handled perviously on the resource, so
89+
// This needs to account for entity merge of new entities, and remove raw
90+
// attributes that would have been removed with new entities.
91+
result.removeIf(key -> hasAttributeKey(entities, key));
92+
// For every "raw" attribute on the other resource, we merge into the
93+
// resource, but check for entity conflicts from previous entities.
94+
ArrayList<Entity> conflicts = new ArrayList<>();
95+
if (!additional.isEmpty()) {
96+
additional.forEach(
97+
(key, value) -> {
98+
for (Entity e : entities) {
99+
if (e.getId().asMap().keySet().contains(key)
100+
|| e.getDescription().asMap().keySet().contains(key)) {
101+
// Remove the entity and push all attributes as raw,
102+
// we have an override.
103+
conflicts.add(e);
104+
result.putAll(e.getId()).putAll(e.getDescription());
105+
}
106+
}
107+
result.put((AttributeKey<Object>) key, value);
108+
});
109+
}
110+
return RawAttributeMergeResult.create(result.build(), conflicts);
111+
}
112+
24113
/**
25114
* Merges entities according to specification rules.
26115
*
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.resources.internal;
7+
8+
import com.google.auto.value.AutoValue;
9+
import io.opentelemetry.api.common.Attributes;
10+
import java.util.Collection;
11+
import javax.annotation.concurrent.Immutable;
12+
13+
/**
14+
* This class is internal and is hence not for public use. Its APIs are unstable and can change at
15+
* any time.
16+
*/
17+
@Immutable
18+
@AutoValue
19+
abstract class RawAttributeMergeResult {
20+
/** Merged raw attributes. */
21+
abstract Attributes getAttributes();
22+
23+
/**
24+
* Entities in conflict that should be removed from resource to avoid reporting invalid attribute
25+
* sets in OTLP resource.
26+
*/
27+
abstract Collection<Entity> getConflicts();
28+
29+
static final RawAttributeMergeResult create(Attributes attributes, Collection<Entity> conflicts) {
30+
return new AutoValue_RawAttributeMergeResult(attributes, conflicts);
31+
}
32+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.resources.internal;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import io.opentelemetry.sdk.resources.ResourceBuilder;
10+
import java.util.Collection;
11+
import javax.annotation.Nullable;
12+
import javax.annotation.concurrent.Immutable;
13+
14+
/**
15+
* {@link Resource} represents a resource, which capture identifying information about the entities
16+
* for which signals (stats or traces) are reported.
17+
*
18+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
19+
* at any time.
20+
*/
21+
@Immutable
22+
public interface ResourceWithEntity {
23+
/**
24+
* Returns the URL of the OpenTelemetry schema used by this resource. May be null.
25+
*
26+
* @return An OpenTelemetry schema URL.
27+
* @since 1.4.0
28+
*/
29+
@Nullable
30+
String getSchemaUrl();
31+
32+
/**
33+
* Returns a map of attributes that describe the resource, not associated with an entity.
34+
*
35+
* @return a map of attributes.
36+
*/
37+
Attributes getRawAttributes();
38+
39+
/**
40+
* Returns a collectoion of associated entities.
41+
*
42+
* @return a collection of entities.
43+
*/
44+
Collection<Entity> getEntities();
45+
46+
/**
47+
* Returns a map of attributes that describe the resource.
48+
*
49+
* <p>Note: this includes all entity attribtues and raw attributes.
50+
*
51+
* @return a map of attributes.
52+
*/
53+
Attributes getAttributes();
54+
55+
/**
56+
* Returns a new {@link ResourceBuilder} instance populated with the data of this {@link
57+
* Resource}.
58+
*/
59+
ResourceWithEntityBuilder toBuilder();
60+
61+
// TODO - Merge
62+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.sdk.resources.internal;
7+
8+
import io.opentelemetry.api.common.Attributes;
9+
import java.util.Collection;
10+
11+
/**
12+
* A builder of {@link ResourceWithEntity} that allows to add key-value pairs and copy attributes
13+
* from other {@link Attributes} or {@link ResourceWithEntity}.
14+
*
15+
* <p>This class is internal and is hence not for public use. Its APIs are unstable and can change
16+
* at any time.
17+
*/
18+
public interface ResourceWithEntityBuilder {
19+
// TODO - Raw Attributes.
20+
/** Appends a new entity on to the end of the list of entities. */
21+
ResourceWithEntityBuilder add(Entity e);
22+
23+
/** Appends a new collection of entities on to the end of the list of entities. */
24+
ResourceWithEntityBuilder addAll(Collection<Entity> entities);
25+
26+
/** Create the {@link ResourceWithEntity} from this. */
27+
ResourceWithEntity build();
28+
}

sdk/common/src/test/java/io/opentelemetry/sdk/resources/internal/EntityUtilTest.java

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
import java.util.Arrays;
1212
import java.util.Collection;
13+
import java.util.Collections;
1314
import org.junit.jupiter.api.Test;
1415

1516
/** Unit tests for {@link EntityUtil} */
@@ -160,4 +161,55 @@ void testMerge_entities_separate_types_and_schema() {
160161
.satisfiesExactlyInAnyOrder(
161162
a -> assertThat(a).hasType("a"), b -> assertThat(b).hasType("b"));
162163
}
164+
165+
@Test
166+
void testSchemaUrlMerge_no_entities_differentUrls() {
167+
// If the we find conflicting schema URLs in resource we must drop schema url (set to null).
168+
String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", "two");
169+
assertThat(result).isNull();
170+
}
171+
172+
@Test
173+
void testSchemaUrlMerge_no_entities_base_null() {
174+
// If the our resource had no schema url it abides by, we use the incoming schema url.
175+
String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), null, "two");
176+
assertThat(result).isEqualTo("two");
177+
}
178+
179+
@Test
180+
void testSchemaUrlMerge_no_entities_next_null() {
181+
// If the new resource had no schema url it abides by, we preserve ours.
182+
// NOTE: this is by specification, but seems problematic if conflicts in merge
183+
// cause violation of SchemaURL.
184+
String result = EntityUtil.mergeResourceSchemaUrl(Collections.emptyList(), "one", null);
185+
assertThat(result).isEqualTo("one");
186+
}
187+
188+
@Test
189+
void testSchemaUrlMerge_entities_same_url() {
190+
// If the new resource had no schema url it abides by, we preserve ours.
191+
// NOTE: this is by specification, but seems problematic if conflicts in merge
192+
// cause violation of SchemaURL.
193+
String result =
194+
EntityUtil.mergeResourceSchemaUrl(
195+
Arrays.asList(
196+
Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build()),
197+
"one",
198+
null);
199+
assertThat(result).isEqualTo("one");
200+
}
201+
202+
@Test
203+
void testSchemaUrlMerge_entities_different_url() {
204+
// When entities have conflciting schema urls, we cannot fill out resource schema url,
205+
// no matter what.
206+
String result =
207+
EntityUtil.mergeResourceSchemaUrl(
208+
Arrays.asList(
209+
Entity.builder("t").setSchemaUrl("one").withId(id -> id.put("id", 1)).build(),
210+
Entity.builder("t2").setSchemaUrl("two").withId(id -> id.put("id2", 1)).build()),
211+
"one",
212+
"one");
213+
assertThat(result).isEqualTo(null);
214+
}
163215
}

0 commit comments

Comments
 (0)