Skip to content

Commit e8f1e8c

Browse files
authored
Merge pull request #806 from upsidedownsmile/main
Add Application Signals contract test for JDBC using postgres
2 parents f1ad3d0 + e513c7b commit e8f1e8c

File tree

7 files changed

+358
-8
lines changed

7 files changed

+358
-8
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,9 @@ atlassian-ide-plugin.xml
4646

4747
# rust
4848
tools/cp-utility/target
49+
50+
# lsp
51+
**/.project
52+
**/.classpath
53+
**/.settings
54+
**/bin

appsignals-tests/contract-tests/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ dependencies {
5757
testImplementation(kotlin("test"))
5858
implementation(project(":appsignals-tests:images:grpc:grpc-base"))
5959
testImplementation("org.testcontainers:kafka:1.19.3")
60+
testImplementation("org.testcontainers:postgresql:1.19.3")
6061
}
6162

6263
project.evaluationDependsOn(":otelagent")
Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import io.opentelemetry.proto.common.v1.KeyValue;
2222
import io.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint;
2323
import java.util.List;
24+
import java.util.Map;
2425
import java.util.Set;
2526
import org.junit.jupiter.api.Test;
2627
import org.junit.jupiter.api.TestInstance;
@@ -33,11 +34,12 @@
3334

3435
@Testcontainers(disabledWithoutDocker = true)
3536
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
36-
public class JDBC extends ContractTestBase {
37+
public class JdbcH2Test extends ContractTestBase {
3738

3839
private static final String DB_SYSTEM = "h2";
3940
private static final String DB_NAME = "testdb";
4041
private static final String DB_USER = "sa";
42+
private static final String DB_PASSWORD = "password";
4143
private static final String DB_OPERATION = "SELECT";
4244

4345
@Test
@@ -69,7 +71,7 @@ public void testFault() {
6971
var path = "fault";
7072
var method = "GET";
7173
var otelStatusCode = "STATUS_CODE_ERROR";
72-
var dbSqlTable = "user";
74+
var dbSqlTable = "userrr";
7375
var response = appClient.get(path).aggregate().join();
7476
assertThat(response.status().isServerError()).isTrue();
7577

@@ -98,6 +100,21 @@ protected String getApplicationWaitPattern() {
98100
return ".*Application Ready.*";
99101
}
100102

103+
@Override
104+
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
105+
return Map.of(
106+
"DB_URL",
107+
String.format("jdbc:h2:mem:%s", DB_NAME),
108+
"DB_DRIVER",
109+
"org.h2.Driver",
110+
"DB_USERNAME",
111+
DB_USER,
112+
"DB_PASSWORD",
113+
DB_PASSWORD,
114+
"DB_PLATFORM",
115+
"org.hibernate.dialect.H2Dialect");
116+
}
117+
101118
protected void assertAwsSpanAttributes(
102119
List<ResourceScopeSpan> resourceScopeSpans, String method, String path) {
103120
assertThat(resourceScopeSpans)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,325 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.opentelemetry.appsignals.test.jdbc;
17+
18+
import static io.opentelemetry.proto.trace.v1.Span.SpanKind.SPAN_KIND_CLIENT;
19+
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.junit.Assert.assertThat;
21+
22+
import io.opentelemetry.proto.common.v1.KeyValue;
23+
import io.opentelemetry.proto.metrics.v1.ExponentialHistogramDataPoint;
24+
import java.util.List;
25+
import java.util.Map;
26+
import java.util.Set;
27+
import org.junit.jupiter.api.AfterEach;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.TestInstance;
30+
import org.testcontainers.containers.PostgreSQLContainer;
31+
import org.testcontainers.containers.wait.strategy.Wait;
32+
import org.testcontainers.images.PullPolicy;
33+
import org.testcontainers.junit.jupiter.Testcontainers;
34+
import org.testcontainers.lifecycle.Startable;
35+
import software.amazon.opentelemetry.appsignals.test.base.ContractTestBase;
36+
import software.amazon.opentelemetry.appsignals.test.utils.AppSignalsConstants;
37+
import software.amazon.opentelemetry.appsignals.test.utils.ResourceScopeMetric;
38+
import software.amazon.opentelemetry.appsignals.test.utils.ResourceScopeSpan;
39+
import software.amazon.opentelemetry.appsignals.test.utils.SemanticConventionsConstants;
40+
41+
@Testcontainers(disabledWithoutDocker = true)
42+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
43+
public class JdbcPostgresTest extends ContractTestBase {
44+
45+
private static final String DB_SYSTEM = "postgresql";
46+
private static final String DB_NAME = "testdb";
47+
private static final String DB_USER = "sa";
48+
private static final String DB_PASSWORD = "password";
49+
private static final String DB_OPERATION = "SELECT";
50+
51+
private static final String NETWORK_ALIAS = "postgres";
52+
53+
private PostgreSQLContainer<?> postgreSqlContainer;
54+
55+
@AfterEach
56+
public void afterEach() {
57+
// dependent containers are not stopped between tests, only the application container.
58+
postgreSqlContainer.stop();
59+
}
60+
61+
@Test
62+
public void testSuccess() {
63+
var path = "success";
64+
var method = "GET";
65+
var otelStatusCode = "STATUS_CODE_UNSET";
66+
var dbSqlTable = "employee";
67+
var response = appClient.get(path).aggregate().join();
68+
assertThat(response.status().isSuccess()).isTrue();
69+
70+
var traces = mockCollectorClient.getTraces();
71+
assertAwsSpanAttributes(traces, method, path);
72+
assertSemanticConventionsSpanAttributes(traces, otelStatusCode, dbSqlTable);
73+
74+
var metrics =
75+
mockCollectorClient.getMetrics(
76+
Set.of(
77+
AppSignalsConstants.LATENCY_METRIC,
78+
AppSignalsConstants.ERROR_METRIC,
79+
AppSignalsConstants.FAULT_METRIC));
80+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.LATENCY_METRIC, 5000.0);
81+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.ERROR_METRIC, 0.0);
82+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.FAULT_METRIC, 0.0);
83+
}
84+
85+
@Test
86+
public void testFault() {
87+
var path = "fault";
88+
var method = "GET";
89+
var otelStatusCode = "STATUS_CODE_ERROR";
90+
var dbSqlTable = "userrr";
91+
var response = appClient.get(path).aggregate().join();
92+
assertThat(response.status().isServerError()).isTrue();
93+
94+
var traces = mockCollectorClient.getTraces();
95+
assertAwsSpanAttributes(traces, method, path);
96+
assertSemanticConventionsSpanAttributes(traces, otelStatusCode, dbSqlTable);
97+
98+
var metrics =
99+
mockCollectorClient.getMetrics(
100+
Set.of(
101+
AppSignalsConstants.LATENCY_METRIC,
102+
AppSignalsConstants.ERROR_METRIC,
103+
AppSignalsConstants.FAULT_METRIC));
104+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.LATENCY_METRIC, 5000.0);
105+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.ERROR_METRIC, 0.0);
106+
assertMetricAttributes(metrics, method, path, AppSignalsConstants.FAULT_METRIC, 1.0);
107+
}
108+
109+
@Override
110+
protected String getApplicationImageName() {
111+
return "aws-appsignals-tests-jdbc-app";
112+
}
113+
114+
@Override
115+
protected String getApplicationWaitPattern() {
116+
return ".*Application Ready.*";
117+
}
118+
119+
@Override
120+
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
121+
return Map.of(
122+
"DB_URL",
123+
String.format("jdbc:postgresql://%s:5432/%s", NETWORK_ALIAS, DB_NAME),
124+
"DB_DRIVER",
125+
"org.postgresql.Driver",
126+
"DB_USERNAME",
127+
DB_USER,
128+
"DB_PASSWORD",
129+
DB_PASSWORD,
130+
"DB_PLATFORM",
131+
"org.hibernate.dialect.PostgreSQLDialect");
132+
}
133+
134+
@Override
135+
protected List<Startable> getApplicationDependsOnContainers() {
136+
this.postgreSqlContainer =
137+
new PostgreSQLContainer<>("postgres:16.3")
138+
.withImagePullPolicy(PullPolicy.alwaysPull())
139+
.withUsername(DB_USER)
140+
.withPassword(DB_PASSWORD)
141+
.withDatabaseName(DB_NAME)
142+
.withNetworkAliases(NETWORK_ALIAS)
143+
.withNetwork(network)
144+
.waitingFor(
145+
Wait.forLogMessage(".*database system is ready to accept connections.*", 1));
146+
return List.of(postgreSqlContainer);
147+
}
148+
149+
protected void assertAwsSpanAttributes(
150+
List<ResourceScopeSpan> resourceScopeSpans, String method, String path) {
151+
assertThat(resourceScopeSpans)
152+
.satisfiesOnlyOnce(
153+
rss -> {
154+
assertThat(rss.getSpan().getKind()).isEqualTo(SPAN_KIND_CLIENT);
155+
var attributesList = rss.getSpan().getAttributesList();
156+
assertAwsAttributes(attributesList, method, path);
157+
});
158+
}
159+
160+
protected void assertAwsAttributes(
161+
List<KeyValue> attributesList, String method, String endpoint) {
162+
assertThat(attributesList)
163+
.satisfiesOnlyOnce(
164+
attribute -> {
165+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_LOCAL_OPERATION);
166+
assertThat(attribute.getValue().getStringValue())
167+
.isEqualTo(String.format("%s /%s", method, endpoint));
168+
})
169+
.satisfiesOnlyOnce(
170+
attribute -> {
171+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_LOCAL_SERVICE);
172+
assertThat(attribute.getValue().getStringValue())
173+
.isEqualTo(getApplicationOtelServiceName());
174+
})
175+
.satisfiesOnlyOnce(
176+
attribute -> {
177+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_REMOTE_SERVICE);
178+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_SYSTEM);
179+
})
180+
.satisfiesOnlyOnce(
181+
attribute -> {
182+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_REMOTE_OPERATION);
183+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_OPERATION);
184+
})
185+
.satisfiesOnlyOnce(
186+
attribute -> {
187+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_SPAN_KIND);
188+
assertThat(attribute.getValue().getStringValue()).isEqualTo("CLIENT");
189+
});
190+
}
191+
192+
protected void assertSemanticConventionsSpanAttributes(
193+
List<ResourceScopeSpan> resourceScopeSpans, String otelStatusCode, String dbSqlTable) {
194+
assertThat(resourceScopeSpans)
195+
.satisfiesOnlyOnce(
196+
rss -> {
197+
assertThat(rss.getSpan().getKind()).isEqualTo(SPAN_KIND_CLIENT);
198+
assertThat(rss.getSpan().getName())
199+
.isEqualTo(String.format("%s %s.%s", DB_OPERATION, DB_NAME, dbSqlTable));
200+
assertThat(rss.getSpan().getStatus().getCode().equals(otelStatusCode));
201+
var attributesList = rss.getSpan().getAttributesList();
202+
assertSemanticConventionsAttributes(attributesList, dbSqlTable);
203+
});
204+
}
205+
206+
protected void assertSemanticConventionsAttributes(
207+
List<KeyValue> attributesList, String dbSqlTable) {
208+
assertThat(attributesList)
209+
.satisfiesOnlyOnce(
210+
attribute -> {
211+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.THREAD_ID);
212+
})
213+
.satisfiesOnlyOnce(
214+
attribute -> {
215+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.THREAD_NAME);
216+
})
217+
.satisfiesOnlyOnce(
218+
attribute -> {
219+
assertThat(attribute.getKey())
220+
.isEqualTo(SemanticConventionsConstants.DB_CONNECTION_STRING);
221+
assertThat(attribute.getValue().getStringValue())
222+
.isEqualTo(String.format("postgresql://%s:5432", NETWORK_ALIAS));
223+
})
224+
.satisfiesOnlyOnce(
225+
attribute -> {
226+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_NAME);
227+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_NAME);
228+
})
229+
.satisfiesOnlyOnce(
230+
attribute -> {
231+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_SQL_TABLE);
232+
assertThat(attribute.getValue().getStringValue()).isEqualTo(dbSqlTable);
233+
})
234+
.satisfiesOnlyOnce(
235+
attribute -> {
236+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_STATEMENT);
237+
assertThat(attribute.getValue().getStringValue())
238+
.isEqualTo(
239+
String.format("%s count(*) from %s", DB_OPERATION.toLowerCase(), dbSqlTable));
240+
})
241+
.satisfiesOnlyOnce(
242+
attribute -> {
243+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_USER);
244+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_USER);
245+
})
246+
.satisfiesOnlyOnce(
247+
attribute -> {
248+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_OPERATION);
249+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_OPERATION);
250+
})
251+
.satisfiesOnlyOnce(
252+
attribute -> {
253+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_SYSTEM);
254+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_SYSTEM);
255+
});
256+
}
257+
258+
protected void assertMetricAttributes(
259+
List<ResourceScopeMetric> resourceScopeMetrics,
260+
String method,
261+
String path,
262+
String metricName,
263+
Double expectedSum) {
264+
assertThat(resourceScopeMetrics)
265+
.anySatisfy(
266+
metric -> {
267+
assertThat(metric.getMetric().getName()).isEqualTo(metricName);
268+
List<ExponentialHistogramDataPoint> dpList =
269+
metric.getMetric().getExponentialHistogram().getDataPointsList();
270+
assertThat(dpList)
271+
.satisfiesOnlyOnce(
272+
dp -> {
273+
List<KeyValue> attributesList = dp.getAttributesList();
274+
assertThat(attributesList).isNotNull();
275+
assertThat(attributesList)
276+
.satisfiesOnlyOnce(
277+
attribute -> {
278+
assertThat(attribute.getKey())
279+
.isEqualTo(AppSignalsConstants.AWS_SPAN_KIND);
280+
assertThat(attribute.getValue().getStringValue())
281+
.isEqualTo("CLIENT");
282+
})
283+
.satisfiesOnlyOnce(
284+
attribute -> {
285+
assertThat(attribute.getKey())
286+
.isEqualTo(AppSignalsConstants.AWS_LOCAL_OPERATION);
287+
assertThat(attribute.getValue().getStringValue())
288+
.isEqualTo(String.format("%s /%s", method, path));
289+
})
290+
.satisfiesOnlyOnce(
291+
attribute -> {
292+
assertThat(attribute.getKey())
293+
.isEqualTo(AppSignalsConstants.AWS_LOCAL_SERVICE);
294+
assertThat(attribute.getValue().getStringValue())
295+
.isEqualTo(getApplicationOtelServiceName());
296+
})
297+
.satisfiesOnlyOnce(
298+
attribute -> {
299+
assertThat(attribute.getKey())
300+
.isEqualTo(AppSignalsConstants.AWS_REMOTE_SERVICE);
301+
assertThat(attribute.getValue().getStringValue())
302+
.isEqualTo(DB_SYSTEM);
303+
})
304+
.satisfiesOnlyOnce(
305+
attribute -> {
306+
assertThat(attribute.getKey())
307+
.isEqualTo(AppSignalsConstants.AWS_REMOTE_OPERATION);
308+
assertThat(attribute.getValue().getStringValue())
309+
.isEqualTo(DB_OPERATION);
310+
});
311+
312+
if (expectedSum != null) {
313+
double actualSum = dp.getSum();
314+
switch (metricName) {
315+
case AppSignalsConstants.LATENCY_METRIC:
316+
assertThat(actualSum).isStrictlyBetween(0.0, expectedSum);
317+
break;
318+
default:
319+
assertThat(actualSum).isEqualTo(expectedSum);
320+
}
321+
}
322+
});
323+
});
324+
}
325+
}

0 commit comments

Comments
 (0)