Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GetUnitFileState API #953

Merged
merged 1 commit into from
Oct 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions data/org.eclipse.bluechi.Node.xml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,18 @@
<arg name="unitfiles" type="a(ss)" direction="out" />
</method>

<!--
GetUnitFileState:
@file: The name of the unit file
@state: The current enablement status of the unit file

Get the current enablement status of specific unit file.
-->
<method name="GetUnitFileState">
<arg name="file" type="s" direction="in" />
<arg name="state" type="s" direction="out" />
</method>

<!--
Reload:

Expand Down
4 changes: 4 additions & 0 deletions data/org.eclipse.bluechi.internal.Agent.xml
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@
<method name="ListUnitFiles">
<arg name="units" type="a(ss)" direction="out" />
</method>
<method name="GetUnitFileState">
<arg name="file" type="s" direction="in" />
<arg name="state" type="s" direction="out" />
</method>
<method name="Subscribe">
<arg name="unit" type="s" direction="in" />
</method>
Expand Down
6 changes: 5 additions & 1 deletion doc/man/bluechictl.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ Performs one of the listed lifecycle operations on the given systemd unit for th

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

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

**Options:**

Expand Down Expand Up @@ -80,6 +80,10 @@ Fetches information about all systemd unit files on the bluechi-agents. If [blue
**--filter**
Use glob filter for the unit file path

### **bluechictl** *is-enabled* [*agent*] [*unit*]

Fetches the current enablement status of the specific unit file on the specific `bluechi-agent`.

### **bluechictl** *list-units* [*agent*]

Fetches information about all systemd units on the bluechi-agents. If [bluechi-agent] is not specified, all agents are queried.
Expand Down
1 change: 1 addition & 0 deletions src/agent/agent.c
Original file line number Diff line number Diff line change
Expand Up @@ -1763,6 +1763,7 @@ static const sd_bus_vtable internal_agent_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("ListUnits", "", UNIT_INFO_STRUCT_ARRAY_TYPESTRING, agent_method_list_units, 0),
SD_BUS_METHOD("ListUnitFiles", "", UNIT_FILE_INFO_STRUCT_ARRAY_TYPESTRING, agent_method_list_unit_files, 0),
SD_BUS_METHOD("GetUnitFileState", "s", "s", agent_method_passthrough_to_systemd, 0),
SD_BUS_METHOD("GetUnitProperties", "ss", "a{sv}", agent_method_get_unit_properties, 0),
SD_BUS_METHOD("GetUnitProperty", "sss", "v", agent_method_get_unit_property, 0),
SD_BUS_METHOD("SetUnitProperties", "sba(sv)", "", agent_method_set_unit_properties, 0),
Expand Down
12 changes: 12 additions & 0 deletions src/bindings/python/bluechi/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -880,6 +880,18 @@ def freeze_unit(self, name: str) -> None:
name,
)

def get_unit_file_state(self, file: str) -> str:
"""
GetUnitFileState:
@file: The name of the unit file
@state: The current enablement status of the unit file

Get the current enablement status of specific unit file.
"""
return self.get_proxy().GetUnitFileState(
file,
)

