Skip to content

Commit 380445f

Browse files
authored
[Key Vault] Custom polling method for admin backup client (Azure#19204)
1 parent d27b0f6 commit 380445f

15 files changed

+1723
-147
lines changed

sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_backup_client.py

Lines changed: 84 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,30 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5+
import base64
56
import functools
7+
import pickle
68
from typing import TYPE_CHECKING
79

8-
from azure.core.polling.base_polling import LROBasePolling
10+
from six import raise_from
11+
from six.moves.urllib_parse import urlparse
912

1013
from ._models import KeyVaultBackupOperation
1114
from ._internal import KeyVaultClientBase, parse_folder_url
12-
from ._internal.polling import KeyVaultBackupClientPolling
15+
from ._internal.polling import KeyVaultBackupClientPolling, KeyVaultBackupClientPollingMethod
1316

1417
if TYPE_CHECKING:
1518
# pylint:disable=unused-import
1619
from typing import Any
1720
from azure.core.polling import LROPoller
1821

1922

23+
def _parse_status_url(url):
24+
parsed = urlparse(url)
25+
job_id = parsed.path.split("/")[2]
26+
return job_id
27+
28+
2029
class KeyVaultBackupClient(KeyVaultClientBase):
2130
"""Performs Key Vault backup and restore operations.
2231
@@ -37,15 +46,48 @@ def begin_backup(self, blob_storage_url, sas_token, **kwargs):
3746
:returns: An :class:`~azure.core.polling.LROPoller` instance. Call `result()` on this object to wait for the
3847
operation to complete and get a :class:`KeyVaultBackupOperation`.
3948
:rtype: ~azure.core.polling.LROPoller[~azure.keyvault.administration.KeyVaultBackupOperation]
49+
50+
Example:
51+
.. literalinclude:: ../tests/test_examples_administration.py
52+
:start-after: [START begin_backup]
53+
:end-before: [END begin_backup]
54+
:language: python
55+
:caption: Create a vault backup
56+
:dedent: 8
4057
"""
4158
polling_interval = kwargs.pop("_polling_interval", 5)
4259
sas_parameter = self._models.SASTokenParameter(storage_resource_uri=blob_storage_url, token=sas_token)
60+
61+
continuation_token = kwargs.pop("continuation_token", None)
62+
status_response = None
63+
if continuation_token:
64+
status_url = base64.b64decode(continuation_token.encode()).decode("ascii")
65+
try:
66+
job_id = _parse_status_url(status_url)
67+
except Exception as ex: # pylint: disable=broad-except
68+
raise_from(
69+
ValueError(
70+
"The provided continuation_token is malformed. A valid token can be obtained from the "
71+
+ "operation poller's continuation_token() method"
72+
),
73+
ex,
74+
)
75+
76+
pipeline_response = self._client.full_backup_status(
77+
vault_base_url=self._vault_url, job_id=job_id, cls=lambda pipeline_response, _, __: pipeline_response
78+
)
79+
if "azure-asyncoperation" not in pipeline_response.http_response.headers:
80+
pipeline_response.http_response.headers["azure-asyncoperation"] = status_url
81+
status_response = base64.b64encode(pickle.dumps(pipeline_response)).decode("ascii")
82+
4383
return self._client.begin_full_backup(
4484
vault_base_url=self._vault_url,
4585
azure_storage_blob_container_uri=sas_parameter,
4686
cls=KeyVaultBackupOperation._from_generated,
47-
continuation_token=kwargs.pop("continuation_token", None),
48-
polling=LROBasePolling(lro_algorithms=[KeyVaultBackupClientPolling()], timeout=polling_interval, **kwargs),
87+
continuation_token=status_response,
88+
polling=KeyVaultBackupClientPollingMethod(
89+
lro_algorithms=[KeyVaultBackupClientPolling()], timeout=polling_interval, **kwargs
90+
),
4991
**kwargs
5092
)
5193

@@ -62,14 +104,50 @@ def begin_restore(self, folder_url, sas_token, **kwargs):
62104
:keyword str continuation_token: a continuation token to restart polling from a saved state
63105
:keyword str key_name: name of a single key in the backup. When set, only this key will be restored.
64106
:rtype: ~azure.core.polling.LROPoller
107+
108+
Examples:
109+
.. literalinclude:: ../tests/test_examples_administration.py
110+
:start-after: [START begin_restore]
111+
:end-before: [END begin_restore]
112+
:language: python
113+
:caption: Restore a vault backup
114+
:dedent: 8
115+
116+
.. literalinclude:: ../tests/test_examples_administration.py
117+
:start-after: [START begin_selective_restore]
118+
:end-before: [END begin_selective_restore]
119+
:language: python
120+
:caption: Restore a single key
121+
:dedent: 8
65122
"""
66123
# LROBasePolling passes its kwargs to pipeline.run(), so we remove unexpected args before constructing it
67124
continuation_token = kwargs.pop("continuation_token", None)
68125
key_name = kwargs.pop("key_name", None)
69126

127+
status_response = None
128+
if continuation_token:
129+
status_url = base64.b64decode(continuation_token.encode()).decode("ascii")
130+
try:
131+
job_id = _parse_status_url(status_url)
132+
except Exception as ex: # pylint: disable=broad-except
133+
raise_from(
134+
ValueError(
135+
"The provided continuation_token is malformed. A valid token can be obtained from the "
136+
+ "operation poller's continuation_token() method"
137+
),
138+
ex,
139+
)
140+
141+
pipeline_response = self._client.restore_status(
142+
vault_base_url=self._vault_url, job_id=job_id, cls=lambda pipeline_response, _, __: pipeline_response
143+
)
144+
if "azure-asyncoperation" not in pipeline_response.http_response.headers:
145+
pipeline_response.http_response.headers["azure-asyncoperation"] = status_url
146+
status_response = base64.b64encode(pickle.dumps(pipeline_response)).decode("ascii")
147+
70148
container_url, folder_name = parse_folder_url(folder_url)
71149
sas_parameter = self._models.SASTokenParameter(storage_resource_uri=container_url, token=sas_token)
72-
polling = LROBasePolling(
150+
polling = KeyVaultBackupClientPollingMethod(
73151
lro_algorithms=[KeyVaultBackupClientPolling()], timeout=kwargs.pop("_polling_interval", 5), **kwargs
74152
)
75153

@@ -88,7 +166,7 @@ def begin_restore(self, folder_url, sas_token, **kwargs):
88166
vault_base_url=self._vault_url,
89167
restore_blob_details=restore_details,
90168
cls=lambda *_: None, # poller.result() returns None
91-
continuation_token=continuation_token,
169+
continuation_token=status_response,
92170
polling=polling,
93171
**kwargs
94172
)
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# ------------------------------------
2+
# Copyright (c) Microsoft Corporation.
3+
# Licensed under the MIT License.
4+
# ------------------------------------
5+
import base64
6+
7+
from azure.core.polling.async_base_polling import AsyncLROBasePolling
8+
9+
10+
class KeyVaultAsyncBackupClientPollingMethod(AsyncLROBasePolling):
11+
def get_continuation_token(self) -> str:
12+
return base64.b64encode(self._operation.get_polling_url().encode()).decode("ascii")

sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/_internal/polling.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,32 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5-
from azure.core.polling.base_polling import OperationResourcePolling
5+
import base64
6+
7+
from azure.core.polling.base_polling import LROBasePolling, OperationFailed, OperationResourcePolling
68

79

810
class KeyVaultBackupClientPolling(OperationResourcePolling):
911
def __init__(self):
12+
self._polling_url = None
1013
super(KeyVaultBackupClientPolling, self).__init__(operation_location_header="azure-asyncoperation")
1114

15+
def get_polling_url(self):
16+
return self._polling_url
17+
1218
def get_final_get_url(self, pipeline_response):
1319
return None
20+
21+
def set_initial_status(self, pipeline_response):
22+
response = pipeline_response.http_response
23+
self._polling_url = response.headers["azure-asyncoperation"]
24+
25+
if response.status_code in {200, 201, 202, 204}:
26+
return self.get_status(pipeline_response)
27+
raise OperationFailed("Operation failed or canceled")
28+
29+
30+
class KeyVaultBackupClientPollingMethod(LROBasePolling):
31+
def get_continuation_token(self):
32+
# type: () -> str
33+
return base64.b64encode(self._operation.get_polling_url().encode()).decode("ascii")

sdk/keyvault/azure-keyvault-administration/azure/keyvault/administration/aio/_backup_client.py

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,14 @@
22
# Copyright (c) Microsoft Corporation.
33
# Licensed under the MIT License.
44
# ------------------------------------
5+
import base64
56
import functools
7+
import pickle
68
from typing import TYPE_CHECKING
79

8-
from azure.core.polling.async_base_polling import AsyncLROBasePolling
9-
10+
from .._backup_client import _parse_status_url
1011
from .._internal import AsyncKeyVaultClientBase, parse_folder_url
12+
from .._internal.async_polling import KeyVaultAsyncBackupClientPollingMethod
1113
from .._internal.polling import KeyVaultBackupClientPolling
1214
from .._models import KeyVaultBackupOperation
1315

@@ -37,15 +39,43 @@ async def begin_backup(
3739
:keyword str continuation_token: a continuation token to restart polling from a saved state
3840
:returns: An AsyncLROPoller. Call `result()` on this object to get a :class:`KeyVaultBackupOperation`.
3941
:rtype: ~azure.core.polling.AsyncLROPoller[~azure.keyvault.administration.KeyVaultBackupOperation]
42+
43+
Example:
44+
.. literalinclude:: ../tests/test_examples_administration_async.py
45+
:start-after: [START begin_backup]
46+
:end-before: [END begin_backup]
47+
:language: python
48+
:caption: Create a vault backup
49+
:dedent: 8
4050
"""
4151
polling_interval = kwargs.pop("_polling_interval", 5)
4252
sas_parameter = self._models.SASTokenParameter(storage_resource_uri=blob_storage_url, token=sas_token)
53+
54+
continuation_token = kwargs.pop("continuation_token", None)
55+
status_response = None
56+
if continuation_token:
57+
status_url = base64.b64decode(continuation_token.encode()).decode("ascii")
58+
try:
59+
job_id = _parse_status_url(status_url)
60+
except Exception as ex: # pylint: disable=broad-except
61+
raise ValueError(
62+
"The provided continuation_token is malformed. A valid token can be obtained from the operation "
63+
+ "poller's continuation_token() method"
64+
) from ex
65+
66+
pipeline_response = await self._client.full_backup_status(
67+
vault_base_url=self._vault_url, job_id=job_id, cls=lambda pipeline_response, _, __: pipeline_response
68+
)
69+
if "azure-asyncoperation" not in pipeline_response.http_response.headers:
70+
pipeline_response.http_response.headers["azure-asyncoperation"] = status_url
71+
status_response = base64.b64encode(pickle.dumps(pipeline_response)).decode("ascii")
72+
4373
return await self._client.begin_full_backup(
4474
vault_base_url=self._vault_url,
4575
azure_storage_blob_container_uri=sas_parameter,
4676
cls=KeyVaultBackupOperation._from_generated,
47-
continuation_token=kwargs.pop("continuation_token", None),
48-
polling=AsyncLROBasePolling(
77+
continuation_token=status_response,
78+
polling=KeyVaultAsyncBackupClientPollingMethod(
4979
lro_algorithms=[KeyVaultBackupClientPolling()], timeout=polling_interval, **kwargs
5080
),
5181
**kwargs
@@ -64,14 +94,47 @@ async def begin_restore(self, folder_url: str, sas_token: str, **kwargs: "Any")
6494
:keyword str continuation_token: a continuation token to restart polling from a saved state
6595
:keyword str key_name: name of a single key in the backup. When set, only this key will be restored.
6696
:rtype: ~azure.core.polling.AsyncLROPoller
97+
98+
Examples:
99+
.. literalinclude:: ../tests/test_examples_administration_async.py
100+
:start-after: [START begin_restore]
101+
:end-before: [END begin_restore]
102+
:language: python
103+
:caption: Restore a vault backup
104+
:dedent: 8
105+
106+
.. literalinclude:: ../tests/test_examples_administration_async.py
107+
:start-after: [START begin_selective_restore]
108+
:end-before: [END begin_selective_restore]
109+
:language: python
110+
:caption: Restore a single key
111+
:dedent: 8
67112
"""
68113
# AsyncLROBasePolling passes its kwargs to pipeline.run(), so we remove unexpected args before constructing it
69114
continuation_token = kwargs.pop("continuation_token", None)
70115
key_name = kwargs.pop("key_name", None)
71116

117+
status_response = None
118+
if continuation_token:
119+
status_url = base64.b64decode(continuation_token.encode()).decode("ascii")
120+
try:
121+
job_id = _parse_status_url(status_url)
122+
except Exception as ex: # pylint: disable=broad-except
123+
raise ValueError(
124+
"The provided continuation_token is malformed. A valid token can be obtained from the operation "
125+
+ "poller's continuation_token() method"
126+
) from ex
127+
128+
pipeline_response = await self._client.restore_status(
129+
vault_base_url=self._vault_url, job_id=job_id, cls=lambda pipeline_response, _, __: pipeline_response
130+
)
131+
if "azure-asyncoperation" not in pipeline_response.http_response.headers:
132+
pipeline_response.http_response.headers["azure-asyncoperation"] = status_url
133+
status_response = base64.b64encode(pickle.dumps(pipeline_response)).decode("ascii")
134+
72135
container_url, folder_name = parse_folder_url(folder_url)
73136
sas_parameter = self._models.SASTokenParameter(storage_resource_uri=container_url, token=sas_token)
74-
polling = AsyncLROBasePolling(
137+
polling = KeyVaultAsyncBackupClientPollingMethod(
75138
lro_algorithms=[KeyVaultBackupClientPolling()], timeout=kwargs.pop("_polling_interval", 5), **kwargs
76139
)
77140

@@ -90,7 +153,7 @@ async def begin_restore(self, folder_url: str, sas_token: str, **kwargs: "Any")
90153
vault_base_url=self._vault_url,
91154
restore_blob_details=restore_details,
92155
cls=lambda *_: None, # poller.result() returns None
93-
continuation_token=continuation_token,
156+
continuation_token=status_response,
94157
polling=polling,
95158
**kwargs
96159
)

sdk/keyvault/azure-keyvault-administration/setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"azure.keyvault",
8181
]
8282
),
83-
install_requires=["azure-common~=1.1", "azure-core<2.0.0,>=1.11.0", "msrest>=0.6.21"],
83+
install_requires=["azure-common~=1.1", "azure-core<2.0.0,>=1.11.0", "msrest>=0.6.21", "six>=1.11.0"],
8484
extras_require={
8585
":python_version<'3.0'": ["azure-keyvault-nspkg"],
8686
":python_version<'3.4'": ["enum34>=1.0.4"],

0 commit comments

Comments
 (0)