|
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
|