Add cancel verb and DBus API

Right now the fact that one can only cancel via `Ctrl-C` of an existing client
process is rather frustrating if for example one's ssh connection to a machine
drops. Now, upon reconnecting, one can easily `rpm-ostree cancel` a hung update
or whatever rather than doing the more forcible `systemctl stop rpm-ostreed`
(which is safe of course, unless livefs is involved).

Closes: #1019
Approved by: jlebon
This commit is contained in:
Colin Walters 2017-12-05 17:14:18 -05:00 committed by Atomic Bot
parent 51c5591ced
commit 95b423afe9
8 changed files with 180 additions and 9 deletions

View File

@ -28,6 +28,7 @@ rpm_ostree_SOURCES = src/app/main.c \
src/app/rpmostree-builtin-deploy.c \
src/app/rpmostree-builtin-reload.c \
src/app/rpmostree-builtin-rebase.c \
src/app/rpmostree-builtin-cancel.c \
src/app/rpmostree-builtin-cleanup.c \
src/app/rpmostree-builtin-initramfs.c \
src/app/rpmostree-builtin-livefs.c \

View File

@ -116,6 +116,20 @@ Boston, MA 02111-1307, USA.
</listitem>
</varlistentry>
<varlistentry>
<term><command>cancel</command></term>
<listitem>
<para>
Cancel a pending transaction. Exits successfully and does
nothing if no transaction is running. Note that it is fully
safe to cancel transactions such as <command>upgrade</command>
in general.
</para>
</listitem>
</varlistentry>
<varlistentry>
<term><command>db</command></term>

View File

