Blame libnmstate/dns.py

Packit Service 0535c1
#
Packit Service 0535c1
# Copyright (c) 2020 Red Hat, Inc.
Packit Service 0535c1
#
Packit Service 0535c1
# This file is part of nmstate
Packit Service 0535c1
#
Packit Service 0535c1
# This program is free software: you can redistribute it and/or modify
Packit Service 0535c1
# it under the terms of the GNU Lesser General Public License as published by
Packit Service 0535c1
# the Free Software Foundation, either version 2.1 of the License, or
Packit Service 0535c1
# (at your option) any later version.
Packit Service 0535c1
#
Packit Service 0535c1
# This program is distributed in the hope that it will be useful,
Packit Service 0535c1
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit Service 0535c1
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit Service 0535c1
# GNU Lesser General Public License for more details.
Packit Service 0535c1
#
Packit Service 0535c1
# You should have received a copy of the GNU Lesser General Public License
Packit Service 0535c1
# along with this program. If not, see <https://www.gnu.org/licenses/>.
Packit Service 0535c1
#
Packit Service 0535c1
Packit Service 0535c1
from copy import deepcopy
Packit Service 0535c1
Packit Service 0535c1
from libnmstate.error import NmstateValueError
Packit Service 0535c1
from libnmstate.error import NmstateVerificationError
Packit Service 0535c1
from libnmstate.error import NmstateNotImplementedError
Packit Service 0535c1
from libnmstate.iplib import is_ipv6_address
Packit Service 0535c1
from libnmstate.prettystate import format_desired_current_state_diff
Packit Service 0535c1
from libnmstate.schema import DNS
Packit Service 0535c1
from libnmstate.schema import Interface
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
class DnsState:
Packit Service 0535c1
    PRIORITY_METADATA = "_priority"
Packit Service 0535c1
Packit Service 0535c1
    def __init__(self, des_dns_state, cur_dns_state):
Packit Service 0535c1
        self._config_changed = False
Packit Service 0535c1
        if des_dns_state is None or des_dns_state.get(DNS.CONFIG) is None:
Packit Service 0535c1
            # Use current config if DNS.KEY not defined or DNS.CONFIG not
Packit Service 0535c1
            # defined.
Packit Service 0535c1
            self._dns_state = cur_dns_state or {}
Packit Service 0535c1
        else:
Packit Service 0535c1
            self._dns_state = des_dns_state
Packit Service 0535c1
            self._validate()
Packit Service 0535c1
            self._config_changed = _is_dns_config_changed(
Packit Service 0535c1
                des_dns_state, cur_dns_state
Packit Service 0535c1
            )
Packit Service 0535c1
        self._cur_dns_state = deepcopy(cur_dns_state) if cur_dns_state else {}
Packit Service 0535c1
Packit Service 0535c1
    @property
Packit Service 0535c1
    def current_config(self):
Packit Service 0535c1
        return _get_config(self._cur_dns_state)
Packit Service 0535c1
Packit Service 0535c1
    @property
Packit Service 0535c1
    def config(self):
Packit Service 0535c1
        return _get_config(self._dns_state)
Packit Service 0535c1
Packit Service 0535c1
    @property
Packit Service 0535c1
    def _config_servers(self):
Packit Service 0535c1
        return _get_config_servers(self._dns_state)
Packit Service 0535c1
Packit Service 0535c1
    @property
Packit Service 0535c1
    def _config_searches(self):
Packit Service 0535c1
        return _get_config_searches(self._dns_state)
Packit Service 0535c1
Packit Service 0535c1
    def gen_metadata(self, ifaces, route_state):
Packit Service 0535c1
        """
Packit Service 0535c1
        Return DNS configure targeting to store as metadata of interface.
Packit Service 0535c1
        Data structure returned is:
Packit Service 0535c1
            {
Packit Service 0535c1
                iface_name: {
Packit Service 0535c1
                    Interface.IPV4: {
Packit Service 0535c1
                        DNS.SERVER: dns_servers,
Packit Service 0535c1
                        DNS.SEARCH: dns_searches,
Packit Service 0535c1
                    },
Packit Service 0535c1
                    Interface.IPV6: {
Packit Service 0535c1
                        DNS.SERVER: dns_servers,
Packit Service 0535c1
                        DNS.SEARCH: dns_searches,
Packit Service 0535c1
                    },
Packit Service 0535c1
                }
Packit Service 0535c1
            }
Packit Service 0535c1
        """
Packit Service 0535c1
        iface_metadata = {}
