Skip to content
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

feat: new headers and payload ( connection id and interval) #346

Merged
merged 24 commits into from
Mar 5, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 30 additions & 6 deletions UnleashClient/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ def __init__(
self.unleash_app_name = app_name
self.unleash_environment = environment
self.unleash_instance_id = instance_id
self._connection_id = str(uuid.uuid4())
self.unleash_refresh_interval = refresh_interval
self.unleash_request_timeout = request_timeout
self.unleash_request_retries = request_retries
Expand Down Expand Up @@ -183,6 +184,18 @@ def __init__(
engine=self.engine,
)

@property
def unleash_refresh_interval_str_millis(self) -> str:
return str(self.unleash_refresh_interval * 1000)

@property
def unleash_metrics_interval_str_millis(self) -> str:
return str(self.unleash_metrics_interval * 1000)

@property
def connection_id(self):
return self._connection_id

def initialize_client(self, fetch_toggles: bool = True) -> None:
"""
Initializes client and starts communication with central unleash server(s).
Expand Down Expand Up @@ -213,19 +226,25 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
if not self.is_initialized:
# pylint: disable=no-else-raise
try:
headers = {
base_headers = {
**self.unleash_custom_headers,
"unleash-connection-id": str(uuid.uuid4()),
"unleash-connection-id": self.connection_id,
"unleash-appname": self.unleash_app_name,
"unleash-sdk": f"{SDK_NAME}:{SDK_VERSION}",
}

metrics_headers = {
**base_headers,
"unleash-interval": self.unleash_metrics_interval_str_millis,
}

# Setup
metrics_args = {
"url": self.unleash_url,
"app_name": self.unleash_app_name,
"connection_id": self.connection_id,
"instance_id": self.unleash_instance_id,
"headers": headers,
"headers": metrics_headers,
"custom_options": self.unleash_custom_options,
"request_timeout": self.unleash_request_timeout,
"engine": self.engine,
Expand All @@ -237,19 +256,25 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
self.unleash_url,
self.unleash_app_name,
self.unleash_instance_id,
self.connection_id,
self.unleash_metrics_interval,
headers,
base_headers,
self.unleash_custom_options,
self.strategy_mapping,
self.unleash_request_timeout,
)

if fetch_toggles:
fetch_headers = {
**base_headers,
"unleash-interval": self.unleash_refresh_interval_str_millis,
}

job_args = {
"url": self.unleash_url,
"app_name": self.unleash_app_name,
"instance_id": self.unleash_instance_id,
"headers": headers,
"headers": fetch_headers,
"custom_options": self.unleash_custom_options,
"cache": self.cache,
"engine": self.engine,
Expand Down Expand Up @@ -277,7 +302,6 @@ def initialize_client(self, fetch_toggles: bool = True) -> None:
executor=self.unleash_executor_name,
kwargs=job_args,
)

if not self.unleash_disable_metrics:
self.metric_job = self.unleash_scheduler.add_job(
aggregate_and_send_metrics,
Expand Down
2 changes: 2 additions & 0 deletions UnleashClient/api/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ def register_client(
url: str,
app_name: str,
instance_id: str,
connection_id: str,
metrics_interval: int,
headers: dict,
custom_options: dict,
Expand All @@ -47,6 +48,7 @@ def register_client(
registration_request = {
"appName": app_name,
"instanceId": instance_id,
"connectionId": connection_id,
"sdkVersion": f"{SDK_NAME}:{SDK_VERSION}",
"strategies": [*supported_strategies],
"started": datetime.now(timezone.utc).isoformat(),
Expand Down
2 changes: 2 additions & 0 deletions UnleashClient/periodic_tasks/send_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ def aggregate_and_send_metrics(
url: str,
app_name: str,
instance_id: str,
connection_id: str,
headers: dict,
custom_options: dict,
request_timeout: int,
Expand All @@ -22,6 +23,7 @@ def aggregate_and_send_metrics(
metrics_request = {
"appName": app_name,
"instanceId": instance_id,
"connectionId": connection_id,
"bucket": metrics_bucket,
"platformName": python_implementation(),
"platformVersion": python_version(),
Expand Down
6 changes: 6 additions & 0 deletions tests/unit_tests/api/test_metrics.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import json

import responses
from pytest import mark, param
from requests import ConnectionError
Expand Down Expand Up @@ -36,5 +38,9 @@ def test_send_metrics(payload, status, expected):
URL, MOCK_METRICS_REQUEST, CUSTOM_HEADERS, CUSTOM_OPTIONS, REQUEST_TIMEOUT
)

request = json.loads(responses.calls[0].request.body)

assert len(responses.calls) == 1
assert expected(result)

assert request["connectionId"] == MOCK_METRICS_REQUEST.get("connectionId")
4 changes: 4 additions & 0 deletions tests/unit_tests/api/test_register.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from tests.utilities.testing_constants import (
APP_NAME,
CONNECTION_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
INSTANCE_ID,
Expand Down Expand Up @@ -40,6 +41,7 @@ def test_register_client(payload, status, expected):
URL,
APP_NAME,
INSTANCE_ID,
CONNECTION_ID,
METRICS_INTERVAL,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
Expand All @@ -59,6 +61,7 @@ def test_register_includes_metadata():
URL,
APP_NAME,
INSTANCE_ID,
CONNECTION_ID,
METRICS_INTERVAL,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
Expand All @@ -71,5 +74,6 @@ def test_register_includes_metadata():

assert request["yggdrasilVersion"] is not None
assert request["specVersion"] == CLIENT_SPEC_VERSION
assert request["connectionId"] == CONNECTION_ID
assert request["platformName"] is not None
assert request["platformVersion"] is not None
3 changes: 3 additions & 0 deletions tests/unit_tests/periodic/test_aggregate_and_send_metrics.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

from tests.utilities.testing_constants import (
APP_NAME,
CONNECTION_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
INSTANCE_ID,
Expand All @@ -31,6 +32,7 @@ def test_no_metrics():
URL,
APP_NAME,
INSTANCE_ID,
CONNECTION_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
Expand All @@ -51,6 +53,7 @@ def test_metrics_metadata_is_sent():
URL,
APP_NAME,
INSTANCE_ID,
CONNECTION_ID,
CUSTOM_HEADERS,
CUSTOM_OPTIONS,
REQUEST_TIMEOUT,
Expand Down
78 changes: 78 additions & 0 deletions tests/unit_tests/test_client.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import json
import time
import uuid
import warnings
from datetime import datetime, timezone
from pathlib import Path
Expand Down Expand Up @@ -1078,3 +1080,79 @@ def test_identification_headers_unique_connection_id():
"UNLEASH-CONNECTION-ID"
]
assert connection_id_first_client != connection_id_second_client


@responses.activate
def test_identification_values_are_passed_in():
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is testing all 3 requests, headers and body

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

responses.add(responses.POST, URL + REGISTER_URL, json={}, status=202)
responses.add(
responses.GET, URL + FEATURES_URL, json=MOCK_FEATURE_RESPONSE, status=200
)
responses.add(responses.POST, URL + METRICS_URL, json={}, status=202)

refresh_interval = 1
metrics_interval = 2
unleash_client = UnleashClient(
URL,
APP_NAME,
refresh_interval=refresh_interval,
metrics_interval=metrics_interval,
)

expected_refresh_interval = str(refresh_interval * 1000)
expected_metrics_interval = str(metrics_interval * 1000)

unleash_client.initialize_client()
register_request = responses.calls[0].request
register_body = json.loads(register_request.body)

assert "connectionId" in register_body, "Key missing: connectionId"
try:
uuid.UUID(register_body["connectionId"])
except ValueError:
assert False, "Invalid UUID format in connectionId"

assert (
"UNLEASH-CONNECTION-ID" in register_request.headers
), "Header missing: UNLEASH-CONNECTION-ID"
try:
uuid.UUID(register_request.headers["UNLEASH-CONNECTION-ID"])
except ValueError:
assert False, "Invalid UUID format in UNLEASH-CONNECTION-ID"

unleash_client.is_enabled("registerMetricsFlag")

features_request = responses.calls[1].request

assert features_request.headers["UNLEASH-INTERVAL"] == expected_refresh_interval

assert (
"UNLEASH-CONNECTION-ID" in features_request.headers
), "Header missing: UNLEASH-CONNECTION-ID"

try:
uuid.UUID(features_request.headers["UNLEASH-CONNECTION-ID"])
except ValueError:
assert False, "Invalid UUID format in UNLEASH-CONNECTION-ID"

time.sleep(3)
metrics_request = [
call for call in responses.calls if METRICS_URL in call.request.url
][0].request
metrics_body = json.loads(metrics_request.body)

assert metrics_request.headers["UNLEASH-INTERVAL"] == expected_metrics_interval

assert "connectionId" in metrics_body, "Key missing: connectionId"
try:
uuid.UUID(metrics_body["connectionId"])
except ValueError:
assert False, "Invalid UUID format in connectionId"

assert (
"UNLEASH-CONNECTION-ID" in metrics_request.headers
), "Header missing: UNLEASH-CONNECTION-ID"
try:
uuid.UUID(metrics_request.headers["UNLEASH-CONNECTION-ID"])
except ValueError:
assert False, "Invalid UUID format in UNLEASH-CONNECTION-ID"
1 change: 1 addition & 0 deletions tests/utilities/mocks/mock_metrics.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
MOCK_METRICS_REQUEST = {
"appName": "appName",
"instanceId": "instanceId",
"connectionId": "connectionId",
"bucket": {
"start": "2016-11-03T07:16:43.572Z",
"stop": "2016-11-03T07:16:53.572Z",
Expand Down
1 change: 1 addition & 0 deletions tests/utilities/testing_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
APP_NAME = "pytest"
ENVIRONMENT = "unit"
INSTANCE_ID = "123"
CONNECTION_ID = "test-connection-id"
REFRESH_INTERVAL = 3
REFRESH_JITTER = None
METRICS_INTERVAL = 2
Expand Down
Loading