Skip to content

Commit 9da35a4

Browse files
committed
Calculate supernets separately per address family
Supernet is calculated only for intern_ip and expected to yield a single value for each Server. Most of our servers and loadbalancers are dual-stack, that is they have IPv4 and IPv6 addresses. The algorithm works fine for calculating route_network, as this is always a single object with IPv4 and IPv6 addresses. But it fails for provider_network: LB Pools might belong to a different provider_network for IPv4 and IPv6. For example IPv4 might be leased from datacenter, while IPv6 is from our own provider-independent prefix. Provide option to obey target address family when calculating a supernet. This allows for adding multiple supernet attributes e.g. provider_network_ipv4 and provider_network_ipv6.
1 parent d69e1bc commit 9da35a4

File tree

2 files changed

+60
-29
lines changed

2 files changed

+60
-29
lines changed

serveradmin/serverdb/models.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -517,12 +517,6 @@ class Meta:
517517
def __str__(self):
518518
return self.hostname
519519

520-
def get_supernet(self, servertype):
521-
return Server.objects.get(
522-
servertype=servertype,
523-
intern_ip__net_contains_or_equals=self.intern_ip,
524-
)
525-
526520
def clean(self):
527521
super(Server, self).clean()
528522

serveradmin/serverdb/query_materializer.py

Lines changed: 60 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,10 @@
88
# a good idea to refactor this by using more top level functions instead of
99
# object methods.
1010

11+
import itertools
12+
1113
from ipaddress import IPv4Address, IPv6Address
14+
from django.db.models import OuterRef, Subquery, AutoField
1215

1316
from adminapi.dataset import DatasetObject
1417
from serveradmin.serverdb.models import (
@@ -18,6 +21,7 @@
1821
Server,
1922
ServerAttribute,
2023
ServerRelationAttribute,
24+
ServerInetAttribute,
2125
)
2226

2327

@@ -192,30 +196,63 @@ def _add_domain_attribute(self, attribute, servers):
192196
server.hostname.split(".", 1)[-1]
193197
)
194198

195-
def _add_supernet_attribute(self, attribute, servers):
196-
"""Merge-join networks to the servers
199+
def _add_supernet_attribute(self, attribute: Attribute, servers_in):
200+
if attribute.inet_address_family:
201+
self._add_supernet_attribute_af(attribute, servers_in)
202+
else:
203+
self._add_supernet_attribute_intern_ip(attribute, servers_in)
204+
205+
def _add_supernet_attribute_af(self, attribute, servers_in):
206+
supernet_id = ServerInetAttribute.objects.filter(
207+
value__net_contains=OuterRef("value"),
208+
server__servertype_id=attribute.target_servertype_id,
209+
attribute__inet_address_family=attribute.inet_address_family,
210+
).values("server__server_id")
211+
212+
attrs = ServerInetAttribute.objects.filter(
213+
server_id__in=[s.server_id for s in servers_in],
214+
attribute__inet_address_family=attribute.inet_address_family,
215+
).annotate(
216+
supernet_id=Subquery(supernet_id, output_field=AutoField()),
217+
)
197218

198-
This function takes advantage of networks in the same servertype not
199-
overlapping with each other.
200-
"""
201-
target = None
202-
for source in sorted(servers, key=lambda s: _sort_key(s.intern_ip)):
203-
# Check the previous target
204-
if target is not None:
205-
network = target.intern_ip.network
206-
if network.version != source.intern_ip.version:
207-
target = None
208-
elif network.broadcast_address < source.intern_ip.ip:
209-
target = None
210-
elif source.intern_ip not in network:
211-
continue
212-
# Check for a new target
213-
if target is None and source.intern_ip:
214-
try:
215-
target = source.get_supernet(attribute.target_servertype)
216-
except Server.DoesNotExist:
217-
continue
218-
self._server_attributes[source][attribute] = target
219+
supernets = Server.objects.filter(server_id__in=attrs.values("supernet_id"))
220+
supernets_by_id = {s.server_id: s for s in supernets}
221+
222+
for a in attrs:
223+
if a.supernet_id in supernets_by_id:
224+
self._server_attributes[a.server][attribute] = supernets_by_id[
225+
a.supernet_id
226+
]
227+
228+
def _add_supernet_attribute_intern_ip(self, attribute, servers):
229+
servers_in_1, servers_in_2 = itertools.tee(servers)
230+
231+
supernet_id = Server.objects.filter(
232+
intern_ip__net_contains=OuterRef("intern_ip"),
233+
servertype_id=attribute.target_servertype_id,
234+
).values("server_id")
235+
236+
servers = Server.objects.filter(
237+
server_id__in=[s.server_id for s in servers_in_1],
238+
).annotate(
239+
supernet_id=Subquery(supernet_id, output_field=AutoField()),
240+
)
241+
242+
supernets = Server.objects.filter(server_id__in=servers.values("supernet_id"))
243+
supernets_by_id = {s.server_id: s for s in supernets}
244+
245+
supernets_by_server_hostname = {
246+
s.hostname: supernets_by_id[s.supernet_id]
247+
for s in servers
248+
if s.supernet_id in supernets_by_id
249+
}
250+
251+
for s in servers_in_2:
252+
if s.hostname in supernets_by_server_hostname:
253+
self._server_attributes[s][attribute] = supernets_by_server_hostname[
254+
s.hostname
255+
]
219256

220257
def _add_related_attribute(self, attribute, servertype_attribute, servers_by_type):
221258
related_via_attribute = servertype_attribute.related_via_attribute

0 commit comments

Comments
 (0)