Skip to content

Commit 0a6e74f

Browse files
committed
Refactor delete code
1 parent 2105972 commit 0a6e74f

File tree

2 files changed

+192
-176
lines changed

2 files changed

+192
-176
lines changed

.github/actions/cleanup-all/cleanup-test-projects.py

Lines changed: 95 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -123,35 +123,25 @@ def __init__(self, api_key):
123123
# In situations where there are a lot of resources, we want to
124124
# slow down the rate of requests just to avoid any concerns about
125125
# rate limits
126-
self.sleep_interval = 10
126+
self.sleep_interval = 5
127+
self.undeleteable_resources = []
127128

128129
def pluralize(self, resource_name):
129130
if resource_name.lower() == "index":
130131
return resource_name + "es"
131132
else:
132133
return resource_name + "s"
133134

134-
def _delete_all_of_resource(
135-
self,
136-
resource_name,
137-
list_func,
138-
describe_func,
139-
delete_func,
140-
get_state_func=None,
141-
configure_func=None,
142-
):
135+
def _delete_all_of_resource(self, resource_name, list_func, delete_func, get_state_func):
143136
resources_to_delete = deque(list_func())
144137
if len(resources_to_delete) == 0:
145138
logger.info(f"No {self.pluralize(resource_name)} to delete")
146139
return
147140

148-
max_retries = 3
149-
max_terminating_status_checks = 10
150-
state_check_counts = RetryCounter(max_retries)
151-
failed_delete_counts = RetryCounter(max_retries)
152-
status_check_counts = RetryCounter(max_retries)
153-
deletion_protection_change_counts = RetryCounter(max_retries)
154-
terminating_status_counts = RetryCounter(max_terminating_status_checks)
141+
state_check_retries = RetryCounter(3)
142+
failed_delete_retries = RetryCounter(3)
143+
is_deletable_retries = RetryCounter(3)
144+
is_terminating_retries = RetryCounter(10)
155145

156146
undeletable_resources = []
157147

