Blame libnmstate/nmstate.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 contextlib import contextmanager
Packit Service 0535c1
import importlib
Packit Service 0535c1
import logging
Packit Service 0535c1
from operator import itemgetter
Packit Service 0535c1
from operator import attrgetter
Packit Service 0535c1
import os
Packit Service 0535c1
import pkgutil
Packit Service 0535c1
Packit Service 0535c1
from libnmstate import validator
Packit Service 0535c1
from libnmstate.error import NmstateError
Packit Service 0535c1
from libnmstate.error import NmstateValueError
Packit Service 0535c1
from libnmstate.nm import NetworkManagerPlugin
Packit Service 0535c1
from libnmstate.schema import DNS
Packit Service 0535c1
from libnmstate.schema import Interface
Packit Service 0535c1
from libnmstate.schema import Route
Packit Service 0535c1
from libnmstate.schema import RouteRule
Packit Service 0535c1
Packit Service 0535c1
from .plugin import NmstatePlugin
Packit Service 0535c1
from .state import merge_dict
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
@contextmanager
Packit Service 0535c1
def plugin_context():
Packit Service 0535c1
    plugins = _load_plugins()
Packit Service 0535c1
    try:
Packit Service 0535c1
        # Lowest priority plugin should perform actions first.
Packit Service 0535c1
        plugins.sort(key=attrgetter("priority"))
Packit Service 0535c1
        yield plugins
Packit Service 0535c1
    except (Exception, KeyboardInterrupt):
Packit Service 0535c1
        for plugin in plugins:
Packit Service 0535c1
            if plugin.checkpoint:
Packit Service 0535c1
                try:
Packit Service 0535c1
                    plugin.rollback_checkpoint()
Packit Service 0535c1
                # Don't complex thing by raise exception when handling another
Packit Service 0535c1
                # exception, just log the rollback failure.
Packit Service 0535c1
                except Exception as e:
Packit Service 0535c1
                    logging.error(f"Rollback failed with error {e}")
Packit Service 0535c1
        raise
Packit Service 0535c1
    finally:
Packit Service 0535c1
        for plugin in plugins:
Packit Service 0535c1
            plugin.unload()
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def show_with_plugins(plugins, include_status_data=None):
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        plugin.refresh_content()
Packit Service 0535c1
    report = {}
Packit Service 0535c1
    if include_status_data:
Packit Service 0535c1
        report["capabilities"] = plugins_capabilities(plugins)
Packit Service 0535c1
Packit Service 0535c1
    report[Interface.KEY] = _get_interface_info_from_plugins(plugins)
Packit Service 0535c1
Packit Service 0535c1
    route_plugin = _find_plugin_for_capability(
Packit Service 0535c1
        plugins, NmstatePlugin.PLUGIN_CAPABILITY_ROUTE
Packit Service 0535c1
    )
Packit Service 0535c1
    if route_plugin:
Packit Service 0535c1
        report[Route.KEY] = route_plugin.get_routes()
Packit Service 0535c1
Packit Service 0535c1
    route_rule_plugin = _find_plugin_for_capability(
Packit Service 0535c1
        plugins, NmstatePlugin.PLUGIN_CAPABILITY_ROUTE_RULE
Packit Service 0535c1
    )
Packit Service 0535c1
    if route_rule_plugin:
Packit Service 0535c1
        report[RouteRule.KEY] = route_rule_plugin.get_route_rules()
Packit Service 0535c1
Packit Service 0535c1
    dns_plugin = _find_plugin_for_capability(
Packit Service 0535c1
        plugins, NmstatePlugin.PLUGIN_CAPABILITY_DNS
Packit Service 0535c1
    )
Packit Service 0535c1
    if dns_plugin:
Packit Service 0535c1
        report[DNS.KEY] = dns_plugin.get_dns_client_config()
Packit Service 0535c1
Packit Service 0535c1
    validator.schema_validate(report)
