Skip to content

Commit

Permalink
implement compress-alarms actions
Browse files Browse the repository at this point in the history
After implementing alarm-history we also must implement actions for
compressing the status changes history. Those two actions delete all
status entries except the latest ones. [1]

We can reuse a part of already written Filter for purging alarms. There
is similar logic so let's subclass these two filters from the same class
implementing the filtering algorithm.

The compress action can filter according to the alarm resource. However,
in case we filter regular alarms, then it is more complicated (see [2],
this has to wait until we support regular expressions in libyang-cpp).

[1] https://datatracker.ietf.org/doc/rfc8632/
[2] #2

Change-Id: Iddcd94976f63f22a7a141faee674dd003424c5f5
  • Loading branch information
peckato1 committed Nov 25, 2024
1 parent 2c0ea2c commit 870fb63
Show file tree
Hide file tree
Showing 11 changed files with 361 additions and 43 deletions.
5 changes: 3 additions & 2 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,8 @@ add_library(alarms STATIC
src/alarms/Daemon.h
src/alarms/Key.cpp
src/alarms/Key.h
src/alarms/PurgeFilter.cpp
src/alarms/PurgeFilter.h
src/alarms/Filters.cpp
src/alarms/Filters.h
src/alarms/ShelfMatch.cpp
src/alarms/ShelfMatch.h
)
Expand Down Expand Up @@ -135,6 +135,7 @@ if(BUILD_TESTING)

ietfalarms_test(NAME alarm_publish FIXTURE fixture-alarms_testing)
ietfalarms_test(NAME alarm_purge FIXTURE fixture-alarms_testing)
ietfalarms_test(NAME alarm_compress FIXTURE fixture-alarms_testing)
ietfalarms_test(NAME alarm_notifications FIXTURE fixture-alarms_testing)
ietfalarms_test(NAME alarm_shelving FIXTURE fixture-alarms_testing)
ietfalarms_test(NAME alarm_summary FIXTURE fixture-alarms_testing)
Expand Down
42 changes: 40 additions & 2 deletions src/alarms/Daemon.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
#include <span>
#include <string>
#include "Daemon.h"
#include "Filters.h"
#include "Key.h"
#include "PurgeFilter.h"
#include "ShelfMatch.h"
#include "utils/benchmark.h"
#include "utils/libyang.h"
Expand All @@ -25,6 +25,8 @@ const auto shelvedAlarmList = "/ietf-alarms:alarms/shelved-alarms"s;
const auto shelvedAlarmListInstances = "/ietf-alarms:alarms/shelved-alarms/shelved-alarm";
const auto purgeRpcPrefix = "/ietf-alarms:alarms/alarm-list/purge-alarms";
const auto purgeShelvedRpcPrefix = "/ietf-alarms:alarms/shelved-alarms/purge-shelved-alarms";
const auto compressAlarmsRpcPrefix = "/ietf-alarms:alarms/alarm-list/compress-alarms";
const auto compressShelvedAlarmsRpcPrefix = "/ietf-alarms:alarms/shelved-alarms/compress-shelved-alarms";
const auto alarmInventoryPrefix = "/ietf-alarms:alarms/alarm-inventory";
const auto controlPrefix = "/ietf-alarms:alarms/control";
const auto ctrlNotifyStatusChanges = controlPrefix + "/notify-status-changes"s;
Expand Down Expand Up @@ -95,6 +97,8 @@ Daemon::Daemon()
});
m_alarmSub->onRPCAction(purgeRpcPrefix, [&](auto, auto, auto, const libyang::DataNode input, auto, auto, libyang::DataNode output) { return purgeAlarms(purgeRpcPrefix, input, output); });
m_alarmSub->onRPCAction(purgeShelvedRpcPrefix, [&](auto, auto, auto, const libyang::DataNode input, auto, auto, libyang::DataNode output) { return purgeAlarms(purgeShelvedRpcPrefix, input, output); });
m_alarmSub->onRPCAction(compressAlarmsRpcPrefix, [&](auto, auto, auto, const libyang::DataNode input, auto, auto, libyang::DataNode output) { return compressAlarms(compressAlarmsRpcPrefix, input, output); });
m_alarmSub->onRPCAction(compressShelvedAlarmsRpcPrefix, [&](auto, auto, auto, const libyang::DataNode input, auto, auto, libyang::DataNode output) { return compressAlarms(compressShelvedAlarmsRpcPrefix, input, output); });