@@ -164,21 +154,21 @@ def _delete_all_of_resource(
164154
resource = resources_to_delete.popleft()
165155
logger.info(f"Processing {resource_name} {resource.name}")
166156

167-
# Get the latest description of the index
157+
# Get the latest description of the resource
168158
try:
169-
state_check_counts.increment(resource.name)
159+
state_check_retries.increment(resource.name)
170160
state = get_state_func(name=resource.name)
171161
logger.info(f"{resource_name} {resource.name} has state {state}")
172162
except NotFoundException:
173163
logger.info(f"{resource_name} {resource.name} has already been deleted, continuing")
174164
continue
175165
except Exception as e:
176-
state_check_counts.increment(resource.name)
177-
if state_check_counts.is_maxed_out(resource.name):
166+
if state_check_retries.is_maxed_out(resource.name):
178167
logger.error(f"Error describing {resource_name} {resource.name}: {e}")
179168
undeletable_resources.append(
180169
{
181170
"resource": resource,
171+
"type": resource_name,
182172
"reason": f"Error describing {resource_name} {resource.name}: {e}",
183173
}
184174
)
@@ -190,15 +180,16 @@ def _delete_all_of_resource(
190180
resources_to_delete.append(resource)
191181
continue
192182

193-
if state == "Terminating":
194-
terminating_status_counts.increment(resource.name)
195-
if terminating_status_counts.is_maxed_out(resource.name):
183+
if state == "Terminating" or state == "Terminated":
184+
is_terminating_retries.increment(resource.name)
185+
if is_terminating_retries.is_maxed_out(resource.name):
196186
logger.error(
197187
f"{resource_name} {resource.name} has been in the terminating state for too long, skipping"
198188
)
199189
undeletable_resources.append(
200190
{
201191
"resource": resource,
192+
"type": resource_name,
202193
"reason": f"{resource_name} has been in the terminating state for too long",
203194
}
204195
)
@@ -213,15 +204,17 @@ def _delete_all_of_resource(
213204
# If the index is not in a deleteable state, add it to the back of the delete queue
214205
deleteable_states = ["Ready", "InitializationFailed"]
215206
if state not in deleteable_states:
216-
status_check_counts.increment(resource.name)
217-
if status_check_counts.is_maxed_out(resource.name):
207+
is_deletable_retries.increment(resource.name)
208+
if is_deletable_retries.is_maxed_out(resource.name):
209+
attempts = is_deletable_retries.get_count(resource.name)
218210
logger.error(
219-
f"{resource_name} {resource.name} did not enter a deleteable state after {max_retries} attempts, skipping"
211+
f"{resource_name} {resource.name} did not enter a deleteable state after {attempts} attempts, skipping"
220212
)
221213
undeletable_resources.append(
222214
{
223215
"resource": resource,
224-
"reason": f"Not in a deleteable state after {max_retries} attempts",
216+
"type": resource_name,
217+
"reason": f"Not in a deleteable state after {attempts} attempts",
225218
}
226219
)
227220
continue
@@ -232,61 +225,23 @@ def _delete_all_of_resource(
232225
resources_to_delete.append(resource)
233226
continue
234227

235-
if configure_func is not None:
236-
description = describe_func(name=resource.name)
237-
logger.info(
238-
f"{resource_name} {resource.name} has deletion protection {description.deletion_protection}"
239-
)
240-
if description.deletion_protection == "enabled":
241-
try:
242-
logger.info(
243-
f"Disabling deletion protection for {resource_name} {resource.name}"
244-
)
245-
configure_func(name=resource.name, deletion_protection="disabled")
246-
resources_to_delete.append(resource)
247-
logger.info(
248-
f"{resource_name} {resource.name} has been returned to the back of the delete queue"
249-
)
250-
continue
251-
except Exception as e:
252-
logger.error(
253-
f"Error disabling deletion protection for {resource_name} {resource.name}: {e}"
254-
)
255-
256-
deletion_protection_change_counts.increment(resource.name)
257-
if deletion_protection_change_counts.is_maxed_out(resource.name):
258-
logger.error(
259-
f"Failed to change deletion protection for {resource_name} {resource.name} after {max_retries} attempts, skipping"
260-
)
261-
undeletable_resources.append(
262-
{
263-
"resource": resource,
264-
"reason": f"Failed to change deletion protection after {max_retries} attempts",
265-
}
266-
)
267-
continue
268-
else:
269-
logger.info(
270-
f"{resource_name} {resource.name} has been returned to the back of the delete queue"
271-
)
272-
resources_to_delete.append(resource)
273-
continue
274-
275228
try:
276229
logger.info(f"Attempting deleting of {resource_name} {resource.name}")
277230
delete_func(name=resource.name)
278231
logger.info(f"Successfully deleted {resource_name} {resource.name}")
279232
except Exception as e:
280233
logger.error(f"Error deleting {resource_name} {resource.name}: {e}")
281-
failed_delete_counts.increment(resource.name)
282-
if failed_delete_counts.is_maxed_out(resource.name):
234+
failed_delete_retries.increment(resource.name)
235+
if failed_delete_retries.is_maxed_out(resource.name):
236+
attempts = failed_delete_retries.get_count(resource.name)
283237
logger.error(
284-
f"Failed to delete {resource_name} {resource.name} after {max_retries} attempts, skipping"
238+
f"Failed to delete {resource_name} {resource.name} after {attempts} attempts, skipping"
285239
)
286240
undeletable_resources.append(
287241
{
288242
"resource": resource,
289-
"reason": f"Failed to delete after {max_retries} attempts",
243+
"type": resource_name,
244+
"reason": f"Failed to delete after {attempts} attempts",
290245
}
291246
)
292247
continue
@@ -301,28 +256,47 @@ def _delete_all_of_resource(
301256
logger.error(
302257
f"There were {len(undeletable_resources)} {self.pluralize(resource_name)} that were not deleted"
303258
)
304-
for resource in undeletable_resources:
259+
for item in undeletable_resources:
305260
logger.error(
306-
f"{resource_name} {resource['resource'].name} was not deleted because {resource['reason']}"
261+
f"{resource_name} {item['resource'].name} was not deleted because {item['reason']}"
307262
)
308-
raise Exception(
309-
f"There were {len(undeletable_resources)} {self.pluralize(resource_name)} that were not deleted"
310-
)
263+
self.undeleteable_resources.append(item)
311264
else:
312265
logger.info(f"All {self.pluralize(resource_name)} were deleted successfully")
313266

314267
def delete_all_indexes(self):
268+
index_list = self.pc.db.index.list()
269+
if len(index_list) == 0:
270+
logger.info("No indexes to delete")
271+
return
272+
273+
index_with_deletion_protection = [
274+
index for index in index_list if index.deletion_protection == "enabled"
275+
]
276+
for index in index_with_deletion_protection:
277+
logger.info(f"Disabling deletion protection for Index {index.name}")
278+
time.sleep(self.sleep_interval)
279+
try:
280+
self.pc.db.index.configure(name=index.name, deletion_protection="disabled")
281+
except Exception as e:
282+
logger.error(f"Error disabling deletion protection for Index {index.name}: {e}")
283+
self.undeleteable_resources.append(
284+
{
285+
"resource": index,
286+
"type": "index",
287+
"reason": f"Failed to disable deletion protection: {e}",
288+
}
289+
)
290+
315291
def get_state_func(name):
316292
desc = self.pc.db.index.describe(name=name)
317293
return desc.status.state
318294

319295
return self._delete_all_of_resource(
320296
resource_name="index",
321297
list_func=self.pc.db.index.list,
322-
describe_func=self.pc.db.index.describe,
323298
delete_func=self.pc.db.index.delete,
324299
get_state_func=get_state_func,
325-
configure_func=self.pc.db.index.configure,
326300
)
327301

328302
def delete_all_collections(self):
@@ -333,7 +307,6 @@ def get_state_func(name):
333307
return self._delete_all_of_resource(
334308
resource_name="collection",
335309
list_func=self.pc.db.collection.list,
336-
describe_func=self.pc.db.collection.describe,
337310
delete_func=self.pc.db.collection.delete,
338311
get_state_func=get_state_func,
339312
)
@@ -345,10 +318,6 @@ def _get_backup_by_name(name):
345318
return backup
346319
raise Exception(f"Backup {name} not found")
347320

348-
def describe_func(name):
349-
backup = _get_backup_by_name(name)
350-
return self.pc.db.backup.describe(backup_id=backup.backup_id)
351-
352321
def delete_func(name):
353322
backup = _get_backup_by_name(name)
354323
return self.pc.db.backup.delete(backup_id=backup.backup_id)
@@ -360,15 +329,54 @@ def get_state_func(name):
360329
return self._delete_all_of_resource(
361330
resource_name="backup",
362331
list_func=self.pc.db.backup.list,
363-
describe_func=describe_func,
364332
delete_func=delete_func,
365333
get_state_func=get_state_func,
366334
)
367335

368-
def cleanup_all(self):
336+
def _cleanup_all(self):
337+
self.undeleteable_resources = []
369338
self.delete_all_backups()
370-
self.delete_all_indexes()
371339
self.delete_all_collections()
340+
self.delete_all_indexes()
341+
342+
def cleanup_all(self):
343+
self._cleanup_all()
344+
345+
if len(self.undeleteable_resources) > 0:
346+
logger.info(
347+
f"There were {len(self.undeleteable_resources)} undeleteable resources, retrying in 60 seconds"
348+
)
349+
time.sleep(60)
350+
self._cleanup_all()
351+
352+
if len(self.undeleteable_resources) > 0:
353+
logger.info(
354+
f"There were {len(self.undeleteable_resources)} undeleteable resources, retrying in 120 seconds"
355+
)
356+
time.sleep(120)
357+
self._cleanup_all()
358+
359+
if len(self.undeleteable_resources) > 0:
360+
logger.info(
361+
f"There were {len(self.undeleteable_resources)} undeleteable resources, retrying in 240 seconds"
362+
)
363+
time.sleep(240)
364+
self._cleanup_all()
365+
366+
if len(self.undeleteable_resources) > 0:
367+
logger.info(
368+
f"There were {len(self.undeleteable_resources)} undeleteable resources, retrying in 240 seconds"
369+
)
370+
time.sleep(240)
371+
self._cleanup_all()
372+
373+
if len(self.undeleteable_resources) > 0:
374+
logger.error(
375+
f"There were {len(self.undeleteable_resources)} undeleteable resources, giving up"
376+
)
377+
raise Exception(
378+
f"There were {len(self.undeleteable_resources)} undeleteable resources, giving up"
379+
)
372380

373381

374382
if __name__ == "__main__":

0 commit comments

Comments
 (0)