Mini Shell

Direktori : /usr/lib64/python3.6/site-packages/pyanaconda/modules/network/
Upload File :
Current File : //usr/lib64/python3.6/site-packages/pyanaconda/modules/network/initialization.py

#
# Copyright (C) 2019 Red Hat, Inc.
#
# This copyrighted material is made available to anyone wishing to use,
# modify, copy, or redistribute it subject to the terms and conditions of
# the GNU General Public License v.2, or (at your option) any later version.
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY expressed or implied, including the implied warranties of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
# Public License for more details.  You should have received a copy of the
# GNU General Public License along with this program; if not, write to the
# Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
# 02110-1301, USA.  Any Red Hat trademarks that are incorporated in the
# source code or documentation are not subject to the GNU General Public
# License and may only be used or replicated with the express permission of
# Red Hat, Inc.
#
import copy
import re

from pyanaconda.core.regexes import NM_MAC_INITRAMFS_CONNECTION
from pyanaconda.modules.common.task import Task
from pyanaconda.anaconda_loggers import get_module_logger
from pyanaconda.modules.network.network_interface import NetworkInitializationTaskInterface
from pyanaconda.modules.network.nm_client import get_device_name_from_network_data, \
    ensure_active_connection_for_device, update_connection_from_ksdata, \
    add_connection_from_ksdata, bound_hwaddr_of_device, get_connections_available_for_iface, \
    update_connection_values, commit_changes_with_autoconnection_blocked, is_ibft_connection, \
    clone_connection_sync, nm_client_in_thread
from pyanaconda.modules.network.ifcfg import get_ifcfg_file_of_device, find_ifcfg_uuid_of_device, \
    get_master_slaves_from_ifcfgs
from pyanaconda.modules.network.device_configuration import supported_wired_device_types, \
    virtual_device_types
from pyanaconda.modules.network.utils import guard_by_system_configuration

log = get_module_logger(__name__)

import gi
gi.require_version("NM", "1.0")
from gi.repository import NM


class ApplyKickstartTask(Task):
    """Task for application of kickstart network configuration."""

    def __init__(self, network_data, supported_devices, bootif, ifname_option_values):
        """Create a new task.

        :param network_data: kickstart network data to be applied
        :type: list(NetworkData)
        :param supported_devices: list of names of supported network devices
        :type supported_devices: list(str)
        :param bootif: MAC addres of device to be used for --device=bootif specification
        :type bootif: str
        :param ifname_option_values: list of ifname boot option values
        :type ifname_option_values: list(str)
        """
        super().__init__()
        self._network_data = network_data
        self._supported_devices = supported_devices
        self._bootif = bootif
        self._ifname_option_values = ifname_option_values

    @property
    def name(self):
        return "Apply kickstart"

    def for_publication(self):
        """Return a DBus representation."""
        return NetworkInitializationTaskInterface(self)

    @guard_by_system_configuration(return_value=[])
    def run(self):
        """Run the kickstart application.

        :returns: names of devices to which kickstart was applied
        :rtype: list(str)
        """
        with nm_client_in_thread() as nm_client:
            return self._run(nm_client)

    def _run(self, nm_client):
        applied_devices = []

        if not self._network_data:
            log.debug("%s: No kickstart data.", self.name)
            return applied_devices

        if not nm_client:
            log.debug("%s: No NetworkManager available.", self.name)
            return applied_devices

        for network_data in self._network_data:
            # Wireless is not supported
            if network_data.essid:
                log.info("%s: Wireless devices configuration is not supported.", self.name)
                continue

            device_name = get_device_name_from_network_data(nm_client,
                                                            network_data,
                                                            self._supported_devices,
                                                            self._bootif)
            if not device_name:
                log.warning("%s: --device %s not found", self.name, network_data.device)
                continue

            ifcfg_file = get_ifcfg_file_of_device(nm_client, device_name)
            if ifcfg_file and ifcfg_file.is_from_kickstart:
                if network_data.activate:
                    if ensure_active_connection_for_device(nm_client, ifcfg_file.uuid,
                                                           device_name):
                        applied_devices.append(device_name)
                continue

            # If there is no kickstart ifcfg from initramfs the command was added
            # in %pre section after switch root, so apply it now
            applied_devices.append(device_name)

            connection = None
            if ifcfg_file:
                connection = nm_client.get_connection_by_uuid(ifcfg_file.uuid)
            if not connection:
                connection = self._find_initramfs_connection_of_iface(nm_client, device_name)

            if connection:
                # if the device was already configured in initramfs update the settings
                log.debug("%s: pre kickstart - updating connection %s of device %s",
                          self.name, connection.get_uuid(), device_name)
                update_connection_from_ksdata(nm_client, connection, network_data,
                                              device_name=device_name)
                if network_data.activate:
                    device = nm_client.get_device_by_iface(device_name)
                    nm_client.activate_connection_async(connection, device, None, None)
                    log.debug("%s: pre kickstart - activating connection %s with device %s",
                              self.name, connection.get_uuid(), device_name)
            else:
                log.debug("%s: pre kickstart - adding connection for %s", self.name, device_name)
                add_connection_from_ksdata(nm_client, network_data, device_name,
                                           activate=network_data.activate,
                                           ifname_option_values=self._ifname_option_values)

        return applied_devices

    def _find_initramfs_connection_of_iface(self, nm_client, iface):
        device = nm_client.get_device_by_iface(iface)
        if device:
            cons = device.get_available_connections()
            for con in cons:
                if con.get_interface_name() == iface and con.get_id() == iface:
                    return con
        return None


