From: Michael Tremer Date: Thu, 13 Feb 2025 20:37:20 +0000 (+0000) Subject: mirrors: Refactor the selection process X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=d50afd892e27a137e283d87d03a9cba4babc461e;p=pbs.git mirrors: Refactor the selection process Signed-off-by: Michael Tremer --- diff --git a/src/buildservice/mirrors.py b/src/buildservice/mirrors.py index 692d9b46..250bdc82 100644 --- a/src/buildservice/mirrors.py +++ b/src/buildservice/mirrors.py @@ -3,6 +3,7 @@ import asyncio import datetime import functools +import ipaddress import logging import os.path import random @@ -83,20 +84,20 @@ class Mirrors(base.Object): Returns all mirrors in random order with preferred mirrors first """ # Lookup the client - network = self.location.lookup(address) + network = self.location.lookup("%s" % address) def __sort(mirror): # Generate some random value for each mirror r = random.random() - # Put preferred mirrors first - if network and mirror.is_preferred_for_network(network): - r += 1 + # Add the preference score + if network: + r += mirror.is_preferred_for_network(network) return r # Fetch all mirrors and shuffle them, but put preferred mirrors first - return sorted([mirror async for mirror in self], key=__sort) + return sorted([mirror async for mirror in self], key=__sort, reverse=True) @functools.cached_property def location(self): @@ -263,6 +264,15 @@ class Mirror(database.Base, database.BackendMixin, database.SoftDeleteMixin): return False + # Addresses + + @property + def addresses(self): + """ + All addresses of the mirror, regardless of family + """ + return self.addresses_ipv6 + self.addresses_ipv4 + async def _update_country_code_and_asn(self): """ Updates the country code of this mirror @@ -292,7 +302,7 @@ class Mirror(database.Base, database.BackendMixin, database.SoftDeleteMixin): ] # Lookup the country code and ASN - for address in self.addresses_ipv6 + self.addresses_ipv4: + for address in self.addresses: network = self.backend.mirrors.location.lookup(address) # Try the next IP address if we didn't find any data @@ -309,24 +319,63 @@ class Mirror(database.Base, database.BackendMixin, database.SoftDeleteMixin): break def is_preferred_for_address(self, address): + """ + Check if this mirror is preferred for this address + """ # Lookup the client network = self.backend.mirrors.location.lookup("%s" % address) + # Check for the entire network return self.is_preferred_for_network(network) def is_preferred_for_network(self, network): """ - Returns True if this mirror is preferred for clients on the given network. + Returns a score of how much the mirror is preferred for this network. """ + first_address = ipaddress.ip_address(network.first_address) + last_address = ipaddress.ip_address(network.last_address) + + # Check if the mirror is on the same network + for address in self.addresses: + # Skip incompatible families + if isinstance(address, ipaddress.IPv6Address): + if not network.family == socket.AF_INET6: + continue + elif isinstance(address, ipaddress.IPv4Address): + if not network.family == socket.AF_INET: + continue + + # Check if the address is within the network + if first_address <= address <= last_address: + return 4 + # If the AS matches, we will prefer this if self.asn and self.asn == network.asn: - return True + return 3 # If the mirror and client are in the same country, we prefer this if self.country_code and self.country_code == network.country_code: - return True + return 2 - return False + # Check if we are on the same continent + if self._continent_match(self.country_code, network.country_code): + return 1 + + return 0 + + def _continent_match(self, cc1, cc2): + """ + Checks if the two given country codes are on the same continent + """ + country1 = self.backend.mirrors.location.get_country(cc1) + country2 = self.backend.mirrors.location.get_country(cc2) + + # If we are missing either country, we don't know + if not country1 or not country2: + return False + + # Return True if both countries are on the same continent + return country1.continent_code == country2.continent_code # Check