Skip to content

Commit 400b9a7

Browse files
Merge pull request #444 from SUSE/fix_deleting_reset
Fix deleting reset
2 parents 1a5b9f1 + fc6fd3b commit 400b9a7

File tree

3 files changed

+128
-7
lines changed

3 files changed

+128
-7
lines changed

ocw/lib/db.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,9 @@ def is_deleting_stale(instance: type[Instance]):
168168
'''
169169
Check if the instance has been in a deleting state longer than the configured threshold.
170170
'''
171+
if instance.ignore:
172+
return False
173+
171174
# Return False if state is not in deleting states
172175
if instance.state not in [StateChoice.DELETING]:
173176
return False
@@ -229,6 +232,9 @@ def reset_stale_deleting() -> None:
229232

230233

231234
def delete_instance(instance: type[Instance]) -> None:
235+
if instance.ignore:
236+
return
237+
232238
if instance.provider == ProviderChoice.AZURE:
233239
Azure(instance.namespace).delete_resource(instance.instance_id)
234240
elif instance.provider == ProviderChoice.EC2:

tests/test_db.py

Lines changed: 89 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def delete_instance(self, instance_id, region):
3535

3636

3737
class InstanceMock:
38-
def __init__(self, provider, state=None, ttl=None, first_seen=None):
38+
def __init__(self, provider, state=None, ttl=None, first_seen=None, last_seen=None, deleting_since=None, ignore=False):
3939
self.state = state if state is not None else None
4040
self.instance_id = fake.uuid4()
4141
self.region = None
@@ -46,6 +46,7 @@ def __init__(self, provider, state=None, ttl=None, first_seen=None):
4646
self.first_seen = first_seen if first_seen else datetime.now(tz=timezone.utc)
4747
self.last_seen = self.first_seen # For all_time_fields()
4848
self.age = timedelta(seconds=0) # Placeholder
49+
self.ignore = ignore
4950

5051
def save(self, update_fields=None):
5152
self.save_called = True
@@ -221,39 +222,53 @@ def test_reset_stale_deleting(monkeypatch):
221222
'state': StateChoice.DELETING,
222223
'deleting_since': now - timedelta(hours=3), # 3 hours ago
223224
'should_reset': True,
224-
'desc': "Stale (3h > 2h)"
225+
'desc': "Stale (3h > 2h)",
226+
'pcw_ignore': False
225227
},
226228
# Case 2: Not Stale (1h < 2h)
227229
{
228230
'instance_id': "test-instance-not-stale",
229231
'state': StateChoice.DELETING,
230232
'deleting_since': now - timedelta(hours=1), # 1 hour ago
231233
'should_reset': False,
232-
'desc': "Not Stale (1h < 2h)"
234+
'desc': "Not Stale (1h < 2h)",
235+
'pcw_ignore': False
233236
},
234237
# Case 3: Non-Deleting (ACTIVE)
235238
{
236239
'instance_id': "test-instance-active",
237240
'state': StateChoice.ACTIVE,
238241
'deleting_since': now - timedelta(hours=3), # Shouldn’t matter
239242
'should_reset': False,
240-
'desc': "Non-Deleting (ACTIVE)"
243+
'desc': "Non-Deleting (ACTIVE)",
244+
'pcw_ignore': False
241245
},
242246
# Case 4: Edge (2h = 2h)
243247
{
244248
'instance_id': "test-instance-edge",
245249
'state': StateChoice.DELETING,
246250
'deleting_since': now - timedelta(hours=2), # Exactly 2 hours ago
247251
'should_reset': True,
248-
'desc': "Edge (2h = 2h)"
252+
'desc': "Edge (2h = 2h)",
253+
'pcw_ignore': False
249254
},
250255
# Case 5: Edge (1h 59m < 2h)
251256
{
252257
'instance_id': "test-instance-edge-2",
253258
'state': StateChoice.DELETING,
254259
'deleting_since': now - timedelta(hours=1) - timedelta(minutes=59), # Exactly 2 hours ago
255260
'should_reset': False,
256-
'desc': "Edge (1h 59m < 2h)"
261+
'desc': "Edge (1h 59m < 2h)",
262+
'pcw_ignore': False
263+
},
264+
# Case 6: Stale (3h > 2h) but pcw_ignore tag is True
265+
{
266+
'instance_id': "test-instance-stale-but-pcw-ignore",
267+
'state': StateChoice.DELETING,
268+
'deleting_since': now - timedelta(hours=3), # 3 hours ago
269+
'should_reset': False,
270+
'desc': "Stale (3h > 2h)",
271+
'pcw_ignore': True
257272
},
258273
]
259274

@@ -279,11 +294,22 @@ def test_reset_stale_deleting(monkeypatch):
279294
active=case['state'] == StateChoice.ACTIVE
280295
)
281296
instance.save()
297+
tags = {'openqa_ttl': str(default_ttl)}
298+
if case['pcw_ignore']:
299+
tags[Instance.TAG_IGNORE] = "True"
300+
282301
CspInfo.objects.create(
283302
instance=instance,
284-
tags=json.dumps({'openqa_ttl': str(default_ttl)}),
303+
tags=json.dumps(tags),
285304
type="test_type"
286305
)
306+
307+
# required so that ignore is recomputed
308+
instance.set_alive()
309+
310+
# required to save the ignore field
311+
instance.save()
312+
287313
instances.append(instance)
288314

