Blame libnmstate/nm/checkpoint.py

Packit b9ca78
#
Packit b9ca78
# Copyright (c) 2018-2020 Red Hat, Inc.
Packit b9ca78
#
Packit b9ca78
# This file is part of nmstate
Packit b9ca78
#
Packit b9ca78
# This program is free software: you can redistribute it and/or modify
Packit b9ca78
# it under the terms of the GNU Lesser General Public License as published by
Packit b9ca78
# the Free Software Foundation, either version 2.1 of the License, or
Packit b9ca78
# (at your option) any later version.
Packit b9ca78
#
Packit b9ca78
# This program is distributed in the hope that it will be useful,
Packit b9ca78
# but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit b9ca78
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit b9ca78
# GNU Lesser General Public License for more details.
Packit b9ca78
#
Packit b9ca78
# You should have received a copy of the GNU Lesser General Public License
Packit b9ca78
# along with this program. If not, see <https://www.gnu.org/licenses/>.
Packit b9ca78
#
Packit b9ca78
Packit b9ca78
import logging
Packit b9ca78
Packit b9ca78
from libnmstate.error import NmstateConflictError
Packit b9ca78
from libnmstate.error import NmstateLibnmError
Packit b9ca78
from libnmstate.error import NmstatePermissionError
Packit b9ca78
from libnmstate.nm import connection
Packit b9ca78
from libnmstate.nm import common
Packit b9ca78
from .connection import is_activated
Packit b9ca78
Packit b9ca78
Packit b9ca78
def get_checkpoints(nm_client):
Packit b9ca78
    checkpoints = [c.get_path() for c in nm_client.get_checkpoints()]
Packit b9ca78
    return checkpoints
Packit b9ca78
Packit b9ca78
Packit b9ca78
class CheckPoint:
Packit b9ca78
    def __init__(self, nm_context, timeout=60, dbuspath=None):
Packit b9ca78
        self._ctx = nm_context
Packit b9ca78
        self._timeout = timeout
Packit b9ca78
        self._dbuspath = dbuspath
Packit b9ca78
        self._timeout_source = None
Packit b9ca78
Packit b9ca78
    def __str__(self):
Packit b9ca78
        return self._dbuspath
Packit b9ca78
Packit b9ca78
    @staticmethod
Packit b9ca78
    def create(nm_context, timeout=60):
Packit b9ca78
        cp = CheckPoint(nm_context=nm_context, timeout=timeout)
Packit b9ca78
        cp._create()
Packit b9ca78
        return cp
Packit b9ca78
Packit b9ca78
    def _create(self):
Packit b9ca78
        devs = []
Packit b9ca78
        timeout = self._timeout
Packit b9ca78
        cp_flags = (
Packit b9ca78
            common.NM.CheckpointCreateFlags.DELETE_NEW_CONNECTIONS
Packit b9ca78
            | common.NM.CheckpointCreateFlags.DISCONNECT_NEW_DEVICES
Packit b9ca78
        )
Packit b9ca78
Packit b9ca78
        self._ctx.register_async("Create checkpoint")
Packit b9ca78
        self._ctx.client.checkpoint_create(
Packit b9ca78
            devs,
Packit b9ca78
            timeout,
Packit b9ca78
            cp_flags,
Packit b9ca78
            self._ctx.cancellable,
Packit b9ca78
            self._checkpoint_create_callback,
Packit b9ca78
            None,
Packit b9ca78
        )
Packit b9ca78
        self._ctx.wait_all_finish()
Packit b9ca78
        self._add_checkpoint_refresh_timeout()
Packit b9ca78
Packit b9ca78
    def _add_checkpoint_refresh_timeout(self):
Packit b9ca78
        self._timeout_source = common.GLib.timeout_source_new(
Packit b9ca78
            self._timeout * 500
Packit b9ca78
        )
Packit b9ca78
        self._timeout_source.set_callback(
Packit b9ca78
            self._refresh_checkpoint_timeout, None
Packit b9ca78
        )
Packit b9ca78
        self._timeout_source.attach(self._ctx.context)
