Skip to content

Commit 2cf58d0

Browse files
committed
Modified the expiration mechanism to be inline with record active status
1 parent df78898 commit 2cf58d0

6 files changed

Lines changed: 109 additions & 94 deletions

File tree

netbox_dns/choices/record.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,8 +93,10 @@ class RecordStatusChoices(ChoiceSet):
9393

9494
STATUS_ACTIVE = "active"
9595
STATUS_INACTIVE = "inactive"
96+
STATUS_EXPIRED = "expired"
9697

9798
CHOICES = [
9899
(STATUS_ACTIVE, _("Active"), "blue"),
99100
(STATUS_INACTIVE, _("Inactive"), "red"),
101+
(STATUS_EXPIRED, _("Expired"), "grey"),
100102
]
Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
1-
from datetime import date, datetime
1+
from datetime import date
22

3-
from django.db.models import F
43
from django.utils.translation import gettext_lazy as _
54

65
from core.choices import JobIntervalChoices
76
from netbox.jobs import JobRunner, system_job
7+
from netbox_dns.choices import RecordStatusChoices
88
from netbox_dns.models import Record
99

1010

11-
@system_job(interval=JobIntervalChoices.INTERVAL_DAILY)
11+
@system_job(interval=JobIntervalChoices.INTERVAL_MINUTELY)
1212
class RecordExpirationJob(JobRunner):
1313
class Meta:
1414
name = "Handle expired records"
@@ -19,26 +19,18 @@ def run(self, *args, **kwargs):
1919
expired_records = Record.objects.filter(
2020
expiration_date__isnull=False,
2121
expiration_date__lte=date.today(),
22-
expiration_date__gte=F("last_updated"),
23-
)
22+
).exclude(status=RecordStatusChoices.STATUS_EXPIRED)
2423

2524
if not expired_records.exists():
2625
self.logger.info(_("No expired records found"))
2726
return
2827

29-
update_zones = set()
30-
3128
for record in expired_records:
3229
self.logger.info(
33-
_("Updating expired record {record}").format(record=record)
30+
_("Setting record {record} status to {status}").format(
31+
record=record, status=RecordStatusChoices.STATUS_EXPIRED
32+
)
3433
)
3534

36-
update_zones.add(record.zone)
37-
38-
record.last_updated = datetime.now()
39-
super(Record, record).save()
40-
41-
for zone in update_zones:
42-
self.logger.info(_("Updating SOA_SERIAL for zone {zone}").format(zone=zone))
43-
44-
zone.update_serial()
35+
record.status = RecordStatusChoices.STATUS_EXPIRED
36+
record.save()

netbox_dns/models/record.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -963,6 +963,9 @@ def clean(self, *args, new_zone=None, **kwargs):
963963
if not self.is_address_record:
964964
self.disable_ptr = False
965965

966+
if self.is_expired:
967+
self.status = RecordStatusChoices.STATUS_EXPIRED
968+
966969
if not self.is_active:
967970
return
968971

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
from datetime import date
2+
3+
from django.test import TestCase
4+
5+
from netbox_dns.choices import RecordStatusChoices, RecordTypeChoices
6+
from netbox_dns.models import NameServer, Record, Zone
7+
8+
9+
class RecordExpirationTestSet(TestCase):
10+
@classmethod
11+
def setUpTestData(cls):
12+
zone = Zone.objects.create(
13+
name="zone1.example.com",
14+
soa_mname=NameServer.objects.create(name="ns1.example.com"),
15+
soa_rname="hostmaster.example.com",
16+
)
17+
18+
cls.record_data = {
19+
"name": "name1",
20+
"zone": zone,
21+
"type": RecordTypeChoices.AAAA,
22+
"value": "2001:db8::1",
23+
}
24+
25+
def test_expired(self):
26+
record = Record.objects.create(
27+
expiration_date="2026-06-02",
28+
**self.record_data,
29+
)
30+
31+
self.assertTrue(record.is_expired)
32+
self.assertFalse(record.is_active)
33+
self.assertEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
34+
35+
def test_expiration_date_in_future(self):
36+
record = Record.objects.create(
37+
expiration_date="2126-01-07",
38+
**self.record_data,
39+
)
40+
41+
self.assertFalse(record.is_expired)
42+
self.assertTrue(record.is_active)
43+
self.assertNotEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
44+
45+
def test_no_expiration(self):
46+
record = Record.objects.create(
47+
**self.record_data,
48+
)
49+
50+
self.assertFalse(record.is_expired)
51+
self.assertTrue(record.is_active)
52+
self.assertNotEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
53+
54+
def test_remove_expiration(self):
55+
record = Record.objects.create(
56+
expiration_date="2026-06-02",
57+
**self.record_data,
58+
)
59+
60+
self.assertTrue(record.is_expired)
61+
self.assertFalse(record.is_active)
62+
self.assertEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
63+
64+
record.expiration_date = None
65+
record.save()
66+
67+
self.assertFalse(record.is_expired)
68+
self.assertFalse(record.is_active)
69+
self.assertEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
70+
71+
def test_postpone_expiration_expired(self):
72+
record = Record.objects.create(
73+
expiration_date="2026-06-02",
74+
**self.record_data,
75+
)
76+
77+
self.assertTrue(record.is_expired)
78+
self.assertFalse(record.is_active)
79+
self.assertEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)
80+
81+
record.expiration_date = date(2126, 1, 7)
82+
record.save()
83+
84+
self.assertFalse(record.is_expired)
85+
self.assertFalse(record.is_active)
86+
self.assertEqual(record.status, RecordStatusChoices.STATUS_EXPIRED)

netbox_dns/tests/record/test_filtersets.py

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99

