Skip to content

Commit 04f0e0d

Browse files
author
mingsing
committed
use aiohttp in dapr health check
1 parent 819e9ea commit 04f0e0d

File tree

2 files changed

+81
-54
lines changed

2 files changed

+81
-54
lines changed

dapr/aio/clients/health.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,29 @@
1212
See the License for the specific language governing permissions and
1313
limitations under the License.
1414
"""
15+
1516
import aiohttp
1617
import asyncio
1718
import time
19+
from warnings import warn
1820

19-
from dapr.clients.http.conf import DAPR_API_TOKEN_HEADER, USER_AGENT_HEADER, DAPR_USER_AGENT
21+
from dapr.clients.http.conf import DAPR_API_TOKEN_HEADER, DAPR_USER_AGENT, USER_AGENT_HEADER
2022
from dapr.clients.http.helpers import get_api_url
2123
from dapr.conf import settings
2224

2325

2426
class DaprHealth:
2527
@staticmethod
2628
async def wait_until_ready():
29+
warn(
30+
'This method is deprecated. Use DaprHealth.wait_for_sidecar instead.',
31+
DeprecationWarning,
32+
stacklevel=2,
33+
)
34+
await DaprHealth.wait_for_sidecar()
35+
36+
@staticmethod
37+
async def wait_for_sidecar():
2738
health_url = f'{get_api_url()}/healthz/outbound'
2839
headers = {USER_AGENT_HEADER: DAPR_USER_AGENT}
2940
if settings.DAPR_API_TOKEN is not None:
@@ -48,7 +59,7 @@ async def wait_until_ready():
4859
remaining = (start + timeout) - time.time()
4960
if remaining <= 0:
5061
raise TimeoutError(f'Dapr health check timed out, after {timeout}.')
51-
await asyncio.sleep(min(1.0, remaining))
62+
await asyncio.sleep(min(1, remaining))
5263

5364
@staticmethod
5465
def get_ssl_context():

tests/clients/test_healthcheck_async.py

Lines changed: 68 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,11 @@
1212
See the License for the specific language governing permissions and
1313
limitations under the License.
1414
"""
15+
1516
import asyncio
1617
import time
1718
import unittest
18-
from unittest.mock import patch, MagicMock
19+
from unittest.mock import AsyncMock, MagicMock, patch
1920

2021
from dapr.aio.clients.health import DaprHealth
2122
from dapr.conf import settings
@@ -24,88 +25,98 @@
2425

2526
class DaprHealthCheckAsyncTests(unittest.IsolatedAsyncioTestCase):
2627
@patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500')
27-
@patch('urllib.request.urlopen')
28-
async def test_wait_until_ready_success(self, mock_urlopen):
29-
mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200)
28+
@patch('aiohttp.ClientSession.get')
29+
async def test_wait_for_sidecar_success(self, mock_get):
30+
# Create mock response
31+
mock_response = MagicMock()
32+
mock_response.status = 200
33+
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
34+
mock_response.__aexit__ = AsyncMock(return_value=None)
35+
mock_get.return_value = mock_response
3036

3137
try:
32-
await DaprHealth.wait_until_ready()
38+
await DaprHealth.wait_for_sidecar()
3339
except Exception as e:
34-
self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}')
40+
self.fail(f'wait_for_sidecar() raised an exception unexpectedly: {e}')
3541

36-
mock_urlopen.assert_called_once()
42+
mock_get.assert_called_once()
3743

38-
called_url = mock_urlopen.call_args[0][0].full_url
44+
# Check URL
45+
called_url = mock_get.call_args[0][0]
3946
self.assertEqual(called_url, 'http://domain.com:3500/v1.0/healthz/outbound')
4047

4148
# Check headers are properly set
42-
headers = mock_urlopen.call_args[0][0].headers
43-
self.assertIn('User-agent', headers)
44-
self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}')
49+
headers = mock_get.call_args[1]['headers']
50+
self.assertIn('User-Agent', headers)
51+
self.assertEqual(headers['User-Agent'], f'dapr-sdk-python/{__version__}')
4552

4653
@patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500')
4754
@patch.object(settings, 'DAPR_API_TOKEN', 'mytoken')
48-
@patch('urllib.request.urlopen')
49-
async def test_wait_until_ready_success_with_api_token(self, mock_urlopen):
50-
mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200)
55+
@patch('aiohttp.ClientSession.get')
56+
async def test_wait_for_sidecar_success_with_api_token(self, mock_get):
57+
# Create mock response
58+
mock_response = MagicMock()
59+
mock_response.status = 200
60+
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
61+
mock_response.__aexit__ = AsyncMock(return_value=None)
62+
mock_get.return_value = mock_response
5163

5264
try:
53-
await DaprHealth.wait_until_ready()
65+
await DaprHealth.wait_for_sidecar()
5466
except Exception as e:
55-
self.fail(f'wait_until_ready() raised an exception unexpectedly: {e}')
67+
self.fail(f'wait_for_sidecar() raised an exception unexpectedly: {e}')
5668

57-
mock_urlopen.assert_called_once()
69+
mock_get.assert_called_once()
5870