Packit Service 0535c1
        if not self._config_servers and not self._config_searches:
Packit Service 0535c1
            return iface_metadata
Packit Service 0535c1
        ipv4_iface, ipv6_iface = self._find_ifaces_for_name_servers(
Packit Service 0535c1
            ifaces, route_state
Packit Service 0535c1
        )
Packit Service 0535c1
        if ipv4_iface == ipv6_iface:
Packit Service 0535c1
            iface_metadata = {
Packit Service 0535c1
                ipv4_iface: {
Packit Service 0535c1
                    Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []},
Packit Service 0535c1
                    Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []},
Packit Service 0535c1
                },
Packit Service 0535c1
            }
Packit Service 0535c1
        else:
Packit Service 0535c1
            if ipv4_iface:
Packit Service 0535c1
                iface_metadata[ipv4_iface] = {
Packit Service 0535c1
                    Interface.IPV4: {DNS.SERVER: [], DNS.SEARCH: []},
Packit Service 0535c1
                }
Packit Service 0535c1
            if ipv6_iface:
Packit Service 0535c1
                iface_metadata[ipv6_iface] = {
Packit Service 0535c1
                    Interface.IPV6: {DNS.SERVER: [], DNS.SEARCH: []},
Packit Service 0535c1
                }
Packit Service 0535c1
        index = 0
Packit Service 0535c1
        searches_saved = False
Packit Service 0535c1
        for server in self._config_servers:
Packit Service 0535c1
            iface_name = None
Packit Service 0535c1
            if is_ipv6_address(server):
Packit Service 0535c1
                iface_name = ipv6_iface
Packit Service 0535c1
                family = Interface.IPV6
Packit Service 0535c1
            else:
Packit Service 0535c1
                iface_name = ipv4_iface
Packit Service 0535c1
                family = Interface.IPV4
Packit Service 0535c1
            if not iface_name:
Packit Service 0535c1
                raise NmstateValueError(
Packit Service 0535c1
                    "Failed to find suitable interface for saving DNS "
Packit Service 0535c1
                    "name servers: %s" % server
Packit Service 0535c1
                )
Packit Service 0535c1
            iface_dns_metada = iface_metadata[iface_name][family]
Packit Service 0535c1
            iface_dns_metada[DNS.SERVER].append(server)
Packit Service 0535c1
            iface_dns_metada.setdefault(DnsState.PRIORITY_METADATA, index)
Packit Service 0535c1
            if not searches_saved:
Packit Service 0535c1
                iface_dns_metada[DNS.SEARCH] = self._config_searches
Packit Service 0535c1
            searches_saved = True
Packit Service 0535c1
            index += 1
Packit Service 0535c1
        return iface_metadata
Packit Service 0535c1
Packit Service 0535c1
    def _find_ifaces_for_name_servers(self, ifaces, route_state):
Packit Service 0535c1
        """
Packit Service 0535c1
        Find interface to store the DNS configurations in the order of:
Packit Service 0535c1
            * Any interface with static gateway
Packit Service 0535c1
            * Any interface configured as dynamic IP with 'auto-dns:False'
Packit Service 0535c1
        Return tuple: (ipv4_iface, ipv6_iface)
Packit Service 0535c1
        """
Packit Service 0535c1
        ipv4_iface, ipv6_iface = self._find_ifaces_with_static_gateways(
Packit Service 0535c1
            route_state
Packit Service 0535c1
        )
Packit Service 0535c1
        if not (ipv4_iface and ipv6_iface):
Packit Service 0535c1
            (
Packit Service 0535c1
                auto_ipv4_iface,
Packit Service 0535c1
                auto_ipv6_iface,
Packit Service 0535c1
            ) = self._find_ifaces_with_auto_dns_false(ifaces)
Packit Service 0535c1
            if not ipv4_iface and auto_ipv4_iface:
Packit Service 0535c1
                ipv4_iface = auto_ipv4_iface
Packit Service 0535c1
            if not ipv6_iface and auto_ipv6_iface:
Packit Service 0535c1
                ipv6_iface = auto_ipv6_iface
Packit Service 0535c1
Packit Service 0535c1
        return ipv4_iface, ipv6_iface
Packit Service 0535c1
Packit Service 0535c1
    def _find_ifaces_with_static_gateways(self, route_state):
Packit Service 0535c1
        """
Packit Service 0535c1
        Return tuple of interfaces with IPv4 and IPv6 static gateways.
Packit Service 0535c1
        """