class ConsolidateInitramfsConnectionsTask(Task):
    """Task for consolidation of initramfs connections."""

    @property
    def name(self):
        return "Consolidate initramfs connections"

    def for_publication(self):
        """Return a DBus representation."""
        return NetworkInitializationTaskInterface(self)

    @guard_by_system_configuration(return_value=[])
    def run(self):
        """Run the connections consolidation.

        :returns: names of devices of which the connections have been consolidated
        :rtype: list(str)
        """
        with nm_client_in_thread() as nm_client:
            return self._run(nm_client)

    def _run(self, nm_client):
        consolidated_devices = []

        if not nm_client:
            log.debug("%s: No NetworkManager available.", self.name)
            return consolidated_devices

        for device in nm_client.get_devices():
            cons = device.get_available_connections()
            number_of_connections = len(cons)
            iface = device.get_iface()

            if number_of_connections < 2:
                continue

            # Ignore devices which are slaves
            if any(con.get_setting_connection().get_master() for con in cons):
                log.debug("%s: %d for %s - it is OK, device is a slave",
                          self.name, number_of_connections, iface)
                continue

            # Ignore devices with iBFT connections
            if self._device_has_ibft_connection(device):
                log.debug("%s: %d for %s - it is OK, device was configured from iBFT",
                          self.name, number_of_connections, iface)
                continue

            ifcfg_file = get_ifcfg_file_of_device(nm_client, iface)
            if not ifcfg_file:
                log.debug("%s: %d for %s - no ifcfg file found",
                          self.name, number_of_connections, iface)
                con_for_iface = self._select_persistent_connection_for_iface(iface, cons)
                if not con_for_iface:
                    log.debug("%s: %d for %s - no suitable connection for the interface found",
                              self.name, number_of_connections, iface)
                    continue
                else:
                    con_uuid = con_for_iface.get_uuid()
            else:
                # Handle only ifcfgs created from boot options in initramfs
                # (Kickstart based ifcfgs are handled when applying kickstart)
                if ifcfg_file.is_from_kickstart:
                    continue
                con_uuid = ifcfg_file.uuid

            log.debug("%s: %d for %s - ensure active ifcfg connection",
                      self.name, number_of_connections, iface)

            ensure_active_connection_for_device(
                nm_client,
                con_uuid,
                iface,
                only_replace=True
            )

            consolidated_devices.append(iface)

        return consolidated_devices

    def _select_persistent_connection_for_iface(self, iface, cons):
        """Select the connection suitable to store configuration for the interface."""
        for con in cons:
            if con.get_interface_name() == iface:
                return con
        return None

    def _device_has_ibft_connection(self, device):
        ac = device.get_active_connection()
        if ac:
            con = ac.get_connection()
            if is_ibft_connection(con):
                return True
        return False


