Skip to content

Commit

Permalink
Add watch option for bluechi status
Browse files Browse the repository at this point in the history
This PR will add watch option for 'bluechi status'.
I added monitor for the units that are requested to be watch.
And every time the status of the service changed the screen is cleared
and every the new status is printed .

Related: eclipse-bluechi#706
Signed-off-by: Artiom Divak <adivak@redhat.com>
  • Loading branch information
ArtiomDivak committed Apr 22, 2024
1 parent 5e0331f commit d565aa7
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 6 deletions.
4 changes: 2 additions & 2 deletions src/client/client.c
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,8 @@ int client_create_message_new_method_call(
int r = 0;
_cleanup_sd_bus_message_ sd_bus_message *outgoing_message = NULL;

free_and_null(client->object_path) r = assemble_object_path_string(
NODE_OBJECT_PATH_PREFIX, node_name, &client->object_path);
free_and_null(client->object_path);
r = assemble_object_path_string(NODE_OBJECT_PATH_PREFIX, node_name, &client->object_path);
if (r < 0) {
return r;
}
Expand Down
129 changes: 125 additions & 4 deletions src/client/method-status.c
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,21 @@ struct bus_properties_map {
size_t offset;
};

typedef struct MethodStatusChange MethodStatusChange;

struct MethodStatusChange {
Client *client;
char **units;
char *node_name;
size_t units_count;
size_t max_len;
};

void clear_screen() {
printf("\033[2J");
printf("\033[%d;%dH", 0, 0);
}

static const struct bus_properties_map property_map[] = {
{ "Id", offsetof(unit_info_t, id) },
{ "LoadState", offsetof(unit_info_t, load_state) },
Expand Down Expand Up @@ -205,7 +220,7 @@ static void print_unit_info(unit_info_t *unit_info, size_t name_col_width) {
return;
}

fprintf(stdout, "%s", unit_info->id);
fprintf(stdout, "%s", unit_info->id ? unit_info->id : "");
name_col_width -= unit_info->id ? strlen(unit_info->id) : 0;
name_col_width += PRINT_TAB_SIZE;
i = name_col_width;
Expand Down Expand Up @@ -277,10 +292,49 @@ static int get_status_unit_on(Client *client, char *node_name, char *unit_name,
return 0;
}

static int method_status_unit_on(Client *client, char *node_name, char **units, size_t units_count) {

static int on_unit_status_changed(UNUSED sd_bus_message *m, UNUSED void *userdata, UNUSED sd_bus_error *error) {
MethodStatusChange *s = (MethodStatusChange *) userdata;

char **units = s->units;
char *node_name = s->node_name;
Client *client = s->client;
size_t units_count = s->units_count;
size_t max_len = s->max_len;

clear_screen();
print_info_header(max_len);
for (unsigned i = 0; i < units_count; i++) {
int r = get_status_unit_on(client, node_name, units[i], max_len);
if (r < 0) {
fprintf(stderr,
"Failed to get status of unit %s on node %s - %s",
units[i],
node_name,
strerror(-r));
return r;
}
}

return 1;
}


static int method_status_unit_on(
Client *client, char *node_name, char **units, size_t units_count, bool do_watch) {
unsigned i = 0;

size_t max_name_len = get_max_name_len(units, units_count);
_cleanup_free_ MethodStatusChange *s = malloc0(sizeof(MethodStatusChange));
if (s == NULL) {
fprintf(stderr, "Failed to malloc memory for MethodStatusChange");
return ENOMEM;
}
s->client = client;
s->max_len = max_name_len;
s->node_name = node_name;
s->units = units;
s->units_count = units_count;

print_info_header(max_name_len);
for (i = 0; i < units_count; i++) {
Expand All @@ -293,9 +347,72 @@ static int method_status_unit_on(Client *client, char *node_name, char **units,
strerror(-r));
return r;
}
if (do_watch) {
_cleanup_sd_bus_error_ sd_bus_error error = SD_BUS_ERROR_NULL;
_cleanup_sd_bus_message_ sd_bus_message *reply = NULL;
char *monitor_path = NULL;
int r = 0;

r = sd_bus_call_method(
client->api_bus,
BC_INTERFACE_BASE_NAME,
BC_OBJECT_PATH,
CONTROLLER_INTERFACE,
"CreateMonitor",
&error,
&reply,
"");
if (r < 0) {
fprintf(stderr, "Failed to create monitor: %s\n", error.message);
return r;
}

r = sd_bus_message_read(reply, "o", &monitor_path);
if (r < 0) {
fprintf(stderr,
"Failed to parse create monitor response message: %s\n",
strerror(-r));
return r;
}
r = sd_bus_match_signal(
client->api_bus,
NULL,
BC_INTERFACE_BASE_NAME,
monitor_path,
MONITOR_INTERFACE,
"UnitStateChanged",
on_unit_status_changed,
s);
if (r < 0) {
fprintf(stderr,
"Failed to create callback for UnitStateChanged: %s\n",
strerror(-r));
return r;
}

_cleanup_sd_bus_message_ sd_bus_message *m = NULL;
r = sd_bus_message_new_method_call(
client->api_bus,
&m,
BC_INTERFACE_BASE_NAME,
monitor_path,
MONITOR_INTERFACE,
"Subscribe");
if (r < 0) {
fprintf(stderr, "Failed creating subscription call: %s\n", strerror(-r));
return r;
}
sd_bus_message_append(m, "ss", node_name, units[i]);
_cleanup_sd_bus_message_ sd_bus_message *subscription_reply = NULL;
r = sd_bus_call(client->api_bus, m, BC_DEFAULT_DBUS_TIMEOUT, &error, &subscription_reply);
if (r < 0) {
fprintf(stderr, "Failed to subscribe to monitor: %s\n", error.message);
return r;
}
}
}

return 0;
return do_watch ? client_start_event_loop(client) : 0;
}

/***************************************************************
Expand Down Expand Up @@ -660,6 +777,10 @@ int method_status(Command *command, void *userdata) {
userdata, command->opargv[0], command_flag_exists(command, ARG_WATCH_SHORT));
default:
return method_status_unit_on(
userdata, command->opargv[0], &command->opargv[1], command->opargc - 1);
userdata,
command->opargv[0],
&command->opargv[1],
command->opargc - 1,
command_flag_exists(command, ARG_WATCH_SHORT));
}
}
2 changes: 2 additions & 0 deletions tests/tests/tier0/bluechictl-status-watch-service/main.fmf
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
summary: Test client watch option by running in the controller container "bluechictl status" of a spasific node in the agent container and watching the status change when the service is down and up
id: bb9d4394-bd53-4d36-b8ba-ee91697c4810
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
#
# Copyright Contributors to the Eclipse BlueChi project
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import re
import unittest

from bluechi_machine_lib.bluechictl import BackgroundRunner, SimpleEventEvaluator

from bluechi.api import Node

node_foo_name = "node-foo"
simple_service = "simple.service"


class TestBluechictlStatusWatch(unittest.TestCase):

def setUp(self) -> None:
service_status_active = re.compile(r"^" + simple_service + ".* active")
service_status_inactive = re.compile(r"^" + simple_service + ".* inactive")
expected_patterns = {service_status_active, service_status_inactive}
args = ["status", node_foo_name, simple_service, "-w"]
self.evaluator = SimpleEventEvaluator(expected_patterns)
self.bgrunner = BackgroundRunner(args, self.evaluator)

def test_bluechictl_status_watch(self):
self.bgrunner.start()

# stop the bluechi controller service to trigger a disconnected message in the agent
# hacky solution to cause a disconnect:
# stop the controller service
# problem:
# dasbus will raise a DBusError for connection timeout on v1.4, v1.7 doesn't
# the command will be executed anyway, resulting in the desired shutdown of the node
# therefore, the DBusError is silenced for now, but all other errors are still raised
node = Node(node_foo_name)
node.stop_unit(simple_service, "replace")
node.start_unit(simple_service, "replace")
node.stop_unit(simple_service, "replace")

self.bgrunner.stop()
assert self.evaluator.processing_finished()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#
# Copyright Contributors to the Eclipse BlueChi project
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import os
from typing import Dict

from bluechi_test.config import BluechiAgentConfig, BluechiControllerConfig
from bluechi_test.machine import BluechiAgentMachine, BluechiControllerMachine
from bluechi_test.service import Option, Section, SimpleRemainingService
from bluechi_test.test import BluechiTest

NODE_FOO = "node-foo"


def exec(ctrl: BluechiControllerMachine, nodes: Dict[str, BluechiAgentMachine]):
node_foo = nodes[NODE_FOO]
service = SimpleRemainingService()
service.set_option(Section.Service, Option.ExecStart, "/bin/true")

node_foo.install_systemd_service(service)
ctrl.bluechictl.daemon_reload_node(NODE_FOO)
ctrl.bluechictl.start_unit(NODE_FOO, service.name)

result, _ = ctrl.run_python(os.path.join("python", "bluechictl_status_watch.py"))
assert result == 0


def test_bluechictl_status_watch_service(
bluechi_test: BluechiTest,
bluechi_node_default_config: BluechiAgentConfig,
bluechi_ctrl_default_config: BluechiControllerConfig,
):
node_foo_cfg = bluechi_node_default_config.deep_copy()
node_foo_cfg.node_name = NODE_FOO

bluechi_test.add_bluechi_agent_config(node_foo_cfg)

bluechi_ctrl_default_config.allowed_node_names = [NODE_FOO]
bluechi_test.set_bluechi_controller_config(bluechi_ctrl_default_config)
bluechi_test.run(exec)

0 comments on commit d565aa7

Please sign in to comment.