def get_unit_properties(self, name: str, interface: str) -> Structure:
"""
GetUnitProperties:
Expand Down
2 changes: 2 additions & 0 deletions src/client/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "method-enable-disable.h"
#include "method-freeze-thaw.h"
#include "method-help.h"
#include "method-is-enabled.h"
#include "method-kill.h"
#include "method-list-unit-files.h"
#include "method-list-units.h"
Expand Down Expand Up @@ -43,6 +44,7 @@ int method_version(UNUSED Command *command, UNUSED void *userdata) {
const Method methods[] = {
{ "help", 0, 0, OPT_NONE, method_help, usage_bluechi },
{ "list-unit-files", 0, 1, OPT_FILTER, method_list_unit_files, usage_method_list_unit_files },
{ "is-enabled", 2, 2, OPT_NONE, method_is_enabled, usage_method_is_enabled },
{ "list-units", 0, 1, OPT_FILTER, method_list_units, usage_method_list_units },
{ "start", 2, 2, OPT_NONE, method_start, usage_method_lifecycle },
{ "stop", 2, 2, OPT_NONE, method_stop, usage_method_lifecycle },
Expand Down
1 change: 1 addition & 0 deletions src/client/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ client_src = [
'main.c',
'client.c',
'method-help.c',
'method-is-enabled.c',
'method-loglevel.c',
'method-list-unit-files.c',
'method-list-units.c',
Expand Down
65 changes: 65 additions & 0 deletions src/client/method-is-enabled.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright Contributors to the Eclipse BlueChi project
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <stdlib.h>

#include "client.h"
#include "method-is-enabled.h"
#include "usage.h"

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

static int method_is_enabled_on(Client *client, char *node_name, char *unit_file) {
int r = 0;
_cleanup_sd_bus_error_ sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_sd_bus_message_ sd_bus_message *result = NULL;
const char *state = 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,
"GetUnitFileState",
&error,
&result,
"s",
unit_file);
if (r < 0) {
fprintf(stderr, "Failed to issue method call: %s\n", error.message);
return r;
}

r = sd_bus_message_read(result, "s", &state);
if (r < 0) {
fprintf(stderr, "Failed to read result of method call: %s\n", error.message);
return r;
}

printf("%s\n", state);
mwperina marked this conversation as resolved.
Show resolved Hide resolved

if (streq(state, "enabled") || streq(state, "enabled-runtime") || streq(state, "static") ||
streq(state, "alias") || streq(state, "indirect") || streq(state, "generated")) {
return 0;
}

return -1;
}

int method_is_enabled(Command *command, void *userdata) {
return method_is_enabled_on(userdata, command->opargv[0], command->opargv[1]);
}

void usage_method_is_enabled() {
usage_print_header();
usage_print_description("Check whether unit file is enabled");
usage_print_usage("bluechictl is-enabled [nodename] [unit]");
}
13 changes: 13 additions & 0 deletions src/client/method-is-enabled.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/*
* Copyright Contributors to the Eclipse BlueChi project
*
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#pragma once

#include <stddef.h>

#include "libbluechi/cli/command.h"

int method_is_enabled(Command *command, void *userdata);
void usage_method_is_enabled();
1 change: 1 addition & 0 deletions src/controller/node.c
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ static const sd_bus_vtable node_vtable[] = {
SD_BUS_VTABLE_START(0),
SD_BUS_METHOD("ListUnits", "", UNIT_INFO_STRUCT_ARRAY_TYPESTRING, node_method_list_units, 0),
SD_BUS_METHOD("ListUnitFiles", "", UNIT_FILE_INFO_STRUCT_ARRAY_TYPESTRING, node_method_list_unit_files, 0),
SD_BUS_METHOD("GetUnitFileState", "s", "s", node_method_passthrough_to_agent, 0),
SD_BUS_METHOD("StartUnit", "ss", "o", node_method_start_unit, 0),
SD_BUS_METHOD("StopUnit", "ss", "o", node_method_stop_unit, 0),
SD_BUS_METHOD("FreezeUnit", "s", "", node_method_passthrough_to_agent, 0),
Expand Down
19 changes: 19 additions & 0 deletions tests/bluechi_test/bluechictl.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,25 @@ def list_unit_files(
expected_result,
)

def is_enabled(
self,
node_name: str,
unit_name: str,
check_result: bool = False,
expected_result: int = 0,
) -> Tuple[Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]]]:
# track started units to stop and reset failures on cleanup
if node_name not in self.tracked_services:
self.tracked_services[node_name] = []
self.tracked_services[node_name].append(unit_name)

return self._run(
f"Fetching enablement status of unit '{unit_name}' on node '{node_name}'",
f"is-enabled {node_name} {unit_name}",
check_result,
expected_result,
)

def get_unit_status(
self,
node_name: str,
Expand Down
37 changes: 29 additions & 8 deletions tests/bluechi_test/systemctl.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,21 +117,42 @@ def is_unit_in_state(self, unit_name: str, expected_state: str) -> bool:
LOGGER.info(f"Got state '{latest_state}' for unit {unit_name}")
return latest_state == expected_state

def is_unit_enabled(
def enable_unit(
self, unit_name: str, check_result: bool = True, expected_result: int = 0
) -> bool:
_, output = self._do_operation_on_unit(
) -> Tuple[Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]]]:
self.tracked_services.append(unit_name)

return self._do_operation_on_unit(
unit_name, "enable", check_result, expected_result
)

def disable_unit(
self, unit_name: str, check_result: bool = True, expected_result: int = 0
) -> Tuple[Optional[int], Union[Iterator[bytes], Any, Tuple[bytes, bytes]]]:
self.tracked_services.append(unit_name)

return self._do_operation_on_unit(
unit_name, "disable", check_result, expected_result
)

def is_enabled(
self, unit_name: str, check_result: bool = False, expected_result: int = 0
) -> str:
return self._do_operation_on_unit(
unit_name, "is-enabled", check_result, expected_result
)
return output == "enabled"

def is_unit_enabled(
self, unit_name: str, check_result: bool = True, expected_result: int = 0
) -> bool:
_, out = self.is_enabled(unit_name=unit_name)
return "enabled" == out

def is_unit_disabled(
self, unit_name: str, check_result: bool = True, expected_result: int = 0
) -> bool:
_, output = self._do_operation_on_unit(
unit_name, "is-enabled", check_result, expected_result
)
return output == "disabled"
_, out = self.is_enabled(unit_name=unit_name)
return "disabled" == out

def service_is_active(self, unit_name: str) -> bool:
result, _ = self.client.exec_run(
Expand Down
3 changes: 3 additions & 0 deletions tests/tests/tier0/bluechictl-is-enabled/main.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
summary: Test if bluechictl is-enabled returns the same enablement status of a specific
node as running systemctl is-enabled on the node for all unit files
id: 387cdca2-fa98-4c35-8bd0-352572a7e206
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
#
# Copyright Contributors to the Eclipse BlueChi project
#
# SPDX-License-Identifier: LGPL-2.1-or-later

from typing import Dict

from bluechi_test.config import BluechiAgentConfig, BluechiControllerConfig
from bluechi_test.machine import BluechiAgentMachine, BluechiControllerMachine
from bluechi_test.systemd_lists import (
RegexPattern,
SystemdUnitFile,
parse_bluechictl_list_output,
)
from bluechi_test.test import BluechiTest

node_foo_name = "node-foo"


def check_execs(
ctrl: BluechiControllerMachine,
node: BluechiAgentMachine,
unit_name: str,
check_output: bool = True,
):
bc_res, bc_out = ctrl.bluechictl.is_enabled(
node_name=node_foo_name, unit_name=unit_name
)
sc_res, sc_out = node.systemctl.is_enabled(unit_name=unit_name)

assert bc_res == sc_res
if check_output:
assert bc_out == sc_out


def exec(ctrl: BluechiControllerMachine, nodes: Dict[str, BluechiAgentMachine]):
node_foo = nodes[node_foo_name]

# Traversing over existing unit files is the easiest way to cover all existing enablement statuses
all_res, all_out = ctrl.bluechictl.list_unit_files(node_name=node_foo_name)
assert all_res == 0
all_unit_files = parse_bluechictl_list_output(
content=all_out,
line_pattern=RegexPattern.BLUECHICTL_LIST_UNIT_FILES,
item_class=SystemdUnitFile,
)
for unit in all_unit_files[node_foo_name].values():
check_execs(ctrl=ctrl, node=node_foo, unit_name=unit.key)

# Error message from bluechictl is not completely the same as from systemctl for non-existent service
check_execs(
ctrl=ctrl, node=node_foo, unit_name="non-existent.service", check_output=False
)


def test_bluechi_list_unit_files_on_a_node(
bluechi_test: BluechiTest,
bluechi_ctrl_default_config: BluechiControllerConfig,
bluechi_node_default_config: BluechiAgentConfig,
):

node_foo_cfg = bluechi_node_default_config.deep_copy()
node_foo_cfg.node_name = node_foo_name

bluechi_ctrl_default_config.allowed_node_names = [node_foo_name]

bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config)
bluechi_test.add_bluechi_agent_config(node_foo_cfg)

bluechi_test.run(exec)