Skip to content

Commit

Permalink
Added KillUnit API
Browse files Browse the repository at this point in the history
Fixes: #938

The KillUnit API has been implemented, forwarding the API
call to systemd. For bluechictl, the command kill has been
implemented to mirror the systemctl kill command - including
the command options. It also added integration tests for the
new KillUnit functionality.

Signed-off-by: Michael Engel <[email protected]>
  • Loading branch information
engelmi committed Sep 12, 2024
1 parent 0dba297 commit 3e8a433
Show file tree
Hide file tree
Showing 21 changed files with 399 additions and 38 deletions.
23 changes: 19 additions & 4 deletions data/org.eclipse.bluechi.Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -96,13 +96,29 @@
<arg name="job" type="o" direction="out" />
</method>

<!--
KillUnit:
@name: The name of the unit to kill
@who: One of [main, control, all]
@signal: Unix signal number
Kills processes of a unit on a node.
If the who parameter is set to main, only the main process of a unit is killed. If control is used, then only the control process of the unit is
killed. And if all is used, all processes are killed.
-->
<method name="KillUnit">
<arg name="name" type="s" direction="in" />
<arg name="who" type="s" direction="in" />
<arg name="signal" type="i" direction="in" />
</method>

<!--
GetUnitProperties:
@name: The name of unit
@interface: The interface name
@props: The as key-value pair with the name of the property as key
@props: The properties as key-value pair with the name of the property as key
Returns the current for a named unit on the node. The returned are the same as you would get in the systemd apis.
Returns the current for a named unit on the node. The returned are the same as you would get in the systemd apis.
-->
<method name="GetUnitProperties">
<arg name="name" type="s" direction="in" />
Expand Down Expand Up @@ -132,7 +148,7 @@
@runtime: Specify if the changes should persist after reboot or not
@keyvalues: A list of the new values as key-value pair with the key being the name of the property
Set named . If runtime is true the property changes do not persist across reboots.
Set named properties. If runtime is true the property changes do not persist across reboots.
-->
<method name="SetUnitProperties">
<arg name="name" type="s" direction="in" />
Expand Down Expand Up @@ -242,7 +258,6 @@
<arg name="name" type="s" direction="in" />
</method>


<!--
Name:
Expand Down
5 changes: 5 additions & 0 deletions data/org.eclipse.bluechi.internal.Agent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,11 @@
<arg name="mode" type="s" direction="in" />
<arg name="jobid" type="u" direction="in" />
</method>
<method name="KillUnit">
<arg name="name" type="s" direction="in" />
<arg name="who" type="s" direction="in" />
<arg name="signal" type="i" direction="in" />
</method>
<method name="GetUnitProperties">
<arg name="name" type="s" direction="in" />
<arg name="interface" type="s" direction="in" />
Expand Down
11 changes: 11 additions & 0 deletions doc/docs/api/description.md
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,11 @@ Object path: `/org/eclipse/bluechi/node/$name`
`DisableUnitFiles()` is similar to `EnableUnitFiles()` but
disables the specified units by removing all symlinks to them in /etc/ and /run/

* `KillUnit(in s name, in s who, in i signal)`

Kills processes of a unit on a node.
If the who parameter is set to main, only the main process of a unit is killed. If control is used, then only the control process of the unit is killed. And if all is used, all processes are killed.

* `GetUnitProperties(in s name, in s interface, out a{sv} props)`

Returns the current properties for a named unit on the node. The returned properties are the same as you would
Expand Down Expand Up @@ -342,6 +347,12 @@ This is the main interface that the node implements and that is used by the cont

* `RestartUnit(in s name, in s mode, in u id)`

* `ResetFailed()`

* `ResetFailedUnit(in s name)`

* `KillUnit(in s name, in s who, in i signal)`

* `GetUnitProperties(in name, out a{sv} props)`

* `ListUnits(out a(ssssssouso) units);`
Expand Down
15 changes: 15 additions & 0 deletions doc/man/bluechictl.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,21 @@ Print current bluechictl version

Performs one of the listed lifecycle operations on the given systemd unit for the `bluechi-agent`.

### **bluechictl** [*kill*] [*agent*] [*unit*]

Kills the processes of (i.e. sends a signal to) the specified unit on the chosen node.

**Options:**

**--kill-whom**
Enum defining which processes of the unit are killed.
Needs to be one of [all, main, control]. Default: all

**--signal**
The signal sent to kill the processes of the unit.
Default: 15 (SIGTERM)


### **bluechictl** [*enable*] [*agent*] [*unit1*,*...*]

