Skip to content

Commit 7c41408

Browse files
Add app signals contract test for postgres
1 parent fe6a8f3 commit 7c41408

File tree

7 files changed

+348
-8
lines changed

7 files changed

+348
-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: 14 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,16 @@ protected String getApplicationWaitPattern() {
98100
return ".*Application Ready.*";
99101
}
100102

103+
@Override
104+
protected Map<String, String> getApplicationExtraEnvironmentVariables() {
105+
return Map.of(
106+
"DB_URL", String.format("jdbc:h2:mem:%s", DB_NAME),
107+
"DB_DRIVER", "org.h2.Driver",
108+
"DB_USERNAME", DB_USER,
109+
"DB_PASSWORD", DB_PASSWORD,
110+
"DB_PLATFORM", "org.hibernate.dialect.H2Dialect");
111+
}
112+
101113
protected void assertAwsSpanAttributes(
102114
List<ResourceScopeSpan> resourceScopeSpans, String method, String path) {
103115
assertThat(resourceScopeSpans)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
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", String.format("jdbc:postgresql://%s:5432/%s", NETWORK_ALIAS, DB_NAME),
123+
"DB_DRIVER", "org.postgresql.Driver",
124+
"DB_USERNAME", DB_USER,
125+
"DB_PASSWORD", DB_PASSWORD,
126+
"DB_PLATFORM", "org.hibernate.dialect.PostgreSQLDialect");
127+
}
128+
129+
@Override
130+
protected List<Startable> getApplicationDependsOnContainers() {
131+
this.postgreSqlContainer =
132+
new PostgreSQLContainer<>("postgres:16.3")
133+
.withImagePullPolicy(PullPolicy.alwaysPull())
134+
.withUsername(DB_USER)
135+
.withPassword(DB_PASSWORD)
136+
.withDatabaseName(DB_NAME)
137+
.withNetworkAliases(NETWORK_ALIAS)
138+
.withNetwork(network)
139+
.waitingFor(
140+
Wait.forLogMessage(".*database system is ready to accept connections.*", 1));
141+
return List.of(postgreSqlContainer);
142+
}
143+
144+
protected void assertAwsSpanAttributes(
145+
List<ResourceScopeSpan> resourceScopeSpans, String method, String path) {
146+
assertThat(resourceScopeSpans)
147+
.satisfiesOnlyOnce(
148+
rss -> {
149+
assertThat(rss.getSpan().getKind()).isEqualTo(SPAN_KIND_CLIENT);
150+
var attributesList = rss.getSpan().getAttributesList();
151+
assertAwsAttributes(attributesList, method, path);
152+
});
153+
}
154+
155+
protected void assertAwsAttributes(
156+
List<KeyValue> attributesList, String method, String endpoint) {
157+
assertThat(attributesList)
158+
.satisfiesOnlyOnce(
159+
attribute -> {
160+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_LOCAL_OPERATION);
161+
assertThat(attribute.getValue().getStringValue())
162+
.isEqualTo(String.format("%s /%s", method, endpoint));
163+
})
164+
.satisfiesOnlyOnce(
165+
attribute -> {
166+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_LOCAL_SERVICE);
167+
assertThat(attribute.getValue().getStringValue())
168+
.isEqualTo(getApplicationOtelServiceName());
169+
})
170+
.satisfiesOnlyOnce(
171+
attribute -> {
172+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_REMOTE_SERVICE);
173+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_SYSTEM);
174+
})
175+
.satisfiesOnlyOnce(
176+
attribute -> {
177+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_REMOTE_OPERATION);
178+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_OPERATION);
179+
})
180+
.satisfiesOnlyOnce(
181+
attribute -> {
182+
assertThat(attribute.getKey()).isEqualTo(AppSignalsConstants.AWS_SPAN_KIND);
183+
assertThat(attribute.getValue().getStringValue()).isEqualTo("CLIENT");
184+
});
185+
}
186+
187+
protected void assertSemanticConventionsSpanAttributes(
188+
List<ResourceScopeSpan> resourceScopeSpans, String otelStatusCode, String dbSqlTable) {
189+
assertThat(resourceScopeSpans)
190+
.satisfiesOnlyOnce(
191+
rss -> {
192+
assertThat(rss.getSpan().getKind()).isEqualTo(SPAN_KIND_CLIENT);
193+
assertThat(rss.getSpan().getName())
194+
.isEqualTo(String.format("%s %s.%s", DB_OPERATION, DB_NAME, dbSqlTable));
195+
assertThat(rss.getSpan().getStatus().getCode().equals(otelStatusCode));
196+
var attributesList = rss.getSpan().getAttributesList();
197+
assertSemanticConventionsAttributes(attributesList, dbSqlTable);
198+
});
199+
}
200+
201+
protected void assertSemanticConventionsAttributes(
202+
List<KeyValue> attributesList, String dbSqlTable) {
203+
assertThat(attributesList)
204+
.satisfiesOnlyOnce(
205+
attribute -> {
206+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.THREAD_ID);
207+
})
208+
.satisfiesOnlyOnce(
209+
attribute -> {
210+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.THREAD_NAME);
211+
})
212+
.satisfiesOnlyOnce(
213+
attribute -> {
214+
assertThat(attribute.getKey())
215+
.isEqualTo(SemanticConventionsConstants.DB_CONNECTION_STRING);
216+
assertThat(attribute.getValue().getStringValue())
217+
.isEqualTo(String.format("postgresql://%s:5432", NETWORK_ALIAS));
218+
})
219+
.satisfiesOnlyOnce(
220+
attribute -> {
221+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_NAME);
222+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_NAME);
223+
})
224+
.satisfiesOnlyOnce(
225+
attribute -> {
226+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_SQL_TABLE);
227+
assertThat(attribute.getValue().getStringValue()).isEqualTo(dbSqlTable);
228+
})
229+
.satisfiesOnlyOnce(
230+
attribute -> {
231+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_STATEMENT);
232+
assertThat(attribute.getValue().getStringValue())
233+
.isEqualTo(
234+
String.format("%s count(*) from %s", DB_OPERATION.toLowerCase(), dbSqlTable));
235+
})
236+
.satisfiesOnlyOnce(
237+
attribute -> {
238+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_USER);
239+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_USER);
240+
})
241+
.satisfiesOnlyOnce(
242+
attribute -> {
243+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_OPERATION);
244+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_OPERATION);
245+
})
246+
.satisfiesOnlyOnce(
247+
attribute -> {
248+
assertThat(attribute.getKey()).isEqualTo(SemanticConventionsConstants.DB_SYSTEM);
249+
assertThat(attribute.getValue().getStringValue()).isEqualTo(DB_SYSTEM);
250+
});
251+
}
252+
253+
protected void assertMetricAttributes(
254+
List<ResourceScopeMetric> resourceScopeMetrics,
255+
String method,
256+
String path,
257+
String metricName,
258+
Double expectedSum) {
259+
assertThat(resourceScopeMetrics)
260+
.anySatisfy(
261+
metric -> {
262+
assertThat(metric.getMetric().getName()).isEqualTo(metricName);
263+
List<ExponentialHistogramDataPoint> dpList =
264+
metric.getMetric().getExponentialHistogram().getDataPointsList();
265+
assertThat(dpList)
266+
.satisfiesOnlyOnce(
267+
dp -> {
268+
List<KeyValue> attributesList = dp.getAttributesList();
269+
assertThat(attributesList).isNotNull();
270+
assertThat(attributesList)
271+
.satisfiesOnlyOnce(
272+
attribute -> {
273+
assertThat(attribute.getKey())
274+
.isEqualTo(AppSignalsConstants.AWS_SPAN_KIND);
275+
assertThat(attribute.getValue().getStringValue())
276+
.isEqualTo("CLIENT");
277+
})
278+
.satisfiesOnlyOnce(
279+
attribute -> {
280+
assertThat(attribute.getKey())
281+
.isEqualTo(AppSignalsConstants.AWS_LOCAL_OPERATION);
282+
assertThat(attribute.getValue().getStringValue())
283+
.isEqualTo(String.format("%s /%s", method, path));
284+
})
285+
.satisfiesOnlyOnce(
286+
attribute -> {
287+
assertThat(attribute.getKey())
288+
.isEqualTo(AppSignalsConstants.AWS_LOCAL_SERVICE);
289+
assertThat(attribute.getValue().getStringValue())
290+
.isEqualTo(getApplicationOtelServiceName());
291+
})
292+
.satisfiesOnlyOnce(
293+
attribute -> {
294+
assertThat(attribute.getKey())
295+
.isEqualTo(AppSignalsConstants.AWS_REMOTE_SERVICE);
296+
assertThat(attribute.getValue().getStringValue())
297+
.isEqualTo(DB_SYSTEM);
298+
})
299+
.satisfiesOnlyOnce(
300+
attribute -> {
301+
assertThat(attribute.getKey())
302+
.isEqualTo(AppSignalsConstants.AWS_REMOTE_OPERATION);
303+
assertThat(attribute.getValue().getStringValue())
304+
.isEqualTo(DB_OPERATION);
305+
});
306+
307+
if (expectedSum != null) {
308+
double actualSum = dp.getSum();
309+
switch (metricName) {
310+
case AppSignalsConstants.LATENCY_METRIC:
311+
assertThat(actualSum).isStrictlyBetween(0.0, expectedSum);
312+
break;
313+
default:
314+
assertThat(actualSum).isEqualTo(expectedSum);
315+
}
316+
}
317+
});
318+
});
319+
}
320+
}

appsignals-tests/images/jdbc/build.gradle.kts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ dependencies {
3131
implementation("org.springframework.boot:spring-boot-starter-jdbc:3.1.4")
3232
implementation("com.h2database:h2:2.2.224")
3333
implementation("org.slf4j:slf4j-simple")
34+
implementation("org.postgresql:postgresql:42.2.0")
3435
}
3536

3637
// not publishing images to hubs in this configuration - local build only through jibDockerBuild

0 commit comments

Comments
 (0)