Packit Service 0535c1
    return report
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def plugins_capabilities(plugins):
Packit Service 0535c1
    capabilities = set()
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        capabilities.update(set(plugin.capabilities))
Packit Service 0535c1
    return list(capabilities)
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _load_plugins():
Packit Service 0535c1
    plugins = [NetworkManagerPlugin()]
Packit Service 0535c1
    plugins.extend(_load_external_py_plugins())
Packit Service 0535c1
    return plugins
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _load_external_py_plugins():
Packit Service 0535c1
    """
Packit Service 0535c1
    Load module from folder defined in system evironment NMSTATE_PLUGIN_DIR,
Packit Service 0535c1
    if empty, use the 'plugins' folder of current python file.
Packit Service 0535c1
    """
Packit Service 0535c1
    plugins = []
Packit Service 0535c1
    plugin_dir = os.environ.get("NMSTATE_PLUGIN_DIR")
Packit Service 0535c1
    if not plugin_dir:
Packit Service 0535c1
        plugin_dir = f"{os.path.dirname(os.path.realpath(__file__))}/plugins"
Packit Service 0535c1
Packit Service 0535c1
    for _, name, ispkg in pkgutil.iter_modules([plugin_dir]):
Packit Service 0535c1
        if name.startswith("nmstate_plugin_"):
Packit Service 0535c1
            try:
Packit Service 0535c1
                spec = importlib.util.spec_from_file_location(
Packit Service 0535c1
                    name, f"{plugin_dir}/{name}.py"
Packit Service 0535c1
                )
Packit Service 0535c1
                plugin_module = importlib.util.module_from_spec(spec)
Packit Service 0535c1
                spec.loader.exec_module(plugin_module)
Packit Service 0535c1
                plugin = plugin_module.NMSTATE_PLUGIN()
Packit Service 0535c1
                plugins.append(plugin)
Packit Service 0535c1
            except Exception as error:
Packit Service 0535c1
                logging.warning(f"Failed to load plugin {name}: {error}")
Packit Service 0535c1
Packit Service 0535c1
    return plugins
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _find_plugin_for_capability(plugins, capability):
Packit Service 0535c1
    """
Packit Service 0535c1
    Return the plugin with specified capability and highest priority.
Packit Service 0535c1
    """
Packit Service 0535c1
    chose_plugin = None
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        if (
Packit Service 0535c1
            chose_plugin
Packit Service 0535c1
            and capability in plugin.plugin_capabilities
Packit Service 0535c1
            and plugin.priority > chose_plugin.priority
Packit Service 0535c1
        ) or not chose_plugin:
Packit Service 0535c1
            chose_plugin = plugin
Packit Service 0535c1
    return chose_plugin
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _get_interface_info_from_plugins(plugins):
Packit Service 0535c1
    all_ifaces = {}
Packit Service 0535c1
    IFACE_PRIORITY_METADATA = "_plugin_priority"
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        if (
Packit Service 0535c1
            NmstatePlugin.PLUGIN_CAPABILITY_IFACE
Packit Service 0535c1
            not in plugin.plugin_capabilities
Packit Service 0535c1
        ):
Packit Service 0535c1
            continue
Packit Service 0535c1
        for iface in plugin.get_interfaces():
Packit Service 0535c1
            iface[IFACE_PRIORITY_METADATA] = plugin.priority
Packit Service 0535c1
            iface_name = iface[Interface.NAME]
Packit Service 0535c1
            if iface_name in all_ifaces:
Packit Service 0535c1
                existing_iface = all_ifaces[iface_name]
Packit Service 0535c1
                existing_priority = existing_iface[IFACE_PRIORITY_METADATA]
Packit Service 0535c1
                current_priority = plugin.priority
Packit Service 0535c1
                if current_priority > existing_priority:
Packit Service 0535c1
                    merge_dict(iface, existing_iface)
