diff --git a/lib/redis/rediswriter-objects.cpp b/lib/redis/rediswriter-objects.cpp index 7923ff5ec..cbbee47b3 100644 --- a/lib/redis/rediswriter-objects.cpp +++ b/lib/redis/rediswriter-objects.cpp @@ -250,6 +250,16 @@ void RedisWriter::SendConfigUpdate(const ConfigObject::Ptr& object, bool useTran propertiesBlacklist.emplace("vars"); checkSums->Set("vars_checksum", CalculateCheckSumVars(customVarObject)); + + auto vars (SerializeVars(customVarObject)); + if (vars) { + auto varsJson (JsonEncode(vars)); + + Log(LogDebug, "RedisWriter") + << "HSET icinga:config:customvars:" << typeName << " " << objectKey << " " << varsJson; + + ExecuteQuery({ "HSET", "icinga:config:customvars:" + typeName, objectKey, varsJson }); + } } checkSums->Set("metadata_checksum", CalculateCheckSumMetadata(object)); diff --git a/lib/redis/rediswriter-utility.cpp b/lib/redis/rediswriter-utility.cpp index ad3a8b5ba..6553da679 100644 --- a/lib/redis/rediswriter-utility.cpp +++ b/lib/redis/rediswriter-utility.cpp @@ -27,6 +27,9 @@ #include "base/objectlock.hpp" #include "base/array.hpp" #include "base/scriptglobal.hpp" +#include +#include +#include using namespace icinga; @@ -89,6 +92,171 @@ String RedisWriter::CalculateCheckSumVars(const CustomVarObject::Ptr& object) return HashValue(vars); } +/** + * Collect the leaves of haystack in needles + * + * haystack = { + * "disks": { + * "disk": {}, + * "disk /": { + * "disk_partitions": "/" + * } + * } + * } + * + * path = [] + * + * needles = { + * "disks": [ + * [ + * ["disks", "disk /", "disk_partitions"], + * "/" + * ] + * ] + * } + * + * @param haystack A config object's custom vars as returned by {@link CustomVarObject#GetVars()} + * @param path Used for buffering only, shall be empty + * @param needles The result, a mapping from the top-level custom var key to a list of leaves with full path and value + */ +static void CollectScalarVars(Value haystack, std::vector& path, std::map>>& needles) +{ + switch (haystack.GetType()) { + case ValueObject: + { + const Object::Ptr& obj = haystack.Get(); + + Dictionary::Ptr dict = dynamic_pointer_cast(obj); + if (dict) { + for (auto& kv : dict) { + path.emplace_back(kv.first); + CollectScalarVars(kv.second, path, needles); + path.pop_back(); + } + break; + } + + Array::Ptr arr = dynamic_pointer_cast(obj); + if (arr) { + double index = 0.0; + for (auto& v : arr) { + path.emplace_back(index); + CollectScalarVars(v, path, needles); + path.pop_back(); + + index += 1.0; + } + break; + } + } + + haystack = Empty; + + case ValueString: + case ValueNumber: + case ValueBoolean: + case ValueEmpty: + needles[path[0].Get()].emplace_back(Array::FromVector(path), haystack); + break; + + default: + VERIFY(!"Invalid variant type."); + } +} + +/** + * Prepare object's custom vars for being written to Redis + * + * object.vars = { + * "disks": { + * "disk": {}, + * "disk /": { + * "disk_partitions": "/" + * } + * } + * } + * + * return { + * SHA1(PackObject([ + * Environment, + * "disks", + * { + * "disk": {}, + * "disk /": { + * "disk_partitions": "/" + * } + * } + * ])): { + * "env_checksum": SHA1(Environment), + * "name_checksum": SHA1("disks"), + * "name": "disks", + * "value": { + * "disk": {}, + * "disk /": { + * "disk_partitions": "/" + * } + * }, + * "flat": { + * SHA1(PackObject(["disks", "disk /", "disk_partitions"])): { + * "name": ["disks", "disk /", "disk_partitions"], + * "value": "/" + * } + * } + * } + * } + * + * @param object Config object with custom vars + * + * @return JSON-like data structure for Redis + */ +Dictionary::Ptr RedisWriter::SerializeVars(const CustomVarObject::Ptr& object) +{ + Dictionary::Ptr vars = object->GetVars(); + + if (!vars) + return nullptr; + + std::map>> scalarVars; + + { + std::vector pathBuf; + CollectScalarVars(vars, pathBuf, scalarVars); + } + + Dictionary::Ptr res = new Dictionary(); + auto env (GetEnvironment()); + auto envChecksum (SHA1(env)); + + for (auto& kv : vars) { + Dictionary::Ptr flatVars = new Dictionary(); + + { + auto it (scalarVars.find(kv.first)); + if (it != scalarVars.end()) { + for (auto& scalarVar : it->second) { + flatVars->Set(SHA1(PackObject(scalarVar.first)), (Dictionary::Ptr)new Dictionary({ + {"name", scalarVar.first}, + {"value", scalarVar.second} + })); + } + } + } + + res->Set( + SHA1(PackObject((Array::Ptr)new Array({env, kv.first, kv.second}))), + (Dictionary::Ptr)new Dictionary({ + {"env_checksum", envChecksum}, + {"name_checksum", SHA1(kv.first)}, + {"name", kv.first}, + {"value", kv.second}, + {"flat", flatVars} + }) + ); + } + + return res; +} + static const std::set propertiesBlacklistEmpty; String RedisWriter::HashValue(const Value& value) diff --git a/lib/redis/rediswriter.hpp b/lib/redis/rediswriter.hpp index 0856ca9f9..a8720e63e 100644 --- a/lib/redis/rediswriter.hpp +++ b/lib/redis/rediswriter.hpp @@ -84,6 +84,7 @@ private: static String CalculateCheckSumProperties(const ConfigObject::Ptr& object, const std::set& propertiesBlacklist); static String CalculateCheckSumMetadata(const ConfigObject::Ptr& object); static String CalculateCheckSumVars(const CustomVarObject::Ptr& object); + static Dictionary::Ptr SerializeVars(const CustomVarObject::Ptr& object); static String HashValue(const Value& value); static String HashValue(const Value& value, const std::set& propertiesBlacklist, bool propertiesWhitelist = false);