Packit b9ca78
Packit b9ca78
    def clean_up(self):
Packit b9ca78
        self._remove_checkpoint_refresh_timeout()
Packit b9ca78
Packit b9ca78
    def _remove_checkpoint_refresh_timeout(self):
Packit b9ca78
        if self._timeout_source:
Packit b9ca78
            self._timeout_source.destroy()
Packit b9ca78
            self._timeout_source = None
Packit b9ca78
Packit b9ca78
    def _refresh_checkpoint_timeout(self, _user_data):
Packit b9ca78
        cancellable, cb, cb_data = (None, None, None)
Packit b9ca78
Packit b9ca78
        if self._ctx and self._ctx.client:
Packit b9ca78
            self._ctx.client.checkpoint_adjust_rollback_timeout(
Packit b9ca78
                self._dbuspath, self._timeout, cancellable, cb, cb_data
Packit b9ca78
            )
Packit b9ca78
            return common.GLib.SOURCE_CONTINUE
Packit b9ca78
        else:
Packit b9ca78
            return common.GLib.SOURCE_REMOVE
Packit b9ca78
Packit b9ca78
    def destroy(self):
Packit b9ca78
        if self._dbuspath:
Packit b9ca78
            action = f"Destroy checkpoint {self._dbuspath}"
Packit b9ca78
            userdata = action
Packit b9ca78
            self._ctx.register_async(action)
Packit b9ca78
            self._ctx.client.checkpoint_destroy(
Packit b9ca78
                self._dbuspath,
Packit b9ca78
                self._ctx.cancellable,
Packit b9ca78
                self._checkpoint_destroy_callback,
Packit b9ca78
                userdata,
Packit b9ca78
            )
Packit b9ca78
            self._ctx.wait_all_finish()
Packit b9ca78
            self.clean_up()
Packit b9ca78
Packit b9ca78
    def rollback(self):
Packit b9ca78
        if self._dbuspath:
Packit b9ca78
            action = f"Rollback to checkpoint {self._dbuspath}"
Packit b9ca78
            self._ctx.register_async(action)
Packit b9ca78
            userdata = action
Packit b9ca78
            self._ctx.client.checkpoint_rollback(
Packit b9ca78
                self._dbuspath,
Packit b9ca78
                self._ctx.cancellable,
Packit b9ca78
                self._checkpoint_rollback_callback,
Packit b9ca78
                userdata,
Packit b9ca78
            )
Packit b9ca78
            self._ctx.wait_all_finish()
Packit b9ca78
            self.clean_up()
Packit b9ca78
Packit b9ca78
    def _checkpoint_create_callback(self, client, result, data):
Packit b9ca78
        try:
Packit b9ca78
            cp = client.checkpoint_create_finish(result)
Packit b9ca78
            if cp:
Packit b9ca78
                logging.debug(
Packit b9ca78
                    "Checkpoint {} created for all devices".format(
Packit b9ca78
                        self._dbuspath
Packit b9ca78
                    )
Packit b9ca78
                )
Packit b9ca78
                self._dbuspath = cp.get_path()
Packit b9ca78
                self._ctx.finish_async("Create checkpoint")
Packit b9ca78
            else:
Packit b9ca78
                error_msg = (
Packit b9ca78
                    f"dbuspath={self._dbuspath} "
Packit b9ca78
                    f"timeout={self._timeout} "
Packit b9ca78
                    f"callback result={cp}"
Packit b9ca78
                )
Packit b9ca78
                self._ctx.fail(
Packit b9ca78
                    NmstateLibnmError(f"Checkpoint create failed: {error_msg}")
Packit b9ca78
                )
Packit b9ca78
        except common.GLib.Error as e:
Packit b9ca78
            if e.matches(
Packit b9ca78
                common.NM.ManagerError.quark(),
Packit b9ca78
                common.NM.ManagerError.PERMISSIONDENIED,
Packit b9ca78
            ):
Packit b9ca78
                self._ctx.fail(
Packit b9ca78
                    NmstatePermissionError(
Packit b9ca78
                        "Checkpoint create failed due to insufficient"
Packit b9ca78
                        " permission"
Packit b9ca78
                    )
Packit b9ca78
                )
