IntegerKeyValueSetting is used as a singleton-style key/value store: callers do get(key=...) and get_or_create(key=..., ...) and assume at most one row per key. The model, however, did not enforce uniqueness on the key field, so nothing prevented duplicate rows at the database level. This was a real race for _create_soa_rdataset in catalog_zone_manager, which calls get_or_create without select_for_update (unlike increment_soa_serial, which does take a lock). Two concurrent AXFR queries for a catalog zone could both pass the get_or_create existence check and both insert a row for key='catalog-zone-soa-serial'. Every subsequent IntegerKeyValueSetting.objects.get(key=...) call would then raise MultipleObjectsReturned, breaking catalog-zone SOA rendering and serial increments. Add unique=True to the key CharField and ship migration 0003 to enforce the invariant at the database level. Existing deployments are assumed to have clean state (no duplicate keys); if duplicates exist, the migration should be preceded by manual deduplication.
60 lines
1.3 KiB
Python
60 lines
1.3 KiB
Python
from django.db import models
|
|
|
|
# import netbox.models
|
|
import netbox_dns.models
|
|
|
|
|
|
class IntegerKeyValueSetting(models.Model):
|
|
_netbox_private = True
|
|
|
|
key = models.CharField(max_length=64, unique=True)
|
|
value = models.IntegerField()
|
|
|
|
def __str__(self):
|
|
return f"{self.key}: {str(self.value)}"
|
|
|
|
|
|
class CatalogZoneMemberIdentifier(models.Model):
|
|
_netbox_private = True
|
|
|
|
name = models.CharField(
|
|
max_length=26,
|
|
unique=True,
|
|
)
|
|
|
|
zone = models.OneToOneField(
|
|
to=netbox_dns.models.Zone,
|
|
on_delete=models.CASCADE,
|
|
related_name="catz_identifier",
|
|
)
|
|
|
|
class Meta:
|
|
ordering = ("name",)
|
|
|
|
def __str__(self):
|
|
return self.name
|
|
|
|
|
|
class SeenTransferClients(models.Model):
|
|
_netbox_private = True
|
|
|
|
source_ip = models.GenericIPAddressField()
|
|
last_seen = models.DateTimeField()
|
|
view = models.ForeignKey(
|
|
to=netbox_dns.models.View,
|
|
on_delete=models.PROTECT,
|
|
related_name="seen_transfer_clients",
|
|
)
|
|
|
|
def __str__(self):
|
|
return f"{self.source_ip} ({self.view.name})"
|
|
|
|
class Meta:
|
|
ordering = ("source_ip",)
|
|
constraints = [
|
|
models.UniqueConstraint(
|
|
fields=["source_ip", "view"],
|
|
name="unique_ip_view_seen_client",
|
|
),
|
|
]
|