Skip to content

Commit bbbccae

Browse files
committed
Refactor API deletes to make customization easier
1 parent 0da0bd4 commit bbbccae

1 file changed

Lines changed: 73 additions & 23 deletions

File tree

api.py

Lines changed: 73 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import re
12
import requests
23
import time
34

@@ -11,6 +12,11 @@
1112
from filelock import FileLock
1213
from requests.adapters import HTTPAdapter
1314
from requests.packages.urllib3.util.retry import Retry
15+
from urllib.parse import urlparse
16+
17+
18+
# Contains delete handlers, see delete_handler below
19+
DELETE_HANDLERS = {}
1420

1521

1622
class CloudscaleHTTPAdapter(HTTPAdapter):
@@ -106,29 +112,7 @@ def post(self, url, data=None, json=None, add_tags=True, **kwargs):
106112
return super().post(url, data=data, json=json, **kwargs)
107113

108114
def delete(self, url):
109-
super().delete(url)
110-
111-
# Wait for snapshots to be deleted
112-
if 'volume-snapshots' in url:
113-
timeout = time.monotonic() + 60
114-
115-
while time.monotonic() < timeout:
116-
time.sleep(1)
117-
118-
try:
119-
snapshot = self.get(url)
120-
except requests.exceptions.HTTPError as e:
121-
if e.response.status_code == 404:
122-
# The snapshot is gone, stop waiting
123-
break
124-
else:
125-
raise e
126-
127-
else:
128-
raise Timeout(
129-
f'Snapshot failed to delete within 60 seconds. Status '
130-
f'is still "{snapshot.json()["status"]}".'
131-
)
115+
delete_handler_for_url(url)(api=self, url=url)
132116

133117
def on_response(self, response, *args, **kwargs):
134118
trigger('request.after', request=response.request, response=response)
@@ -194,3 +178,69 @@ def cleanup(self, limit_to_scope=True, limit_to_process=True):
194178

195179
if exceptions:
196180
raise ExceptionGroup("Failures during cleanup.", exceptions)
181+
182+
183+
def delete_handler(path):
184+
""" Registers the decorated function as delete handler for the given
185+
path. The path is treated as regular expression pattern, used to search
186+
the path (not the whole URL).
187+
188+
The decorated function is supposed to delete the given URL.
189+
190+
"""
191+
192+
def delete_handler_decorator(fn):
193+
DELETE_HANDLERS[path] = fn
194+
return fn
195+
return delete_handler_decorator
196+
197+
198+
def delete_handler_for_url(url):
199+
""" Evaluates the registered delete handlers and picks the first matching
200+
one, or a default.
201+
202+
The order of the evaluation is not strictly defined, handlers are
203+
expected to not overlap.
204+
205+
"""
206+
path = urlparse(url).path
207+
208+
for pattern, fn in DELETE_HANDLERS.items():
209+
if re.fullmatch(pattern, path):
210+
return fn
211+
212+
# Use the low-level method, as we are downstream from api.delete and cannot
213+
# call api.delete, lest we want an infinite loop.
214+
return lambda api, url: api.request("DELETE", url)
215+
216+
217+
@delete_handler(path='/v1/volume-snapshots/.+')
218+
def delete_volume_snapshots(api, url):
219+
""" When deleting volume-snapshots, we need to wait for the snapshots to
220+
be deleted, or we won't be able to delete the servers later.
221+
222+
"""
223+
224+
# Delete the snapshot first
225+
api.request("DELETE", url)
226+
227+
# Wait for snapshots to be deleted
228+
timeout = time.monotonic() + 60
229+
230+
while time.monotonic() < timeout:
231+
time.sleep(1)
232+
233+
try:
234+
snapshot = api.get(url)
235+
except requests.exceptions.HTTPError as e:
236+
if e.response.status_code == 404:
237+
# The snapshot is gone, stop waiting
238+
break
239+
else:
240+
raise e
241+
242+
else:
243+
raise Timeout(
244+
f'Snapshot failed to delete within 60 seconds. Status '
245+
f'is still "{snapshot.json()["status"]}".'
246+
)

0 commit comments

Comments
 (0)