Closed kemeris2000 closed 1 year ago
This is correct and a result of the way Django works internally.
The nameservers
relation is a ManyToManyField
, and thus it is not populated when the zone itself is created. So technically when the zone create event is triggered there are no nameservers yet, they get added in a separate step (which is why you see them in a zone update event).
This has some implications in other places in the code as well, but normally you don't see them - except when you handle events. There is, for instance, the following code in models.py
:
@receiver(m2m_changed, sender=Zone.nameservers.through)
def update_ns_records(**kwargs):
if kwargs.get("action") not in ["post_add", "post_remove"]:
return
zone = kwargs.get("instance")
zone.update_ns_records()
This is necessary to internally handle updates to the name servers of a zone, because the nameservers are not necessarily up to date when a zone is created or updated. To create the NS
records for a zone, we need to wait for the m2m_changed
event for the name servers field.
This is also explained in https://github.com/netbox-community/netbox/issues/6284.
I'm not sure if there's a simple way to handle the issue, but I will look into it.
I just had a closer look at it and created a webhook debug setup that triggers on zone creation and update. Then I created a zone and got in total three events: The create event and two update events.
[1] Sun, 14 May 2023 15:41:46 GMT 127.0.0.1 "POST / HTTP/1.1" 200 -
Host: localhost:9000
Accept-Encoding: identity
Content-Type: application/json
Content-Length: 1449
User-Agent: python-urllib3/1.26.15
{
"event": "created",
"timestamp": "2023-05-14 15:41:46.214018+00:00",
"model": "zone",
"username": "admin",
"request_id": "bb74c9b4-72ee-49f3-b797-57c0c3263cfc",
"data": {
"id": 61,
"url": "/api/plugins/netbox-dns/zones/61/",
"name": "zone31.example.com",
"view": {
"id": 2,
"url": "/api/plugins/netbox-dns/views/2/",
"display": "external",
"name": "external"
},
"display": "[external] zone31.example.com",
"nameservers": [],
"status": "active",
"description": "",
"tags": [],
"created": "2023-05-14T15:41:46.055321Z",
"last_updated": "2023-05-14T15:41:46.055479Z",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": {
"id": 1,
"url": "/api/plugins/netbox-dns/nameservers/1/",
"display": "ns1.example.com",
"name": "ns1.example.com"
},
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_serial_auto": true,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"custom_fields": {}
},
"snapshots": {
"prechange": null,
"postchange": {
"created": "2023-05-14T15:41:46.055Z",
"last_updated": "2023-05-14T15:41:46.055Z",
"view": 2,
"name": "zone31.example.com",
"status": "active",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": 1,
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"soa_serial_auto": true,
"description": "",
"arpa_network": null,
"nameservers": [],
"custom_fields": {},
"tags": []
}
}
}
Completed request #1
------------
[2] Sun, 14 May 2023 15:41:46 GMT 127.0.0.1 "POST / HTTP/1.1" 200 -
Host: localhost:9000
Accept-Encoding: identity
Content-Type: application/json
Content-Length: 1685
User-Agent: python-urllib3/1.26.15
{
"event": "updated",
"timestamp": "2023-05-14 15:41:46.222865+00:00",
"model": "zone",
"username": "admin",
"request_id": "bb74c9b4-72ee-49f3-b797-57c0c3263cfc",
"data": {
"id": 61,
"url": "/api/plugins/netbox-dns/zones/61/",
"name": "zone31.example.com",
"view": {
"id": 2,
"url": "/api/plugins/netbox-dns/views/2/",
"display": "external",
"name": "external"
},
"display": "[external] zone31.example.com",
"nameservers": [
{
"id": 2,
"url": "/api/plugins/netbox-dns/nameservers/2/",
"display": "ns2.example.com",
"name": "ns2.example.com"
},
{
"id": 3,
"url": "/api/plugins/netbox-dns/nameservers/3/",
"display": "ns3.example.com",
"name": "ns3.example.com"
}
],
"status": "active",
"description": "",
"tags": [],
"created": "2023-05-14T15:41:46.055321Z",
"last_updated": "2023-05-14T15:41:46.140365Z",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": {
"id": 1,
"url": "/api/plugins/netbox-dns/nameservers/1/",
"display": "ns1.example.com",
"name": "ns1.example.com"
},
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_serial_auto": true,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"custom_fields": {}
},
"snapshots": {
"prechange": null,
"postchange": {
"created": "2023-05-14T15:41:46.055Z",
"last_updated": "2023-05-14T15:41:46.140Z",
"view": 2,
"name": "zone31.example.com",
"status": "active",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": 1,
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"soa_serial_auto": true,
"description": "",
"arpa_network": null,
"nameservers": [
2,
3
],
"custom_fields": {},
"tags": []
}
}
}
Completed request #2
------------
[3] Sun, 14 May 2023 15:41:46 GMT 127.0.0.1 "POST / HTTP/1.1" 200 -
Host: localhost:9000
Accept-Encoding: identity
Content-Type: application/json
Content-Length: 1685
User-Agent: python-urllib3/1.26.15
{
"event": "updated",
"timestamp": "2023-05-14 15:41:46.223925+00:00",
"model": "zone",
"username": "admin",
"request_id": "bb74c9b4-72ee-49f3-b797-57c0c3263cfc",
"data": {
"id": 61,
"url": "/api/plugins/netbox-dns/zones/61/",
"name": "zone31.example.com",
"view": {
"id": 2,
"url": "/api/plugins/netbox-dns/views/2/",
"display": "external",
"name": "external"
},
"display": "[external] zone31.example.com",
"nameservers": [
{
"id": 2,
"url": "/api/plugins/netbox-dns/nameservers/2/",
"display": "ns2.example.com",
"name": "ns2.example.com"
},
{
"id": 3,
"url": "/api/plugins/netbox-dns/nameservers/3/",
"display": "ns3.example.com",
"name": "ns3.example.com"
}
],
"status": "active",
"description": "",
"tags": [],
"created": "2023-05-14T15:41:46.055321Z",
"last_updated": "2023-05-14T15:41:46.176236Z",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": {
"id": 1,
"url": "/api/plugins/netbox-dns/nameservers/1/",
"display": "ns1.example.com",
"name": "ns1.example.com"
},
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_serial_auto": true,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"custom_fields": {}
},
"snapshots": {
"prechange": null,
"postchange": {
"created": "2023-05-14T15:41:46.055Z",
"last_updated": "2023-05-14T15:41:46.176Z",
"view": 2,
"name": "zone31.example.com",
"status": "active",
"default_ttl": 86400,
"soa_ttl": 86400,
"soa_mname": 1,
"soa_rname": "hostmaster.example.com",
"soa_serial": 1684078907,
"soa_refresh": 172800,
"soa_retry": 7200,
"soa_expire": 2592000,
"soa_minimum": 3600,
"soa_serial_auto": true,
"description": "",
"arpa_network": null,
"nameservers": [
2,
3
],
"custom_fields": {},
"tags": []
}
}
}
Completed request #3
------------
Because of the behaviour mentioned above, there will always be a zone update event for a new zone after the zone create event if the zone actually has name servers.
I also checked the events sent when objects are created with tags. Now it gets interesting: When I create a zone with a tag (but no nameserver), I get one event. When I do the same with an IPAM IP Address, I get one create event with the tags included. So there is a way to queue m2m updates properly. Let me see if we can jump on that train ... :-)
I just had a closer look at it and created a webhook debug setup that triggers on zone creation and update. Then I created a zone and got in total three events: The create event and two update events.
Because of the behaviour mentioned above, there will always be a zone update event for a new zone after the zone create event if the zone actually has name servers.
I also checked the events sent when objects are created with tags. Now it gets interesting: When I create a zone with a tag (but no nameserver), I get one event. When I do the same with an IPAM IP Address, I get one create event with the tags included. So there is a way to queue m2m updates properly. Let me see if we can jump on that train ... :-)
I observe the following:
case: 1 nameserver 0 tags result: create, update
case: 2 nameserver 0 tags result: create, update, update
case: 1 nameserver 1 tags result: create, update, update
case: 2 nameserver 1 tags result: create, update, update, update
In last case events "create" and first "update" produce identical "last_updated" timestamp.
I guess at the moment of script execution on create event, database table "netbox_dns_zone_nameservers" is not populated yet?
At the moment I see only one dirty solution to store zone "id" and "last_updated" values locally on disk on create event, and send zone to BIND on last update event if "id" matches, difference of "last_updated" is less 1 second or so and nameservers exist.
I delved a bit deeper into this yesterday, and I found some information that I currently can't piece together properly. Concentrating on tags first, because that is something that applies to all models, I found that for some reason tags are included in the create event for View
and NameServer
, but not for Record
and Zone
.
That indicates that some of NetBox DNS' internal processing interferes with the way NetBox works around the m2m_changed
problem. I'll debug that further as soon as I can, this is really interesting.
At the moment I see only one dirty solution to store zone "id" and "last_updated" values locally on disk on create event, and send zone to BIND on last update event if "id" matches, difference of "last_updated" is less 1 second or so and nameservers exist.
This is very similar to the way NetBox implements the workaround ... they queue Webhooks and flush them once everything is done.
Considering my earlier advice not to flush every change to the name server database it's not that dirty at all, though I would really like to find the root cause of the incomplete create events.
I will wait for results of your attempt.
Hi peteeckel, any progress on this issue?
Hi @kemeris2000, there are three ways to deal with this:
Have a nice weekend!
Hi @kemeris2000, it does not look like this can be solved easily within NetBox DNS or even NetBox.
I ran some tests with custom signal listeners on post_save
and m2m_changed
events for Zone
and Zone.nameservers.through
, and the serialize_object()
method on the zone object does not seem to have the nameservers
array populated in either.
That means that the code within NetBox that triggers the Webhooks can't see them either, which explains why the Webhook data does not contain data for nameservers
as well.
This might be an issue within NetBox (the serialize_object() method could be flawed in some way) or within Django (unlikely, but possible). I'm not sure what triggers the issue, but clearly something is not working as expected.
Thank you peteeckel for you time and effort! I guess it's not worth to investigate further. At the moment I propose simple workaround for everyone else who is interested: do not trigger any actions on create event and handle zone creation and modifications on update event. Netbox webhook condition filters out unneeded update events:
{
"and": [
{
"attr": "nameservers",
"negate": true,
"value": null
}
]
}
nameserver array is empty in netbox {{ data }} context data on zone create event. On zone update event for example array is filled.