class SetRealOnbootValuesFromKickstartTask(Task):
    """Task for setting of real ONBOOT values from kickstart."""

    def __init__(self, network_data, supported_devices, bootif, ifname_option_values):
        """Create a new task.

        :param network_data: kickstart network data to be applied
        :type: list(NetworkData)
        :param supported_devices: list of names of supported network devices
        :type supported_devices: list(str)
        :param bootif: MAC addres of device to be used for --device=bootif specification
        :type bootif: str
        :param ifname_option_values: list of ifname boot option values
        :type ifname_option_values: list(str)
        """
        super().__init__()
        self._network_data = network_data
        self._supported_devices = supported_devices
        self._bootif = bootif
        self._ifname_option_values = ifname_option_values

    @property
    def name(self):
        return "Set real ONBOOT values from kickstart"

    def for_publication(self):
        """Return a DBus representation."""
        return NetworkInitializationTaskInterface(self)

    @guard_by_system_configuration(return_value=[])
    def run(self):
        """Run the ONBOOT values updating.

        :return: names of devices for which ONBOOT was updated
        :rtype: list(str)
        """
        with nm_client_in_thread() as nm_client:
            return self._run(nm_client)

    def _run(self, nm_client):
        updated_devices = []

        if not nm_client:
            log.debug("%s: No NetworkManager available.", self.name)
            return updated_devices

        if not self._network_data:
            log.debug("%s: No kickstart data.", self.name)
            return updated_devices

        for network_data in self._network_data:
            device_name = get_device_name_from_network_data(nm_client,
                                                            network_data,
                                                            self._supported_devices,
                                                            self._bootif)
            if not device_name:
                log.warning("%s: --device %s does not exist.", self.name, network_data.device)

            devices_to_update = [device_name]
            master = device_name
            # When defining both bond/team and vlan in one command we need more care
            # network --onboot yes --device bond0 --bootproto static --bondslaves ens9,ens10
            # --bondopts mode=active-backup,miimon=100,primary=ens9,fail_over_mac=2
            # --ip 192.168.111.1 --netmask 255.255.255.0 --gateway 192.168.111.222 --noipv6
            # --vlanid 222 --no-activate
            if network_data.vlanid and (network_data.bondslaves or network_data.teamslaves):
                master = network_data.device
                devices_to_update.append(master)

            cons_to_update = []
            for devname in devices_to_update:
                cons = get_connections_available_for_iface(nm_client, devname)
                n_cons = len(cons)
                con = None
                if n_cons == 1:
                    cons_to_update.append((devname, cons[0]))
                else:
                    log.debug("%s: %d connections found for %s", self.name, n_cons, devname)
                    if n_cons > 1:
                        ifcfg_uuid = find_ifcfg_uuid_of_device(nm_client, devname) or ""
                        con = nm_client.get_connection_by_uuid(ifcfg_uuid)
                        if con:
                            cons_to_update.append((devname, con))

            # Handle slaves if there are any
            if network_data.bondslaves or network_data.teamslaves or network_data.bridgeslaves:
                # Master can be identified by devname or uuid, try to find master uuid
                master_uuid = None
                device = nm_client.get_device_by_iface(master)
                if device:
                    cons = device.get_available_connections()
                    n_cons = len(cons)
                    if n_cons == 1:
                        master_uuid = cons[0].get_uuid()
                    else:
                        log.debug("%s: %d connections found for %s", self.name, n_cons, master)

                for name, con_uuid in get_master_slaves_from_ifcfgs(nm_client,
                                                                    master, uuid=master_uuid):
                    con = nm_client.get_connection_by_uuid(con_uuid)
                    cons_to_update.append((name, con))

            for devname, con in cons_to_update:
                log.debug("updating ONBOOT values of connection %s for device %s",
                          con.get_uuid(), devname)
                update_connection_values(
                    con,
                    [("connection", NM.SETTING_CONNECTION_AUTOCONNECT, network_data.onboot)]
                )
                commit_changes_with_autoconnection_blocked(con, nm_client)
                updated_devices.append(devname)

        return updated_devices