Packit Service 0535c1
                    all_ifaces[iface_name] = iface
Packit Service 0535c1
                else:
Packit Service 0535c1
                    merge_dict(existing_iface, iface)
Packit Service 0535c1
            else:
Packit Service 0535c1
                all_ifaces[iface_name] = iface
Packit Service 0535c1
Packit Service 0535c1
    # Remove metadata
Packit Service 0535c1
    for iface in all_ifaces.values():
Packit Service 0535c1
        iface.pop(IFACE_PRIORITY_METADATA)
Packit Service 0535c1
Packit Service 0535c1
    return sorted(all_ifaces.values(), key=itemgetter(Interface.NAME))
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def create_checkpoints(plugins, timeout):
Packit Service 0535c1
    """
Packit Service 0535c1
    Return a string containing all the check point created by each plugin in
Packit Service 0535c1
    the format:
Packit Service 0535c1
        plugin.name|<checkpoing_path>|plugin.name|
Packit Service 0535c1
Packit Service 0535c1
    """
Packit Service 0535c1
    checkpoints = []
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        checkpoint = plugin.create_checkpoint(timeout)
Packit Service 0535c1
        if checkpoint:
Packit Service 0535c1
            checkpoints.append(f"{plugin.name}|{checkpoint}")
Packit Service 0535c1
    return "|".join(checkpoints)
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def destroy_checkpoints(plugins, checkpoints):
Packit Service 0535c1
    _checkpoint_action(plugins, _parse_checkpoints(checkpoints), "destroy")
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def rollback_checkpoints(plugins, checkpoints):
Packit Service 0535c1
    _checkpoint_action(plugins, _parse_checkpoints(checkpoints), "rollback")
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _checkpoint_action(plugins, checkpoint_index, action):
Packit Service 0535c1
    errors = []
Packit Service 0535c1
    for plugin in plugins:
Packit Service 0535c1
        if checkpoint_index and plugin.name not in checkpoint_index:
Packit Service 0535c1
            continue
Packit Service 0535c1
        checkpoint = (
Packit Service 0535c1
            checkpoint_index[plugin.name] if checkpoint_index else None
Packit Service 0535c1
        )
Packit Service 0535c1
        try:
Packit Service 0535c1
            if action == "destroy":
Packit Service 0535c1
                plugin.destroy_checkpoint(checkpoint)
Packit Service 0535c1
            else:
Packit Service 0535c1
                plugin.rollback_checkpoint(checkpoint)
Packit Service 0535c1
        except (Exception, KeyboardInterrupt) as error:
Packit Service 0535c1
            errors.append(error)
Packit Service 0535c1
Packit Service 0535c1
    if errors:
Packit Service 0535c1
        if len(errors) == 1:
Packit Service 0535c1
            raise errors[0]
Packit Service 0535c1
        else:
Packit Service 0535c1
            raise NmstateError(
Packit Service 0535c1
                "Got multiple exception during checkpoint "
Packit Service 0535c1
                f"{action}: {errors}"
Packit Service 0535c1
            )
Packit Service 0535c1
Packit Service 0535c1
Packit Service 0535c1
def _parse_checkpoints(checkpoints):
Packit Service 0535c1
    """
Packit Service 0535c1
    Return a dict mapping plugin name to checkpoint
Packit Service 0535c1
    """
Packit Service 0535c1
    if not checkpoints:
Packit Service 0535c1
        return None
Packit Service 0535c1
    parsed = checkpoints.split("|")
Packit Service 0535c1
    if len(parsed) % 2:
Packit Service 0535c1
        raise NmstateValueError("Invalid format of checkpoint")
Packit Service 0535c1
    checkpoint_index = {}
Packit Service 0535c1
    for plugin_name, checkpoint in zip(parsed[0::2], parsed[1::2]):
Packit Service 0535c1
        checkpoint_index[plugin_name] = checkpoint