Skip to content

Commit 35f545c

Browse files
authored
App Config Provider - Minimum Up Time (#33445)
* Adding Pre-Kill * Adding Tests * Update CHANGELOG.md * Fixes live test issue. SP.
1 parent 727f510 commit 35f545c

File tree

4 files changed

+52
-2
lines changed

4 files changed

+52
-2
lines changed

sdk/appconfiguration/azure-appconfiguration-provider/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
### Features Added
66

77
- Added on_refresh_success callback to load method. This callback is called when the refresh method successfully refreshes the configuration.
8+
- Added minimum up time. This is the minimum amount of time the provider will try to be up before throwing an error. This is to prevent quick restart loops.
89

910
### Breaking Changes
1011

sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/_azureappconfigurationprovider.py

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import json
88
import random
99
import time
10+
import datetime
1011
from threading import Lock
1112
import logging
1213
from typing import (
@@ -51,6 +52,8 @@
5152

5253
logger = logging.getLogger(__name__)
5354

55+
min_uptime = 5
56+
5457

5558
@overload
5659
def load(
@@ -151,6 +154,7 @@ def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
151154
credential: Optional["TokenCredential"] = kwargs.pop("credential", None)
152155
connection_string: Optional[str] = kwargs.pop("connection_string", None)
153156
key_vault_options: Optional[AzureAppConfigurationKeyVaultOptions] = kwargs.pop("key_vault_options", None)
157+
start_time = datetime.datetime.now()
154158

155159
# Update endpoint and credential if specified positionally.
156160
if len(args) > 2:
@@ -186,7 +190,11 @@ def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
186190
provider = _buildprovider(
187191
connection_string, endpoint, credential, uses_key_vault="UsesKeyVault" in headers, **kwargs
188192
)
189-
provider._load_all(headers=headers)
193+
try:
194+
provider._load_all(headers=headers)
195+
except Exception as e:
196+
_prekill(start_time)
197+
raise e
190198

191199
# Refresh-All sentinels are not updated on load_all, as they are not necessarily included in the provider.
192200
for (key, label), etag in provider._refresh_on.items():
@@ -203,10 +211,23 @@ def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
203211
label,
204212
)
205213
else:
214+
_prekill(start_time)
206215
raise e
216+
except Exception as e:
217+
_prekill(start_time)
218+
raise e
207219
return provider
208220

209221

222+
def _prekill(start_time: datetime.datetime) -> None:
223+
# We want to make sure we are up a minimum amount of time before we kill the process. Otherwise, we could get stuck
224+
# in a quick restart loop.
225+
min_time = datetime.timedelta(seconds=min_uptime)
226+
current_time = datetime.datetime.now()
227+
if current_time - start_time < min_time:
228+
time.sleep(min_time - (current_time - start_time))
229+
230+
210231
def _get_headers(request_type, **kwargs) -> str:
211232
headers = kwargs.pop("headers", {})
212233
if os.environ.get(REQUEST_TRACING_DISABLED_ENVIRONMENT_VARIABLE, default="").lower() != "true":

sdk/appconfiguration/azure-appconfiguration-provider/azure/appconfiguration/provider/aio/_azureappconfigurationproviderasync.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
# license information.
55
# -------------------------------------------------------------------------
66
import json
7+
import datetime
78
from asyncio.locks import Lock
89
import logging
910
from typing import (
@@ -42,6 +43,7 @@
4243
_get_headers,
4344
_RefreshTimer,
4445
_build_sentinel,
46+
_prekill,
4547
)
4648
from .._user_agent import USER_AGENT
4749

@@ -153,6 +155,7 @@ async def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
153155
credential: Optional["AsyncTokenCredential"] = kwargs.pop("credential", None)
154156
connection_string: Optional[str] = kwargs.pop("connection_string", None)
155157
key_vault_options: Optional[AzureAppConfigurationKeyVaultOptions] = kwargs.pop("key_vault_options", None)
158+
start_time = datetime.datetime.now()
156159

157160
# Update endpoint and credential if specified positionally.
158161
if len(args) > 2:
@@ -186,7 +189,12 @@ async def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
186189

187190
headers = _get_headers("Startup", **kwargs)
188191
provider = _buildprovider(connection_string, endpoint, credential, **kwargs)
189-
await provider._load_all(headers=headers)
192+
193+
try:
194+
await provider._load_all(headers=headers)
195+
except Exception as e:
196+
_prekill(start_time)
197+
raise e
190198

191199
# Refresh-All sentinels are not updated on load_all, as they are not necessarily included in the provider.
192200
for (key, label), etag in provider._refresh_on.items():
@@ -204,7 +212,11 @@ async def load(*args, **kwargs) -> "AzureAppConfigurationProvider":
204212
)
205213
provider._refresh_on[(key, label)] = None
206214
else:
215+
_prekill(start_time)
207216
raise e
217+
except Exception as e:
218+
_prekill(start_time)
219+
raise e
208220
return provider
209221

210222

sdk/appconfiguration/azure-appconfiguration-provider/tests/test_provider.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
from devtools_testutils import recorded_by_proxy
88
from preparers import app_config_decorator
99
from testcase import AppConfigTestCase
10+
import datetime
11+
from unittest.mock import patch
12+
13+
from azure.appconfiguration.provider._azureappconfigurationprovider import _prekill
1014

1115

1216
class TestAppConfigurationProvider(AppConfigTestCase):
@@ -104,6 +108,18 @@ def test_provider_secret_resolver_options(self, appconfiguration_connection_stri
104108
)
105109
assert client["secret"] == "Reslover Value"
106110

111+
# method: _prekill
112+
@patch("time.sleep")
113+
def test_prekill(self, mock_sleep, **kwargs):
114+
start_time = datetime.datetime.now()
115+
_prekill(start_time)
116+
assert mock_sleep.call_count == 1
117+
118+
mock_sleep.reset_mock()
119+
start_time = datetime.datetime.now() - datetime.timedelta(seconds=10)
120+
_prekill(start_time)
121+
mock_sleep.assert_not_called()
122+
107123

108124
def secret_resolver(secret_id):
109125
return "Reslover Value"

0 commit comments

Comments
 (0)