class DumpMissingIfcfgFilesTask(Task):
    """Task for dumping of missing ifcfg files."""

    def __init__(self, default_network_data, ifname_option_values):
        """Create a new task.

        :param default_network_data: kickstart network data of default device configuration
        :type default_network_data: NetworkData
        :param ifname_option_values: list of ifname boot option values
        :type ifname_option_values: list(str)
        """
        super().__init__()
        self._default_network_data = default_network_data
        self._ifname_option_values = ifname_option_values

    @property
    def name(self):
        return "Dump missing ifcfg files"

    def for_publication(self):
        """Return a DBus representation."""
        return NetworkInitializationTaskInterface(self)

    def _select_persistent_connection_for_device(self, device, cons):
        """Select the connection suitable to store configuration for the device."""
        iface = device.get_iface()
        ac = device.get_active_connection()
        if ac:
            con = ac.get_connection()
            if con.get_interface_name() == iface and con in cons:
                return con
            else:
                log.debug("%s: active connection for %s can't be used as persistent",
                          self.name, iface)
        for con in cons:
            if con.get_interface_name() == iface:
                return con
        return None

    def _update_connection(self, nm_client, con, iface):
        log.debug("%s: updating id and binding (interface-name) of connection %s for %s",
                  self.name, con.get_uuid(), iface)
        s_con = con.get_setting_connection()
        s_con.set_property(NM.SETTING_CONNECTION_ID, iface)
        s_con.set_property(NM.SETTING_CONNECTION_INTERFACE_NAME, iface)
        s_wired = con.get_setting_wired()
        if s_wired:
            # By default connections are bound to interface name
            s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, None)
            bound_mac = bound_hwaddr_of_device(nm_client, iface, self._ifname_option_values)
            if bound_mac:
                s_wired.set_property(NM.SETTING_WIRED_MAC_ADDRESS, bound_mac)
                log.debug("%s: iface %s bound to mac address %s by ifname boot option",
                          self.name, iface, bound_mac)
        log.debug("%s: updating addr-gen-mode of connection %s for %s",
                  self.name, con.get_uuid(), iface)
        s_ipv6 = con.get_setting_ip6_config()
        # For example slave connections do not have ipv6 setting present
        if s_ipv6:
            s_ipv6.set_property(NM.SETTING_IP6_CONFIG_ADDR_GEN_MODE,
                                NM.SettingIP6ConfigAddrGenMode.EUI64)

    @guard_by_system_configuration(return_value=[])
    def run(self):
        """Run dumping of missing ifcfg files.

        :returns: names of devices for which ifcfg file was created
        :rtype: list(str)
        """
        with nm_client_in_thread() as nm_client:
            return self._run(nm_client)

    def _run(self, nm_client):
        new_ifcfgs = []

        if not nm_client:
            log.debug("%s: No NetworkManager available.", self.name)
            return new_ifcfgs

        dumped_device_types = supported_wired_device_types + virtual_device_types
        for device in nm_client.get_devices():
            if device.get_device_type() not in dumped_device_types:
                continue

            iface = device.get_iface()
            if get_ifcfg_file_of_device(nm_client, iface):
                continue

            cons = device.get_available_connections()
            log.debug("%s: %s connections found for device %s", self.name,
                      [con.get_uuid() for con in cons], iface)
            n_cons = len(cons)

            device_is_slave = any(con.get_setting_connection().get_master() for con in cons)
            if device_is_slave:
                # We have to dump persistent ifcfg files for slaves created in initramfs
                if n_cons == 1 and self._is_initramfs_connection(cons[0], iface):
                    log.debug("%s: device %s has an initramfs slave connection",
                              self.name, iface)
                else:
                    log.debug("%s: not creating default connection for slave device %s",
                              self.name, iface)
                    continue

            con = self._select_persistent_connection_for_device(device, cons)

            has_initramfs_con = any(self._is_initramfs_connection(con, iface) for con in cons)
            if has_initramfs_con:
                log.debug("%s: device %s has initramfs connection", self.name, iface)
                if not con and n_cons == 1:
                    # Try to clone the persistent connection for the device
                    # from the connection which should be a generic (not bound
                    # to iface) connection created by NM in initramfs
                    con = clone_connection_sync(nm_client, cons[0], con_id=iface)


            if not con:
                log.debug("%s: none of the connections can be dumped as persistent",
                          self.name)
                if n_cons == 1:
                    # TODO: Try to clone the persistent connection for the device
                    # from the connection which should be a generic (not bound
                    # to iface) connection created by NM in initramfs
                    pass
                elif n_cons > 1:
                    log.warning("%s: unexpected number of connections, not dumping any",
                                self.name)
                    continue

            if con:
                self._update_connection(nm_client, con, iface)
                # Update some values of connection generated in initramfs so it
                # can be used as persistent configuration.
                if has_initramfs_con:
                    update_connection_values(
                        con,
                        [
                            # Make sure ONBOOT is yes
                            (NM.SETTING_CONNECTION_SETTING_NAME,
                             NM.SETTING_CONNECTION_AUTOCONNECT,
                             True),
                            # Update cloned generic connection from initramfs
                            (NM.SETTING_CONNECTION_SETTING_NAME,
                             NM.SETTING_CONNECTION_MULTI_CONNECT,
                             0),
                            # Update cloned generic connection from initramfs
                            (NM.SETTING_CONNECTION_SETTING_NAME,
                             NM.SETTING_CONNECTION_WAIT_DEVICE_TIMEOUT,
                             -1)
                        ]
                    )
                log.debug("%s: dumping connection %s to ifcfg file for %s",
                          self.name, con.get_uuid(), iface)
                commit_changes_with_autoconnection_blocked(con, nm_client)
            else:
                log.debug("%s: creating default connection for %s", self.name, iface)
                network_data = copy.deepcopy(self._default_network_data)
                if has_initramfs_con:
                    network_data.onboot = True
                add_connection_from_ksdata(
                    nm_client,
                    network_data,
                    iface,
                    activate=False,
                    ifname_option_values=self._ifname_option_values
                )

            new_ifcfgs.append(iface)

        return new_ifcfgs

    def _is_initramfs_connection(self, con, iface):
        con_id = con.get_id()
        return con_id in ["Wired Connection", iface] \
            or re.match(NM_MAC_INITRAMFS_CONNECTION, con_id)