-
-
Notifications
You must be signed in to change notification settings - Fork 62
reliability(consumers): add end to end test (WIP) #7266
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
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,141 @@ | ||
| import subprocess | ||
| import time | ||
| import uuid | ||
| from contextlib import contextmanager | ||
| from datetime import UTC, datetime, timedelta | ||
| from typing import Generator, Optional | ||
|
|
||
| from confluent_kafka import Message as KafkaMessage | ||
| from confluent_kafka import Producer | ||
| from confluent_kafka.admin import AdminClient, NewTopic | ||
| from google.protobuf.timestamp_pb2 import Timestamp | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. High severity and reachable issue identified in your code: ℹ️ Why this is reachableA reachable issue is a real security risk because your project actually executes the vulnerable code. This issue is reachable because your code uses a certain version of protobuf. To resolve this comment: 💬 Ignore this findingTo ignore this, reply with:
You can view more details on this finding in the Semgrep AppSec Platform here. |
||
| from sentry_protos.snuba.v1.request_common_pb2 import TraceItemType | ||
| from sentry_protos.snuba.v1.trace_item_pb2 import AnyValue, TraceItem | ||
|
|
||
| from snuba.clusters.cluster import ClickhouseClientSettings | ||
| from snuba.datasets.storages.factory import get_storage | ||
| from snuba.datasets.storages.storage_key import StorageKey | ||
|
|
||
| # Kafka configuration | ||
| kafka_config = {"bootstrap.servers": "localhost:9092", "client.id": "items_producer"} | ||
|
|
||
| producer = Producer(kafka_config) | ||
|
|
||
|
|
||
| def generate_item_message(start_timestamp: Optional[datetime] = None) -> bytes: | ||
| if start_timestamp is None: | ||
| start_timestamp = datetime.now(tz=UTC) | ||
|
|
||
| item_timestamp = Timestamp() | ||
| item_timestamp.FromDatetime(start_timestamp) | ||
|
|
||
| received = Timestamp() | ||
| received.GetCurrentTime() | ||
|
|
||
| end_timestamp = start_timestamp + timedelta(seconds=1) | ||
|
|
||
| attributes = { | ||
| "category": AnyValue(string_value="http"), | ||
| "description": AnyValue(string_value="/api/0/events/"), | ||
| "environment": AnyValue(string_value="production"), | ||
| "http.status_code": AnyValue(string_value="200"), | ||
| "op": AnyValue(string_value="http.server"), | ||
| "platform": AnyValue(string_value="python"), | ||
| "sentry.received": AnyValue(double_value=received.seconds), | ||
| "sentry.start_timestamp_precise": AnyValue( | ||
| double_value=start_timestamp.timestamp() | ||
| ), | ||
| "sentry.end_timestamp_precise": AnyValue( | ||
| double_value=end_timestamp.timestamp() | ||
| ), | ||
| "start_timestamp_ms": AnyValue( | ||
| double_value=int(start_timestamp.timestamp() * 1000) | ||
| ), | ||
| } | ||
|
|
||
| return TraceItem( | ||
| organization_id=1, | ||
| project_id=1, | ||
| item_type=TraceItemType.TRACE_ITEM_TYPE_SPAN, | ||
| timestamp=item_timestamp, | ||
| trace_id=uuid.uuid4().hex, | ||
| item_id=uuid.uuid4().int.to_bytes(16, byteorder="little"), | ||
| received=received, | ||
| retention_days=90, | ||
| server_sample_rate=1.0, | ||
| attributes=attributes, | ||
| ).SerializeToString() | ||
|
|
||
|
|
||
| @contextmanager | ||
| def tmp_kafka_topic() -> Generator[str, None, None]: | ||
| # Create a unique topic name | ||
| topic_name = f"snuba-items-{uuid.uuid4().hex}" | ||
|
|
||
| # Create the topic | ||
| admin_client = AdminClient({"bootstrap.servers": "localhost:9092"}) | ||
| new_topic = NewTopic(topic_name, num_partitions=1, replication_factor=1) | ||
| admin_client.create_topics([new_topic]) | ||
|
|
||
| try: | ||
| yield topic_name | ||
| finally: | ||
| # Clean up the topic | ||
| admin_client.delete_topics([topic_name]) | ||
|
|
||
|
|
||
| def test_items_consumer() -> None: | ||
| # Launch the consumer process | ||
| with tmp_kafka_topic() as topic: | ||
| consumer_process = subprocess.Popen( | ||
| [ | ||
| "snuba", | ||
| "rust-consumer", | ||
| "--raw-events-topic", | ||
| topic, | ||
| "--consumer-version", | ||
| "v2", | ||
| "--consumer-group", | ||
| f"eap_items_consumer_{uuid.uuid4().hex}", | ||
| "--storage", | ||
| "eap_items", | ||
| "--no-strict-offset-reset", | ||
| ], | ||
| stdout=subprocess.PIPE, | ||
| stderr=subprocess.PIPE, | ||
| ) | ||
| storage = get_storage(StorageKey("eap_items")) | ||
| storage.get_cluster().get_query_connection( | ||
| ClickhouseClientSettings.QUERY | ||
| ).execute("TRUNCATE TABLE IF EXISTS eap_items_1_local") | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This will truncate after starting the consumer and consumer may get empty table mid-way. Is it safer to truncate before starting the consumer?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There's nothing on the consumer topic at the time it starts so no, it's not an issue |
||
|
|
||
| # Wait for consumer to initialize | ||
| time.sleep(2) | ||
|
Comment on lines
+112
to
+113
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this be fragile? Can we poll until ClickHouse is ready to reduce the fragility?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not waiting for clickhouse, I'm waiting for the consumer to start and I can start producing things to the topic before it fully starts |
||
| num_items = 1000 | ||
|
|
||
| def delivery_report(err: Optional[Exception], msg: KafkaMessage) -> None: | ||
| if err is not None: | ||
| raise err | ||
|
|
||
| try: | ||
| count = 0 | ||
| while count < num_items: | ||
| count += 1 | ||
| if count % 100 == 0: | ||
| producer.poll(0) | ||
| message = generate_item_message() | ||
| producer.produce(topic, message, callback=delivery_report) | ||
| finally: | ||
| # Wait for any outstanding messages to be delivered | ||
| producer.flush() | ||
|
|
||
| res = ( | ||
| storage.get_cluster() | ||
| .get_query_connection(ClickhouseClientSettings.QUERY) | ||
| .execute("SELECT count(*) FROM default.eap_items_1_local") | ||
| ) | ||
| try: | ||
| consumer_process.terminate() | ||
| except Exception: | ||
| pass | ||
| assert res.results[0][0] == num_items | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Will this fail if some records aren't yet flushed? How about if we retry until a timeout instead of asserting immediately?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. calling |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unintended break point?