Management framework: add get_id_value dispatch

This adds support for retrieving the value of a global identifier from any
subset of cluster nodes. It relies on the lookup_ID() BiF to retrieve the val,
and to_json() to render the value to an easily parsed string. Ideally we'd send
the val directly, but this hits several roadblocks, including the fact that
Broker won't serialize arbitrary values.
This commit is contained in:
Christian Kreibich 2022-04-12 22:01:02 -07:00
parent 788348f9d6
commit 497b2723d7
5 changed files with 121 additions and 2 deletions

View file

@ -66,7 +66,11 @@ export {
## The controller sends this to every agent to request a dispatch (the ## The controller sends this to every agent to request a dispatch (the
## execution of a pre-implemented activity) to all cluster nodes. This ## execution of a pre-implemented activity) to all cluster nodes. This
## is the generic controller-agent "back-end" implementation of explicit ## is the generic controller-agent "back-end" implementation of explicit
## client-controller "front-end" interactions. ## client-controller "front-end" interactions, including:
##
## - :zeek:see:`Management::Controller::API::get_id_value_request`: two
## arguments, the first being "get_id_value" and the second the name
## of the ID to look up.
## ##
## reqid: a request identifier string, echoed in the response event. ## reqid: a request identifier string, echoed in the response event.
## ##

View file

@ -321,6 +321,10 @@ event Management::Node::API::node_dispatch_response(reqid: string, result: Manag
# confirm their type here based on the requested dispatch command. # confirm their type here based on the requested dispatch command.
switch req$node_dispatch_state$action[0] switch req$node_dispatch_state$action[0]
{ {
case "get_id_value":
if ( result?$data )
result$data = result$data as string;
break;
default: default:
Management::Log::error(fmt("unexpected dispatch command %s", Management::Log::error(fmt("unexpected dispatch command %s",
req$node_dispatch_state$action[0])); req$node_dispatch_state$action[0]));

View file

@ -80,6 +80,31 @@ export {
result: Management::ResultVec); result: Management::ResultVec);
## zeek-client sends this event to retrieve the current value of a
## variable in Zeek's global namespace, referenced by the given
## identifier (i.e., variable name). The controller asks all agents
## to retrieve this value from each cluster node, accumulates the
## returned responses, and responds with a get_id_value_response
## event back to the client.
##
## reqid: a request identifier string, echoed in the response event.
##
## id: the name of the variable whose value to retrieve.
global get_id_value_request: event(reqid: string, id: string);
## Response to a get_id_value_request event. The controller sends this
## back to the client.
##
## reqid: the request identifier used in the request event.
##
## result: a :zeek:type:`vector` of :zeek:see:`Management::Result`
## records. Each record covers one Zeek cluster node. Each record's
## data field contains a string with the JSON rendering (as produced
## by :zeek:id:`to_json`, including the error strings it potentially
## returns).
global get_id_value_response: event(reqid: string, result: Management::ResultVec);
# Testing events. These don't provide operational value but expose # Testing events. These don't provide operational value but expose
# internal functionality, triggered by test cases. # internal functionality, triggered by test cases.

View file

@ -42,7 +42,11 @@ export {
## and report their outcomes. See ## and report their outcomes. See
## :zeek:see:`Management::Agent::API::node_dispatch_request` and ## :zeek:see:`Management::Agent::API::node_dispatch_request` and
## :zeek:see:`Management::Agent::API::node_dispatch_response` for the ## :zeek:see:`Management::Agent::API::node_dispatch_response` for the
## agent/controller interaction. ## agent/controller interaction, and
## :zeek:see:`Management::Controller::API::get_id_value_request` and
## :zeek:see:`Management::Controller::API::get_id_value_response`
## for an example of a specific API the controller generalizes into
## a dispatch.
type NodeDispatchState: record { type NodeDispatchState: record {
## The dispatched action. The first string is a command, ## The dispatched action. The first string is a command,
## any remaining strings its arguments. ## any remaining strings its arguments.
@ -598,6 +602,10 @@ event Management::Agent::API::node_dispatch_response(reqid: string, results: Man
# type "any": confirm their (known) type here. # type "any": confirm their (known) type here.
switch req$node_dispatch_state$action[0] switch req$node_dispatch_state$action[0]
{ {
case "get_id_value":
if ( results[i]?$data )
results[i]$data = results[i]$data as string;
break;
default: default:
Management::Log::error(fmt("unexpected dispatch command %s", Management::Log::error(fmt("unexpected dispatch command %s",
req$node_dispatch_state$action[0])); req$node_dispatch_state$action[0]));
@ -620,6 +628,12 @@ event Management::Agent::API::node_dispatch_response(reqid: string, results: Man
# Send response event to the client based upon the dispatch type. # Send response event to the client based upon the dispatch type.
switch req$node_dispatch_state$action[0] switch req$node_dispatch_state$action[0]
{ {
case "get_id_value":
Management::Log::info(fmt(
"tx Management::Controller::API::get_id_value_response %s",
Management::Request::to_string(req)));
event Management::Controller::API::get_id_value_response(req$id, req$results);
break;
default: default:
Management::Log::error(fmt("unexpected dispatch command %s", Management::Log::error(fmt("unexpected dispatch command %s",
req$node_dispatch_state$action[0])); req$node_dispatch_state$action[0]));
@ -629,6 +643,42 @@ event Management::Agent::API::node_dispatch_response(reqid: string, results: Man
Management::Request::finish(req$id); Management::Request::finish(req$id);
} }
event Management::Controller::API::get_id_value_request(reqid: string, id: string)
{
Management::Log::info(fmt("rx Management::Controller::API::get_id_value_request %s %s", reqid, id));
# Special case: if we have no instances, respond right away.
if ( |g_instances| == 0 )
{
Management::Log::info(fmt("tx Management::Controller::API::get_id_value_response %s", reqid));
event Management::Controller::API::get_id_value_response(reqid, vector(
Management::Result($reqid=reqid, $success=F, $error="no instances connected")));
return;
}
local action = vector("get_id_value", id);
local req = Management::Request::create(reqid);
req$node_dispatch_state = NodeDispatchState($action=action);
for ( name in g_instances )
{
if ( name !in g_instances_ready )
next;
local agent_topic = Management::Agent::topic_prefix + "/" + name;
local areq = Management::Request::create();
areq$parent_id = req$id;
add req$node_dispatch_state$requests[areq$id];
Management::Log::info(fmt(
"tx Management::Agent::API::node_dispatch_request %s %s to %s",
areq$id, action, name));
Broker::publish(agent_topic, Management::Agent::API::node_dispatch_request, areq$id, action);
}
}
event Management::Request::request_expired(req: Management::Request::Request) event Management::Request::request_expired(req: Management::Request::Request)
{ {
# Various handlers for timed-out request state. We use the state members # Various handlers for timed-out request state. We use the state members
@ -665,6 +715,12 @@ event Management::Request::request_expired(req: Management::Request::Request)
switch req$node_dispatch_state$action[0] switch req$node_dispatch_state$action[0]
{ {
case "get_id_value":
Management::Log::info(fmt(
"tx Management::Controller::API::get_id_value_response %s",
Management::Request::to_string(req)));
event Management::Controller::API::get_id_value_response(req$id, req$results);
break;
default: default:
Management::Log::error(fmt("unexpected dispatch command %s", Management::Log::error(fmt("unexpected dispatch command %s",
req$node_dispatch_state$action[0])); req$node_dispatch_state$action[0]));
@ -717,6 +773,7 @@ event zeek_init()
Management::Controller::API::get_instances_response, Management::Controller::API::get_instances_response,
Management::Controller::API::set_configuration_response, Management::Controller::API::set_configuration_response,
Management::Controller::API::get_nodes_response, Management::Controller::API::get_nodes_response,
Management::Controller::API::get_id_value_response,
Management::Controller::API::test_timeout_response Management::Controller::API::test_timeout_response
]; ];

View file

@ -19,7 +19,36 @@ redef Management::Log::role = Management::NODE;
## provided result record. ## provided result record.
type DispatchCallback: function(args: vector of string, res: Management::Result); type DispatchCallback: function(args: vector of string, res: Management::Result);
## Implementation of the "get_id_value" dispatch. Its only argument is the name
## of the ID to look up.
function dispatch_get_id_value(args: vector of string, res: Management::Result)
{
if ( |args| == 0 )
{
res$success = F;
res$error = "get_id_value expects name of global identifier";
return;
}
local val = lookup_ID(args[0]);
# The following lookup_ID() result strings indicate errors:
if ( type_name(val) == "string" )
{
local valstr: string = val;
if ( valstr == "<unknown id>" || valstr == "<no ID value>" )
{
res$success = F;
res$error = valstr[1:-1];
}
}
if ( res$success )
res$data = to_json(val);
}
global g_dispatch_table: table[string] of DispatchCallback = { global g_dispatch_table: table[string] of DispatchCallback = {
["get_id_value"] = dispatch_get_id_value,
}; };
event Management::Node::API::node_dispatch_request(reqid: string, action: vector of string) event Management::Node::API::node_dispatch_request(reqid: string, action: vector of string)