utf-queue-client 1.15.2__tar.gz → 1.17.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- utf-queue-client-1.17.2/PKG-INFO +11 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/setup.py +2 -3
- utf-queue-client-1.17.2/test/test_amqp_connections.py +46 -0
- utf-queue-client-1.17.2/test/test_http_producer.py +90 -0
- utf-queue-client-1.17.2/test/test_publish_release_info.py +145 -0
- utf-queue-client-1.17.2/test/test_rail_data_producer_tests.py +64 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_sqa_test_result_producer.py +6 -1
- utf-queue-client-1.17.2/test/test_utils.py +77 -0
- utf-queue-client-1.17.2/utf_queue_client/clients/http_producer.py +207 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/sqa_test_result_producer.py +23 -5
- utf-queue-client-1.17.2/utf_queue_client/clients/test_rail_data_producer.py +95 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/base_model.py +8 -4
- utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/models/python/__init__.py +0 -0
- utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/models/python/generated_models.py +291 -0
- utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/schema/queue_message.json +662 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/publish_release_info_cli.py +40 -21
- utf-queue-client-1.17.2/utf_queue_client/utils/__init__.py +89 -0
- utf-queue-client-1.17.2/utf_queue_client.egg-info/PKG-INFO +11 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/SOURCES.txt +9 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/entry_points.txt +0 -1
- utf-queue-client-1.15.2/PKG-INFO +0 -13
- utf-queue-client-1.15.2/test/test_publish_release_info.py +0 -72
- utf-queue-client-1.15.2/utf_queue_client/utils/__init__.py +0 -14
- utf-queue-client-1.15.2/utf_queue_client.egg-info/PKG-INFO +0 -13
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/README.md +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/setup.cfg +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_async_consumer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_connection_retry.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_data_lake_data_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_legacy_base_model.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_models.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_opentelemetry_data_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_publish_test_results.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_copy.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_search.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_update_retention.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_upload.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/async_consumer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/base_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/data_lake_data_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/opentelemetry_data_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/ubai_artifact_upload_request_producer.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/exceptions/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/model_factory.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/schemas/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/__init__.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/publish_test_results_cli.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_copy_cli.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_search_cli.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_update_retention_cli.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_upload_cli.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/utils/queue_metrics_exporter.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/utils/queue_span_exporter.py +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/dependency_links.txt +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/requires.txt +0 -0
- {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/top_level.txt +0 -0
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
|
|
6
5
|
from setuptools import setup, find_packages # noqa: H301
|
|
7
6
|
|
|
8
7
|
NAME = "utf-queue-client"
|
|
9
|
-
VERSION = "1.
|
|
8
|
+
VERSION = "1.17.2"
|
|
10
9
|
# To install the library, run the following
|
|
11
10
|
#
|
|
12
11
|
# python setup.py install
|
|
@@ -54,7 +53,7 @@ setup(
|
|
|
54
53
|
"publish_test_results_cli = utf_queue_client.scripts.publish_test_results_cli:cli_entrypoint",
|
|
55
54
|
"ubai_update_retention_cli = utf_queue_client.scripts.ubai_update_retention_cli:cli_entrypoint",
|
|
56
55
|
"publish_release_info_cli = utf_queue_client.scripts.publish_release_info_cli:cli_entrypoint",
|
|
57
|
-
"ubai_copy_cli = utf_queue_client.scripts.ubai_copy_cli:cli_entrypoint"
|
|
56
|
+
"ubai_copy_cli = utf_queue_client.scripts.ubai_copy_cli:cli_entrypoint",
|
|
58
57
|
],
|
|
59
58
|
},
|
|
60
59
|
include_package_data=True,
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from utf_queue_client.clients.base_producer import BlockingProducer
|
|
3
|
+
import pytest
|
|
4
|
+
import logging
|
|
5
|
+
import pika
|
|
6
|
+
import time
|
|
7
|
+
from utf_queue_client.utils import UTF_QUEUE_REGION_MAP
|
|
8
|
+
from urllib.parse import quote
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
regions = list(UTF_QUEUE_REGION_MAP.values())
|
|
12
|
+
servers = [f"utf-queue-{region}" for region in regions]
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@pytest.fixture(params=servers)
|
|
16
|
+
def queue_server(request):
|
|
17
|
+
yield request.param
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
@pytest.mark.parametrize(
|
|
21
|
+
"username, password",
|
|
22
|
+
[
|
|
23
|
+
(os.environ["UTF_QUEUE_USERNAME_LDAP"], os.environ["UTF_QUEUE_PASSWORD_LDAP"]),
|
|
24
|
+
(os.environ["UTF_QUEUE_USERNAME"], os.environ["UTF_QUEUE_PASSWORD"]),
|
|
25
|
+
],
|
|
26
|
+
)
|
|
27
|
+
def test_producer(queue_server, username, password):
|
|
28
|
+
suffix = ".silabs.net"
|
|
29
|
+
queue_server = f"{queue_server}{suffix}"
|
|
30
|
+
l = logging.getLogger("test_producer")
|
|
31
|
+
l.info("Creating producer...")
|
|
32
|
+
p = BlockingProducer(
|
|
33
|
+
url=f"amqps://{username}:{quote(password)}@{queue_server}:443/testing?stack_timeout=30",
|
|
34
|
+
)
|
|
35
|
+
l.info(f"{queue_server}: Created producer")
|
|
36
|
+
params = pika.URLParameters(p.url)
|
|
37
|
+
l.info(f"stack timeout: {params.stack_timeout}")
|
|
38
|
+
begin = time.time()
|
|
39
|
+
for _ in range(0, 10):
|
|
40
|
+
l.info(f"{queue_server}: declaring queue...")
|
|
41
|
+
start = time.time()
|
|
42
|
+
p.queue_declare("default", durable=True)
|
|
43
|
+
stop = time.time()
|
|
44
|
+
l.info(
|
|
45
|
+
f"{queue_server}: done declaring queue in {stop - start} sec. Total time: {stop - begin}"
|
|
46
|
+
)
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from utf_queue_client.clients.base_producer import (
|
|
3
|
+
ConnectionError as QueueServerConnectionError,
|
|
4
|
+
)
|
|
5
|
+
from utf_queue_client.clients.http_producer import (
|
|
6
|
+
HTTPProducer,
|
|
7
|
+
HTTPChannel,
|
|
8
|
+
CompositeHTTPProducer,
|
|
9
|
+
)
|
|
10
|
+
from pika.spec import BasicProperties, PERSISTENT_DELIVERY_MODE
|
|
11
|
+
from unittest.mock import patch
|
|
12
|
+
import utf_queue_client.clients.http_producer
|
|
13
|
+
|
|
14
|
+
FAKE_CREDS = "notarealusername:notarealpassword"
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_basic_properties_to_json():
|
|
18
|
+
assert HTTPChannel.basic_properties_to_json(BasicProperties(app_id="foo")) == {
|
|
19
|
+
"app_id": "foo"
|
|
20
|
+
}
|
|
21
|
+
assert HTTPChannel.basic_properties_to_json(
|
|
22
|
+
BasicProperties(app_id="foo", delivery_mode=PERSISTENT_DELIVERY_MODE)
|
|
23
|
+
) == {"app_id": "foo", "delivery_mode": 2}
|
|
24
|
+
assert HTTPChannel.basic_properties_to_json(None) == {}
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_http_channel_connection_error():
|
|
28
|
+
channel = HTTPChannel(
|
|
29
|
+
"https://localshmost", ("user", "pass"), "/", retries=0, retry_delay=0
|
|
30
|
+
)
|
|
31
|
+
with pytest.raises(QueueServerConnectionError):
|
|
32
|
+
channel.queue_declare("queue")
|
|
33
|
+
with pytest.raises(QueueServerConnectionError):
|
|
34
|
+
channel.basic_publish("exchange", "routing_key", b"foo", None)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def test_composite_producer_errors():
|
|
38
|
+
with patch.object(
|
|
39
|
+
utf_queue_client.clients.http_producer, "DEFAULT_NUM_CONNECTION_RETRIES", 0
|
|
40
|
+
):
|
|
41
|
+
composite_producer = CompositeHTTPProducer(
|
|
42
|
+
f"amqps://{FAKE_CREDS}@localshmost:443/%2F", "app_id"
|
|
43
|
+
)
|
|
44
|
+
composite_producer.producers.append(composite_producer.producers[0])
|
|
45
|
+
with pytest.raises(QueueServerConnectionError):
|
|
46
|
+
composite_producer.queue_declare("queue")
|
|
47
|
+
with pytest.raises(QueueServerConnectionError):
|
|
48
|
+
composite_producer.publish("exchange", "routing_key", {"foo": "bar"}, True)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
@pytest.mark.parametrize(
|
|
52
|
+
("amqp_url", "expected_http_urls"),
|
|
53
|
+
[
|
|
54
|
+
(
|
|
55
|
+
f"amqps://{FAKE_CREDS}@localshmost:443/%2F",
|
|
56
|
+
["https://localshmost-admin:443"],
|
|
57
|
+
),
|
|
58
|
+
(
|
|
59
|
+
f"amqps://{FAKE_CREDS}@utf-queue-central:443/%2F",
|
|
60
|
+
["https://utf-queue-central-admin:443"],
|
|
61
|
+
),
|
|
62
|
+
(
|
|
63
|
+
f"amqps://{FAKE_CREDS}@utf-queue-central.silabs.net:443/%2F",
|
|
64
|
+
[
|
|
65
|
+
"https://utf-queue-central-admin.silabs.net:443",
|
|
66
|
+
"https://utf-queue-aus-admin.silabs.net:443",
|
|
67
|
+
"https://utf-queue-bos-admin.silabs.net:443",
|
|
68
|
+
"https://utf-queue-bud-admin.silabs.net:443",
|
|
69
|
+
"https://utf-queue-hyd-admin.silabs.net:443",
|
|
70
|
+
"https://utf-queue-yul-admin.silabs.net:443",
|
|
71
|
+
],
|
|
72
|
+
),
|
|
73
|
+
(
|
|
74
|
+
f"amqps://{FAKE_CREDS}@utf-queue-aus.silabs.net:443/%2F",
|
|
75
|
+
[
|
|
76
|
+
"https://utf-queue-central-admin.silabs.net:443",
|
|
77
|
+
"https://utf-queue-aus-admin.silabs.net:443",
|
|
78
|
+
"https://utf-queue-bos-admin.silabs.net:443",
|
|
79
|
+
"https://utf-queue-bud-admin.silabs.net:443",
|
|
80
|
+
"https://utf-queue-hyd-admin.silabs.net:443",
|
|
81
|
+
"https://utf-queue-yul-admin.silabs.net:443",
|
|
82
|
+
],
|
|
83
|
+
),
|
|
84
|
+
],
|
|
85
|
+
)
|
|
86
|
+
def test_composite_producer_creation(amqp_url, expected_http_urls):
|
|
87
|
+
composite_producer = CompositeHTTPProducer(amqp_url, "app_id")
|
|
88
|
+
assert len(composite_producer.producers) == len(expected_http_urls)
|
|
89
|
+
for index, value in enumerate(expected_http_urls):
|
|
90
|
+
assert composite_producer.producers[index].channel.url == value
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from otel_extensions import inject_context_to_env, instrumented
|
|
3
|
+
import os
|
|
4
|
+
import subprocess
|
|
5
|
+
import sys
|
|
6
|
+
from utf_queue_client.scripts.publish_release_info_cli import publish_release_information, \
|
|
7
|
+
update_artifacts_retention_days_to_keep_forever, cli
|
|
8
|
+
import requests
|
|
9
|
+
from unittest import mock
|
|
10
|
+
from unittest.mock import MagicMock, patch
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@pytest.fixture
|
|
14
|
+
def publish_release_info():
|
|
15
|
+
yield [
|
|
16
|
+
("releaseDate", "2023-06-16"),
|
|
17
|
+
("branchName", "release/gecko_sdk_3.2"),
|
|
18
|
+
("releaseType", "GA"),
|
|
19
|
+
("releaseName", "23Q2-GA"),
|
|
20
|
+
("SDKBuildNum", "407"),
|
|
21
|
+
("installerURLSDK", "https://jenkins-cbs-gecko-sdk.silabs.net/job/Gecko_SDK_Suite_super/job/release%252F21q4/284/"),
|
|
22
|
+
("IARVersionNum", "9.20.4"),
|
|
23
|
+
("GCCVersionNum", "10.3.1"),
|
|
24
|
+
("CommanderVersionNum", "v1.8.1"),
|
|
25
|
+
("SEVersionNum", "1.2.4"),
|
|
26
|
+
("WSTKFirmwareVersionNum", "v1.4.4")
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@instrumented
|
|
31
|
+
def test_publish_release_info(request, publish_release_info):
|
|
32
|
+
args = ["--username", "svc_sqabot", "--password", "password"]
|
|
33
|
+
for k, v in publish_release_info:
|
|
34
|
+
args += ["--data", k, v]
|
|
35
|
+
|
|
36
|
+
base_dir = os.path.dirname(os.path.dirname(__file__))
|
|
37
|
+
@inject_context_to_env
|
|
38
|
+
def call_cli_script():
|
|
39
|
+
assert "TRACEPARENT" in os.environ
|
|
40
|
+
process = subprocess.Popen(
|
|
41
|
+
[
|
|
42
|
+
sys.executable,
|
|
43
|
+
os.path.join(
|
|
44
|
+
base_dir, "utf_queue_client", "scripts", "publish_release_info_cli.py"
|
|
45
|
+
),
|
|
46
|
+
]
|
|
47
|
+
+ args,
|
|
48
|
+
)
|
|
49
|
+
process.communicate()
|
|
50
|
+
assert process.poll() == 0
|
|
51
|
+
|
|
52
|
+
call_cli_script()
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def test_publish_release_information():
|
|
56
|
+
requests_post = requests.post
|
|
57
|
+
requests.post = MagicMock()
|
|
58
|
+
publish_release_information("", "", {})
|
|
59
|
+
assert requests.post.call_count == 2
|
|
60
|
+
requests.post = requests_post
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def test_publish_release_cli_insufficient_params():
|
|
64
|
+
requests_post = requests.post
|
|
65
|
+
requests_post_mock = MagicMock()
|
|
66
|
+
requests.post = requests_post_mock
|
|
67
|
+
cli("", "", {})
|
|
68
|
+
assert requests.post.call_count == 0
|
|
69
|
+
requests.post = requests_post
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def test_publish_release_cli():
|
|
73
|
+
requests_post = requests.post
|
|
74
|
+
requests_post_mock = MagicMock()
|
|
75
|
+
requests.post = requests_post_mock
|
|
76
|
+
cli("", "", {("branchName", "test"), ("releaseData", "")})
|
|
77
|
+
assert requests_post_mock.call_count == 2
|
|
78
|
+
post_params = requests_post_mock.call_args.kwargs.get('json')
|
|
79
|
+
assert post_params.get("branchName") == "test"
|
|
80
|
+
assert post_params.get("releaseData") is None
|
|
81
|
+
requests.post = requests_post
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def test_update_artifacts_retention_days_to_keep_forever():
|
|
85
|
+
requests_patch = requests.patch
|
|
86
|
+
requests.patch = MagicMock()
|
|
87
|
+
data_dict = {"branchName": "release/gec", "SDKBuildNum": "407"}
|
|
88
|
+
update_artifacts_retention_days_to_keep_forever(data_dict)
|
|
89
|
+
assert requests.patch.call_count == 1
|
|
90
|
+
requests.patch = requests_patch
|
|
91
|
+
patch_response = MagicMock()
|
|
92
|
+
mocked_patch =mock.patch.object(requests, "patch", return_value=patch_response)
|
|
93
|
+
patch_response.status_code = 200
|
|
94
|
+
update_artifacts_retention_days_to_keep_forever(data_dict)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@patch('requests.post')
|
|
98
|
+
def test_publish_release_information_success(mock_post):
|
|
99
|
+
mock_response_auth = MagicMock()
|
|
100
|
+
mock_response_auth.json.return_value = {"access_token": "test_token"}
|
|
101
|
+
mock_response_auth.raise_for_status.return_value = None
|
|
102
|
+
|
|
103
|
+
mock_response_release = MagicMock()
|
|
104
|
+
mock_response_release.status_code = 201
|
|
105
|
+
mock_response_release.json.return_value = {"message": "success"}
|
|
106
|
+
|
|
107
|
+
mock_post.side_effect = [mock_response_auth, mock_response_release]
|
|
108
|
+
|
|
109
|
+
result = publish_release_information("test_username", "test_password", {"test_key": "test_value"})
|
|
110
|
+
assert mock_response_release.json.called
|
|
111
|
+
assert result
|
|
112
|
+
|
|
113
|
+
@patch('requests.post')
|
|
114
|
+
def test_publish_release_information_release_fail(mock_post):
|
|
115
|
+
# Mock the responses
|
|
116
|
+
mock_response_auth = MagicMock()
|
|
117
|
+
mock_response_auth.json.return_value = {"access_token": "test_token"}
|
|
118
|
+
mock_response_auth.raise_for_status.return_value = None
|
|
119
|
+
|
|
120
|
+
mock_response_release = MagicMock()
|
|
121
|
+
mock_response_release.status_code = 400
|
|
122
|
+
mock_response_release.text = "Release failed"
|
|
123
|
+
|
|
124
|
+
mock_post.side_effect = [mock_response_auth, mock_response_release]
|
|
125
|
+
|
|
126
|
+
result = publish_release_information("test_username", "test_password", {"test_key": "test_value"})
|
|
127
|
+
assert result is None
|
|
128
|
+
|
|
129
|
+
@patch('requests.post')
|
|
130
|
+
def test_publish_release_information_release_fail_422(mock_post):
|
|
131
|
+
# Mock the responses
|
|
132
|
+
mock_response_auth = MagicMock()
|
|
133
|
+
mock_response_auth.json.return_value = {"access_token": "test_token"}
|
|
134
|
+
mock_response_auth.raise_for_status.return_value = None
|
|
135
|
+
|
|
136
|
+
mock_response_release = MagicMock()
|
|
137
|
+
mock_response_release.status_code = 422
|
|
138
|
+
mock_response_release.json.return_value = {"detail": [{"loc": ["", "test_field"], "msg": "test_error"}]}
|
|
139
|
+
|
|
140
|
+
mock_post.side_effect = [mock_response_auth, mock_response_release]
|
|
141
|
+
|
|
142
|
+
# Call the function
|
|
143
|
+
result = publish_release_information("test_username", "test_password", {"test_key": "test_value"})
|
|
144
|
+
assert mock_response_release.json.called
|
|
145
|
+
assert result is None
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
from unittest.mock import MagicMock
|
|
2
|
+
|
|
3
|
+
from utf_queue_client.clients.test_rail_data_producer import (
|
|
4
|
+
TestRailDataProducer,
|
|
5
|
+
TestRailDataProducerFactory,
|
|
6
|
+
DummyTestRailDataProducer,
|
|
7
|
+
LocalTestRailDataProducer,
|
|
8
|
+
)
|
|
9
|
+
import pytest
|
|
10
|
+
import os
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def test_result_producer_empty_url():
|
|
14
|
+
with pytest.raises(RuntimeError):
|
|
15
|
+
_ = TestRailDataProducer()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@pytest.mark.parametrize("queue_consumer", [60, 120], indirect=True)
|
|
19
|
+
def test_result_producer_central_queue(
|
|
20
|
+
request,
|
|
21
|
+
test_rail_add_test_result,
|
|
22
|
+
amqp_url,
|
|
23
|
+
queue_consumer,
|
|
24
|
+
):
|
|
25
|
+
os.environ["UTF_PRODUCER_APP_ID"] = request.node.name
|
|
26
|
+
producer = TestRailDataProducerFactory.create_producer()
|
|
27
|
+
producer.publish_add_test_result(test_rail_add_test_result)
|
|
28
|
+
queue_consumer.expect_messages(1)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def test_dummy_producer(test_rail_add_test_result):
|
|
32
|
+
producer = DummyTestRailDataProducer()
|
|
33
|
+
producer.publish_add_test_result(test_rail_add_test_result)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_local_test_rail_producer(test_rail_add_test_result, mocker):
|
|
37
|
+
mocker.patch(
|
|
38
|
+
"utf_queue_client.clients.base_producer.BlockingProducer.__init__",
|
|
39
|
+
return_value=None,
|
|
40
|
+
)
|
|
41
|
+
mocker.patch(
|
|
42
|
+
"utf_queue_client.clients.base_producer.BlockingProducer.queue_declare"
|
|
43
|
+
)
|
|
44
|
+
mock_producer_publish = mocker.patch(
|
|
45
|
+
"utf_queue_client.clients.base_producer.BlockingProducer.publish"
|
|
46
|
+
)
|
|
47
|
+
producer = LocalTestRailDataProducer()
|
|
48
|
+
producer.producer_app_id = "DUMMY"
|
|
49
|
+
producer.publish_add_test_result(test_rail_add_test_result)
|
|
50
|
+
assert mock_producer_publish.called
|
|
51
|
+
kwargs = mock_producer_publish.call_args.kwargs
|
|
52
|
+
payload = kwargs.get("payload")
|
|
53
|
+
assert payload.get("recordType") == "TEST_RAIL_EVENT"
|
|
54
|
+
assert payload.get("recordSubType") == "TEST_RESULT"
|
|
55
|
+
assert payload.get("tenantKey") == "DUMMY"
|
|
56
|
+
payload_data = payload.get("payload")
|
|
57
|
+
assert payload_data.get("version") == "v1"
|
|
58
|
+
assert payload_data.get("comment") == "testing"
|
|
59
|
+
assert payload_data.get("status") == "pass"
|
|
60
|
+
assert payload_data.get("run_id") == 1234
|
|
61
|
+
assert payload_data.get("id") == "C123"
|
|
62
|
+
custom_props = payload_data.get("custom_props")
|
|
63
|
+
assert custom_props.get("custom_interface_type") == "gcc"
|
|
64
|
+
assert custom_props.get("custom_evk_version") == "v2"
|
|
@@ -15,6 +15,7 @@ def test_result_producer_empty_url():
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
@pytest.mark.parametrize("queue_consumer", [60, 120], indirect=True)
|
|
18
|
+
@pytest.mark.parametrize("use_http_producer", [True, False])
|
|
18
19
|
def test_result_producer_central_queue(
|
|
19
20
|
request,
|
|
20
21
|
sqa_app_build_result,
|
|
@@ -22,9 +23,13 @@ def test_result_producer_central_queue(
|
|
|
22
23
|
sqa_test_session,
|
|
23
24
|
amqp_url,
|
|
24
25
|
queue_consumer,
|
|
26
|
+
use_http_producer,
|
|
25
27
|
):
|
|
26
28
|
os.environ["UTF_PRODUCER_APP_ID"] = request.node.name
|
|
27
|
-
|
|
29
|
+
os.environ["UTF_QUEUE_USE_HTTP_PRODUCER"] = "true" if use_http_producer else "false"
|
|
30
|
+
producer = SqaTestResultProducerFactory.create_producer(
|
|
31
|
+
use_http_producer=use_http_producer
|
|
32
|
+
)
|
|
28
33
|
producer.publish_app_build_result(sqa_app_build_result)
|
|
29
34
|
queue_consumer.expect_messages(1)
|
|
30
35
|
producer.publish_test_session_start(sqa_test_session)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import pytest
|
|
3
|
+
from utf_queue_client.utils import (
|
|
4
|
+
get_queue_url_list,
|
|
5
|
+
UTF_QUEUE_REGION_MAP,
|
|
6
|
+
get_closest_queue_region,
|
|
7
|
+
)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
NOMAD_DC_PARAMS = [
|
|
11
|
+
("NOMAD_DC", value)
|
|
12
|
+
for value in [
|
|
13
|
+
None,
|
|
14
|
+
"aws-us-east-1",
|
|
15
|
+
"vmw-us-aus-1",
|
|
16
|
+
"vmw-us-bos-1",
|
|
17
|
+
"vmw-eu-bud-1",
|
|
18
|
+
"vmw-ap-hyd-1",
|
|
19
|
+
"vmw-ca-yul-1",
|
|
20
|
+
"vmw-ap-syd-1",
|
|
21
|
+
]
|
|
22
|
+
]
|
|
23
|
+
FAKE_CREDS = "notarealusername:notarealpassword"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.mark.parametrize(
|
|
27
|
+
"set_environment_variable",
|
|
28
|
+
NOMAD_DC_PARAMS,
|
|
29
|
+
indirect=True,
|
|
30
|
+
)
|
|
31
|
+
@pytest.mark.parametrize("domain", ["dev.silabs.net", "silabs.net"])
|
|
32
|
+
def test_get_queue_url_list(set_environment_variable, domain):
|
|
33
|
+
nomad_dc = os.getenv("NOMAD_DC")
|
|
34
|
+
url = f"amqps://{FAKE_CREDS}@utf-queue-central.{domain}:443/%2F?stack_timeout=30"
|
|
35
|
+
urls = get_queue_url_list(url)
|
|
36
|
+
assert len(urls) == 6
|
|
37
|
+
|
|
38
|
+
region_list = list(UTF_QUEUE_REGION_MAP.values())
|
|
39
|
+
if nomad_dc is None or nomad_dc not in UTF_QUEUE_REGION_MAP:
|
|
40
|
+
assert urls[0] == url
|
|
41
|
+
region_list.remove("central")
|
|
42
|
+
else:
|
|
43
|
+
region = UTF_QUEUE_REGION_MAP[nomad_dc]
|
|
44
|
+
assert urls[0] == url.replace("utf-queue-central", f"utf-queue-{region}")
|
|
45
|
+
region_list.remove(region)
|
|
46
|
+
assert urls[1:] == [
|
|
47
|
+
url.replace("utf-queue-central", f"utf-queue-{region}")
|
|
48
|
+
for region in region_list
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_get_queue_url_list_no_match():
|
|
53
|
+
url_list = [
|
|
54
|
+
f"amqps://{FAKE_CREDS}@custom-server.silabs.net:443/%2F?stack_timeout=30",
|
|
55
|
+
f"amqp://{FAKE_CREDS}@utf-queue-central.silabs.net:5672/%2F",
|
|
56
|
+
f"amqps://{FAKE_CREDS}@utf-queue-central.silabs.net:5672/%2F",
|
|
57
|
+
f"amqps://{FAKE_CREDS}@utf-queue-central.silabs.com:443/%2F",
|
|
58
|
+
]
|
|
59
|
+
for url in url_list:
|
|
60
|
+
urls = get_queue_url_list(url)
|
|
61
|
+
assert urls == [url]
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@pytest.mark.parametrize(
|
|
65
|
+
"set_environment_variable",
|
|
66
|
+
NOMAD_DC_PARAMS,
|
|
67
|
+
indirect=True,
|
|
68
|
+
)
|
|
69
|
+
def test_get_closest_queue_region(set_environment_variable):
|
|
70
|
+
nomad_dc = os.getenv("NOMAD_DC")
|
|
71
|
+
try:
|
|
72
|
+
if nomad_dc is None or nomad_dc not in UTF_QUEUE_REGION_MAP:
|
|
73
|
+
assert get_closest_queue_region() == "central"
|
|
74
|
+
else:
|
|
75
|
+
assert get_closest_queue_region() == UTF_QUEUE_REGION_MAP[nomad_dc]
|
|
76
|
+
finally:
|
|
77
|
+
os.environ.pop("NOMAD_DC", None)
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import pika
|
|
2
|
+
from pika.spec import BasicProperties
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
|
|
5
|
+
from utf_queue_client.clients.base_producer import (
|
|
6
|
+
BaseProducer,
|
|
7
|
+
PikaChannel,
|
|
8
|
+
ConnectionError,
|
|
9
|
+
)
|
|
10
|
+
import base64
|
|
11
|
+
from utf_queue_client.utils import get_queue_url_list
|
|
12
|
+
from requests import Session as RequestsSession, RequestException
|
|
13
|
+
from requests.adapters import HTTPAdapter
|
|
14
|
+
from urllib3.util import Retry
|
|
15
|
+
from . import Loggable
|
|
16
|
+
from dataclasses import dataclass
|
|
17
|
+
from urllib.parse import urlparse
|
|
18
|
+
|
|
19
|
+
__all__ = ["HTTPProducer", "HTTPChannel", "CompositeHTTPProducer"]
|
|
20
|
+
|
|
21
|
+
DEFAULT_NUM_CONNECTION_RETRIES = 6
|
|
22
|
+
DEFAULT_CONNECTION_RETRY_DELAY = 10
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class UrlInfo:
|
|
27
|
+
amqp_url: str
|
|
28
|
+
http_url: str
|
|
29
|
+
virtual_host: str
|
|
30
|
+
username: str
|
|
31
|
+
password: str
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class HTTPChannel(PikaChannel):
|
|
35
|
+
def __init__(
|
|
36
|
+
self,
|
|
37
|
+
url: str,
|
|
38
|
+
credentials: Tuple[str, str],
|
|
39
|
+
virtual_host: str,
|
|
40
|
+
retries=None,
|
|
41
|
+
retry_delay=None,
|
|
42
|
+
):
|
|
43
|
+
if retries is None:
|
|
44
|
+
retries = DEFAULT_NUM_CONNECTION_RETRIES
|
|
45
|
+
if retry_delay is None:
|
|
46
|
+
retry_delay = DEFAULT_CONNECTION_RETRY_DELAY
|
|
47
|
+
self.client = RequestsSession()
|
|
48
|
+
self.client.verify = False
|
|
49
|
+
self.client.auth = credentials
|
|
50
|
+
self.client.mount(
|
|
51
|
+
"https://",
|
|
52
|
+
HTTPAdapter(
|
|
53
|
+
max_retries=Retry(
|
|
54
|
+
total=retries,
|
|
55
|
+
backoff_factor=retry_delay,
|
|
56
|
+
status_forcelist=[404, 429, 500, 502, 503, 504],
|
|
57
|
+
)
|
|
58
|
+
),
|
|
59
|
+
)
|
|
60
|
+
self.url = url
|
|
61
|
+
self.virtual_host = virtual_host
|
|
62
|
+
if self.virtual_host == "/":
|
|
63
|
+
self.virtual_host = "%2F"
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def basic_properties_to_json(cls, properties: Optional[BasicProperties]) -> dict:
|
|
67
|
+
props_json = {}
|
|
68
|
+
if properties:
|
|
69
|
+
for key in [
|
|
70
|
+
"app_id",
|
|
71
|
+
"content_type",
|
|
72
|
+
"content_encoding",
|
|
73
|
+
"correlation_id",
|
|
74
|
+
"cluster_id",
|
|
75
|
+
"delivery_mode",
|
|
76
|
+
"expiration",
|
|
77
|
+
"headers",
|
|
78
|
+
"message_id",
|
|
79
|
+
"priority",
|
|
80
|
+
"reply_to",
|
|
81
|
+
"timestamp",
|
|
82
|
+
"type",
|
|
83
|
+
"user_id",
|
|
84
|
+
]:
|
|
85
|
+
val = getattr(properties, key)
|
|
86
|
+
if val is not None:
|
|
87
|
+
props_json[key] = val
|
|
88
|
+
return props_json
|
|
89
|
+
|
|
90
|
+
def basic_publish(
|
|
91
|
+
self,
|
|
92
|
+
exchange: str,
|
|
93
|
+
routing_key: str,
|
|
94
|
+
body: bytes,
|
|
95
|
+
properties: Optional[BasicProperties] = None,
|
|
96
|
+
mandatory: bool = False,
|
|
97
|
+
):
|
|
98
|
+
if not exchange:
|
|
99
|
+
exchange = "amq.default"
|
|
100
|
+
publish_url = f"{self.url}/api/exchanges/{self.virtual_host}/{exchange}/publish"
|
|
101
|
+
try:
|
|
102
|
+
r = self.client.post(
|
|
103
|
+
publish_url,
|
|
104
|
+
json={
|
|
105
|
+
"properties": self.basic_properties_to_json(properties),
|
|
106
|
+
"routing_key": routing_key,
|
|
107
|
+
"payload": base64.b64encode(body).decode("utf-8"),
|
|
108
|
+
"payload_encoding": "base64",
|
|
109
|
+
},
|
|
110
|
+
)
|
|
111
|
+
r.raise_for_status()
|
|
112
|
+
except RequestException as e:
|
|
113
|
+
raise ConnectionError(e)
|
|
114
|
+
|
|
115
|
+
def queue_declare(
|
|
116
|
+
self,
|
|
117
|
+
queue,
|
|
118
|
+
passive=False,
|
|
119
|
+
durable=False,
|
|
120
|
+
exclusive=False,
|
|
121
|
+
auto_delete=False,
|
|
122
|
+
arguments=None,
|
|
123
|
+
callback=None,
|
|
124
|
+
):
|
|
125
|
+
declare_url = f"{self.url}/api/queues/{self.virtual_host}/{queue}"
|
|
126
|
+
try:
|
|
127
|
+
r = self.client.put(
|
|
128
|
+
declare_url, json={"auto_delete": auto_delete, "durable": durable}
|
|
129
|
+
)
|
|
130
|
+
r.raise_for_status()
|
|
131
|
+
except RequestException as e:
|
|
132
|
+
raise ConnectionError(e)
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
class HTTPProducer(BaseProducer):
|
|
136
|
+
def __init__(
|
|
137
|
+
self,
|
|
138
|
+
url_info: UrlInfo,
|
|
139
|
+
producer_app_id: Optional[str] = None,
|
|
140
|
+
):
|
|
141
|
+
super().__init__(url_info.amqp_url, producer_app_id)
|
|
142
|
+
self._channel = HTTPChannel(
|
|
143
|
+
url_info.http_url,
|
|
144
|
+
(url_info.username, url_info.password),
|
|
145
|
+
url_info.virtual_host,
|
|
146
|
+
)
|
|
147
|
+
self.hostname = urlparse(url_info.http_url).hostname
|
|
148
|
+
|
|
149
|
+
def _connect(self):
|
|
150
|
+
# no long-lived connection
|
|
151
|
+
pass
|
|
152
|
+
|
|
153
|
+
def is_connected(self):
|
|
154
|
+
return True # pragma: nocover
|
|
155
|
+
|
|
156
|
+
@property
|
|
157
|
+
def channel(self) -> HTTPChannel:
|
|
158
|
+
return self._channel
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
class CompositeHTTPProducer(Loggable):
|
|
162
|
+
def __init__(self, url: str, producer_app_id: str = None):
|
|
163
|
+
url_list = get_queue_url_list(url)
|
|
164
|
+
self.producers = [
|
|
165
|
+
HTTPProducer(self.convert_amqp_url_to_http_url(alt_url), producer_app_id)
|
|
166
|
+
for alt_url in url_list
|
|
167
|
+
]
|
|
168
|
+
|
|
169
|
+
@classmethod
|
|
170
|
+
def convert_amqp_url_to_http_url(cls, amqp_url: str) -> UrlInfo:
|
|
171
|
+
url_params = pika.URLParameters(amqp_url)
|
|
172
|
+
host, delim, domain = url_params.host.partition(".")
|
|
173
|
+
hostname = f"{host}-admin{delim}{domain}:{url_params.port}"
|
|
174
|
+
scheme = "https" if url_params.ssl_options else "http"
|
|
175
|
+
return UrlInfo(
|
|
176
|
+
amqp_url=amqp_url,
|
|
177
|
+
http_url=f"{scheme}://{hostname}",
|
|
178
|
+
virtual_host=url_params.virtual_host,
|
|
179
|
+
username=url_params.credentials.username,
|
|
180
|
+
password=url_params.credentials.password,
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
def queue_declare(self, queue, **kwargs):
|
|
184
|
+
for index, producer in enumerate(self.producers):
|
|
185
|
+
if index:
|
|
186
|
+
self.logger.warning(
|
|
187
|
+
f"Failed to execute queue_declare for host {self.producers[index-1].hostname}, retrying with {producer.hostname}..."
|
|
188
|
+
)
|
|
189
|
+
try:
|
|
190
|
+
return producer.queue_declare(queue, **kwargs)
|
|
191
|
+
except ConnectionError:
|
|
192
|
+
if index == len(self.producers) - 1:
|
|
193
|
+
raise
|
|
194
|
+
continue
|
|
195
|
+
|
|
196
|
+
def publish(self, exchange: str, routing_key: str, payload: dict, persistent: bool):
|
|
197
|
+
for index, producer in enumerate(self.producers):
|
|
198
|
+
if index:
|
|
199
|
+
self.logger.warning(
|
|
200
|
+
f"Failed to execute publish for host {self.producers[index-1].hostname}, retrying with {producer.hostname}..."
|
|
201
|
+
)
|
|
202
|
+
try:
|
|
203
|
+
return producer.publish(exchange, routing_key, payload, persistent)
|
|
204
|
+
except ConnectionError:
|
|
205
|
+
if index == len(self.producers) - 1:
|
|
206
|
+
raise
|
|
207
|
+
continue
|