{
utils::ScopedDatastoreSwitch sw(m_session, sysrepo::Datastore::Running);
Expand Down Expand Up @@ -490,7 +494,7 @@ sysrepo::ErrorCode Daemon::purgeAlarms(const std::string& rpcPath, const libyang
++it;
continue;
}
if (!filter.matches(entry)) {
if (!filter.matches(index, entry)) {
++it;
continue;
}
Expand All @@ -516,6 +520,40 @@ sysrepo::ErrorCode Daemon::purgeAlarms(const std::string& rpcPath, const libyang
return sysrepo::ErrorCode::Ok;
}

sysrepo::ErrorCode Daemon::compressAlarms(const std::string& rpcPath, const libyang::DataNode& rpcInput, libyang::DataNode output)
{
WITH_TIME_MEASUREMENT{};

std::vector<std::string> discardPaths;
libyang::DataNode edit = m_session.getContext().newPath(rootPath);

bool doingShelved = rpcPath == compressShelvedAlarmsRpcPrefix;
CompressFilter filter(rpcInput);
int compressedAlarmEntries = 0;

std::unique_lock lck{m_mtx};

for (auto& [key, alarm] : m_alarms) {
if (doingShelved == !!alarm.shelf && filter.matches(key, alarm)) {
auto discardTimestamps = alarm.shrinkStatusChanges(1);
std::transform(discardTimestamps.begin(), discardTimestamps.end(), std::back_inserter(discardPaths), [&](const auto& discardedTimestamp) {
return (doingShelved ? shelvedAlarmListInstances : alarmListInstances) + key.xpathIndex() + "/status-change[time='" + yangTimeFormat(discardedTimestamp) + "']";
});

if (!discardTimestamps.empty()) {
compressedAlarmEntries += 1;
}
}
}

utils::removeFromOperationalDS(m_session.getContext(), edit, discardPaths);
m_session.editBatch(edit, sysrepo::DefaultOperation::Merge);
m_session.applyChanges();

output.newPath(rpcPath + "/compressed-alarms", std::to_string(compressedAlarmEntries), libyang::CreationOptions::Output);
return sysrepo::ErrorCode::Ok;
}

namespace {

/** @brief Copy contents of shared leaves from existing alarm node into edit. */
Expand Down
1 change: 1 addition & 0 deletions src/alarms/Daemon.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ class Daemon {

sysrepo::ErrorCode submitAlarm(sysrepo::Session rpcSession, const libyang::DataNode& input);
sysrepo::ErrorCode purgeAlarms(const std::string& rpcPath, const libyang::DataNode& rpcInput, libyang::DataNode output);
sysrepo::ErrorCode compressAlarms(const std::string& rpcPath, const libyang::DataNode& rpcInput, libyang::DataNode output);
libyang::DataNode createStatusChangeNotification(const libyang::DataNode& alarmNode);
std::optional<std::string> inventoryValidationError(const InstanceKey& key, const int32_t severity);
void reshelve(sysrepo::Session running);
Expand Down
40 changes: 33 additions & 7 deletions src/alarms/PurgeFilter.cpp → src/alarms/Filters.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
#include <libyang-cpp/DataNode.hpp>
#include <libyang-cpp/Value.hpp>
#include "AlarmEntry.h"
#include "PurgeFilter.h"
#include "Filters.h"
#include "utils/libyang.h"

namespace {
Expand All @@ -23,10 +23,15 @@ T getValue(const libyang::DataNode& node)
}
namespace alarms {

bool AlarmFilter::matches(const InstanceKey& alarmKey, const AlarmEntry& alarmEntry) const
{
return std::all_of(m_filters.begin(), m_filters.end(), [&](const auto& filter) { return filter(alarmKey, alarmEntry); });
}

PurgeFilter::PurgeFilter(const libyang::DataNode& filterInput)
{
auto clearanceStatus = utils::childValue(filterInput, "alarm-clearance-status");
m_filters.emplace_back([clearanceStatus](const AlarmEntry& alarm) {
m_filters.emplace_back([clearanceStatus](const InstanceKey&, const AlarmEntry& alarm) {
if (clearanceStatus == "any") {
return true;
} else if (clearanceStatus == "cleared") {
Expand Down Expand Up @@ -54,7 +59,7 @@ PurgeFilter::PurgeFilter(const libyang::DataNode& filterInput)
throw std::logic_error("purge: Invalid choice value below severity");
}

m_filters.emplace_back([severityCheck](const AlarmEntry& alarm) {
m_filters.emplace_back([severityCheck](const InstanceKey&, const AlarmEntry& alarm) {
return severityCheck(alarm.lastSeverity);
});
}
Expand All @@ -76,15 +81,36 @@ PurgeFilter::PurgeFilter(const libyang::DataNode& filterInput)
throw std::logic_error("purge: Invalid choice value below older-than");
}

m_filters.emplace_back([threshold](const AlarmEntry& alarm) {
m_filters.emplace_back([threshold](const InstanceKey&, const AlarmEntry& alarm) {
return alarm.lastChanged < threshold;
});
}
}

bool PurgeFilter::matches(const AlarmEntry& alarm) const
CompressFilter::CompressFilter(const libyang::DataNode& filterInput)
{
return std::all_of(m_filters.begin(), m_filters.end(), [&](const Filter& filter) { return filter(alarm); });
}
if (auto resourceNode = filterInput.findPath("resource")) {
auto resource = resourceNode->asTerm().valueStr();
/* FIXME:
* for unshelved alarms, the type is resource-match, it is not enough to just compare the resource name, see https://github.com/CESNET/sysrepo-ietf-alarms/issues/2
*/
m_filters.emplace_back([resource](const InstanceKey& key, const AlarmEntry&) {
return key.resource == resource;
});
}

if (auto alarmTypeIdNode = filterInput.findPath("alarm-type-id")) {
auto alarmTypeId = alarmTypeIdNode->asTerm().valueStr();
m_filters.emplace_back([alarmTypeId](const InstanceKey& key, const AlarmEntry&) {
return key.type.id == alarmTypeId;
});
}

if (auto alarmTypeQualifierNode = filterInput.findPath("alarm-type-qualifier")) {
auto alarmTypeQualifier = alarmTypeQualifierNode->asTerm().valueStr();
m_filters.emplace_back([alarmTypeQualifier](const InstanceKey& key, const AlarmEntry&) {
return key.type.qualifier == alarmTypeQualifier;
});
}
}
}
40 changes: 40 additions & 0 deletions src/alarms/Filters.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright (C) 2022 CESNET, https://photonics.cesnet.cz/
*
* Written by Tomáš Pecka <[email protected]>
*
*/

#pragma once
#include <functional>
#include "alarms/Key.h"

namespace libyang {
class DataNode;
}

namespace alarms {

struct AlarmEntry;

class AlarmFilter {
public:
bool matches(const InstanceKey& key, const AlarmEntry& alarmNode) const;

protected:
AlarmFilter() = default; // disable public instantiation of this class
std::vector<std::function<bool(const InstanceKey&, const AlarmEntry&)>> m_filters;
};

class PurgeFilter : public AlarmFilter {
public:
PurgeFilter(const libyang::DataNode& filterInput);
};

class CompressFilter : public AlarmFilter {
public:
CompressFilter(const libyang::DataNode& filterInput);
};

}

4 changes: 2 additions & 2 deletions src/alarms/Key.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ struct Type {
std::string qualifier; /**< Dynamic qualifier, `alarm-type-qualifier` from RFC 8632 */

std::string xpathIndex() const;
bool operator==(const Type& other) const = default;
auto operator<=>(const Type& other) const = default;
};

inline std::size_t hash_value(const Type& t)
Expand All @@ -45,7 +45,7 @@ struct InstanceKey {

std::string xpathIndex() const;
static InstanceKey fromNode(const libyang::DataNode& node);
bool operator==(const InstanceKey& other) const = default;
auto operator<=>(const InstanceKey& other) const = default;
};

inline std::size_t hash_value(const InstanceKey& k)
Expand Down
30 changes: 0 additions & 30 deletions src/alarms/PurgeFilter.h

This file was deleted.

Loading

0 comments on commit 870fb63

Please sign in to comment.