289315
# Run the function
@@ -302,6 +328,8 @@ def test_reset_stale_deleting(monkeypatch):
302328
print(f"deleting_since: {instance.deleting_since}")
303329
print(f"time_in_deleting: {time_in_deleting}s")
304330
print(f"threshold: {threshold}s")
331+
print(f"pcw_ignore expected: {case['pcw_ignore']}, actual: {instance.ignore}")
332+
print(f"should_reset: {case['should_reset']}")
305333

306334
if case['should_reset']:
307335
assert instance.state == StateChoice.ACTIVE, f"State should reset to ACTIVE for {case['desc']}"
@@ -348,6 +376,24 @@ def test_delete_instances_azure(monkeypatch):
348376
assert instance.save_called
349377
assert instance.state == StateChoice.DELETING
350378

379+
instance_with_state = InstanceMock(ProviderChoice.AZURE, StateChoice.ACTIVE)
380+
381+
delete_instance(instance_with_state)
382+
383+
assert instance_with_state.save_called
384+
assert instance_with_state.state == StateChoice.DELETING
385+
386+
387+
def test_delete_instances_azure_pcw_ignore(monkeypatch):
388+
monkeypatch.setattr(Azure, '__new__', lambda cls, namespace: AzureMock())
389+
390+
instance = InstanceMock(ProviderChoice.AZURE, StateChoice.ACTIVE, None, None, None, None, True)
391+
392+
delete_instance(instance)
393+
394+
assert not instance.save_called
395+
assert instance.state == StateChoice.ACTIVE
396+
351397

352398
def test_delete_instances_ec2(monkeypatch):
353399
monkeypatch.setattr(EC2, '__new__', lambda cls, namespace: EC2Mock())
@@ -359,6 +405,24 @@ def test_delete_instances_ec2(monkeypatch):
359405
assert instance.save_called
360406
assert instance.state == StateChoice.DELETING
361407

408+
instance_with_state = InstanceMock(ProviderChoice.EC2, StateChoice.ACTIVE)
409+
410+
delete_instance(instance_with_state)
411+
412+
assert instance_with_state.save_called
413+
assert instance_with_state.state == StateChoice.DELETING
414+
415+
416+
def test_delete_instances_ec2_pcw_ignore(monkeypatch):
417+
monkeypatch.setattr(EC2, '__new__', lambda cls, namespace: EC2Mock())
418+
419+
instance = InstanceMock(ProviderChoice.EC2, StateChoice.ACTIVE, None, None, None, None, True)
420+
421+
delete_instance(instance)
422+
423+
assert not instance.save_called
424+
assert instance.state == StateChoice.ACTIVE
425+
362426

363427
def test_delete_instances_gce(monkeypatch):
364428
monkeypatch.setattr(GCE, '__new__', lambda cls, namespace: GCEMock())
@@ -369,3 +433,21 @@ def test_delete_instances_gce(monkeypatch):
369433

370434
assert instance.save_called
371435
assert instance.state == StateChoice.DELETING
436+
437+
instance_with_state = InstanceMock(ProviderChoice.GCE, StateChoice.ACTIVE)
438+
439+
delete_instance(instance_with_state)
440+
441+
assert instance_with_state.save_called
442+
assert instance_with_state.state == StateChoice.DELETING
443+
444+
445+
def test_delete_instances_gce_pcw_ignore(monkeypatch):
446+
monkeypatch.setattr(GCE, '__new__', lambda cls, namespace: GCEMock())
447+
448+
instance = InstanceMock(ProviderChoice.GCE, StateChoice.ACTIVE, None, None, None, None, True)
449+
450+
delete_instance(instance)
451+
452+
assert not instance.save_called
453+
assert instance.state == StateChoice.ACTIVE

tests/test_models.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,36 @@ def test_get_tag(example_instance_data, example_cspinfo_data):
201201
assert cspinfo.get_tag('openqa_var_name') == "Test Job"
202202
assert cspinfo.get_tag('non_existent_tag') is None
203203
assert cspinfo.get_tag('non_existent_tag', default_value="N/A") == "N/A"
204+
205+
206+
@pytest.mark.django_db
207+
def test_ignore(example_instance_data, example_cspinfo_data):
208+
instance = Instance.objects.create(**example_instance_data)
209+
cspinfo = CspInfo.objects.create(instance=instance, **example_cspinfo_data)
210+
211+
# Test when pcw_ignore tag is not present
212+
cspinfo.tags = '{"openqa_var_server": "http://example.com", "openqa_var_job_id": "12345"}'
213+
cspinfo.save()
214+
instance.set_alive()
215+
assert instance.ignore is False
216+
217+
# Test when pcw_ignore tag is present and set to true but set_alive wasn't called on instance
218+
cspinfo.tags = '{"openqa_var_server": "http://example.com", "openqa_var_job_id": "12345", "pcw_ignore": "true"}'
219+
cspinfo.save()
220+
assert instance.ignore is False
221+
222+
# Test when pcw_ignore tag is present and set to true and set_alive was called on instance
223+
instance.set_alive()
224+
assert instance.ignore is True
225+
226+
# Test when pcw_ignore tag is present but set to false
227+
cspinfo.tags = '{"openqa_var_server": "http://example.com", "openqa_var_job_id": "12345", "pcw_ignore": "false"}'
228+
cspinfo.save()
229+
instance.set_alive()
230+
assert instance.ignore is True
231+
232+
# Test when tags are empty
233+
cspinfo.tags = '{}'
234+
cspinfo.save()
235+
instance.set_alive()
236+
assert instance.ignore is False

0 commit comments

Comments
 (0)