Add support for setting environment variables via supervisor

The NodeConfig record now has a table for specifying environment variable names
and values, which the supervisor sets in the created node.

This also repositions the cpu_affinity member to keep the order the same in
the corresponding script-layer and in-core types.

Includes testcase.
This commit is contained in:
Christian Kreibich 2021-07-06 17:06:39 -07:00
parent 39f96d4720
commit 36051dc9a1
6 changed files with 120 additions and 0 deletions

View file

@ -44,6 +44,8 @@ export {
stderr_file: string &optional;
## Additional script filenames/paths that the node should load.
scripts: vector of string &default = vector();
## Environment variables to define in the supervised node.
env: table[string] of string &default=table();
## A cpu/core number to which the node will try to pin itself.
cpu_affinity: int &optional;
## The Cluster Layout definition. Each node in the Cluster Framework

View file

@ -1259,6 +1259,21 @@ Supervisor::NodeConfig Supervisor::NodeConfig::FromRecord(const RecordVal* node)
rval.scripts.emplace_back(std::move(script));
}
auto env_table_val = node->GetField("env")->AsTableVal();
auto env_table = env_table_val->AsTable();
for ( const auto& ee : *env_table )
{
auto k = ee.GetHashKey();
auto* v = ee.GetValue<TableEntryVal*>();
auto key = env_table_val->RecreateIndex(*k);
auto name = key->Idx(0)->AsStringVal()->ToStdString();
auto val = v->GetVal()->AsStringVal()->ToStdString();
rval.env[name] = val;
}
auto cluster_table_val = node->GetField("cluster")->AsTableVal();
auto cluster_table = cluster_table_val->AsTable();
@ -1314,6 +1329,11 @@ Supervisor::NodeConfig Supervisor::NodeConfig::FromJSON(std::string_view json)
for ( auto it = scripts.Begin(); it != scripts.End(); ++it )
rval.scripts.emplace_back(it->GetString());
auto& env = j["env"];
for ( auto it = env.MemberBegin(); it != env.MemberEnd(); ++it )
rval.env[it->name.GetString()] = it->value.GetString();
auto& cluster = j["cluster"];
for ( auto it = cluster.MemberBegin(); it != cluster.MemberEnd(); ++it )
@ -1373,6 +1393,17 @@ RecordValPtr Supervisor::NodeConfig::ToRecord() const
rval->AssignField("scripts", std::move(scripts_val));
auto et = rt->GetFieldType<TableType>("env");
auto env_val = make_intrusive<TableVal>(std::move(et));
rval->AssignField("env", env_val);
for ( const auto& e : env )
{
auto name = make_intrusive<StringVal>(e.first);
auto val = make_intrusive<StringVal>(e.second);
env_val->Assign(std::move(name), std::move(val));
}
auto tt = rt->GetFieldType<TableType>("cluster");
auto cluster_val = make_intrusive<TableVal>(std::move(tt));
rval->AssignField("cluster", cluster_val);
@ -1454,6 +1485,7 @@ bool SupervisedNode::InitCluster() const
{
const auto& node_name = e.first;
const auto& ep = e.second;
auto key = make_intrusive<StringVal>(node_name);
auto val = make_intrusive<RecordVal>(cluster_node_type);
@ -1533,6 +1565,19 @@ void SupervisedNode::Init(Options* options) const
node_name.data(), strerror(errno));
}
if ( ! config.env.empty() )
{
for ( const auto& e : config.env )
{
if ( setenv(e.first.c_str(), e.second.c_str(), true) == -1 )
{
fprintf(stderr, "node '%s' failed to setenv: %s\n",
node_name.data(), strerror(errno));
exit(1);
}
}
}
if ( ! config.cluster.empty() )
{
if ( setenv("CLUSTER_NODE", node_name.data(), true) == -1 )

View file

@ -190,6 +190,10 @@ public:
* Additional script filename/paths that the node should load.
*/
std::vector<std::string> scripts;
/**
* Environment variables and values to define in the node.
*/
std::map<std::string, std::string> env;
/**
* The Cluster Layout definition. Each node in the Cluster Framework
* knows about the full, static cluster topology to which it belongs.

View file

@ -0,0 +1,3 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
supervised node zeek_init() with env foo=bar
supervised node zeek_done()

View file

@ -0,0 +1,4 @@
### BTest baseline data generated by btest-diff. Do not edit. Use "btest -U/-u" to update. Requires BTest >= 0.63.
supervisor zeek_init()
destroying node
supervisor zeek_done()

View file

@ -0,0 +1,62 @@
# @TEST-PORT: BROKER_PORT
# @TEST-EXEC: btest-bg-run zeek zeek -j -b %INPUT
# @TEST-EXEC: btest-bg-wait 30
# @TEST-EXEC: btest-diff zeek/supervisor.out
# @TEST-EXEC: btest-diff zeek/node.out
# So the supervised node doesn't terminate right away.
redef exit_only_after_terminate=T;
global supervisor_output_file: file;
global node_output_file: file;
global topic = "test-topic";
event do_destroy()
{
print supervisor_output_file, "destroying node";
Supervisor::destroy("grault");
}
event zeek_init()
{
if ( Supervisor::is_supervisor() )
{
Broker::subscribe(topic);
Broker::listen("127.0.0.1", to_port(getenv("BROKER_PORT")));
supervisor_output_file = open("supervisor.out");
print supervisor_output_file, "supervisor zeek_init()";
local sn = Supervisor::NodeConfig($name="grault", $env=table(
["foo"] = "bar"
));
local res = Supervisor::create(sn);
if ( res != "" )
print supervisor_output_file, res;
}
else
{
Broker::peer("127.0.0.1", to_port(getenv("BROKER_PORT")));
node_output_file = open("node.out");
print node_output_file, fmt("supervised node zeek_init() with env foo=%s", getenv("foo"));
}
}
event Broker::peer_added(endpoint: Broker::EndpointInfo, msg: string)
{
if ( Supervisor::is_supervised() )
Broker::publish(topic, do_destroy);
}
event Broker::peer_lost(endpoint: Broker::EndpointInfo, msg: string)
{
# Should only be run by supervisor
terminate();
}
event zeek_done()
{
if ( Supervisor::is_supervised() )
print node_output_file, "supervised node zeek_done()";
else
print supervisor_output_file, "supervisor zeek_done()";
}