@ -65,6 +65,9 @@ static RpmOstreeCommand commands[] = {
{ "reload", 0,
"Reload configuration",
rpmostree_builtin_reload },
{ "cancel", 0,
"Cancel an active transaction",
rpmostree_builtin_cancel },
{ "initramfs", 0,
"Enable or disable local initramfs regeneration",
rpmostree_builtin_initramfs },

View File

@ -0,0 +1,121 @@
/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*-
*
* Copyright (C) 2017 Red Hat, Inc.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published
* by the Free Software Foundation; either version 2 of the licence or (at
* your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General
* Public License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*/
#include "config.h"
#include <string.h>
#include <glib-unix.h>
#include "rpmostree-builtins.h"
#include "rpmostree-util.h"
#include "rpmostree-libbuiltin.h"
#include "rpmostree-dbus-helpers.h"
#include <libglnx.h>
static GOptionEntry option_entries[] = {
{ NULL }
};
static void
on_cancel_method_completed (GObject *src,
GAsyncResult *res,
gpointer user_data)
{
/* Nothing right now - we ideally should check for an error, but the
* transaction could have gone away. A better fix here would be to keep
* transactions around for some period of time.
*/
(void) rpmostree_transaction_call_cancel_finish ((RPMOSTreeTransaction*)src, res, NULL);
}
static void
on_active_txn_path_changed (GObject *object,
GParamSpec *pspec,
gpointer user_data)
{
gboolean *donep = user_data;
*donep = TRUE;
g_main_context_wakeup (NULL);
}
int
rpmostree_builtin_cancel (int argc,
char **argv,
RpmOstreeCommandInvocation *invocation,
GCancellable *cancellable,
GError **error)
{
g_autoptr(GOptionContext) context = g_option_context_new ("");
glnx_unref_object RPMOSTreeSysroot *sysroot_proxy = NULL;
_cleanup_peer_ GPid peer_pid = 0;
if (!rpmostree_option_context_parse (context,
option_entries,
&argc, &argv,
invocation,
cancellable,
NULL, NULL,
&sysroot_proxy,
&peer_pid,
error))
return EXIT_FAILURE;
/* Keep track of the txn path we saw first, as well as checking if it changed
* over time.
*/
g_autofree char *txn_path = NULL;
glnx_unref_object RPMOSTreeTransaction *txn_proxy = NULL;
if (!rpmostree_transaction_connect_active (sysroot_proxy, &txn_path, &txn_proxy,
cancellable, error))
return EXIT_FAILURE;
if (!txn_proxy)
{
/* Let's not make this an error; cancellation may race with completion.
* Perhaps in the future we could try to see whether a txn exited
* "recently" but eh.
*/
g_print ("No active transaction.\n");
return EXIT_SUCCESS;
}
const char *title = rpmostree_transaction_get_title (txn_proxy);
g_print ("Cancelling transaction: %s\n", title);
/* Asynchronously cancel, waiting for the sysroot property to change */
rpmostree_transaction_call_cancel (txn_proxy, cancellable,
on_cancel_method_completed, NULL);
gboolean done = FALSE;
g_signal_connect (txn_proxy, "notify::active-transaction-path",
G_CALLBACK (on_active_txn_path_changed), &done);
/* Wait for the transaction to go away */
while (!done)
{
const char *current_txn_path = rpmostree_sysroot_get_active_transaction_path (sysroot_proxy);
if (g_strcmp0 (txn_path, current_txn_path) != 0)
break;
g_main_context_iteration (NULL, TRUE);
}
g_print ("Cancelled.\n");
return EXIT_SUCCESS;
}

View File

@ -64,6 +64,7 @@ BUILTINPROTO(upgrade);
BUILTINPROTO(reload);
BUILTINPROTO(deploy);
BUILTINPROTO(rebase);
BUILTINPROTO(cancel);
BUILTINPROTO(cleanup);
BUILTINPROTO(rollback);
BUILTINPROTO(initramfs);

View File

@ -106,6 +106,16 @@
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.cancel">
<description>Cancel active transaction</description>
<message>Authentication is required to cancel a transaction</message>
<defaults>
<allow_any>auth_admin</allow_any>
<allow_inactive>auth_admin</allow_inactive>
<allow_active>auth_admin_keep</allow_active>
</defaults>
</action>
<action id="org.projectatomic.rpmostree1.cleanup">
<description>Clear cache</description>
<message>Authentication is required to clear cache / pending data</message>

View File

@ -569,6 +569,10 @@ sysroot_authorize_method (GDBusInterfaceSkeleton *interface,
{
action = "org.projectatomic.rpmostree1.reload-daemon";
}
else if (g_strcmp0 (method_name, "Cancel") == 0)
{
action = "org.projectatomic.rpmostree1.cancel";
}
else if (g_strcmp0 (method_name, "RegisterClient") == 0 ||
g_strcmp0 (method_name, "UnregisterClient") == 0)
{

View File

@ -159,22 +159,39 @@ if vm_rpmostree install rofiles-violation; then
assert_not_reached "installed test-post-rofiles-violation!"
fi
# Test cancellation via having a script hang
cursor=$(vm_get_journal_cursor)
# Test cancellation via having a script hang; we interrupt directly by sending
# SIGINT to the client binary.
vm_build_rpm post-that-hangs \
post "echo entering post-that-hangs-infloop 1>&2; while true; do sleep 1h; done"
# use a systemd transient service as an easy way to run in the background
vm_cmd systemd-run --unit vmcheck-install-hang rpm-ostree install post-that-hangs
if ! vm_wait_content_after_cursor "${cursor}" "entering post-that-hangs-infloop"; then
vm_cmd systemctl stop vmcheck-install-hang
assert_not_reached "failed to wait for post-that-hangs"
fi
background_install_post_that_hangs() {
local cursor="$1"
# use a systemd transient service as an easy way to run in the background; be
# sure any previous failed instances are cleaned up
vm_cmd systemctl stop vmcheck-install-hang || true
vm_cmd systemctl reset-failed vmcheck-install-hang || true
vm_cmd systemd-run --unit vmcheck-install-hang rpm-ostree install post-that-hangs
if ! vm_wait_content_after_cursor "${cursor}" "entering post-that-hangs-infloop"; then
vm_cmd systemctl stop vmcheck-install-hang || true
assert_not_reached "failed to wait for post-that-hangs"
fi
}
cursor=$(vm_get_journal_cursor)
background_install_post_that_hangs "${cursor}"
vm_cmd pkill --signal INT -f "'rpm-ostree install post-that-hangs'"
# Wait for our expected result
vm_wait_content_after_cursor "${cursor}" "Txn.*failed.*Running %post for post-that-hangs"
# Forcibly restart now to avoid any races with the txn finally exiting
vm_cmd systemctl restart rpm-ostreed
echo "ok cancel infinite post"
echo "ok cancel infinite post via SIGINT"
# Test `rpm-ostree cancel` (which is the same as doing a Ctrl-C on the client)
cursor=$(vm_get_journal_cursor)
background_install_post_that_hangs "${cursor}"
vm_rpmostree cancel
vm_wait_content_after_cursor "${cursor}" "Txn.*failed.*Running %post for post-that-hangs"
# Forcibly restart now to avoid any races with the txn finally exiting
vm_cmd systemctl restart rpm-ostreed
echo "ok cancel infinite post via `rpm-ostree cancel`"
# Test rm -rf /!
vm_cmd 'useradd testuser || true'