Packit Service 0535c1
        ipv4_iface = None
Packit Service 0535c1
        ipv6_iface = None
Packit Service 0535c1
        for iface_name, route_set in route_state.config_iface_routes.items():
Packit Service 0535c1
            for route in route_set:
Packit Service 0535c1
                if ipv4_iface and ipv6_iface:
Packit Service 0535c1
                    return (ipv4_iface, ipv6_iface)
Packit Service 0535c1
                if route.is_gateway:
Packit Service 0535c1
                    if route.is_ipv6:
Packit Service 0535c1
                        ipv6_iface = iface_name
Packit Service 0535c1
                    else:
Packit Service 0535c1
                        ipv4_iface = iface_name
Packit Service 0535c1
        return (ipv4_iface, ipv6_iface)
Packit Service 0535c1
Packit Service 0535c1
    def _find_ifaces_with_auto_dns_false(self, ifaces):
Packit Service 0535c1
        ipv4_iface = None
Packit Service 0535c1
        ipv6_iface = None
Packit Service 0535c1
        for iface in ifaces.values():
Packit Service 0535c1
            if ipv4_iface and ipv6_iface:
Packit Service 0535c1
                return (ipv4_iface, ipv6_iface)
Packit Service 0535c1
            for family in (Interface.IPV4, Interface.IPV6):
Packit Service 0535c1
                ip_state = iface.ip_state(family)
Packit Service 0535c1
                if ip_state.is_dynamic and (not ip_state.auto_dns):
Packit Service 0535c1
                    if family == Interface.IPV4:
Packit Service 0535c1
                        ipv4_iface = iface.name
Packit Service 0535c1
                    else:
Packit Service 0535c1
                        ipv6_iface = iface.name
Packit Service 0535c1
Packit Service 0535c1
        return (ipv4_iface, ipv6_iface)
Packit Service 0535c1
Packit Service 0535c1
    def verify(self, cur_dns_state):
Packit Service 0535c1
        cur_dns = DnsState(des_dns_state=None, cur_dns_state=cur_dns_state,)
Packit Service 0535c1
        if self.config.get(DNS.SERVER, []) != cur_dns.config.get(
Packit Service 0535c1
            DNS.SERVER, []
Packit Service 0535c1
        ) or self.config.get(DNS.SEARCH, []) != cur_dns.config.get(
Packit Service 0535c1
            DNS.SEARCH, []
Packit Service 0535c1
        ):
Packit Service 0535c1
            raise NmstateVerificationError(
Packit Service 0535c1
                format_desired_current_state_diff(
Packit Service 0535c1
                    {DNS.KEY: self.config}, {DNS.KEY: cur_dns.config},
Packit Service 0535c1
                )
Packit Service 0535c1
            )
Packit Service 0535c1
Packit Service 0535c1
    def _validate(self):
Packit Service 0535c1
        if (
Packit Service 0535c1
            len(self._config_servers) > 2
Packit Service 0535c1
            and any(is_ipv6_address(n) for n in self._config_servers)
Packit Service 0535c1
            and any(not is_ipv6_address(n) for n in self._config_servers)
Packit Service 0535c1
        ):
Packit Service 0535c1
            raise NmstateNotImplementedError(
Packit Service 0535c1
                "Three or more nameservers are only supported when using "
Packit Service 0535c1
                "either IPv4 or IPv6 nameservers but not both."
Packit Service 0535c1
            )
Packit Service 0535c1
Packit Service 0535c1
    @property
Packit Service 0535c1
    def config_changed(self):
Packit Service 0535c1
        return self._config_changed
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_config(state):
Packit Service 0535c1
    conf = state.get(DNS.CONFIG, {})
Packit Service 0535c1
    if not conf:
Packit Service 0535c1
        conf = {DNS.SERVER: [], DNS.SEARCH: []}
Packit Service 0535c1
    return conf
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_config_servers(state):
Packit Service 0535c1
    return _get_config(state).get(DNS.SERVER, [])
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_config_searches(state):
Packit Service 0535c1
    return _get_config(state).get(DNS.SEARCH, [])
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _is_dns_config_changed(des_dns_state, cur_dns_state):
Packit Service 0535c1
    return _get_config_servers(des_dns_state) != _get_config_servers(
Packit Service 0535c1
        cur_dns_state
Packit Service 0535c1
    ) or _get_config_searches(des_dns_state) != _get_config_searches(
Packit Service 0535c1
        cur_dns_state
Packit Service 0535c1
    )