-
Notifications
You must be signed in to change notification settings - Fork 22
Implement Contract Test for Psychopg2 #72
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
143 commits
Select commit
Hold shift + click to select a range
a6297d0
Test application
XinRanZhAWS 34bbba7
test-app and basic test
XinRanZhAWS 54dfb0b
Test application
XinRanZhAWS 375632d
minor fix for docker
5d277c0
docker compose related minor fix
320a020
Merge branch 'main' into psychopg2-contract-test
7bfd827
add debug info
767c1e7
remove debug information
b2377d4
temp comment out bug line
a9407be
connect db network
4303261
connect db network
31e0496
connect db network
6a20033
connect db network
179dd1f
connect db network
5d2c04f
connect db network
05ebea6
connect db network
60cf592
connect db network
06bd0ca
adjust test env
941c621
adjust test env
7a48bb9
debug line
2c207bf
debug line
fc05a6b
Debug to see whats wrong
04a5934
Debug to see whats wrong
20c127b
Debug to see whats wrong
eecbd5c
Debug to see whats wrong
abdd708
Debug to see whats wrong
4cacb92
Debug to see whats wrong
6f77f8f
Debug to see whats wrong
195443a
Fix an issue that broke the run
5029ba7
Fix an issue that broke the run
4487d38
Fix an issue that broke the run
3ccffd5
init, functional
7d4a98f
Checkstyle fix
651b677
remove un-used print and local bypass
d08362f
Check style fix
f903367
Check style fix && clean resource after use
3ae303d
Checkstyle fix
0b465e9
Checkstyle fix
1eacd82
Checkstyle fix
7440369
Checkstyle fix
71a74d4
drop unneeded requests
1b6d3bc
local bypass for ec2 issue
8e3ecb9
try to avoid psycopg2-binary
abce245
use official python try to get through psychopg2 problem
64fcaa9
Fix issue related to test
2bda077
Fix issue related to test
04f5e0f
Fix issue related to test
7401b56
Fix test
1b80e6d
Fix test
622b5fb
Fix test
63e7bf3
Fix test
d8e7bd6
Fix test
f0a1077
Fix test
ccf94a2
Fix test
a775ca8
Fix test
5ba7196
Fix test
82d9c11
Fix test
3e3573d
Fix test
d6122ab
Fix test
5ed5f36
Fix test
1abf1d1
Fix test
933f2e5
Fix test
7390503
Fix test
dab6d28
Checkstyle and naming
83e984f
Checkstyle
8ce89e0
Checkstyle and naming
7e8a5f8
Remove unneeded debug message
ac74f38
Remove unneeded debug message, naming and env
a76d9cd
Checkstyle and
cfbcd94
Reflect to comment
a057cab
Merge branch 'main' into psychopg2-contract-test
XinRanZhAWS 83608cf
Local bypass
2248bad
Remove local bypass
a6c29b8
Solve an issue cause non-stable workflow run
0699982
Merge branch 'main' into psychopg2-contract-test
448ed99
Solve an issue cause non-stable workflow run
e37fd13
use testcontainers to create postgres container
1d1da66
use testcontainers to create postgres container
9115e51
use testcontainers to create postgres container
5d9a3df
use testcontainers to create postgres container
33c0ae5
use testcontainers to create postgres container
088b8a5
use testcontainers to create postgres container
eb359fa
use testcontainers to create postgres container
ebfafb4
checkstyle
16abdb6
install dependency
db933f3
install dependency
8051291
reflect comment part 1
1c581fe
Clear signals after done with db setup to avoid issue
4a904b0
Clear signals after done with db setup to avoid issue
aea8b68
Clear signals after done with db setup to avoid issue
401230d
fix tests
d0c03b6
fix tests
1155c8d
fix tests
9408e48
fix tests
e8d7233
Adjust test behaviour
f560f81
Adjust test behaviour
df0699d
Adjust test behaviour
e57bceb
fix a typo
c04278a
Change the way test implemented
377e9a6
Change the way test implemented
1b86274
Change the way test implemented && Checkstyle
e2c74fb
checkstyle
XinRanZhAWS 4b90f51
Change the way to do test
9db1e03
Change the way to do test
c69a07e
Change the way to do test
214c5e5
Move request test do_request to above level
a55a48d
Move request test do_request to above level
f184656
Improve integration
7b9cd39
Improve integration
1aae385
checkstyle
XinRanZhAWS fd52a06
checkstyle
7edd191
checkstyle fix
7ae1699
checkstyle fix
822d53a
checkstyle fix
a004c74
checkstyle
XinRanZhAWS b352d65
fix based on comment
39f298a
Merge remote-tracking branch 'origin/psychopg2-contract-test' into ps…
4ac6d27
fix a typo
b262f18
fix an issue
14dff5b
Update contract-tests/tests/test/amazon/psycopg2/psycopg2_test.py
XinRanZhAWS ecd4e3f
Update contract-tests/tests/test/amazon/psycopg2/psycopg2_test.py
XinRanZhAWS d8011c1
Adjust based on comments
1d4901c
Adjust based on comments
c279ff8
fix a typo
5835dde
move some tests based on comments
3c01bcf
typo fix
ebfa375
typo fix
9edafde
fix the way some test implemented
8fa68e2
checkstyle
XinRanZhAWS 5c9cc13
checkstyle
1790ccb
debug msg
89333a3
add test for status_code
ec0f028
add test for status_code
d6b3b88
debug print
0f39d66
change behaviour for test
e4fbb7c
change the way test implemented
eb13fb8
change the way test implemented
7b89f5e
change the way test implemented
c30284e
change the way test implemented
d8ea43b
Pythonic
10bcc44
checktyle
XinRanZhAWS 88312d0
Merge branch 'main' into psychopg2-contract-test
XinRanZhAWS 03af765
remove a debug msg
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
# Meant to be run from aws-otel-python-instrumentation/contract-tests. | ||
# Assumes existence of dist/aws_opentelemetry_distro-<pkg_version>-py3-none-any.whl. | ||
# Assumes filename of aws_opentelemetry_distro-<pkg_version>-py3-none-any.whl is passed in as "DISTRO" arg. | ||
FROM python:3.10 | ||
WORKDIR /psycopg2 | ||
COPY ./dist/$DISTRO /psycopg2 | ||
COPY ./contract-tests/images/applications/psycopg2 /psycopg2 | ||
|
||
ENV PIP_ROOT_USER_ACTION=ignore | ||
ARG DISTRO | ||
RUN pip install --upgrade pip && pip install -r requirements.txt && pip install ${DISTRO} --force-reinstall | ||
RUN opentelemetry-bootstrap -a install | ||
|
||
# Without `-u`, logs will be buffered and `wait_for_logs` will never return. | ||
CMD ["opentelemetry-instrument", "python", "-u", "./psycopg2_server.py"] |
96 changes: 96 additions & 0 deletions
96
contract-tests/images/applications/psycopg2/psycopg2_server.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
import atexit | ||
import os | ||
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer | ||
from threading import Thread | ||
from typing import Tuple | ||
|
||
import psycopg2 | ||
from typing_extensions import override | ||
|
||
_PORT: int = 8080 | ||
_SUCCESS: str = "success" | ||
_ERROR: str = "error" | ||
_FAULT: str = "fault" | ||
|
||
_DB_HOST = os.getenv("DB_HOST") | ||
_DB_USER = os.getenv("DB_USER") | ||
_DB_PASS = os.getenv("DB_PASS") | ||
_DB_NAME = os.getenv("DB_NAME") | ||
|
||
|
||
def prepare_database() -> None: | ||
conn = psycopg2.connect(dbname=_DB_NAME, user=_DB_USER, password=_DB_PASS, host=_DB_HOST) | ||
cur = conn.cursor() | ||
cur.execute("DROP TABLE IF EXISTS test_table") | ||
cur.execute( | ||
""" | ||
CREATE TABLE test_table ( | ||
id SERIAL PRIMARY KEY, | ||
name TEXT NOT NULL | ||
) | ||
""" | ||
) | ||
|
||
cur.execute("INSERT INTO test_table (name) VALUES (%s)", ("Alice",)) | ||
cur.execute("INSERT INTO test_table (name) VALUES (%s)", ("Bob",)) | ||
|
||
conn.commit() | ||
|
||
cur.close() | ||
conn.close() | ||
|
||
|
||
class RequestHandler(BaseHTTPRequestHandler): | ||
@override | ||
# pylint: disable=invalid-name | ||
def do_GET(self): | ||
status_code: int = 200 | ||
conn = psycopg2.connect(dbname=_DB_NAME, user=_DB_USER, password=_DB_PASS, host=_DB_HOST) | ||
if self.in_path(_SUCCESS): | ||
cur = conn.cursor() | ||
cur.execute("SELECT id, name FROM test_table") | ||
rows = cur.fetchall() | ||
cur.close() | ||
if len(rows) == 2: | ||
status_code = 200 | ||
else: | ||
status_code = 400 | ||
elif self.in_path(_FAULT): | ||
cur = conn.cursor() | ||
try: | ||
cur.execute("SELECT DISTINCT id, name FROM invalid_table") | ||
except psycopg2.ProgrammingError as exception: | ||
print("Expected Exception with Invalid SQL occurred:", exception) | ||
status_code = 500 | ||
except Exception as exception: # pylint: disable=broad-except | ||
print("Exception Occurred:", exception) | ||
else: | ||
status_code = 200 | ||
finally: | ||
cur.close() | ||
else: | ||
status_code = 404 | ||
conn.close() | ||
self.send_response_only(status_code) | ||
self.end_headers() | ||
|
||
def in_path(self, sub_path: str): | ||
return sub_path in self.path | ||
|
||
|
||
def main() -> None: | ||
prepare_database() | ||
server_address: Tuple[str, int] = ("0.0.0.0", _PORT) | ||
request_handler_class: type = RequestHandler | ||
requests_server: ThreadingHTTPServer = ThreadingHTTPServer(server_address, request_handler_class) | ||
atexit.register(requests_server.shutdown) | ||
server_thread: Thread = Thread(target=requests_server.serve_forever) | ||
server_thread.start() | ||
print("Ready") | ||
server_thread.join() | ||
|
||
|
||
if __name__ == "__main__": | ||
main() |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
[project] | ||
name = "psycopg2-server" | ||
description = "Simple server that relies on psycopg2 library" | ||
version = "1.0.0" | ||
license = "Apache-2.0" | ||
requires-python = ">=3.8" |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
opentelemetry-distro==0.43b0 | ||
opentelemetry-exporter-otlp-proto-grpc==1.22.0 | ||
typing-extensions==4.9.0 | ||
psycopg2==2.9.9 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
152 changes: 152 additions & 0 deletions
152
contract-tests/tests/test/amazon/psycopg2/psycopg2_test.py
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
from typing import Dict, List | ||
|
||
from mock_collector_client import ResourceScopeMetric, ResourceScopeSpan | ||
from testcontainers.postgres import PostgresContainer | ||
from typing_extensions import override | ||
|
||
from amazon.base.contract_test_base import NETWORK_NAME, ContractTestBase | ||
from amazon.utils.app_signals_constants import ( | ||
AWS_LOCAL_OPERATION, | ||
AWS_LOCAL_SERVICE, | ||
AWS_REMOTE_OPERATION, | ||
AWS_REMOTE_SERVICE, | ||
AWS_SPAN_KIND, | ||
) | ||
from opentelemetry.proto.common.v1.common_pb2 import AnyValue, KeyValue | ||
from opentelemetry.proto.metrics.v1.metrics_pb2 import ExponentialHistogramDataPoint, Metric | ||
from opentelemetry.proto.trace.v1.trace_pb2 import Span | ||
from opentelemetry.trace import StatusCode | ||
|
||
|
||
class Psycopg2Test(ContractTestBase): | ||
@override | ||
@classmethod | ||
def set_up_dependency_container(cls) -> None: | ||
cls.container = ( | ||
PostgresContainer(user="dbuser", password="example", dbname="postgres") | ||
.with_kwargs(network=NETWORK_NAME) | ||
.with_name("mydb") | ||
) | ||
cls.container.start() | ||
|
||
@override | ||
@classmethod | ||
def tear_down_dependency_container(cls) -> None: | ||
cls.container.stop() | ||
|
||
@override | ||
def get_application_extra_environment_variables(self) -> Dict[str, str]: | ||
return { | ||
"DB_HOST": "mydb", | ||
"DB_USER": "dbuser", | ||
"DB_PASS": "example", | ||
"DB_NAME": "postgres", | ||
thpierce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
@override | ||
def get_application_image_name(self) -> str: | ||
return "aws-appsignals-tests-psycopg2-app" | ||
|
||
def test_success(self) -> None: | ||
self.mock_collector_client.clear_signals() | ||
self.do_test_requests("success", "GET", 200, 0, 0, sql_command="SELECT") | ||
|
||
def test_fault(self) -> None: | ||
self.mock_collector_client.clear_signals() | ||
self.do_test_requests("fault", "GET", 500, 0, 1, sql_command="SELECT DISTINCT") | ||
|
||
@override | ||
def _assert_aws_span_attributes(self, resource_scope_spans: List[ResourceScopeSpan], path: str, **kwargs) -> None: | ||
target_spans: List[Span] = [] | ||
for resource_scope_span in resource_scope_spans: | ||
# pylint: disable=no-member | ||
if resource_scope_span.span.kind == Span.SPAN_KIND_CLIENT: | ||
target_spans.append(resource_scope_span.span) | ||
|
||
self.assertEqual(len(target_spans), 1) | ||
self._assert_aws_attributes(target_spans[0].attributes, **kwargs) | ||
|
||
@override | ||
def _assert_aws_attributes(self, attributes_list: List[KeyValue], **kwargs) -> None: | ||
attributes_dict: Dict[str, AnyValue] = self._get_attributes_dict(attributes_list) | ||
self._assert_str_attribute(attributes_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) | ||
# InternalOperation as OTEL does not instrument the basic server we are using, so the client span is a local | ||
# root. | ||
self._assert_str_attribute(attributes_dict, AWS_LOCAL_OPERATION, "InternalOperation") | ||
self._assert_str_attribute(attributes_dict, AWS_REMOTE_SERVICE, "postgresql") | ||
command: str = kwargs.get("sql_command") | ||
self._assert_str_attribute(attributes_dict, AWS_REMOTE_OPERATION, f"{command}") | ||
# See comment above AWS_LOCAL_OPERATION | ||
self._assert_str_attribute(attributes_dict, AWS_SPAN_KIND, "LOCAL_ROOT") | ||
|
||
def _get_attributes_dict(self, attributes_list: List[KeyValue]) -> Dict[str, AnyValue]: | ||
attributes_dict: Dict[str, AnyValue] = {} | ||
for attribute in attributes_list: | ||
key: str = attribute.key | ||
value: AnyValue = attribute.value | ||
if key in attributes_dict: | ||
old_value: AnyValue = attributes_dict[key] | ||
self.fail(f"Attribute {key} unexpectedly duplicated. Value 1: {old_value} Value 2: {value}") | ||
attributes_dict[key] = value | ||
return attributes_dict | ||
|
||
@override | ||
def _assert_semantic_conventions_span_attributes( | ||
thpierce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
self, resource_scope_spans: List[ResourceScopeSpan], method: str, path: str, status_code: int, **kwargs | ||
thpierce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
) -> None: | ||
target_spans: List[Span] = [] | ||
for resource_scope_span in resource_scope_spans: | ||
# pylint: disable=no-member | ||
if resource_scope_span.span.kind == Span.SPAN_KIND_CLIENT: | ||
target_spans.append(resource_scope_span.span) | ||
|
||
self.assertEqual(target_spans[0].name, kwargs.get("sql_command").split()[0]) | ||
if status_code == 200: | ||
self.assertEqual(target_spans[0].status.code, StatusCode.UNSET.value) | ||
else: | ||
self.assertEqual(target_spans[0].status.code, StatusCode.ERROR.value) | ||
|
||
self._assert_semantic_conventions_attributes(target_spans[0].attributes, kwargs.get("sql_command")) | ||
|
||
def _assert_semantic_conventions_attributes(self, attributes_list: List[KeyValue], command: str) -> None: | ||
thpierce marked this conversation as resolved.
Show resolved
Hide resolved
|
||
attributes_dict: Dict[str, AnyValue] = self._get_attributes_dict(attributes_list) | ||
self.assertTrue(attributes_dict.get("db.statement").string_value.startswith(command)) | ||
self._assert_str_attribute(attributes_dict, "db.system", "postgresql") | ||
self._assert_str_attribute(attributes_dict, "db.name", "postgres") | ||
self.assertTrue("db.operation" not in attributes_dict) | ||
|
||
@override | ||
def _assert_metric_attributes( | ||
self, resource_scope_metrics: List[ResourceScopeMetric], metric_name: str, expected_sum: int, **kwargs | ||
) -> None: | ||
target_metrics: List[Metric] = [] | ||
for resource_scope_metric in resource_scope_metrics: | ||
if resource_scope_metric.metric.name.lower() == metric_name.lower(): | ||
target_metrics.append(resource_scope_metric.metric) | ||
|
||
self.assertEqual(len(target_metrics), 1) | ||
target_metric: Metric = target_metrics[0] | ||
dp_list: List[ExponentialHistogramDataPoint] = target_metric.exponential_histogram.data_points | ||
|
||
self.assertEqual(len(dp_list), 2) | ||
dependency_dp: ExponentialHistogramDataPoint = dp_list[0] | ||
service_dp: ExponentialHistogramDataPoint = dp_list[1] | ||
if len(dp_list[1].attributes) > len(dp_list[0].attributes): | ||
dependency_dp = dp_list[1] | ||
service_dp = dp_list[0] | ||
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(dependency_dp.attributes) | ||
self._assert_str_attribute(attribute_dict, AWS_LOCAL_SERVICE, self.get_application_otel_service_name()) | ||
# See comment on AWS_LOCAL_OPERATION in _assert_aws_attributes | ||
self._assert_str_attribute(attribute_dict, AWS_LOCAL_OPERATION, "InternalOperation") | ||
self._assert_str_attribute(attribute_dict, AWS_REMOTE_SERVICE, "postgresql") | ||
self._assert_str_attribute(attribute_dict, AWS_REMOTE_OPERATION, kwargs.get("sql_command")) | ||
self._assert_str_attribute(attribute_dict, AWS_SPAN_KIND, "CLIENT") | ||
self.check_sum(metric_name, dependency_dp.sum, expected_sum) | ||
|
||
attribute_dict: Dict[str, AnyValue] = self._get_attributes_dict(service_dp.attributes) | ||
# See comment on AWS_LOCAL_OPERATION in _assert_aws_attributes | ||
self._assert_str_attribute(attribute_dict, AWS_LOCAL_OPERATION, "InternalOperation") | ||
self._assert_str_attribute(attribute_dict, AWS_SPAN_KIND, "LOCAL_ROOT") | ||
self.check_sum(metric_name, service_dp.sum, expected_sum) |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.