Enable the list of systemd unit files for the `bluechi-agent`.
Expand Down
15 changes: 8 additions & 7 deletions src/agent/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1778,6 +1778,14 @@ static const sd_bus_vtable internal_agent_vtable[] = {
SD_BUS_METHOD("DisableMetrics", "", "", agent_method_disable_metrics, 0),
SD_BUS_METHOD("SetLogLevel", "s", "", agent_method_set_log_level, 0),
SD_BUS_METHOD("JobCancel", "u", "", agent_method_job_cancel, 0),
SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("Reload", "", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("ResetFailed", "", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("ResetFailedUnit", "s", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("KillUnit", "ssi", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("StartDep", "s", "", agent_method_start_dep, 0),
SD_BUS_METHOD("StopDep", "s", "", agent_method_stop_dep, 0),
SD_BUS_SIGNAL_WITH_NAMES("JobDone", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(result), 0),
SD_BUS_SIGNAL_WITH_NAMES("JobStateChanged", "us", SD_BUS_PARAM(id) SD_BUS_PARAM(state), 0),
SD_BUS_SIGNAL_WITH_NAMES(
Expand All @@ -1794,13 +1802,6 @@ static const sd_bus_vtable internal_agent_vtable[] = {
0),
SD_BUS_SIGNAL_WITH_NAMES("UnitRemoved", "s", SD_BUS_PARAM(unit), 0),
SD_BUS_SIGNAL("Heartbeat", "", 0),
SD_BUS_METHOD("StartDep", "s", "", agent_method_start_dep, 0),
SD_BUS_METHOD("StopDep", "s", "", agent_method_stop_dep, 0),
SD_BUS_METHOD("EnableUnitFiles", "asbb", "ba(sss)", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("DisableUnitFiles", "asb", "a(sss)", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("Reload", "", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("ResetFailed", "", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("ResetFailedUnit", "s", "", agent_method_passthrough_to_systemd, 0),
SD_BUS_VTABLE_END
};

Expand Down
23 changes: 20 additions & 3 deletions src/bindings/python/bluechi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -885,9 +885,9 @@ def get_unit_properties(self, name: str, interface: str) -> Structure:
GetUnitProperties:
@name: The name of unit
@interface: The interface name
@props: The as key-value pair with the name of the property as key
@props: The properties as key-value pair with the name of the property as key
Returns the current for a named unit on the node. The returned are the same as you would get in the systemd apis.
Returns the current for a named unit on the node. The returned are the same as you would get in the systemd apis.
"""
return self.get_proxy().GetUnitProperties(
name,
Expand All @@ -910,6 +910,23 @@ def get_unit_property(self, name: str, interface: str, property: str) -> Variant
property,
)

def kill_unit(self, name: str, who: str, signal: Int32) -> None:
"""
KillUnit:
@name: The name of the unit to kill
@who: One of [main, control, all]
@signal: Unix signal number
Kills processes of a unit on a node.
If the who parameter is set to main, only the main process of a unit is killed. If control is used, then only the control process of the unit is
killed. And if all is used, all processes are killed.
"""
self.get_proxy().KillUnit(
name,
who,
signal,
)

def list_unit_files(self) -> List[Tuple[str, str]]:
"""
ListUnitFiles:
Expand Down Expand Up @@ -1014,7 +1031,7 @@ def set_unit_properties(
@runtime: Specify if the changes should persist after reboot or not
@keyvalues: A list of the new values as key-value pair with the key being the name of the property
Set named . If runtime is true the property changes do not persist across reboots.
Set named properties. If runtime is true the property changes do not persist across reboots.
"""
self.get_proxy().SetUnitProperties(
name,
Expand Down
46 changes: 27 additions & 19 deletions src/client/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@

#include "client.h"
#include "method-help.h"
#include "method-kill.h"
#include "method-list-unit-files.h"
#include "method-list-units.h"
#include "method-loglevel.h"
Expand All @@ -27,32 +28,35 @@
#define OPT_RUNTIME 1u << 3u
#define OPT_NO_RELOAD 1u << 4u
#define OPT_WATCH 1u << 5u
#define OPT_KILL_WHOM 1u << 6u
#define OPT_SIGNAL 1u << 7u

int method_version(UNUSED Command *command, UNUSED void *userdata) {
printf("bluechictl version %s\n", CONFIG_H_BC_VERSION);
return 0;
}

const Method methods[] = {
{ "help", 0, 0, OPT_NONE, method_help, usage_bluechi },
{ "list-unit-files", 0, 1, OPT_FILTER, method_list_unit_files, usage_bluechi },
{ "list-units", 0, 1, OPT_FILTER, method_list_units, usage_bluechi },
{ "start", 2, 2, OPT_NONE, method_start, usage_bluechi },
{ "stop", 2, 2, OPT_NONE, method_stop, usage_bluechi },
{ "freeze", 2, 2, OPT_NONE, method_freeze, usage_bluechi },
{ "thaw", 2, 2, OPT_NONE, method_thaw, usage_bluechi },
{ "restart", 2, 2, OPT_NONE, method_restart, usage_bluechi },
{ "reload", 2, 2, OPT_NONE, method_reload, usage_bluechi },
{ "reset-failed", 0, ARG_ANY, OPT_NONE, method_reset_failed, usage_bluechi },
{ "monitor", 0, 2, OPT_NONE, method_monitor, usage_bluechi },
{ "metrics", 1, 1, OPT_NONE, method_metrics, usage_bluechi },
{ "enable", 2, ARG_ANY, OPT_FORCE | OPT_RUNTIME | OPT_NO_RELOAD, method_enable, usage_bluechi },
{ "disable", 2, ARG_ANY, OPT_NO_RELOAD, method_disable, usage_bluechi },
{ "daemon-reload", 1, 1, OPT_NONE, method_daemon_reload, usage_bluechi },
{ "status", 0, ARG_ANY, OPT_WATCH, method_status, usage_bluechi },
{ "set-loglevel", 1, 2, OPT_NONE, method_set_loglevel, usage_bluechi },
{ "version", 0, 0, OPT_NONE, method_version, usage_bluechi },
{ NULL, 0, 0, 0, NULL, NULL }
{ "help", 0, 0, OPT_NONE, method_help, usage_bluechi },
{ "list-unit-files", 0, 1, OPT_FILTER, method_list_unit_files, usage_bluechi },
{ "list-units", 0, 1, OPT_FILTER, method_list_units, usage_bluechi },
{ "start", 2, 2, OPT_NONE, method_start, usage_bluechi },
{ "stop", 2, 2, OPT_NONE, method_stop, usage_bluechi },
{ "freeze", 2, 2, OPT_NONE, method_freeze, usage_bluechi },
{ "thaw", 2, 2, OPT_NONE, method_thaw, usage_bluechi },
{ "restart", 2, 2, OPT_NONE, method_restart, usage_bluechi },
{ "reload", 2, 2, OPT_NONE, method_reload, usage_bluechi },
{ "reset-failed", 0, ARG_ANY, OPT_NONE, method_reset_failed, usage_bluechi },
{ "kill", 2, 2, OPT_KILL_WHOM | OPT_SIGNAL, method_kill, usage_method_kill },
{ "monitor", 0, 2, OPT_NONE, method_monitor, usage_bluechi },
{ "metrics", 1, 1, OPT_NONE, method_metrics, usage_bluechi },
{ "enable", 2, ARG_ANY, OPT_FORCE | OPT_RUNTIME | OPT_NO_RELOAD, method_enable, usage_bluechi },
{ "disable", 2, ARG_ANY, OPT_NO_RELOAD, method_disable, usage_bluechi },
{ "daemon-reload", 1, 1, OPT_NONE, method_daemon_reload, usage_bluechi },
{ "status", 0, ARG_ANY, OPT_WATCH, method_status, usage_bluechi },
{ "set-loglevel", 1, 2, OPT_NONE, method_set_loglevel, usage_bluechi },
{ "version", 0, 0, OPT_NONE, method_version, usage_bluechi },
{ NULL, 0, 0, 0, NULL, NULL }
};

const OptionType option_types[] = {
Expand All @@ -61,6 +65,8 @@ const OptionType option_types[] = {
{ ARG_RUNTIME_SHORT, ARG_RUNTIME, OPT_RUNTIME },
{ ARG_NO_RELOAD_SHORT, ARG_NO_RELOAD, OPT_NO_RELOAD },
{ ARG_WATCH_SHORT, ARG_WATCH, OPT_WATCH },
{ ARG_KILL_WHOM_SHORT, ARG_KILL_WHOM, OPT_KILL_WHOM },
{ ARG_SIGNAL_SHORT, ARG_SIGNAL, OPT_SIGNAL },
{ 0, NULL, 0 }
};

Expand All @@ -72,6 +78,8 @@ const struct option getopt_options[] = {
{ ARG_RUNTIME, no_argument, 0, ARG_RUNTIME_SHORT },
{ ARG_NO_RELOAD, no_argument, 0, ARG_NO_RELOAD_SHORT },
{ ARG_WATCH, no_argument, 0, ARG_WATCH_SHORT },
{ ARG_KILL_WHOM, required_argument, 0, ARG_KILL_WHOM_SHORT },
{ ARG_SIGNAL, required_argument, 0, ARG_SIGNAL_SHORT },
{ NULL, 0, 0, '\0' }
};

Expand Down
4 changes: 3 additions & 1 deletion src/client/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,9 @@ client_src = [
'method-metrics.c',
'method-monitor.c',
'method-unit-actions.c',
'method-status.c'
'method-status.c',
'method-kill.c',
'usage.c',
]

executable(
Expand Down
9 changes: 6 additions & 3 deletions src/client/method-help.c
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,12 @@
*/
#include "method-help.h"
#include "client.h"
#include "usage.h"

void usage_bluechi() {
printf("bluechictl is a convenience CLI tool to interact with bluechi\n");
printf("Usage: bluechictl COMMAND\n\n");
printf("Available command:\n");
usage_print_header();
usage_print_usage("bluechictl [command]");
printf("Available commands:\n");
printf(" - help: shows this help message\n");
printf(" usage: help\n");
printf(" - list-unit-files: returns the list of systemd service files installed on a specific or on all nodes\n");
Expand All @@ -30,6 +31,8 @@ void usage_bluechi() {
printf(" usage: restart nodename unitname\n");
printf(" - reset-failed: reset failed node on all node or all units on a specific node or specidec units on a node \n");
printf(" usage: reset-failed [nodename] [unit1, unit2 ...]\n");
printf(" - kill: kills processes of a specific unit on the given node\n");
printf(" usage: kill [nodename] [unit] [--kill-whom=all|main|control] [--signal=<number>]\n");
printf(" - enable: enables the specified systemd files on a specific node\n");
printf(" usage: enable nodename unitfilename...\n");
printf(" - disable: disables the specified systemd files on a specific node\n");
Expand Down
96 changes: 96 additions & 0 deletions src/client/method-kill.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
/*
* Copyright Contributors to the Eclipse BlueChi project
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include "method-kill.h"
#include "client.h"
#include "usage.h"

#include "libbluechi/common/opt.h"
#include "libbluechi/common/parse-util.h"


static int method_kill_unit_on(
Client *client, const char *node_name, const char *unit_name, const char *whom, uint32_t signal) {

int r = 0;
_cleanup_sd_bus_error_ sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_sd_bus_message_ sd_bus_message *result = NULL;

r = assemble_object_path_string(NODE_OBJECT_PATH_PREFIX, node_name, &client->object_path);
if (r < 0) {
return r;
}

r = sd_bus_call_method(
client->api_bus,
BC_INTERFACE_BASE_NAME,
client->object_path,
NODE_INTERFACE,
"KillUnit",
&error,
&result,
"ssi",
unit_name,
whom,
signal);
if (r < 0) {
fprintf(stderr, "Failed to issue method call for KillUnit: %s\n", error.message);
return r;
}

return 0;
}

static const char *default_whom = "all";
static const char *default_signal = "15";

// Based on values listed for KillUnit
// https://www.freedesktop.org/wiki/Software/systemd/dbus/
static const char *whom_values[] = { "all", "control", "main", NULL };

static bool is_whom_value_valid(const char *v) {
for (int i = 0; whom_values[i]; i++) {
if (whom_values[i] && streq(whom_values[i], v)) {
return true;
}
}
return false;
}

int method_kill(Command *command, UNUSED void *userdata) {
const char *whom = command_get_option(command, ARG_KILL_WHOM_SHORT);
if (whom == NULL) {
whom = default_whom;
}
if (!is_whom_value_valid(whom)) {
fprintf(stderr, "Value '%s' of option --%s is invalid\n", whom, ARG_KILL_WHOM);
return -EINVAL;
}

uint32_t sig = 0;
const char *signal = command_get_option(command, ARG_SIGNAL_SHORT);
if (signal == NULL) {
signal = default_signal;
}
if (!parse_linux_signal(signal, &sig)) {
fprintf(stderr, "Value '%s' of option --%s is invalid\n", signal, ARG_SIGNAL);
return -EINVAL;
}

return method_kill_unit_on(userdata, command->opargv[0], command->opargv[1], whom, sig);
}

void usage_method_kill() {
usage_print_header();
usage_print_description("Kill (i.e. send a signal to) the processes of a unit");
usage_print_usage("bluechictl kill [nodename] [unit] [options]");
printf("Available options:\n");
printf(" --%s \t shows this help message\n", ARG_HELP);
printf(" --%s \t Enum defining which processes of the unit are killed.\n", ARG_KILL_WHOM);
printf("\t\t Needs to be one of [all, main, control]. Default: '%s'\n", default_whom);
printf(" --%s \t The signal sent to kill the processes of the unit. Default: '%s'\n",
ARG_SIGNAL,
default_signal);
}
Loading

0 comments on commit 3e8a433

Please sign in to comment.