1010
class RecordFilterSetTestCase(TestCase, ChangeLoggedFilterSetTests):
11-
queryset = Record.objects.all()
11+
queryset = Record.objects.exclude(type=RecordTypeChoices.SOA)
1212
filterset = RecordFilterSet
1313

1414
@classmethod
@@ -52,7 +52,6 @@ def setUpTestData(cls):
5252
type=RecordTypeChoices.AAAA,
5353
value="fe80::dead:beef",
5454
tenant=cls.tenants[0],
55-
expiration_date="2026-05-21",
5655
),
5756
Record(
5857
name="name2",
@@ -71,7 +70,6 @@ def setUpTestData(cls):
7170
value="fe80::dead:beef",
7271
tenant=cls.tenants[1],
7372
managed=True,
74-
expiration_date="2026-06-02",
7573
),
7674
Record(
7775
name="name5",
@@ -89,6 +87,7 @@ def setUpTestData(cls):
8987
type=RecordTypeChoices.AAAA,
9088
value="fe80::dead:beef",
9189
tenant=cls.tenants[0],
90+
expiration_date="2026-05-21",
9291
),
9392
Record(
9493
name="name2",
@@ -97,6 +96,7 @@ def setUpTestData(cls):
9796
type=RecordTypeChoices.A,
9897
value="10.0.0.42",
9998
tenant=cls.tenants[0],
99+
expiration_date="2026-06-02",
100100
),
101101
Record(
102102
name="name3",
@@ -128,9 +128,9 @@ def test_description(self):
128128

129129
def test_zone(self):
130130
params = {"zone": [self.zones[0]]}
131-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
132-
params = {"zone_id": [self.zones[1].pk]}
133131
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
132+
params = {"zone_id": [self.zones[1].pk]}
133+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
134134

135135
def test_fqdn(self):
136136
params = {
@@ -160,7 +160,7 @@ def test_status(self):
160160
params = {"status": [RecordStatusChoices.STATUS_INACTIVE]}
161161
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
162162
params = {"status": [RecordStatusChoices.STATUS_ACTIVE]}
163-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 8)
163+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
164164

165165
def test_value(self):
166166
params = {"value": ["10.0.0.42"]}
@@ -174,7 +174,7 @@ def test_managed(self):
174174
params = {"managed": False}
175175
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
176176
params = {"managed": True}
177-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
177+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
178178

179179
def test_tenant(self):
180180
params = {"tenant_id": [self.tenants[0].pk]}
@@ -197,11 +197,8 @@ def test_ip_address(self):
197197
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
198198

199199
def test_active(self):
200-
# *
201-
# Note: SOA records show up here as well!
202-
# -
203200
params = {"active": True}
204-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
201+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
205202
params = {"active": False}
206203
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 5)
207204

@@ -215,7 +212,7 @@ def test_expired(self):
215212
params = {"expired": True}
216213
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 3)
217214
params = {"expired": False}
218-
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 6)
215+
self.assertEqual(self.filterset(params, self.queryset).qs.count(), 4)
219216

220217
def test_ptr_record(self):
221218
Zone.objects.create(name="1.0.10.in-addr.arpa", **self.zone_data)

netbox_dns/tests/record/test_views.py

Lines changed: 0 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -459,68 +459,3 @@ def test_warning_mask_record_nonglue_aaaa_warn(self):
459459
response.content.decode(),
460460
"Record is masked by a child zone and may not be visible in DNS",
461461
)
462-
463-
def test_warning_record_expired(self):
464-
self.add_permissions("netbox_dns.view_record")
465-
466-
zone = Zone.objects.create(name="example.com", **self.zone_data)
467-
468-
record = Record.objects.create(
469-
name="name1",
470-
zone=zone,
471-
type=RecordTypeChoices.AAAA,
472-
value="2001:db8::1",
473-
expiration_date="2026-06-02",
474-
)
475-
476-
url = reverse("plugins:netbox_dns:record", kwargs={"pk": record.pk})
477-
478-
response = self.client.get(path=url)
479-
self.assertHttpStatus(response, status.HTTP_200_OK)
480-
self.assertRegex(
481-
response.content.decode(),
482-
"Record is expired",
483-
)
484-
485-
def test_warning_record_expired_future_ok(self):
486-
self.add_permissions("netbox_dns.view_record")
487-
488-
zone = Zone.objects.create(name="example.com", **self.zone_data)
489-
490-
record = Record.objects.create(
491-
name="name1",
492-
zone=zone,
493-
type=RecordTypeChoices.AAAA,
494-
value="2001:db8::1",
495-
expiration_date="2226-06-02",
496-
)
497-
498-
url = reverse("plugins:netbox_dns:record", kwargs={"pk": record.pk})
499-
500-
response = self.client.get(path=url)
501-
self.assertHttpStatus(response, status.HTTP_200_OK)
502-
self.assertNotRegex(
503-
response.content.decode(),
504-
"Record is expired",
505-
)
506-
507-
def test_warning_record_expired_no_expiration_ok(self):
508-
self.add_permissions("netbox_dns.view_record")
509-
510-
zone = Zone.objects.create(name="example.com", **self.zone_data)
511-
512-
record = Record.objects.create(
513-
name="name1",
514-
zone=zone,
515-
type=RecordTypeChoices.AAAA,
516-
value="2001:db8::1",
517-
)
518-
519-
url = reverse("plugins:netbox_dns:record", kwargs={"pk": record.pk})
520-
521-
response = self.client.get(path=url)
522-
self.assertHttpStatus(response, status.HTTP_200_OK)
523-
self.assertNotRegex(
524-
response.content.decode(),
525-
"Record is expired",
526-
)

0 commit comments

Comments
 (0)