5971
# Check headers are properly set
60-
headers = mock_urlopen.call_args[0][0].headers
61-
self.assertIn('User-agent', headers)
62-
self.assertEqual(headers['User-agent'], f'dapr-sdk-python/{__version__}')
63-
self.assertIn('Dapr-api-token', headers)
64-
self.assertEqual(headers['Dapr-api-token'], 'mytoken')
72+
headers = mock_get.call_args[1]['headers']
73+
self.assertIn('User-Agent', headers)
74+
self.assertEqual(headers['User-Agent'], f'dapr-sdk-python/{__version__}')
75+
self.assertIn('dapr-api-token', headers)
76+
self.assertEqual(headers['dapr-api-token'], 'mytoken')
6577

6678
@patch.object(settings, 'DAPR_HEALTH_TIMEOUT', '2.5')
67-
@patch('urllib.request.urlopen')
68-
async def test_wait_until_ready_timeout(self, mock_urlopen):
69-
mock_urlopen.return_value.__enter__.return_value = MagicMock(status=500)
79+
@patch('aiohttp.ClientSession.get')
80+
async def test_wait_for_sidecar_timeout(self, mock_get):
81+
# Create mock response that always returns 500
82+
mock_response = MagicMock()
83+
mock_response.status = 500
84+
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
85+
mock_response.__aexit__ = AsyncMock(return_value=None)
86+
mock_get.return_value = mock_response
7087

7188
start = time.time()
7289

7390
with self.assertRaises(TimeoutError):
74-
await DaprHealth.wait_until_ready()
91+
await DaprHealth.wait_for_sidecar()
7592

7693
self.assertGreaterEqual(time.time() - start, 2.5)
77-
self.assertGreater(mock_urlopen.call_count, 1)
94+
self.assertGreater(mock_get.call_count, 1)
7895

7996
@patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500')
8097
@patch.object(settings, 'DAPR_HEALTH_TIMEOUT', '5.0')
81-
@patch('urllib.request.urlopen')
82-
async def test_health_check_does_not_block(self, mock_urlopen):
98+
@patch('aiohttp.ClientSession.get')
99+
async def test_health_check_does_not_block(self, mock_get):
83100
"""Test that health check doesn't block other async tasks from running"""
84101
# Mock health check to retry several times before succeeding
85102
call_count = [0] # Use list to allow modification in nested function
86103

87-
class MockResponse:
88-
def __init__(self, status):
89-
self.status = status
90-
91-
def __enter__(self):
92-
return self
93-
94-
def __exit__(self, exc_type, exc_val, exc_tb):
95-
return None
96-
97104
def side_effect(*args, **kwargs):
98105
call_count[0] += 1
99-
# First 2 calls fail with URLError, then succeed
106+
# First 2 calls fail with ClientError, then succeed
100107
# This will cause ~2 seconds of retries (1 second sleep after each failure)
101108
if call_count[0] <= 2:
102-
import urllib.error
109+
import aiohttp
103110

104-
raise urllib.error.URLError('Connection refused')
111+
raise aiohttp.ClientError('Connection refused')
105112
else:
106-
return MockResponse(status=200)
113+
mock_response = MagicMock()
114+
mock_response.status = 200
115+
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
116+
mock_response.__aexit__ = AsyncMock(return_value=None)
117+
return mock_response
107118

108-
mock_urlopen.side_effect = side_effect
119+
mock_get.side_effect = side_effect
109120

110121
# Counter that will be incremented by background task
111122
counter = [0] # Use list to allow modification in nested function
@@ -122,7 +133,7 @@ async def increment_counter():
122133

123134
try:
124135
# Run health check (will take ~2 seconds with retries)
125-
await DaprHealth.wait_until_ready()
136+
await DaprHealth.wait_for_sidecar()
126137

127138
# Stop the background task
128139
is_running[0] = False
@@ -150,17 +161,22 @@ async def increment_counter():
150161
pass
151162

152163
@patch.object(settings, 'DAPR_HTTP_ENDPOINT', 'http://domain.com:3500')
153-
@patch('urllib.request.urlopen')
154-
async def test_multiple_health_checks_concurrent(self, mock_urlopen):
164+
@patch('aiohttp.ClientSession.get')
165+
async def test_multiple_health_checks_concurrent(self, mock_get):
155166
"""Test that multiple health check calls can run concurrently"""
156-
mock_urlopen.return_value.__enter__.return_value = MagicMock(status=200)
167+
# Create mock response
168+
mock_response = MagicMock()
169+
mock_response.status = 200
170+
mock_response.__aenter__ = AsyncMock(return_value=mock_response)
171+
mock_response.__aexit__ = AsyncMock(return_value=None)
172+
mock_get.return_value = mock_response
157173

158174
# Run multiple health checks concurrently
159175
start_time = time.time()
160176
results = await asyncio.gather(
161-
DaprHealth.wait_until_ready(),
162-
DaprHealth.wait_until_ready(),
163-
DaprHealth.wait_until_ready(),
177+
DaprHealth.wait_for_sidecar(),
178+
DaprHealth.wait_for_sidecar(),
179+
DaprHealth.wait_for_sidecar(),
164180
)
165181
elapsed = time.time() - start_time
166182

@@ -174,7 +190,7 @@ async def test_multiple_health_checks_concurrent(self, mock_urlopen):
174190
self.assertLess(elapsed, 1.0)
175191

176192
# Verify multiple calls were made
177-
self.assertGreaterEqual(mock_urlopen.call_count, 3)
193+
self.assertGreaterEqual(mock_get.call_count, 3)
178194

179195

180196
if __name__ == '__main__':

0 commit comments

Comments
 (0)