Packit b9ca78
            elif e.matches(
Packit b9ca78
                common.NM.ManagerError.quark(),
Packit b9ca78
                common.NM.ManagerError.INVALIDARGUMENTS,
Packit b9ca78
            ):
Packit b9ca78
                self._ctx.fail(
Packit b9ca78
                    NmstateConflictError(
Packit b9ca78
                        "Checkpoint create failed due to a"
Packit b9ca78
                        " conflict with an existing checkpoint"
Packit b9ca78
                    )
Packit b9ca78
                )
Packit b9ca78
            else:
Packit b9ca78
                self._ctx.fail(
Packit b9ca78
                    NmstateLibnmError(f"Checkpoint create failed: error={e}")
Packit b9ca78
                )
Packit b9ca78
        except Exception as e:
Packit b9ca78
            self._ctx.fail(
Packit b9ca78
                NmstateLibnmError(f"Checkpoint create failed: error={e}")
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
    def _checkpoint_rollback_callback(self, client, result, data):
Packit b9ca78
        action = data
Packit b9ca78
        try:
Packit b9ca78
            self._check_rollback_result(client, result, self._dbuspath)
Packit b9ca78
            self._dbuspath = None
Packit b9ca78
            self._ctx.finish_async(action)
Packit b9ca78
        except Exception as e:
Packit b9ca78
            self._ctx.fail(
Packit b9ca78
                NmstateLibnmError(f"Checkpoint rollback failed: error={e}")
Packit b9ca78
            )
Packit b9ca78
Packit b9ca78
    def _check_rollback_result(self, client, result, dbus_path):
Packit b9ca78
        ret = client.checkpoint_rollback_finish(result)
Packit b9ca78
        logging.debug(f"Checkpoint {dbus_path} rollback executed")
Packit b9ca78
        for path in ret:
Packit b9ca78
            nm_dev = client.get_device_by_path(path)
Packit b9ca78
            iface = path if nm_dev is None else nm_dev.get_iface()
Packit b9ca78
            if nm_dev and (
Packit b9ca78
                (
Packit b9ca78
                    nm_dev.get_state_reason()
Packit b9ca78
                    == common.NM.DeviceStateReason.NEW_ACTIVATION
Packit b9ca78
                )
Packit b9ca78
                or nm_dev.get_state() == common.NM.DeviceState.IP_CONFIG
Packit b9ca78
            ):
Packit b9ca78
                nm_ac = nm_dev.get_active_connection()
Packit b9ca78
                if not is_activated(nm_ac, nm_dev):
Packit b9ca78
                    profile = connection.ConnectionProfile(self._ctx)
Packit b9ca78
                    profile.nmdevice = nm_dev
Packit b9ca78
                    action = f"Waiting for rolling back {iface}"
Packit b9ca78
                    self._ctx.register_async(action)
Packit b9ca78
                    profile.wait_dev_activation(action)
Packit b9ca78
            if ret[path] != 0:
Packit b9ca78
                logging.error(f"Interface {iface} rollback failed")
Packit b9ca78
            else:
Packit b9ca78
                logging.debug(f"Interface {iface} rollback succeeded")
Packit b9ca78
Packit b9ca78
    def _checkpoint_destroy_callback(self, client, result, data):
Packit b9ca78
        action = data
Packit b9ca78
        try:
Packit b9ca78
            client.checkpoint_destroy_finish(result)
Packit b9ca78
            logging.debug(f"Checkpoint {self._dbuspath} destroyed")
Packit b9ca78
            self._dbuspath = None
Packit b9ca78
            self._ctx.finish_async(action)
Packit b9ca78
        except Exception as e:
Packit b9ca78
            self._ctx.fail(
Packit b9ca78
                NmstateLibnmError(
Packit b9ca78
                    f"Checkpoint {self._dbuspath} destroy failed: "
Packit b9ca78
                    f"error={e}"
Packit b9ca78
                )
Packit b9ca78
            )