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 21, 2024
1 parent d6fd2a9 commit fa07a8e
Show file tree
Hide file tree
Showing 5 changed files with 225 additions and 3 deletions.
1 change: 1 addition & 0 deletions src/client/client.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#include "libbluechi/bus/utils.h"
#include "libbluechi/common/common.h"
#include "libbluechi/service/shutdown.h"

#include "types.h"

Expand Down
131 changes: 128 additions & 3 deletions src/client/method-status.c
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
#include "libbluechi/common/opt.h"
#include "libbluechi/common/time-util.h"


typedef struct unit_info_t {
const char *id;
const char *load_state;
Expand All @@ -25,6 +26,25 @@ 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 free_method_status_change(MethodStatusChange *s) {
free(s);
}

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 @@ -277,10 +297,48 @@ 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) {
return 0;
}
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 +351,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 +781,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,50 @@
#
# Copyright Contributors to the Eclipse BlueChi project
#
# SPDX-License-Identifier: LGPL-2.1-or-later

import logging
import re
import unittest

from bluechi_machine_lib.bluechictl import BackgroundRunner, SimpleEventEvaluator
from dasbus.error import DBusError

from bluechi.api import Node

LOGGER = logging.getLogger(__name__)
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,44 @@
#
# 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

LOGGER = logging.getLogger(__name__)

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 fa07a8e

Please sign in to comment.