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.
Files changed (59) hide show
  1. utf-queue-client-1.17.2/PKG-INFO +11 -0
  2. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/setup.py +2 -3
  3. utf-queue-client-1.17.2/test/test_amqp_connections.py +46 -0
  4. utf-queue-client-1.17.2/test/test_http_producer.py +90 -0
  5. utf-queue-client-1.17.2/test/test_publish_release_info.py +145 -0
  6. utf-queue-client-1.17.2/test/test_rail_data_producer_tests.py +64 -0
  7. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_sqa_test_result_producer.py +6 -1
  8. utf-queue-client-1.17.2/test/test_utils.py +77 -0
  9. utf-queue-client-1.17.2/utf_queue_client/clients/http_producer.py +207 -0
  10. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/sqa_test_result_producer.py +23 -5
  11. utf-queue-client-1.17.2/utf_queue_client/clients/test_rail_data_producer.py +95 -0
  12. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/base_model.py +8 -4
  13. utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/models/python/__init__.py +0 -0
  14. utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/models/python/generated_models.py +291 -0
  15. utf-queue-client-1.17.2/utf_queue_client/models/schemas/utf_queue_models/schema/queue_message.json +662 -0
  16. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/publish_release_info_cli.py +40 -21
  17. utf-queue-client-1.17.2/utf_queue_client/utils/__init__.py +89 -0
  18. utf-queue-client-1.17.2/utf_queue_client.egg-info/PKG-INFO +11 -0
  19. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/SOURCES.txt +9 -0
  20. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/entry_points.txt +0 -1
  21. utf-queue-client-1.15.2/PKG-INFO +0 -13
  22. utf-queue-client-1.15.2/test/test_publish_release_info.py +0 -72
  23. utf-queue-client-1.15.2/utf_queue_client/utils/__init__.py +0 -14
  24. utf-queue-client-1.15.2/utf_queue_client.egg-info/PKG-INFO +0 -13
  25. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/README.md +0 -0
  26. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/setup.cfg +0 -0
  27. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_async_consumer.py +0 -0
  28. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_connection_retry.py +0 -0
  29. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_data_lake_data_producer.py +0 -0
  30. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_legacy_base_model.py +0 -0
  31. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_models.py +0 -0
  32. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_opentelemetry_data_producer.py +0 -0
  33. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_publish_test_results.py +0 -0
  34. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_copy.py +0 -0
  35. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_search.py +0 -0
  36. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_update_retention.py +0 -0
  37. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/test/test_ubai_upload.py +0 -0
  38. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/__init__.py +0 -0
  39. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/__init__.py +0 -0
  40. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/async_consumer.py +0 -0
  41. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/base_producer.py +0 -0
  42. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/data_lake_data_producer.py +0 -0
  43. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/opentelemetry_data_producer.py +0 -0
  44. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/clients/ubai_artifact_upload_request_producer.py +0 -0
  45. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/exceptions/__init__.py +0 -0
  46. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/__init__.py +0 -0
  47. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/model_factory.py +0 -0
  48. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/models/schemas/__init__.py +0 -0
  49. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/__init__.py +0 -0
  50. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/publish_test_results_cli.py +0 -0
  51. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_copy_cli.py +0 -0
  52. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_search_cli.py +0 -0
  53. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_update_retention_cli.py +0 -0
  54. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/scripts/ubai_upload_cli.py +0 -0
  55. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/utils/queue_metrics_exporter.py +0 -0
  56. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client/utils/queue_span_exporter.py +0 -0
  57. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/dependency_links.txt +0 -0
  58. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/requires.txt +0 -0
  59. {utf-queue-client-1.15.2 → utf-queue-client-1.17.2}/utf_queue_client.egg-info/top_level.txt +0 -0
@@ -0,0 +1,11 @@
1
+ Metadata-Version: 2.1
2
+ Name: utf-queue-client
3
+ Version: 1.17.2
4
+ Home-page:
5
+ Author: Joe Savage
6
+ Author-email: joe.savage@silabs.com
7
+ Keywords: UTF,
8
+ Requires-Python: >=3.6
9
+
10
+ No description provided
11
+
@@ -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.15.2"
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
- producer = SqaTestResultProducerFactory.create_producer()
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