From 9beea3525dcd681e0b26579da660e1fdd1d69127 Mon Sep 17 00:00:00 2001 From: mazora Date: Mon, 18 Mar 2024 14:18:04 +0200 Subject: [PATCH 01/16] First Commit created schedulermgrd and schedulermgr files --- cfgmgr/schedulermgr.cpp | 154 +++++++++++++++++++++++++++++++++++++++ cfgmgr/schedulermgr.h | 38 ++++++++++ cfgmgr/schedulermgrd.cpp | 118 ++++++++++++++++++++++++++++++ 3 files changed, 310 insertions(+) create mode 100644 cfgmgr/schedulermgr.cpp create mode 100644 cfgmgr/schedulermgr.h create mode 100644 cfgmgr/schedulermgrd.cpp diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp new file mode 100644 index 0000000000..180f4e8698 --- /dev/null +++ b/cfgmgr/schedulermgr.cpp @@ -0,0 +1,154 @@ +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "producerstatetable.h" +#include "tokenize.h" +#include "ipprefix.h" +#include "timer.h" +#include "schedulermgr.h" +#include "exec.h" +#include "shellcmd.h" +#include "warm_restart.h" +#include "converter.h" + +using namespace std; +using namespace swss; + +#define PORT_NAME_GLOBAL "global" + +SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, string pg_lookup_file, const vector &tableNames) : + Orch{cfgDb, tableNames}, + m_cfgTimeRangeTable{cfgDb, CFG_TIME_RANGE_TABLE_NAME}, + m_cfgScheduledConfigurationTable{cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME}, + m_stateTimeRangeStatusTable{stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME} +{} + +void writeSystemdUnitFile(const string& fileNameWithoutExtension, const string& taskType, const string& taskCommand) { + string unitFileName = fileNameWithoutExtension + ".service"; + ofstream unitFile(unitFileName); + unitFile << "[Unit]\n"; + unitFile << "Description=" << fileNameWithoutExtension << " service\n\n"; + unitFile << "[Service]\n"; + unitFile << "Type=" << taskType << "\n"; + unitFile << "ExecStart=" << taskCommand << "\n"; + unitFile.close(); + + SWSS_LOG_DEBUG("Systemd unit file for %s has been created", fileNameWithoutExtension.c_str()); +} + +void writeSystemdTimerFile(const string& fileNameWithoutExtension, const string& taskOnCalendar, bool isPersistent) { + string timerFileName = fileNameWithoutExtension + ".timer"; + ofstream timerFile(timerFileName); + timerFile << "[Unit]\n"; + timerFile << "Description=Timer for " << fileNameWithoutExtension << " service\n\n"; + timerFile << "[Timer]\n"; + timerFile << "OnCalendar=" << taskOnCalendar << "\n"; + if (isPersistent) { + timerFile << "Persistent=true\n"; + } + timerFile << "\n" + timerFile << "[Install]\n"; + timerFile << "WantedBy=timers.target\n"; + timerFile.close(); + + SWSS_LOG_DEBUG("Systemd timer file for %s has been created", fileNameWithoutExtension.c_str()); +} + +void writeSystemdFiles(const string& taskName, const string& startTime, const string& endTime) { + // Service file for enabling the task + string enableServiceFileName = taskName + "-enable"; + writeSystemdUnitFile(enableServiceFileName, "oneshot", "/bin/echo 'Starting " + taskName + "'"); // TODO Replace with actual command + + // Timer file for enabling the task + string enableTimerFileName = taskName + "-enable"; + writeSystemdTimerFile(enableTimerFileName, startTime, false); + + // Service file for disabling the task + string disableServiceFileName = taskName + "-disable"; + writeSystemdUnitFile(disableServiceFileName, "oneshot", "/bin/echo 'Stopping " + taskName + "'"); // TODO Replace with actual command + + // Timer file for disabling the task + string disableTimerFileName = taskName + "-disable"; + writeSystemdTimerFile(disableTimerFileName, endTime, false); + + SWSS_LOG_INFO("Systemd files for %s have been created", taskName.c_str()) +} + + +task_process_status SchedulerMgr::doTimeRangeTask(string rangeName, vector fieldValues){ + SWSS_LOG_ENTER(); + string start = ""; + string end = ""; + + for (auto i : fieldValues) + { + if (fvField(i) == "start"){ + start = fvValue(i); + } else if(fvField(i) == "end"){ + end = fvValue(i); + } else{ + SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); + // Can skip instead of returning invalid entry + return task_process_status::task_invalid_entry; + } + } + + if (start == "" || end == ""){ + SWSS_LOG_ERROR("Time range %s is missing start or end time", rangeName.c_str()); + return task_process_status::task_invalid_entry; + } + + // Create systemd files for time range + writeSystemdFiles(rangeName, start, end); + + // Add time range status to range status table in state db + m_stateTimeRangeStatusTable.set(rangeName, TIME_RANGE_DISABLED); + + return task_process_status::task_success; +} + +void SchedulerMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string keySeparator = CONFIGDB_KEY_SEPARATOR; + vector keys = tokenize(kfvKey(t), keySeparator[0]); + string rangeName(keys[0]); + + string op = kfvOp(t); + task_process_status task_status = task_process_status::task_success; + if (op == SET_COMMAND) + { + if (table_name == CFG_TIME_RANGE_TABLE_NAME) + { + doTimeRangeTask(rangeName, kfvFieldsValues(t)); + } + } + switch (task_status) + { + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process table update"); + return; + case task_process_status::task_need_retry: + SWSS_LOG_INFO("Unable to process table update. Will retry..."); + ++it; + break; + case task_process_status::task_invalid_entry: + SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); + it = consumer.m_toSync.erase(it); + break; + default: + it = consumer.m_toSync.erase(it); + break; + } + } +} diff --git a/cfgmgr/schedulermgr.h b/cfgmgr/schedulermgr.h new file mode 100644 index 0000000000..756cddcce1 --- /dev/null +++ b/cfgmgr/schedulermgr.h @@ -0,0 +1,38 @@ +#ifndef __SCHEDULERMGR__ +#define __SCHEDULERMGR__ + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "orch.h" + +#include +#include + +namespace swss { + +#define TIME_RANGE_ENABLED "enabled" +#define TIME_RANGE_DISABLED "disabled" + +class SchedulerMgr : public Orch +{ +public: + SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const std::vector &tableNames); + using Orch::doTask; + +private: + + Table m_cfgTimeRangeTable; + Table m_cfgScheduledConfigurationTable; + + ProducerStateTable m_stateTimeRangeStatusTable; + + void writeSystemdUnitFile(string taskName, string taskType, string taskCommand); + void writeSystemdTimerFile(string taskName, string taskOnCalendar, bool isPersistent); + void writeSystemdFiles(string rangeName, string start, string end); + doTimeRangeTask doTimeRangeTask(string rangeName, vector &fieldValues); + void doTask(Consumer &consumer); +}; + +} + +#endif /* __SCHEDULERMGR__ */ diff --git a/cfgmgr/schedulermgrd.cpp b/cfgmgr/schedulermgrd.cpp new file mode 100644 index 0000000000..72b92809e4 --- /dev/null +++ b/cfgmgr/schedulermgrd.cpp @@ -0,0 +1,118 @@ +#include +#include +#include +#include +#include "dbconnector.h" +#include "select.h" +#include "exec.h" +#include "schema.h" +#include "schedulermgr.h" +#include +#include +#include "json.h" +#include +#include "warm_restart.h" + +using namespace std; +using namespace swss; +using json = nlohmann::json; + +/* SELECT() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +void usage() +{ + cout << "Usage: schedulermgrd <-l pg_lookup.ini|-a asic_table.json [-p peripheral_table.json] [-z zero_profiles.json]>" << endl; + cout << " -l pg_lookup.ini: PG profile look up table file (mandatory for static mode)" << endl; + cout << " format: csv" << endl; + cout << " values: 'speed, cable, size, xon, xoff, dynamic_threshold, xon_offset'" << endl; + cout << " -a asic_table.json: ASIC-specific parameters definition (mandatory for dynamic mode)" << endl; + cout << " -p peripheral_table.json: Peripheral (eg. gearbox) parameters definition (optional for dynamic mode)" << endl; + cout << " -z zero_profiles.json: Zero profiles definition for reclaiming unused buffers (optional for dynamic mode)" << endl; +} + + +int main(int argc, char **argv) +{ + int opt; + + Logger::linkToDbNative("schedulermgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting schedulermgrd ---"); + + while ((opt = getopt(argc, argv, "l:a:p:z:h")) != -1 ) + { + switch (opt) + { + case 'l': + pg_lookup_file = optarg; + break; + case 'h': + usage(); + return 1; + case 'a': + asic_table_file = optarg; + break; + case 'p': + peripherial_table_file = optarg; + break; + case 'z': + zero_profile_file = optarg; + break; + default: /* '?' */ + usage(); + return EXIT_FAILURE; + } + } + + try + { + std::vector cfgOrchList; + + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector stateDb("STATE_DB", 0); + + + vector cfg_buffer_tables = { + CFG_TIME_RANGE_TABLE_NAME, + CFG_SCHEDULED_CONFIGURATION_TABLE_NAME + }; + cfgOrchList.emplace_back(new SchedulerMgr(&cfgDb, &stateDb, cfg_buffer_tables)); + + auto schedulermgr = cfgOrchList[0]; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + SWSS_LOG_NOTICE("starting main loop"); + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + schedulermgr->doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} From e776f3034e5834557d8adf8129d994a1bb687e76 Mon Sep 17 00:00:00 2001 From: mazora Date: Wed, 20 Mar 2024 17:12:30 +0200 Subject: [PATCH 02/16] Fixed bugs and Added Schedulermgr(d) to Makefile Fixed minor and return task_process_status Added systemd infrastructure functions to reload and update Removed options for schedulermgrd application --- cfgmgr/Makefile.am | 9 ++- cfgmgr/schedulermgr.cpp | 124 +++++++++++++++++++++++++++------------ cfgmgr/schedulermgr.h | 23 +++++--- cfgmgr/schedulermgrd.cpp | 33 ----------- 4 files changed, 112 insertions(+), 77 deletions(-) diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index a8cbddb4e7..bd0265461d 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -5,7 +5,7 @@ LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 SAIMETA_LIBS = -lsaimeta -lsaimetadata -lzmq COMMON_LIBS = -lswsscommon -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd schedulermgrd cfgmgrdir = $(datadir)/swss @@ -96,6 +96,11 @@ tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CF tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) tunnelmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) +schedulermgrd_SOURCES = schedulermgrd.cpp schedulermgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +schedulermgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +schedulermgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +schedulermgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) + macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) macsecmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) @@ -115,6 +120,7 @@ sflowmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp natmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp coppmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp tunnelmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp +schedulermgrd_SOURCES += ../gcovpreload/gcovpreload.cpp macsecmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp endif @@ -133,5 +139,6 @@ coppmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp tunnelmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp macsecmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp fabricmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp +schedulermgrd_SOURCES += $(top_srcdir)/lib/asan.cpp endif diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index 180f4e8698..3f8af06517 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -18,77 +18,126 @@ using namespace swss; #define PORT_NAME_GLOBAL "global" -SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, string pg_lookup_file, const vector &tableNames) : - Orch{cfgDb, tableNames}, - m_cfgTimeRangeTable{cfgDb, CFG_TIME_RANGE_TABLE_NAME}, - m_cfgScheduledConfigurationTable{cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME}, - m_stateTimeRangeStatusTable{stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME} +SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames): + Orch(cfgDb, tableNames), + m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), + m_cfgScheduledConfigurationTable(cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME), + m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) {} -void writeSystemdUnitFile(const string& fileNameWithoutExtension, const string& taskType, const string& taskCommand) { - string unitFileName = fileNameWithoutExtension + ".service"; - ofstream unitFile(unitFileName); +bool SchedulerMgr::reloadSystemd() { + // Reload systemd to recognize changes + return system(SYSTEMD_RELOAD_CMD_STR) == 0; +} + +bool SchedulerMgr::enableSystemdTimer(const std::string& timerFileName) { + // Enable the systemd timer + string enableTimerCmd = SYSTEMD_TIMER_ENABLE_PREFIX_CMD_STR + timerFileName; + return system(enableTimerCmd.c_str()) == 0; +} + +task_process_status SchedulerMgr::writeSystemdUnitFile(const string& fileName, const string& taskType, const string& taskCommand) { + string unitFilePath = SYSTEMD_FILES_PATH_PREFIX_STR + fileName; + ofstream unitFile{unitFilePath}; + if (unitFile.fail()) { + SWSS_LOG_ERROR("Failed to create systemd file for %s", fileName.c_str()); + return task_process_status::task_need_retry; + } unitFile << "[Unit]\n"; - unitFile << "Description=" << fileNameWithoutExtension << " service\n\n"; + unitFile << "Description=" << fileName << " service\n\n"; unitFile << "[Service]\n"; unitFile << "Type=" << taskType << "\n"; unitFile << "ExecStart=" << taskCommand << "\n"; unitFile.close(); - SWSS_LOG_DEBUG("Systemd unit file for %s has been created", fileNameWithoutExtension.c_str()); + SWSS_LOG_DEBUG("Systemd unit file for %s has been created", fileName.c_str()); + return task_process_status::task_success; } -void writeSystemdTimerFile(const string& fileNameWithoutExtension, const string& taskOnCalendar, bool isPersistent) { - string timerFileName = fileNameWithoutExtension + ".timer"; - ofstream timerFile(timerFileName); +task_process_status SchedulerMgr::writeSystemdTimerFile(const string& fileName, const string& taskOnCalendar, bool isPersistent) { + string timerFilePath = SYSTEMD_FILES_PATH_PREFIX_STR + fileName; + ofstream timerFile{timerFilePath}; + if (timerFile.fail()) { + SWSS_LOG_ERROR("Failed to create systemd timer file for %s", fileName.c_str()); + return task_process_status::task_need_retry; + } timerFile << "[Unit]\n"; - timerFile << "Description=Timer for " << fileNameWithoutExtension << " service\n\n"; + timerFile << "Description=Timer for " << fileName << " service\n\n"; timerFile << "[Timer]\n"; timerFile << "OnCalendar=" << taskOnCalendar << "\n"; if (isPersistent) { timerFile << "Persistent=true\n"; } - timerFile << "\n" + timerFile << "\n"; timerFile << "[Install]\n"; timerFile << "WantedBy=timers.target\n"; timerFile.close(); - SWSS_LOG_DEBUG("Systemd timer file for %s has been created", fileNameWithoutExtension.c_str()); + SWSS_LOG_DEBUG("Systemd timer file for %s has been created", fileName.c_str()); + return task_process_status::task_success; } -void writeSystemdFiles(const string& taskName, const string& startTime, const string& endTime) { +// TODO add rollback mechanism +task_process_status SchedulerMgr::createSystemdFiles(const string& taskName, const string& startTime, const string& endTime) { // Service file for enabling the task - string enableServiceFileName = taskName + "-enable"; - writeSystemdUnitFile(enableServiceFileName, "oneshot", "/bin/echo 'Starting " + taskName + "'"); // TODO Replace with actual command + // TODO Replace with actual command + string enableServiceFileName = taskName + "-enable" + SYSTEMD_UNIT_FILE_EXTENSION_STR; + if (writeSystemdUnitFile(enableServiceFileName, "oneshot", "/bin/echo 'Starting " + taskName + "'") != task_process_status::task_success) { + return task_process_status::task_need_retry; + } // Timer file for enabling the task - string enableTimerFileName = taskName + "-enable"; - writeSystemdTimerFile(enableTimerFileName, startTime, false); + string enableTimerFileName = taskName + "-enable" + SYSTEMD_TIMER_FILE_EXTENSION_STR; + if (writeSystemdTimerFile(enableTimerFileName, startTime, false) != task_process_status::task_success) { + return task_process_status::task_need_retry; + } // Service file for disabling the task - string disableServiceFileName = taskName + "-disable"; - writeSystemdUnitFile(disableServiceFileName, "oneshot", "/bin/echo 'Stopping " + taskName + "'"); // TODO Replace with actual command + // TODO Replace with actual command + string disableServiceFileName = taskName + "-disable" + SYSTEMD_UNIT_FILE_EXTENSION_STR; + if (writeSystemdUnitFile(disableServiceFileName, "oneshot", "/bin/echo 'Stopping " + taskName + "'") != task_process_status::task_success) { + return task_process_status::task_need_retry; + } // Timer file for disabling the task - string disableTimerFileName = taskName + "-disable"; - writeSystemdTimerFile(disableTimerFileName, endTime, false); + string disableTimerFileName = taskName + "-disable" + SYSTEMD_TIMER_FILE_EXTENSION_STR; + if (writeSystemdTimerFile(disableTimerFileName, endTime, false) != task_process_status::task_success) { + return task_process_status::task_need_retry; + } - SWSS_LOG_INFO("Systemd files for %s have been created", taskName.c_str()) -} + SWSS_LOG_INFO("Succesfully created Systemd files for %s", taskName.c_str()); + + // Reload systemd to pick up changes, then enable systemd timer files + if (!reloadSystemd()) { + SWSS_LOG_ERROR("Failed to reload systemd"); + return task_process_status::task_need_retry; + } + if (!enableSystemdTimer(enableTimerFileName) || !enableSystemdTimer(disableTimerFileName)) { + SWSS_LOG_ERROR("Failed to enable systemd timer files for %s", taskName.c_str()); + return task_process_status::task_need_retry; + } + SWSS_LOG_DEBUG("Succesfully reloaded and enabled systemd timer files for %s", taskName.c_str()); + return task_process_status::task_success; +} -task_process_status SchedulerMgr::doTimeRangeTask(string rangeName, vector fieldValues){ +task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const vector &fieldValues){ SWSS_LOG_ENTER(); string start = ""; string end = ""; - for (auto i : fieldValues) + // Set time range status to disabled by default + vector stateTableFieldValues; + string key = rangeName; + stateTableFieldValues.emplace_back(FieldValueTuple(TIME_RANGE_STATUS_STR, TIME_RANGE_DISABLED_STR)); + + for (const auto &i : fieldValues) { if (fvField(i) == "start"){ start = fvValue(i); - } else if(fvField(i) == "end"){ + } else if (fvField(i) == "end"){ end = fvValue(i); - } else{ + } else { SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); // Can skip instead of returning invalid entry return task_process_status::task_invalid_entry; @@ -100,11 +149,14 @@ task_process_status SchedulerMgr::doTimeRangeTask(string rangeName, vector &fieldValues); + + bool reloadSystemd(); + bool enableSystemdTimer(const std::string& timerFileName); + task_process_status writeSystemdUnitFile(const std::string& taskName, const std::string& taskType, const std::string& taskCommand); + task_process_status writeSystemdTimerFile(const std::string& taskName, const std::string& taskOnCalendar, const bool isPersistent); + task_process_status createSystemdFiles(const std::string& rangeName, const std::string& start, const std::string& end); + task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); void doTask(Consumer &consumer); }; diff --git a/cfgmgr/schedulermgrd.cpp b/cfgmgr/schedulermgrd.cpp index 72b92809e4..f81cea2236 100644 --- a/cfgmgr/schedulermgrd.cpp +++ b/cfgmgr/schedulermgrd.cpp @@ -1,5 +1,4 @@ #include -#include #include #include #include "dbconnector.h" @@ -9,13 +8,9 @@ #include "schedulermgr.h" #include #include -#include "json.h" -#include -#include "warm_restart.h" using namespace std; using namespace swss; -using json = nlohmann::json; /* SELECT() function timeout retry time, in millisecond */ #define SELECT_TIMEOUT 1000 @@ -34,38 +29,11 @@ void usage() int main(int argc, char **argv) { - int opt; - Logger::linkToDbNative("schedulermgrd"); SWSS_LOG_ENTER(); SWSS_LOG_NOTICE("--- Starting schedulermgrd ---"); - while ((opt = getopt(argc, argv, "l:a:p:z:h")) != -1 ) - { - switch (opt) - { - case 'l': - pg_lookup_file = optarg; - break; - case 'h': - usage(); - return 1; - case 'a': - asic_table_file = optarg; - break; - case 'p': - peripherial_table_file = optarg; - break; - case 'z': - zero_profile_file = optarg; - break; - default: /* '?' */ - usage(); - return EXIT_FAILURE; - } - } - try { std::vector cfgOrchList; @@ -73,7 +41,6 @@ int main(int argc, char **argv) DBConnector cfgDb("CONFIG_DB", 0); DBConnector stateDb("STATE_DB", 0); - vector cfg_buffer_tables = { CFG_TIME_RANGE_TABLE_NAME, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME From 175468ceb30f0f6a971566259a031e45b5e54489 Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 24 Mar 2024 10:25:43 +0200 Subject: [PATCH 03/16] Remove systemd and use cron instead --- cfgmgr/schedulermgr.cpp | 117 ++++++++++++++------------------------- cfgmgr/schedulermgr.h | 16 ++---- cfgmgr/schedulermgrd.cpp | 12 ---- 3 files changed, 47 insertions(+), 98 deletions(-) diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index 3f8af06517..aa5c2c3087 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include "logger.h" #include "dbconnector.h" #include "producerstatetable.h" @@ -16,8 +17,6 @@ using namespace std; using namespace swss; -#define PORT_NAME_GLOBAL "global" - SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames): Orch(cfgDb, tableNames), m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), @@ -25,99 +24,62 @@ SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vecto m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) {} -bool SchedulerMgr::reloadSystemd() { - // Reload systemd to recognize changes - return system(SYSTEMD_RELOAD_CMD_STR) == 0; -} - -bool SchedulerMgr::enableSystemdTimer(const std::string& timerFileName) { - // Enable the systemd timer - string enableTimerCmd = SYSTEMD_TIMER_ENABLE_PREFIX_CMD_STR + timerFileName; - return system(enableTimerCmd.c_str()) == 0; -} - -task_process_status SchedulerMgr::writeSystemdUnitFile(const string& fileName, const string& taskType, const string& taskCommand) { - string unitFilePath = SYSTEMD_FILES_PATH_PREFIX_STR + fileName; - ofstream unitFile{unitFilePath}; - if (unitFile.fail()) { - SWSS_LOG_ERROR("Failed to create systemd file for %s", fileName.c_str()); - return task_process_status::task_need_retry; - } - unitFile << "[Unit]\n"; - unitFile << "Description=" << fileName << " service\n\n"; - unitFile << "[Service]\n"; - unitFile << "Type=" << taskType << "\n"; - unitFile << "ExecStart=" << taskCommand << "\n"; - unitFile.close(); - - SWSS_LOG_DEBUG("Systemd unit file for %s has been created", fileName.c_str()); - return task_process_status::task_success; -} - -task_process_status SchedulerMgr::writeSystemdTimerFile(const string& fileName, const string& taskOnCalendar, bool isPersistent) { - string timerFilePath = SYSTEMD_FILES_PATH_PREFIX_STR + fileName; - ofstream timerFile{timerFilePath}; - if (timerFile.fail()) { - SWSS_LOG_ERROR("Failed to create systemd timer file for %s", fileName.c_str()); +task_process_status SchedulerMgr::writeCrontabFile(const string& fileName, const string& schedule, const string& command, bool deleteSelfAfterCompletion) { + string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; + ofstream crontabFile{cronFileName}; + + if (crontabFile.fail()) { + SWSS_LOG_ERROR("Failed to create crontab file for %s", fileName.c_str()); return task_process_status::task_need_retry; } - timerFile << "[Unit]\n"; - timerFile << "Description=Timer for " << fileName << " service\n\n"; - timerFile << "[Timer]\n"; - timerFile << "OnCalendar=" << taskOnCalendar << "\n"; - if (isPersistent) { - timerFile << "Persistent=true\n"; + crontabFile << schedule << " "; + crontabFile << CRON_USERNAME_STR << " "; + crontabFile << command; + if (deleteSelfAfterCompletion) { + crontabFile << " && rm " << cronFileName; } - timerFile << "\n"; - timerFile << "[Install]\n"; - timerFile << "WantedBy=timers.target\n"; - timerFile.close(); + crontabFile.close(); - SWSS_LOG_DEBUG("Systemd timer file for %s has been created", fileName.c_str()); + SWSS_LOG_DEBUG("Crontab file for %s has been created", fileName.c_str()); return task_process_status::task_success; } // TODO add rollback mechanism -task_process_status SchedulerMgr::createSystemdFiles(const string& taskName, const string& startTime, const string& endTime) { - // Service file for enabling the task - // TODO Replace with actual command - string enableServiceFileName = taskName + "-enable" + SYSTEMD_UNIT_FILE_EXTENSION_STR; - if (writeSystemdUnitFile(enableServiceFileName, "oneshot", "/bin/echo 'Starting " + taskName + "'") != task_process_status::task_success) { - return task_process_status::task_need_retry; +task_process_status SchedulerMgr::createCronjobs(const string& taskName, const string& startTime, const string& endTime, bool runOnce) { + string enableCrontabName = taskName + "-enable"; + string disableCrontabName = taskName + "-disable"; + + // Create command for enabling the task + string command_enabled; + { + stringstream ss; + ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; + command_enabled = ss.str(); } - // Timer file for enabling the task - string enableTimerFileName = taskName + "-enable" + SYSTEMD_TIMER_FILE_EXTENSION_STR; - if (writeSystemdTimerFile(enableTimerFileName, startTime, false) != task_process_status::task_success) { - return task_process_status::task_need_retry; + // Create command for disabling the task + string command_disabled; + { + stringstream ss; + ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; + command_disabled = ss.str(); } - // Service file for disabling the task + // Service file for enabling the task // TODO Replace with actual command - string disableServiceFileName = taskName + "-disable" + SYSTEMD_UNIT_FILE_EXTENSION_STR; - if (writeSystemdUnitFile(disableServiceFileName, "oneshot", "/bin/echo 'Stopping " + taskName + "'") != task_process_status::task_success) { + if (writecCrontabFile(enableCrontabName, endTime, command_enabled, runOnce) != task_process_status::task_success) { return task_process_status::task_need_retry; } - // Timer file for disabling the task - string disableTimerFileName = taskName + "-disable" + SYSTEMD_TIMER_FILE_EXTENSION_STR; - if (writeSystemdTimerFile(disableTimerFileName, endTime, false) != task_process_status::task_success) { + // Service file for disabling the task + // TODO Replace with actual command + if (writecCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) { return task_process_status::task_need_retry; - } + } - SWSS_LOG_INFO("Succesfully created Systemd files for %s", taskName.c_str()); - // Reload systemd to pick up changes, then enable systemd timer files - if (!reloadSystemd()) { - SWSS_LOG_ERROR("Failed to reload systemd"); - return task_process_status::task_need_retry; - } - if (!enableSystemdTimer(enableTimerFileName) || !enableSystemdTimer(disableTimerFileName)) { - SWSS_LOG_ERROR("Failed to enable systemd timer files for %s", taskName.c_str()); - return task_process_status::task_need_retry; - } + SWSS_LOG_INFO("Succesfully created crontab files for %s", taskName.c_str()); - SWSS_LOG_DEBUG("Succesfully reloaded and enabled systemd timer files for %s", taskName.c_str()); return task_process_status::task_success; } @@ -125,6 +87,7 @@ task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const SWSS_LOG_ENTER(); string start = ""; string end = ""; + string runOnce = ""; // Set time range status to disabled by default vector stateTableFieldValues; @@ -137,6 +100,8 @@ task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const start = fvValue(i); } else if (fvField(i) == "end"){ end = fvValue(i); + } else if (fvField(i) == "runOnce"){ + runOnce = fvValue(i); } else { SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); // Can skip instead of returning invalid entry @@ -151,7 +116,7 @@ task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const // Create systemd files for time range and enable them // TODO sanitize inputs - if (task_process_status::task_need_retry == createSystemdFiles(rangeName, start, end)) { + if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) { return task_process_status::task_need_retry; } diff --git a/cfgmgr/schedulermgr.h b/cfgmgr/schedulermgr.h index 2ce48e8054..5570ac4f8c 100644 --- a/cfgmgr/schedulermgr.h +++ b/cfgmgr/schedulermgr.h @@ -14,11 +14,10 @@ namespace swss { #define TIME_RANGE_DISABLED_STR "disabled" #define TIME_RANGE_STATUS_STR "status" -#define SYSTEMD_FILES_PATH_PREFIX_STR "/etc/systemd/system/" -#define SYSTEMD_UNIT_FILE_EXTENSION_STR ".service" -#define SYSTEMD_TIMER_FILE_EXTENSION_STR ".timer" -#define SYSTEMD_RELOAD_CMD_STR "systemctl daemon-reload" -#define SYSTEMD_TIMER_ENABLE_PREFIX_CMD_STR "systemctl enable " + +#define CRON_FILES_PATH_PREFIX_STR "/etc/cron.d/" +#define CRON_USERNAME_STR "root" + class SchedulerMgr : public Orch { @@ -33,11 +32,8 @@ class SchedulerMgr : public Orch ProducerStateTable m_stateTimeRangeStatusTable; - bool reloadSystemd(); - bool enableSystemdTimer(const std::string& timerFileName); - task_process_status writeSystemdUnitFile(const std::string& taskName, const std::string& taskType, const std::string& taskCommand); - task_process_status writeSystemdTimerFile(const std::string& taskName, const std::string& taskOnCalendar, const bool isPersistent); - task_process_status createSystemdFiles(const std::string& rangeName, const std::string& start, const std::string& end); + task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); + task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); void doTask(Consumer &consumer); }; diff --git a/cfgmgr/schedulermgrd.cpp b/cfgmgr/schedulermgrd.cpp index f81cea2236..80ff33b09e 100644 --- a/cfgmgr/schedulermgrd.cpp +++ b/cfgmgr/schedulermgrd.cpp @@ -15,18 +15,6 @@ using namespace swss; /* SELECT() function timeout retry time, in millisecond */ #define SELECT_TIMEOUT 1000 -void usage() -{ - cout << "Usage: schedulermgrd <-l pg_lookup.ini|-a asic_table.json [-p peripheral_table.json] [-z zero_profiles.json]>" << endl; - cout << " -l pg_lookup.ini: PG profile look up table file (mandatory for static mode)" << endl; - cout << " format: csv" << endl; - cout << " values: 'speed, cable, size, xon, xoff, dynamic_threshold, xon_offset'" << endl; - cout << " -a asic_table.json: ASIC-specific parameters definition (mandatory for dynamic mode)" << endl; - cout << " -p peripheral_table.json: Peripheral (eg. gearbox) parameters definition (optional for dynamic mode)" << endl; - cout << " -z zero_profiles.json: Zero profiles definition for reclaiming unused buffers (optional for dynamic mode)" << endl; -} - - int main(int argc, char **argv) { Logger::linkToDbNative("schedulermgrd"); From e5e4b191f440c0b2ac04d13957007980bc11f127 Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 24 Mar 2024 18:11:34 +0200 Subject: [PATCH 04/16] Fixed Bugs and Ran Code Formatter - Fixed bug where endtime was used for enable cronjob - Fixed bug where EOL was not added to crontab file, causing cron to not be able to read the file - Removed extra c in writeContabFile() funcname - Removed unnecessary scheduledConfigurationTable in schedulermgr - Changed type of m_stateTimeRangeStatusTable from ProducerStateTable to Table in order for set command to work correctly - Formatted code --- cfgmgr/schedulermgr.cpp | 96 ++++++++++++++++++++++++---------------- cfgmgr/schedulermgr.h | 4 +- cfgmgr/schedulermgrd.cpp | 1 - 3 files changed, 58 insertions(+), 43 deletions(-) diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index aa5c2c3087..f34f861c38 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -17,27 +17,30 @@ using namespace std; using namespace swss; -SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames): - Orch(cfgDb, tableNames), - m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), - m_cfgScheduledConfigurationTable(cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME), - m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) -{} - -task_process_status SchedulerMgr::writeCrontabFile(const string& fileName, const string& schedule, const string& command, bool deleteSelfAfterCompletion) { +SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), + m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), + m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) +{ +} + +task_process_status SchedulerMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) +{ string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; ofstream crontabFile{cronFileName}; - - if (crontabFile.fail()) { + + if (crontabFile.fail()) + { SWSS_LOG_ERROR("Failed to create crontab file for %s", fileName.c_str()); return task_process_status::task_need_retry; } crontabFile << schedule << " "; crontabFile << CRON_USERNAME_STR << " "; crontabFile << command; - if (deleteSelfAfterCompletion) { + if (deleteSelfAfterCompletion) + { crontabFile << " && rm " << cronFileName; } + crontabFile << endl; crontabFile.close(); SWSS_LOG_DEBUG("Crontab file for %s has been created", fileName.c_str()); @@ -45,7 +48,8 @@ task_process_status SchedulerMgr::writeCrontabFile(const string& fileName, const } // TODO add rollback mechanism -task_process_status SchedulerMgr::createCronjobs(const string& taskName, const string& startTime, const string& endTime, bool runOnce) { +task_process_status SchedulerMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) +{ string enableCrontabName = taskName + "-enable"; string disableCrontabName = taskName + "-disable"; @@ -62,28 +66,33 @@ task_process_status SchedulerMgr::createCronjobs(const string& taskName, const s { stringstream ss; ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; + if (runOnce){ + // Delete the time range configuration entry after the task has been disabled + // writeCrontabFile() will delete the crontab file itself after the task has been executed + ss << " && /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName; + } command_disabled = ss.str(); } // Service file for enabling the task - // TODO Replace with actual command - if (writecCrontabFile(enableCrontabName, endTime, command_enabled, runOnce) != task_process_status::task_success) { + if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) + { return task_process_status::task_need_retry; } // Service file for disabling the task - // TODO Replace with actual command - if (writecCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) { + if (writeCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) + { return task_process_status::task_need_retry; } - SWSS_LOG_INFO("Succesfully created crontab files for %s", taskName.c_str()); return task_process_status::task_success; } -task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const vector &fieldValues){ +task_process_status SchedulerMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) +{ SWSS_LOG_ENTER(); string start = ""; string end = ""; @@ -96,30 +105,39 @@ task_process_status SchedulerMgr::doTimeRangeTask(const string& rangeName, const for (const auto &i : fieldValues) { - if (fvField(i) == "start"){ + if (fvField(i) == "start") + { start = fvValue(i); - } else if (fvField(i) == "end"){ + } + else if (fvField(i) == "end") + { end = fvValue(i); - } else if (fvField(i) == "runOnce"){ + } + else if (fvField(i) == "runOnce") + { runOnce = fvValue(i); - } else { + } + else + { SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); // Can skip instead of returning invalid entry return task_process_status::task_invalid_entry; } } - if (start == "" || end == ""){ + if (start == "" || end == "") + { SWSS_LOG_ERROR("Time range %s is missing start or end time", rangeName.c_str()); return task_process_status::task_invalid_entry; } // Create systemd files for time range and enable them // TODO sanitize inputs - if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) { + if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) + { return task_process_status::task_need_retry; } - + // Add time range status to range status table in state db m_stateTimeRangeStatusTable.set(key, stateTableFieldValues); @@ -152,20 +170,20 @@ void SchedulerMgr::doTask(Consumer &consumer) } switch (task_status) { - case task_process_status::task_failed: - SWSS_LOG_ERROR("Failed to process table update"); - return; - case task_process_status::task_need_retry: - SWSS_LOG_INFO("Unable to process table update. Will retry..."); - ++it; - break; - case task_process_status::task_invalid_entry: - SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); - it = consumer.m_toSync.erase(it); - break; - default: - it = consumer.m_toSync.erase(it); - break; + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process table update"); + return; + case task_process_status::task_need_retry: + SWSS_LOG_INFO("Unable to process table update. Will retry..."); + ++it; + break; + case task_process_status::task_invalid_entry: + SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); + it = consumer.m_toSync.erase(it); + break; + default: + it = consumer.m_toSync.erase(it); + break; } } } diff --git a/cfgmgr/schedulermgr.h b/cfgmgr/schedulermgr.h index 5570ac4f8c..0eb7a850dc 100644 --- a/cfgmgr/schedulermgr.h +++ b/cfgmgr/schedulermgr.h @@ -26,11 +26,9 @@ class SchedulerMgr : public Orch using Orch::doTask; private: - Table m_cfgTimeRangeTable; - Table m_cfgScheduledConfigurationTable; - ProducerStateTable m_stateTimeRangeStatusTable; + Table m_stateTimeRangeStatusTable; task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); diff --git a/cfgmgr/schedulermgrd.cpp b/cfgmgr/schedulermgrd.cpp index 80ff33b09e..aaee936aec 100644 --- a/cfgmgr/schedulermgrd.cpp +++ b/cfgmgr/schedulermgrd.cpp @@ -31,7 +31,6 @@ int main(int argc, char **argv) vector cfg_buffer_tables = { CFG_TIME_RANGE_TABLE_NAME, - CFG_SCHEDULED_CONFIGURATION_TABLE_NAME }; cfgOrchList.emplace_back(new SchedulerMgr(&cfgDb, &stateDb, cfg_buffer_tables)); From 7b860c15c99255439b1d0aef0b6a2a193a80c8d3 Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 24 Mar 2024 23:25:03 +0200 Subject: [PATCH 05/16] Missed ending single quote for runOnce field Forgot an ending quote when adding a runOnce==true boolean --- cfgmgr/schedulermgr.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index f34f861c38..bb796eae3b 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -69,7 +69,7 @@ task_process_status SchedulerMgr::createCronjobs(const string &taskName, const s if (runOnce){ // Delete the time range configuration entry after the task has been disabled // writeCrontabFile() will delete the crontab file itself after the task has been executed - ss << " && /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName; + ss << " && /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; } command_disabled = ss.str(); } From 4e26a4f41940a8a9090daa4a5d9cd23c3f3d4b9d Mon Sep 17 00:00:00 2001 From: mazora Date: Tue, 26 Mar 2024 13:44:21 +0200 Subject: [PATCH 06/16] Replace "&&" to ";" for cronjobs Switching to ";" allows the configuration to be deleted after a "runOnce" whether or not the configuration changes succeed or not. --- cfgmgr/schedulermgr.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index bb796eae3b..a6f0346fd3 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -38,7 +38,7 @@ task_process_status SchedulerMgr::writeCrontabFile(const string &fileName, const crontabFile << command; if (deleteSelfAfterCompletion) { - crontabFile << " && rm " << cronFileName; + crontabFile << " ; rm " << cronFileName; } crontabFile << endl; crontabFile.close(); @@ -69,7 +69,7 @@ task_process_status SchedulerMgr::createCronjobs(const string &taskName, const s if (runOnce){ // Delete the time range configuration entry after the task has been disabled // writeCrontabFile() will delete the crontab file itself after the task has been executed - ss << " && /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; + ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; } command_disabled = ss.str(); } From 5f37ccc07a94dfcc5b9387807e61c159c4bceceb Mon Sep 17 00:00:00 2001 From: mazora Date: Wed, 3 Apr 2024 09:47:20 +0300 Subject: [PATCH 07/16] Create timebasedconfigmgrd and rename schedulermgrd to timerangemgrd --- cfgmgr/Makefile.am | 21 +- cfgmgr/schedulermgr.cpp | 55 ++- cfgmgr/timebasedconfigmgr.cpp | 403 ++++++++++++++++++ cfgmgr/timebasedconfigmgr.h | 51 +++ cfgmgr/timebasedconfigmgrd.cpp | 76 ++++ cfgmgr/timerangemgr.cpp | 189 ++++++++ cfgmgr/{schedulermgr.h => timerangemgr.h} | 14 +- .../{schedulermgrd.cpp => timerangemgrd.cpp} | 12 +- 8 files changed, 773 insertions(+), 48 deletions(-) create mode 100644 cfgmgr/timebasedconfigmgr.cpp create mode 100644 cfgmgr/timebasedconfigmgr.h create mode 100644 cfgmgr/timebasedconfigmgrd.cpp create mode 100644 cfgmgr/timerangemgr.cpp rename cfgmgr/{schedulermgr.h => timerangemgr.h} (81%) rename cfgmgr/{schedulermgrd.cpp => timerangemgrd.cpp} (83%) diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index bd0265461d..48c9f3e8fc 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -5,7 +5,7 @@ LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 SAIMETA_LIBS = -lsaimeta -lsaimetadata -lzmq COMMON_LIBS = -lswsscommon -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd schedulermgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd timerangemgrd timebasedconfigmgrd cfgmgrdir = $(datadir)/swss @@ -96,10 +96,15 @@ tunnelmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CF tunnelmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) tunnelmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) -schedulermgrd_SOURCES = schedulermgrd.cpp schedulermgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h -schedulermgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) -schedulermgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) -schedulermgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) +timerangemgrd_SOURCES = timerangemgrd.cpp timerangemgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +timerangemgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +timerangemgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +timerangemgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) + +timebasedconfigmgrd_SOURCES = timebasedconfigmgrd.cpp timebasedconfigmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +timebasedconfigmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +timebasedconfigmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +timebasedconfigmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) @@ -120,7 +125,8 @@ sflowmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp natmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp coppmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp tunnelmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp -schedulermgrd_SOURCES += ../gcovpreload/gcovpreload.cpp +timerangemgrd_SOURCES += ../gcovpreload/gcovpreload.cpp +timebasedconfigmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp macsecmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp endif @@ -139,6 +145,7 @@ coppmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp tunnelmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp macsecmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp fabricmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp -schedulermgrd_SOURCES += $(top_srcdir)/lib/asan.cpp +timerangemgrd_SOURCES += $(top_srcdir)/lib/asan.cpp +timebasedconfigmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp endif diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp index a6f0346fd3..067be35c79 100644 --- a/cfgmgr/schedulermgr.cpp +++ b/cfgmgr/schedulermgr.cpp @@ -4,26 +4,19 @@ #include #include "logger.h" #include "dbconnector.h" -#include "producerstatetable.h" -#include "tokenize.h" -#include "ipprefix.h" #include "timer.h" -#include "schedulermgr.h" -#include "exec.h" -#include "shellcmd.h" -#include "warm_restart.h" -#include "converter.h" +#include "timerangemgr.h" using namespace std; using namespace swss; -SchedulerMgr::SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), +TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) { } -task_process_status SchedulerMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) +task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) { string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; ofstream crontabFile{cronFileName}; @@ -48,31 +41,37 @@ task_process_status SchedulerMgr::writeCrontabFile(const string &fileName, const } // TODO add rollback mechanism -task_process_status SchedulerMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) +task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) { string enableCrontabName = taskName + "-enable"; string disableCrontabName = taskName + "-disable"; // Create command for enabling the task - string command_enabled; - { - stringstream ss; - ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; - command_enabled = ss.str(); - } + string command_enabled = string("/usr/bin/redis-cli -n ") + STATE_DB + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ENABLED_STR + "'"; + // { + // stringstream ss; + // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; + // command_enabled = ss.str(); + // } // Create command for disabling the task - string command_disabled; + string command_disabled = string("/usr/bin/redis-cli -n ") + STATE_DB + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_DISABLED_STR + "'"; + if (runOnce) { - stringstream ss; - ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; - if (runOnce){ - // Delete the time range configuration entry after the task has been disabled - // writeCrontabFile() will delete the crontab file itself after the task has been executed - ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; - } - command_disabled = ss.str(); + // Delete the time range configuration entry after the task has been disabled + // writeCrontabFile() will delete the crontab file itself after the task has been executed + command_disabled += " ; /usr/bin/redis-cli -n " + CONFIG_DB + " del '" + CFG_TIME_RANGE_TABLE_NAME + "|" + taskName + "'"; } + // { + // stringstream ss; + // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; + // if (runOnce){ + // // Delete the time range configuration entry after the task has been disabled + // // writeCrontabFile() will delete the crontab file itself after the task has been executed + // ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; + // } + // command_disabled = ss.str(); + // } // Service file for enabling the task if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) @@ -91,7 +90,7 @@ task_process_status SchedulerMgr::createCronjobs(const string &taskName, const s return task_process_status::task_success; } -task_process_status SchedulerMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) +task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) { SWSS_LOG_ENTER(); string start = ""; @@ -144,7 +143,7 @@ task_process_status SchedulerMgr::doTimeRangeTask(const string &rangeName, const return task_process_status::task_success; } -void SchedulerMgr::doTask(Consumer &consumer) +void TimeRangeMgr::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); diff --git a/cfgmgr/timebasedconfigmgr.cpp b/cfgmgr/timebasedconfigmgr.cpp new file mode 100644 index 0000000000..53d776b7c5 --- /dev/null +++ b/cfgmgr/timebasedconfigmgr.cpp @@ -0,0 +1,403 @@ +#include +#include +#include "select.h" +#include "schema.h" +#include +#include +#include "dbconnector.h" +#include "timebasedconfigmgr.h" +#include "json.h" +#include +#include "tokenize.h" + +using namespace std; +using namespace swss; +using json = nlohmann::json; + +TimeBasedConfigMgr::TimeBasedConfigMgr(vector &connectors, DBConnector *appDb) : Orch(connectors) +{ + m_appDb = appDb; +} + +string join(const json &jsonArray, const string &delimiter) +{ + string result; + + for (auto it = jsonArray.begin(); it != jsonArray.end(); ++it) + { + if (!result.empty()) + { + result += delimiter; + } + // Convert each element to a string, handling non-string types appropriately + if (it->is_string()) + { + result += it->get(); + } + else + { + // For non-string types, use dump() to serialize + // dump() serializes the json object to a string representation + // Since dump() includes quotes around strings, we remove them for consistency + string element = it->dump(); + // Optionally trim quotes if desired, especially if consistency with direct strings is needed + if (element.front() == '"' && element.back() == '"') + { + element = element.substr(1, element.length() - 2); + } + result += element; + } + } + + return result; +} + +vector convertJsonToFieldValues(const json &jsonObj) +{ + vector fieldValues{}; + for (const auto &item : jsonObj.items()) + { + string key = item.key(); + if (item.value().is_primitive()) + { + // Removing quotes from primitive types to match the expected format + string value = item.value().dump(); + if (value.front() == '"' && value.back() == '"') + { + value = value.substr(1, value.length() - 2); + } + fieldValues.emplace_back(key, value); + } + else if (item.value().is_array()) + { + string arrayValues = join(item.value(), ","); + fieldValues.emplace_back(key, arrayValues); + } + } + return fieldValues; +} + +bool TimeBasedConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) +{ + SWSS_LOG_ENTER(); + + // Create a Table object for the given tableName + Table tableObj(m_appDb, tableName); + // Extract the key and fieldValues from the JSON object + for (auto it = tableKeyFields.begin(); it != tableKeyFields.end(); ++it) { + // Extract the key and value from the iterator + const string& key = it.key(); + const json& fieldValuesJson = it.value(); + + // Here, we convert the JSON tableDetails into the vector format. + vector fieldValues = convertJsonToFieldValues(fieldValuesJson); + if (fieldValues.empty()) + { + SWSS_LOG_ERROR("Failed to convert JSON to FieldValueTuple for table: %s", tableName.c_str()); + return false; + } + + // Create a Table object and set the field values + tableObj.set(key, fieldValues); + } + return true; +} + +task_process_status TimeBasedConfigMgr::applyConfiguration(const std::string &configName, const json &configJson) +{ + SWSS_LOG_ENTER(); + + for (const auto &tableEntry : configJson.items()) + { + string tableName = tableEntry.key(); // Table name + const json &tableKeyFields = tableEntry.value(); // Table details + + if (!applyTableConfiguration(tableName, tableKeyFields)) + { + SWSS_LOG_ERROR("Failed to apply configuration %s for table: %s", configName.c_str(), tableName.c_str()); + return task_process_status::task_failed; + } + } + + return task_process_status::task_success; +} + +// TODO - Implement this function +bool TimeBasedConfigMgr::validateConfiguration(const json &configJson) +{ + SWSS_LOG_ENTER(); + return true; +} + +task_process_status TimeBasedConfigMgr::doProcessScheduledConfiguration(string timeRangeName, string scheduledConfigName, string configuration) +{ + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Processing scheduled configuration %s for time range %s", scheduledConfigName.c_str(), timeRangeName.c_str()); + task_process_status task_status = task_process_status::task_success; + + // Validate timeRangeName, scheduledConfigName, and configuration + if (timeRangeName.empty() || scheduledConfigName.empty() || configuration.empty()) + { + SWSS_LOG_ERROR("Invalid arguments for scheduled configuration: timeRangeName, scheduledConfigName, or configuration is empty"); + return task_process_status::task_invalid_entry; + } + + try + { + // Parse the configuration string into a JSON object for validation + // Assuming the configuration is in a JSON string format + + //TODO change to debug + SWSS_LOG_INFO("===JSON CONFIGURATION STRING BEFORE PROCESS==="); + SWSS_LOG_INFO("%s", configuration.c_str()); + + // Simple replacement of single quotes with double quotes + // Necessary for json to succesfully parse the data + replace(configuration.begin(), configuration.end(), '\'', '\"'); + + SWSS_LOG_INFO("===JSON CONFIGURATION STRING AFTER PROCESS==="); + SWSS_LOG_INFO("%s", configuration.c_str()); + + json configJson = json::parse(configuration); + + if (!validateConfiguration(configJson)) + { + SWSS_LOG_ERROR("Configuration validation failed for %s", scheduledConfigName.c_str()); + return task_process_status::task_failed; + } + + scheduledConfigurations[timeRangeName].emplace_back(scheduledConfigName, configJson); + SWSS_LOG_INFO("Successfully added %s to time range %s ", scheduledConfigName.c_str(), timeRangeName.c_str()); + } + catch (const json::exception &e) + { + SWSS_LOG_ERROR("JSON parsing error: %s", e.what()); + task_status = task_process_status::task_failed; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Error processing scheduled configuration: %s", e.what()); + task_status = task_process_status::task_failed; + } + return task_status; +} + +task_process_status TimeBasedConfigMgr::doProcessTimeRangeStatus(string timeRangeName, string status) +{ + SWSS_LOG_ENTER(); + SWSS_LOG_INFO("Processing time range status for time range %s", timeRangeName.c_str()); + task_process_status task_status = task_process_status::task_success; + + // Validate timeRangeName and status + if (timeRangeName.empty() || status.empty()) + { + SWSS_LOG_ERROR("Invalid arguments for time range status: timeRangeName or status is empty"); + return task_process_status::task_invalid_entry; + } + + try + { + // Check if the time range exists in the scheduledConfigurations hashmap + if (scheduledConfigurations.find(timeRangeName) == scheduledConfigurations.end()) + { + SWSS_LOG_INFO("Time range %s is being created in the local db", timeRangeName.c_str()); + // Create the time range in the local db + scheduledConfigurations[timeRangeName] = ConfigList{}; + SWSS_LOG_INFO("Time range %s created in local db, will retry to decide what to do next", timeRangeName.c_str()); + return task_process_status::task_need_retry; + } + + // If the time range exists, apply the configuration based on the status + if (status == "enabled") + task_status = enableTimeRange(timeRangeName); + else if (status == "disabled") + { + // Remove the configuration + SWSS_LOG_INFO("Removing configuration for time range %s -- STUB", timeRangeName.c_str()); + } + else + { + SWSS_LOG_ERROR("Invalid status for time range %s: %s", timeRangeName.c_str(), status.c_str()); + task_status = task_process_status::task_failed; + } + } + catch (const json::exception &e) + { + SWSS_LOG_ERROR("JSON parsing error: %s", e.what()); + task_status = task_process_status::task_failed; + } + catch (const std::exception &e) + { + SWSS_LOG_ERROR("Error processing time range status: %s", e.what()); + task_status = task_process_status::task_failed; + } + + return task_status; +} + +task_process_status TimeBasedConfigMgr::enableTimeRange(const string &timeRangeName) +{ + SWSS_LOG_ENTER(); + + string configName{}; + json configJson{}; + + // Check if there are any configurations for the time range + if (scheduledConfigurations[timeRangeName].empty()) + { + SWSS_LOG_INFO("No configuration found for time range %s", timeRangeName.c_str()); + return task_process_status::task_success; + } + + // Apply the configuration + // scheduledConfigurations[timeRangeName].first is the configName + // scheduledConfigurations[timeRangeName].second is the configuration JSON + SWSS_LOG_INFO("Applying configuration for time range %s", timeRangeName.c_str()); + + for (const auto &configData : scheduledConfigurations[timeRangeName]) + { + configName = configData.first; + configJson = configData.second; + if (task_process_status::task_success != applyConfiguration(configName, configJson)) + { + SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + return task_process_status::task_need_retry; + } + SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + } + return task_process_status::task_success; +} + +void TimeBasedConfigMgr::doTimeRangeTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + string timeRangeName = ""; + string status = ""; + + KeyOpFieldsValuesTuple t = it->second; + + string keySeparator = CONFIGDB_KEY_SEPARATOR; + vector keys = tokenize(kfvKey(t), keySeparator[0]); + timeRangeName = keys[0]; + + string op = kfvOp(t); + task_process_status task_status = task_process_status::task_success; + if (op == SET_COMMAND) + { + for (const auto &i : kfvFieldsValues(t)) + { + if (fvField(i) == "status") + { + status = fvValue(i); + } + else + { + SWSS_LOG_ERROR("%s has unknown field %s", STATE_TIME_RANGE_STATUS_TABLE_NAME, fvField(i).c_str()); + // Can skip instead of returning invalid entry + task_status = task_process_status::task_invalid_entry; + } + } + + task_status = doProcessTimeRangeStatus(timeRangeName, status); + } + switch (task_status) + { + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process table update"); + return; + case task_process_status::task_need_retry: + SWSS_LOG_INFO("Unable to process table update. Will retry..."); + ++it; + break; + case task_process_status::task_invalid_entry: + SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); + it = consumer.m_toSync.erase(it); + break; + default: + it = consumer.m_toSync.erase(it); + break; + } + } +} + +void TimeBasedConfigMgr::doScheduledConfigurationTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + string timeRangeName = ""; + string configType = ""; + string configuration = ""; + + KeyOpFieldsValuesTuple t = it->second; + + string keySeparator = CONFIGDB_KEY_SEPARATOR; + vector keys = tokenize(kfvKey(t), keySeparator[0]); + string scheduledConfigurationName(keys[0]); + + string op = kfvOp(t); + task_process_status task_status = task_process_status::task_success; + if (op == SET_COMMAND) + { + for (const auto &i : kfvFieldsValues(t)) + { + if (fvField(i) == "time_range") + { + timeRangeName = fvValue(i); + } + else if (fvField(i) == "config_type") + { + configType = fvValue(i); + } + else if (fvField(i) == "configuration") + { + configuration = fvValue(i); + } + else + { + SWSS_LOG_ERROR("%s has unknown field %s", CFG_SCHEDULED_CONFIGURATION_TABLE_NAME, fvField(i).c_str()); + // Can skip instead of returning invalid entry + task_status = task_process_status::task_invalid_entry; + } + } + task_status = doProcessScheduledConfiguration(timeRangeName, scheduledConfigurationName, configuration); + } + + switch (task_status) + { + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process table update"); + return; + case task_process_status::task_need_retry: + SWSS_LOG_INFO("Unable to process table update. Will retry..."); + ++it; + break; + case task_process_status::task_invalid_entry: + SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); + it = consumer.m_toSync.erase(it); + break; + default: + it = consumer.m_toSync.erase(it); + break; + } + } +} + +void TimeBasedConfigMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + if (table_name == CFG_SCHEDULED_CONFIGURATION_TABLE_NAME) + { + doScheduledConfigurationTask(consumer); + } + else if (table_name == STATE_TIME_RANGE_STATUS_TABLE_NAME) + { + doTimeRangeTask(consumer); + } +} \ No newline at end of file diff --git a/cfgmgr/timebasedconfigmgr.h b/cfgmgr/timebasedconfigmgr.h new file mode 100644 index 0000000000..55166e9247 --- /dev/null +++ b/cfgmgr/timebasedconfigmgr.h @@ -0,0 +1,51 @@ +#ifndef __TIMEBASEDCONFIGMGR__ +#define __TIMEBASEDCONFIGMGR__ + +#include "dbconnector.h" +#include "producerstatetable.h" +#include "orch.h" +#include +#include +#include +#include + +using json = nlohmann::json; +// Define a type alias for the configuration data structure +using ConfigData = std::pair; // Pair of configName and configuration JSON object +// using ConfigData = std::tuple; // Tuple of configName, configType, and configuration JSON object +using ConfigList = std::vector; // A list of configurations +using TimeRangeConfigMap = std::unordered_map; // Maps time range names to lists of configurations + +namespace swss { + +class TimeBasedConfigMgr : public Orch +{ +public: + TimeBasedConfigMgr(std::vector& connectors, DBConnector* appDb); + using Orch::doTask; + +private: + DBConnector *m_appDb; + TimeRangeConfigMap scheduledConfigurations; + + // Configuration functions + bool validateConfiguration(const json &configJson); + task_process_status enableTimeRange(const std::string &timeRangeName); + task_process_status applyConfiguration(const std::string &configName, const json &configJson); + bool applyTableConfiguration(const std::string &tableName, const json &tableKeyFields); + + + // task processing functions + task_process_status doProcessScheduledConfiguration(std::string timeRangeName, std::string configType, std::string configuration); + task_process_status doProcessTimeRangeStatus(std::string timeRangeName, std::string status); + + + // task functions + void doTimeRangeTask(Consumer &consumer); + void doScheduledConfigurationTask(Consumer &consumer); + void doTask(Consumer &consumer); +}; + +} + +#endif /* __TIMEBASEDCONFIGMGR__ */ \ No newline at end of file diff --git a/cfgmgr/timebasedconfigmgrd.cpp b/cfgmgr/timebasedconfigmgrd.cpp new file mode 100644 index 0000000000..e0928c0210 --- /dev/null +++ b/cfgmgr/timebasedconfigmgrd.cpp @@ -0,0 +1,76 @@ +#include +#include +#include +#include "dbconnector.h" +#include "select.h" +#include "exec.h" +#include "schema.h" +#include "timebasedconfigmgr.h" +#include +#include + +using namespace std; +using namespace swss; + +/* SELECT() function timeout retry time, in millisecond */ +#define SELECT_TIMEOUT 1000 + +int main(int argc, char **argv) +{ + Logger::linkToDbNative("timebasedconfigmgrd"); + SWSS_LOG_ENTER(); + + SWSS_LOG_NOTICE("--- Starting timebasedconfigmgrd ---"); + + try + { + std::vector cfgOrchList; + + // Create DB connectors + DBConnector cfgDb("CONFIG_DB", 0); + DBConnector stateDb("STATE_DB", 0); + DBConnector appDb("APPL_DB", 0); + + // Create table connectors that TimeBasedConfigMgr will subscribe to + TableConnector cfgDbScheduledConfigurations(&cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME); + TableConnector stateDbTimeRangeStatusTable(&stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME); + vector connectors = {cfgDbScheduledConfigurations, stateDbTimeRangeStatusTable}; + + cfgOrchList.emplace_back(new TimeBasedConfigMgr(connectors, &appDb)); + + auto timebasedconfigmgr = cfgOrchList[0]; + + swss::Select s; + for (Orch *o : cfgOrchList) + { + s.addSelectables(o->getSelectables()); + } + + SWSS_LOG_NOTICE("starting main loop"); + while (true) + { + Selectable *sel; + int ret; + + ret = s.select(&sel, SELECT_TIMEOUT); + if (ret == Select::ERROR) + { + SWSS_LOG_NOTICE("Error: %s!", strerror(errno)); + continue; + } + if (ret == Select::TIMEOUT) + { + timebasedconfigmgr->doTask(); + continue; + } + + auto *c = (Executor *)sel; + c->execute(); + } + } + catch(const std::exception &e) + { + SWSS_LOG_ERROR("Runtime error: %s", e.what()); + } + return -1; +} diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp new file mode 100644 index 0000000000..8d404f9ea1 --- /dev/null +++ b/cfgmgr/timerangemgr.cpp @@ -0,0 +1,189 @@ +#include +#include +#include +#include +#include "logger.h" +#include "dbconnector.h" +#include "timer.h" +#include "timerangemgr.h" +#include "tokenize.h" +#include + +using namespace std; +using namespace swss; + +TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), + m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) +{ +} + +task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) +{ + string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; + ofstream crontabFile{cronFileName}; + + if (crontabFile.fail()) + { + SWSS_LOG_ERROR("Failed to create crontab file for %s", fileName.c_str()); + return task_process_status::task_need_retry; + } + crontabFile << schedule << " "; + crontabFile << CRON_USERNAME_STR << " "; + crontabFile << command; + if (deleteSelfAfterCompletion) + { + crontabFile << " ; rm " << cronFileName; + } + crontabFile << endl; + crontabFile.close(); + + SWSS_LOG_DEBUG("Crontab file for %s has been created", fileName.c_str()); + return task_process_status::task_success; +} + +// TODO add rollback mechanism +task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) +{ + string enableCrontabName = taskName + "-enable"; + string disableCrontabName = taskName + "-disable"; + + // Create command for enabling the task + string command_enabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ENABLED_STR + "'"; + // { + // stringstream ss; + // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; + // command_enabled = ss.str(); + // } + + // Create command for disabling the task + string command_disabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_DISABLED_STR + "'"; + if (runOnce) + { + // Delete the time range configuration entry after the task has been disabled + // writeCrontabFile() will delete the crontab file itself after the task has been executed + command_disabled += " ; /usr/bin/redis-cli -n " + to_string(CONFIG_DB) + " del '" + CFG_TIME_RANGE_TABLE_NAME + "|" + taskName + "'"; + } + // { + // stringstream ss; + // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; + // if (runOnce){ + // // Delete the time range configuration entry after the task has been disabled + // // writeCrontabFile() will delete the crontab file itself after the task has been executed + // ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; + // } + // command_disabled = ss.str(); + // } + + // Service file for enabling the task + if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) + { + return task_process_status::task_need_retry; + } + + // Service file for disabling the task + if (writeCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) + { + return task_process_status::task_need_retry; + } + + SWSS_LOG_INFO("Succesfully created crontab files for %s", taskName.c_str()); + + return task_process_status::task_success; +} + +task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) +{ + SWSS_LOG_ENTER(); + string start = ""; + string end = ""; + string runOnce = ""; + + // Set time range status to disabled by default + vector stateTableFieldValues; + string key = rangeName; + stateTableFieldValues.emplace_back(FieldValueTuple(TIME_RANGE_STATUS_STR, TIME_RANGE_DISABLED_STR)); + + for (const auto &i : fieldValues) + { + if (fvField(i) == "start") + { + start = fvValue(i); + } + else if (fvField(i) == "end") + { + end = fvValue(i); + } + else if (fvField(i) == "runOnce") + { + runOnce = fvValue(i); + } + else + { + SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); + // Can skip instead of returning invalid entry + return task_process_status::task_invalid_entry; + } + } + + if (start == "" || end == "") + { + SWSS_LOG_ERROR("Time range %s is missing start or end time", rangeName.c_str()); + return task_process_status::task_invalid_entry; + } + + // Create systemd files for time range and enable them + // TODO sanitize inputs + if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) + { + return task_process_status::task_need_retry; + } + + // Add time range status to range status table in state db + m_stateTimeRangeStatusTable.set(key, stateTableFieldValues); + + return task_process_status::task_success; +} + +void TimeRangeMgr::doTask(Consumer &consumer) +{ + SWSS_LOG_ENTER(); + + string table_name = consumer.getTableName(); + + auto it = consumer.m_toSync.begin(); + while (it != consumer.m_toSync.end()) + { + KeyOpFieldsValuesTuple t = it->second; + + string keySeparator = CONFIGDB_KEY_SEPARATOR; + vector keys = tokenize(kfvKey(t), keySeparator[0]); + string rangeName(keys[0]); + + string op = kfvOp(t); + task_process_status task_status = task_process_status::task_success; + if (op == SET_COMMAND) + { + if (table_name == CFG_TIME_RANGE_TABLE_NAME) + { + task_status = doTimeRangeTask(rangeName, kfvFieldsValues(t)); + } + } + switch (task_status) + { + case task_process_status::task_failed: + SWSS_LOG_ERROR("Failed to process table update"); + return; + case task_process_status::task_need_retry: + SWSS_LOG_INFO("Unable to process table update. Will retry..."); + ++it; + break; + case task_process_status::task_invalid_entry: + SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); + it = consumer.m_toSync.erase(it); + break; + default: + it = consumer.m_toSync.erase(it); + break; + } + } +} diff --git a/cfgmgr/schedulermgr.h b/cfgmgr/timerangemgr.h similarity index 81% rename from cfgmgr/schedulermgr.h rename to cfgmgr/timerangemgr.h index 0eb7a850dc..5df2e8bb51 100644 --- a/cfgmgr/schedulermgr.h +++ b/cfgmgr/timerangemgr.h @@ -1,5 +1,5 @@ -#ifndef __SCHEDULERMGR__ -#define __SCHEDULERMGR__ +#ifndef __TIMERANGEMGR__ +#define __TIMERANGEMGR__ #include "dbconnector.h" #include "producerstatetable.h" @@ -7,6 +7,8 @@ #include #include +#include +#include namespace swss { @@ -19,15 +21,13 @@ namespace swss { #define CRON_USERNAME_STR "root" -class SchedulerMgr : public Orch +class TimeRangeMgr : public Orch { public: - SchedulerMgr(DBConnector *cfgDb, DBConnector *stateDb, const std::vector &tableNames); + TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const std::vector &tableNames); using Orch::doTask; private: - Table m_cfgTimeRangeTable; - Table m_stateTimeRangeStatusTable; task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); @@ -38,4 +38,4 @@ class SchedulerMgr : public Orch } -#endif /* __SCHEDULERMGR__ */ +#endif /* __TIMERANGEMGR__ */ diff --git a/cfgmgr/schedulermgrd.cpp b/cfgmgr/timerangemgrd.cpp similarity index 83% rename from cfgmgr/schedulermgrd.cpp rename to cfgmgr/timerangemgrd.cpp index aaee936aec..95e07772b6 100644 --- a/cfgmgr/schedulermgrd.cpp +++ b/cfgmgr/timerangemgrd.cpp @@ -5,7 +5,7 @@ #include "select.h" #include "exec.h" #include "schema.h" -#include "schedulermgr.h" +#include "timerangemgr.h" #include #include @@ -17,10 +17,10 @@ using namespace swss; int main(int argc, char **argv) { - Logger::linkToDbNative("schedulermgrd"); + Logger::linkToDbNative("timerangemgrd"); SWSS_LOG_ENTER(); - SWSS_LOG_NOTICE("--- Starting schedulermgrd ---"); + SWSS_LOG_NOTICE("--- Starting timerangemgrd ---"); try { @@ -32,9 +32,9 @@ int main(int argc, char **argv) vector cfg_buffer_tables = { CFG_TIME_RANGE_TABLE_NAME, }; - cfgOrchList.emplace_back(new SchedulerMgr(&cfgDb, &stateDb, cfg_buffer_tables)); + cfgOrchList.emplace_back(new TimeRangeMgr(&cfgDb, &stateDb, cfg_buffer_tables)); - auto schedulermgr = cfgOrchList[0]; + auto timerangemgr = cfgOrchList[0]; swss::Select s; for (Orch *o : cfgOrchList) @@ -56,7 +56,7 @@ int main(int argc, char **argv) } if (ret == Select::TIMEOUT) { - schedulermgr->doTask(); + timerangemgr->doTask(); continue; } From 4d56f594117e6cd5ad35b0829ff8fd575f663a08 Mon Sep 17 00:00:00 2001 From: mazora Date: Tue, 16 Apr 2024 13:36:35 +0300 Subject: [PATCH 08/16] Rename timebasedconfigmgrd to scheduledconfigmgrd --- cfgmgr/Makefile.am | 14 +++++----- ...edconfigmgr.cpp => scheduledconfigmgr.cpp} | 26 +++++++++---------- ...ebasedconfigmgr.h => scheduledconfigmgr.h} | 10 +++---- ...configmgrd.cpp => scheduledconfigmgrd.cpp} | 14 +++++----- 4 files changed, 32 insertions(+), 32 deletions(-) rename cfgmgr/{timebasedconfigmgr.cpp => scheduledconfigmgr.cpp} (94%) rename cfgmgr/{timebasedconfigmgr.h => scheduledconfigmgr.h} (88%) rename cfgmgr/{timebasedconfigmgrd.cpp => scheduledconfigmgrd.cpp} (82%) diff --git a/cfgmgr/Makefile.am b/cfgmgr/Makefile.am index 48c9f3e8fc..b5a0086c9a 100644 --- a/cfgmgr/Makefile.am +++ b/cfgmgr/Makefile.am @@ -5,7 +5,7 @@ LIBNL_LIBS = -lnl-genl-3 -lnl-route-3 -lnl-3 SAIMETA_LIBS = -lsaimeta -lsaimetadata -lzmq COMMON_LIBS = -lswsscommon -bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd timerangemgrd timebasedconfigmgrd +bin_PROGRAMS = vlanmgrd teammgrd portmgrd intfmgrd buffermgrd vrfmgrd nbrmgrd vxlanmgrd sflowmgrd natmgrd coppmgrd tunnelmgrd macsecmgrd fabricmgrd timerangemgrd scheduledconfigmgrd cfgmgrdir = $(datadir)/swss @@ -101,10 +101,10 @@ timerangemgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $ timerangemgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) timerangemgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) -timebasedconfigmgrd_SOURCES = timebasedconfigmgrd.cpp timebasedconfigmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h -timebasedconfigmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) -timebasedconfigmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) -timebasedconfigmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) +scheduledconfigmgrd_SOURCES = scheduledconfigmgrd.cpp scheduledconfigmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h +scheduledconfigmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +scheduledconfigmgrd_CPPFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) +scheduledconfigmgrd_LDADD = $(LDFLAGS_ASAN) $(COMMON_LIBS) $(SAIMETA_LIBS) macsecmgrd_SOURCES = macsecmgrd.cpp macsecmgr.cpp $(COMMON_ORCH_SOURCE) shellcmd.h macsecmgrd_CFLAGS = $(DBGFLAGS) $(AM_CFLAGS) $(CFLAGS_COMMON) $(CFLAGS_SAI) $(CFLAGS_ASAN) @@ -126,7 +126,7 @@ natmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp coppmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp tunnelmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp timerangemgrd_SOURCES += ../gcovpreload/gcovpreload.cpp -timebasedconfigmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp +scheduledconfigmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp macsecmgrd_SOURCES += ../gcovpreload/gcovpreload.cpp endif @@ -146,6 +146,6 @@ tunnelmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp macsecmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp fabricmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp timerangemgrd_SOURCES += $(top_srcdir)/lib/asan.cpp -timebasedconfigmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp +scheduledconfigmgrd_SOURCES += $(top_srcdir)/lib/asan.cpp endif diff --git a/cfgmgr/timebasedconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp similarity index 94% rename from cfgmgr/timebasedconfigmgr.cpp rename to cfgmgr/scheduledconfigmgr.cpp index 53d776b7c5..f0166c776a 100644 --- a/cfgmgr/timebasedconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -5,7 +5,7 @@ #include #include #include "dbconnector.h" -#include "timebasedconfigmgr.h" +#include "scheduledconfigmgr.h" #include "json.h" #include #include "tokenize.h" @@ -14,7 +14,7 @@ using namespace std; using namespace swss; using json = nlohmann::json; -TimeBasedConfigMgr::TimeBasedConfigMgr(vector &connectors, DBConnector *appDb) : Orch(connectors) +ScheduledConfigMgr::ScheduledConfigMgr(vector &connectors, DBConnector *appDb) : Orch(connectors) { m_appDb = appDb; } @@ -77,12 +77,12 @@ vector convertJsonToFieldValues(const json &jsonObj) return fieldValues; } -bool TimeBasedConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) +bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) { SWSS_LOG_ENTER(); // Create a Table object for the given tableName - Table tableObj(m_appDb, tableName); + ProducerStateTable tableObj(m_appDb, tableName); // Extract the key and fieldValues from the JSON object for (auto it = tableKeyFields.begin(); it != tableKeyFields.end(); ++it) { // Extract the key and value from the iterator @@ -103,7 +103,7 @@ bool TimeBasedConfigMgr::applyTableConfiguration(const std::string &tableName, c return true; } -task_process_status TimeBasedConfigMgr::applyConfiguration(const std::string &configName, const json &configJson) +task_process_status ScheduledConfigMgr::applyConfiguration(const std::string &configName, const json &configJson) { SWSS_LOG_ENTER(); @@ -123,13 +123,13 @@ task_process_status TimeBasedConfigMgr::applyConfiguration(const std::string &co } // TODO - Implement this function -bool TimeBasedConfigMgr::validateConfiguration(const json &configJson) +bool ScheduledConfigMgr::validateConfiguration(const json &configJson) { SWSS_LOG_ENTER(); return true; } -task_process_status TimeBasedConfigMgr::doProcessScheduledConfiguration(string timeRangeName, string scheduledConfigName, string configuration) +task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string timeRangeName, string scheduledConfigName, string configuration) { SWSS_LOG_ENTER(); SWSS_LOG_INFO("Processing scheduled configuration %s for time range %s", scheduledConfigName.c_str(), timeRangeName.c_str()); @@ -182,7 +182,7 @@ task_process_status TimeBasedConfigMgr::doProcessScheduledConfiguration(string t return task_status; } -task_process_status TimeBasedConfigMgr::doProcessTimeRangeStatus(string timeRangeName, string status) +task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRangeName, string status) { SWSS_LOG_ENTER(); SWSS_LOG_INFO("Processing time range status for time range %s", timeRangeName.c_str()); @@ -213,7 +213,7 @@ task_process_status TimeBasedConfigMgr::doProcessTimeRangeStatus(string timeRang else if (status == "disabled") { // Remove the configuration - SWSS_LOG_INFO("Removing configuration for time range %s -- STUB", timeRangeName.c_str()); + SWSS_LOG_WARN("Removing configuration for time range %s -- STUB", timeRangeName.c_str()); } else { @@ -235,7 +235,7 @@ task_process_status TimeBasedConfigMgr::doProcessTimeRangeStatus(string timeRang return task_status; } -task_process_status TimeBasedConfigMgr::enableTimeRange(const string &timeRangeName) +task_process_status ScheduledConfigMgr::enableTimeRange(const string &timeRangeName) { SWSS_LOG_ENTER(); @@ -268,7 +268,7 @@ task_process_status TimeBasedConfigMgr::enableTimeRange(const string &timeRangeN return task_process_status::task_success; } -void TimeBasedConfigMgr::doTimeRangeTask(Consumer &consumer) +void ScheduledConfigMgr::doTimeRangeTask(Consumer &consumer) { SWSS_LOG_ENTER(); auto it = consumer.m_toSync.begin(); @@ -323,7 +323,7 @@ void TimeBasedConfigMgr::doTimeRangeTask(Consumer &consumer) } } -void TimeBasedConfigMgr::doScheduledConfigurationTask(Consumer &consumer) +void ScheduledConfigMgr::doScheduledConfigurationTask(Consumer &consumer) { SWSS_LOG_ENTER(); auto it = consumer.m_toSync.begin(); @@ -387,7 +387,7 @@ void TimeBasedConfigMgr::doScheduledConfigurationTask(Consumer &consumer) } } -void TimeBasedConfigMgr::doTask(Consumer &consumer) +void ScheduledConfigMgr::doTask(Consumer &consumer) { SWSS_LOG_ENTER(); diff --git a/cfgmgr/timebasedconfigmgr.h b/cfgmgr/scheduledconfigmgr.h similarity index 88% rename from cfgmgr/timebasedconfigmgr.h rename to cfgmgr/scheduledconfigmgr.h index 55166e9247..0223d4bc2b 100644 --- a/cfgmgr/timebasedconfigmgr.h +++ b/cfgmgr/scheduledconfigmgr.h @@ -1,5 +1,5 @@ -#ifndef __TIMEBASEDCONFIGMGR__ -#define __TIMEBASEDCONFIGMGR__ +#ifndef __SCHEDULEDCONFIGMGR_H__ +#define __SCHEDULEDCONFIGMGR_H__ #include "dbconnector.h" #include "producerstatetable.h" @@ -18,10 +18,10 @@ using TimeRangeConfigMap = std::unordered_map; // Maps namespace swss { -class TimeBasedConfigMgr : public Orch +class ScheduledConfigMgr : public Orch { public: - TimeBasedConfigMgr(std::vector& connectors, DBConnector* appDb); + ScheduledConfigMgr(std::vector& connectors, DBConnector* appDb); using Orch::doTask; private: @@ -48,4 +48,4 @@ class TimeBasedConfigMgr : public Orch } -#endif /* __TIMEBASEDCONFIGMGR__ */ \ No newline at end of file +#endif /* __SCHEDULEDCONFIGMGR_H__ */ \ No newline at end of file diff --git a/cfgmgr/timebasedconfigmgrd.cpp b/cfgmgr/scheduledconfigmgrd.cpp similarity index 82% rename from cfgmgr/timebasedconfigmgrd.cpp rename to cfgmgr/scheduledconfigmgrd.cpp index e0928c0210..0059a85740 100644 --- a/cfgmgr/timebasedconfigmgrd.cpp +++ b/cfgmgr/scheduledconfigmgrd.cpp @@ -5,7 +5,7 @@ #include "select.h" #include "exec.h" #include "schema.h" -#include "timebasedconfigmgr.h" +#include "scheduledconfigmgr.h" #include #include @@ -17,10 +17,10 @@ using namespace swss; int main(int argc, char **argv) { - Logger::linkToDbNative("timebasedconfigmgrd"); + Logger::linkToDbNative("scheduledconfigmgrd"); SWSS_LOG_ENTER(); - SWSS_LOG_NOTICE("--- Starting timebasedconfigmgrd ---"); + SWSS_LOG_NOTICE("--- Starting scheduledconfigmgrd ---"); try { @@ -31,14 +31,14 @@ int main(int argc, char **argv) DBConnector stateDb("STATE_DB", 0); DBConnector appDb("APPL_DB", 0); - // Create table connectors that TimeBasedConfigMgr will subscribe to + // Create table connectors that ScheduledConfigMgr will subscribe to TableConnector cfgDbScheduledConfigurations(&cfgDb, CFG_SCHEDULED_CONFIGURATION_TABLE_NAME); TableConnector stateDbTimeRangeStatusTable(&stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME); vector connectors = {cfgDbScheduledConfigurations, stateDbTimeRangeStatusTable}; - cfgOrchList.emplace_back(new TimeBasedConfigMgr(connectors, &appDb)); + cfgOrchList.emplace_back(new ScheduledConfigMgr(connectors, &appDb)); - auto timebasedconfigmgr = cfgOrchList[0]; + auto scheduledconfigmgr = cfgOrchList[0]; swss::Select s; for (Orch *o : cfgOrchList) @@ -60,7 +60,7 @@ int main(int argc, char **argv) } if (ret == Select::TIMEOUT) { - timebasedconfigmgr->doTask(); + scheduledconfigmgr->doTask(); continue; } From b1a64707eb413607843365750a604b68229d7409 Mon Sep 17 00:00:00 2001 From: mazora Date: Tue, 7 May 2024 15:45:46 +0300 Subject: [PATCH 09/16] Add support for removing configuration when inactive Also clean up code, and began validation of cron syntax --- cfgmgr/scheduledconfigmgr.cpp | 81 +++++++++++++++++++++++++++++++---- cfgmgr/scheduledconfigmgr.h | 13 ++++++ cfgmgr/timerangemgr.cpp | 15 ------- 3 files changed, 85 insertions(+), 24 deletions(-) diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index f0166c776a..c459e38bfe 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -83,8 +83,10 @@ bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, c // Create a Table object for the given tableName ProducerStateTable tableObj(m_appDb, tableName); + // Extract the key and fieldValues from the JSON object for (auto it = tableKeyFields.begin(); it != tableKeyFields.end(); ++it) { + // Extract the key and value from the iterator const string& key = it.key(); const json& fieldValuesJson = it.value(); @@ -103,6 +105,18 @@ bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, c return true; } +bool ScheduledConfigMgr::removeTableConfiguration(const std::string &tableName) +{ + SWSS_LOG_ENTER(); + + // Create a Table object for the given tableName + ProducerStateTable tableObj(m_appDb, tableName); + + // Create a Table object and set the field values + tableObj.clear(); + return true; +} + task_process_status ScheduledConfigMgr::applyConfiguration(const std::string &configName, const json &configJson) { SWSS_LOG_ENTER(); @@ -122,6 +136,25 @@ task_process_status ScheduledConfigMgr::applyConfiguration(const std::string &co return task_process_status::task_success; } +task_process_status ScheduledConfigMgr::removeConfiguration(const std::string &configName, const json &configJson) +{ + SWSS_LOG_ENTER(); + string tableName = ""; + + for (const auto &tableEntry : configJson.items()) + { + tableName = tableEntry.key(); + + if (!removeTableConfiguration(tableName)) + { + SWSS_LOG_ERROR("Failed to remove configuration %s for table: %s", configName.c_str(), tableName.c_str()); + return task_process_status::task_failed; + } + } + + return task_process_status::task_success; +} + // TODO - Implement this function bool ScheduledConfigMgr::validateConfiguration(const json &configJson) { @@ -144,19 +177,18 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t try { + // Parse the configuration string into a JSON object for validation // Assuming the configuration is in a JSON string format - - //TODO change to debug - SWSS_LOG_INFO("===JSON CONFIGURATION STRING BEFORE PROCESS==="); - SWSS_LOG_INFO("%s", configuration.c_str()); + SWSS_LOG_DEBUG("===JSON CONFIGURATION STRING BEFORE PROCESS==="); + SWSS_LOG_DEBUG("%s", configuration.c_str()); // Simple replacement of single quotes with double quotes // Necessary for json to succesfully parse the data replace(configuration.begin(), configuration.end(), '\'', '\"'); - SWSS_LOG_INFO("===JSON CONFIGURATION STRING AFTER PROCESS==="); - SWSS_LOG_INFO("%s", configuration.c_str()); + SWSS_LOG_DEBUG("===JSON CONFIGURATION STRING AFTER PROCESS==="); + SWSS_LOG_DEBUG("%s", configuration.c_str()); json configJson = json::parse(configuration); @@ -212,8 +244,7 @@ task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRang task_status = enableTimeRange(timeRangeName); else if (status == "disabled") { - // Remove the configuration - SWSS_LOG_WARN("Removing configuration for time range %s -- STUB", timeRangeName.c_str()); + task_status = disableTimeRange(timeRangeName); } else { @@ -252,7 +283,7 @@ task_process_status ScheduledConfigMgr::enableTimeRange(const string &timeRangeN // Apply the configuration // scheduledConfigurations[timeRangeName].first is the configName // scheduledConfigurations[timeRangeName].second is the configuration JSON - SWSS_LOG_INFO("Applying configuration for time range %s", timeRangeName.c_str()); + SWSS_LOG_INFO("Applying configurations for time range %s", timeRangeName.c_str()); for (const auto &configData : scheduledConfigurations[timeRangeName]) { @@ -268,6 +299,38 @@ task_process_status ScheduledConfigMgr::enableTimeRange(const string &timeRangeN return task_process_status::task_success; } +task_process_status ScheduledConfigMgr::disableTimeRange(const string &timeRangeName) +{ + SWSS_LOG_ENTER(); + + string configName{}; + + // Check if there are any configurations for the time range + if (scheduledConfigurations[timeRangeName].empty()) + { + SWSS_LOG_INFO("No configuration found for time range %s", timeRangeName.c_str()); + return task_process_status::task_success; + } + + // Remove the configuration + // scheduledConfigurations[timeRangeName].first is the configName + // scheduledConfigurations[timeRangeName].second is the configuration JSON + SWSS_LOG_INFO("Removing configurations for time range %s", timeRangeName.c_str()); + + for (const auto &configData : scheduledConfigurations[timeRangeName]) + { + configName = configData.first; + configJson = configData.second; + if (task_process_status::task_success != removeConfiguration(configName, configJson)) + { + SWSS_LOG_ERROR("Could not remove configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + return task_process_status::task_need_retry; + } + SWSS_LOG_INFO("Removed configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + } + return task_process_status::task_success; +} + void ScheduledConfigMgr::doTimeRangeTask(Consumer &consumer) { SWSS_LOG_ENTER(); diff --git a/cfgmgr/scheduledconfigmgr.h b/cfgmgr/scheduledconfigmgr.h index 0223d4bc2b..162c4b8bc9 100644 --- a/cfgmgr/scheduledconfigmgr.h +++ b/cfgmgr/scheduledconfigmgr.h @@ -27,13 +27,26 @@ class ScheduledConfigMgr : public Orch private: DBConnector *m_appDb; TimeRangeConfigMap scheduledConfigurations; + + // Taken from https://www.codeproject.com/Tips/5299523/Regex-for-Cron-Expressions + std::string m_cronMinuteRegex = R"((\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)))"; + std::string m_cronHourRegex = R"((\*|(?:\*|(?:\*|(?:[0-9]|1[0-9]|2[0-3])))\/(?:[0-9]|1[0-9]|2[0-3])|(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)))"; + std::string m_cronDayOfMonthRegex = R"((\*|\?|L(?:W|\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:W|\/(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)))"; + std::string m_cronMonthRegex = R"((\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)))"; + std::string m_cronDayOfWeekRegex = R"((\*|\?|[0-6](?:L|\#[1-5])?|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*)))"; + // Configuration functions bool validateConfiguration(const json &configJson); + task_process_status enableTimeRange(const std::string &timeRangeName); task_process_status applyConfiguration(const std::string &configName, const json &configJson); bool applyTableConfiguration(const std::string &tableName, const json &tableKeyFields); + task_process_status disableTimeRange(const std::string &timeRangeName); + task_process_status removeConfiguration(const std::string &configName, const json &configJson); + bool removeTableConfiguration(const std::string &tableName); + // task processing functions task_process_status doProcessScheduledConfiguration(std::string timeRangeName, std::string configType, std::string configuration); diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index 8d404f9ea1..5d07e94e95 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -49,11 +49,6 @@ task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const s // Create command for enabling the task string command_enabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ENABLED_STR + "'"; - // { - // stringstream ss; - // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; - // command_enabled = ss.str(); - // } // Create command for disabling the task string command_disabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_DISABLED_STR + "'"; @@ -63,16 +58,6 @@ task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const s // writeCrontabFile() will delete the crontab file itself after the task has been executed command_disabled += " ; /usr/bin/redis-cli -n " + to_string(CONFIG_DB) + " del '" + CFG_TIME_RANGE_TABLE_NAME + "|" + taskName + "'"; } - // { - // stringstream ss; - // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; - // if (runOnce){ - // // Delete the time range configuration entry after the task has been disabled - // // writeCrontabFile() will delete the crontab file itself after the task has been executed - // ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; - // } - // command_disabled = ss.str(); - // } // Service file for enabling the task if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) From 71f11ed5959933310bbad24736f6d32f09d1621c Mon Sep 17 00:00:00 2001 From: mazora Date: Thu, 16 May 2024 15:22:04 +0300 Subject: [PATCH 10/16] Added Multiple Updates (NOT TESTED) - Added 3 new files to help with cron validation & time checking. Leaning to use croncpp.h and removing cronhelper.cpp ands cronhelper.h - Added check that scheduled configuration does not already exist when adding a scheduled configuration - Automatically apply scheduled configuration if time range is currently active - Save scheduled configurations in "unboundConfiguration" hashmap when time range is deleted - Check if scheduled configurations saved in "unboundConfiguration" hashmap should be bound to newly created time range, and then add it to that new time range if applicable - Added if statement to not process time range/scheduled configuration if an invalid field is found in the asssociated table - Implemented DEL operation processing for scheduled configuration and time range deletion - New function "isTimeRangeActive()" in scheduledconfigmgr.cpp to check TIME_RANGE_STATUS table if the time range is active according to the field - New function "is_time_in_range()" that uses the croncpp.h file in order to test if the local time is within the scheduled time range interval -- NOT TESTED - Implemented DEL operation for when time range key is deleted from CONFIG_DB --- cfgmgr/croncpp.h | 982 ++++++++++++++++++++++++++++++++++ cfgmgr/cronhelper.cpp | 123 +++++ cfgmgr/cronhelper.h | 50 ++ cfgmgr/scheduledconfigmgr.cpp | 87 ++- cfgmgr/scheduledconfigmgr.h | 20 +- cfgmgr/timerangemgr.cpp | 76 ++- cfgmgr/timerangemgr.h | 4 + 7 files changed, 1317 insertions(+), 25 deletions(-) create mode 100644 cfgmgr/croncpp.h create mode 100644 cfgmgr/cronhelper.cpp create mode 100644 cfgmgr/cronhelper.h diff --git a/cfgmgr/croncpp.h b/cfgmgr/croncpp.h new file mode 100644 index 0000000000..a5b4642443 --- /dev/null +++ b/cfgmgr/croncpp.h @@ -0,0 +1,982 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __cplusplus > 201402L +#include +#define CRONCPP_IS_CPP17 +#endif + +namespace cron +{ +#ifdef CRONCPP_IS_CPP17 + #define CRONCPP_STRING_VIEW std::string_view + #define CRONCPP_STRING_VIEW_NPOS std::string_view::npos + #define CRONCPP_CONSTEXPTR constexpr +#else + #define CRONCPP_STRING_VIEW std::string const & + #define CRONCPP_STRING_VIEW_NPOS std::string::npos + #define CRONCPP_CONSTEXPTR +#endif + + using cron_int = uint8_t; + + constexpr std::time_t INVALID_TIME = static_cast(-1); + + constexpr size_t INVALID_INDEX = static_cast(-1); + + class cronexpr; + + namespace detail + { + enum class cron_field + { + second, + minute, + hour_of_day, + day_of_week, + day_of_month, + month, + year + }; + + template + static bool find_next(cronexpr const & cex, + std::tm& date, + size_t const dot); + } + + struct bad_cronexpr : public std::runtime_error + { + public: + explicit bad_cronexpr(CRONCPP_STRING_VIEW message) : + std::runtime_error(message.data()) + {} + }; + + + struct cron_standard_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 0; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 6; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 1; + static const cron_int CRON_MAX_MONTHS = 12; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + static std::vector& DAYS() + { + static std::vector days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + struct cron_oracle_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 0; + static const cron_int CRON_MAX_MONTHS = 11; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + + static std::vector& DAYS() + { + static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + struct cron_quartz_traits + { + static const cron_int CRON_MIN_SECONDS = 0; + static const cron_int CRON_MAX_SECONDS = 59; + + static const cron_int CRON_MIN_MINUTES = 0; + static const cron_int CRON_MAX_MINUTES = 59; + + static const cron_int CRON_MIN_HOURS = 0; + static const cron_int CRON_MAX_HOURS = 23; + + static const cron_int CRON_MIN_DAYS_OF_WEEK = 1; + static const cron_int CRON_MAX_DAYS_OF_WEEK = 7; + + static const cron_int CRON_MIN_DAYS_OF_MONTH = 1; + static const cron_int CRON_MAX_DAYS_OF_MONTH = 31; + + static const cron_int CRON_MIN_MONTHS = 1; + static const cron_int CRON_MAX_MONTHS = 12; + + static const cron_int CRON_MAX_YEARS_DIFF = 4; + +#ifdef CRONCPP_IS_CPP17 + static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; +#else + static std::vector& DAYS() + { + static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + return days; + } + + static std::vector& MONTHS() + { + static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + return months; + } +#endif + }; + + class cronexpr; + + template + static cronexpr make_cron(CRONCPP_STRING_VIEW expr); + + class cronexpr + { + std::bitset<60> seconds; + std::bitset<60> minutes; + std::bitset<24> hours; + std::bitset<7> days_of_week; + std::bitset<31> days_of_month; + std::bitset<12> months; + std::string expr; + + friend bool operator==(cronexpr const & e1, cronexpr const & e2); + friend bool operator!=(cronexpr const & e1, cronexpr const & e2); + + template + friend bool detail::find_next(cronexpr const & cex, + std::tm& date, + size_t const dot); + + friend std::string to_cronstr(cronexpr const& cex); + friend std::string to_string(cronexpr const & cex); + + template + friend cronexpr make_cron(CRONCPP_STRING_VIEW expr); + }; + + inline bool operator==(cronexpr const & e1, cronexpr const & e2) + { + return + e1.seconds == e2.seconds && + e1.minutes == e2.minutes && + e1.hours == e2.hours && + e1.days_of_week == e2.days_of_week && + e1.days_of_month == e2.days_of_month && + e1.months == e2.months; + } + + inline bool operator!=(cronexpr const & e1, cronexpr const & e2) + { + return !(e1 == e2); + } + + inline std::string to_string(cronexpr const & cex) + { + return + cex.seconds.to_string() + " " + + cex.minutes.to_string() + " " + + cex.hours.to_string() + " " + + cex.days_of_month.to_string() + " " + + cex.months.to_string() + " " + + cex.days_of_week.to_string(); + } + + inline std::string to_cronstr(cronexpr const& cex) + { + return cex.expr; + } + + namespace utils + { + inline std::time_t tm_to_time(std::tm& date) + { + return std::mktime(&date); + } + + inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out) + { +#ifdef _WIN32 + errno_t err = localtime_s(out, date); + return 0 == err ? out : nullptr; +#else + return localtime_r(date, out); +#endif + } + + inline std::tm to_tm(CRONCPP_STRING_VIEW time) + { + std::tm result; +#if __cplusplus > 201103L + std::istringstream str(time.data()); + str.imbue(std::locale(setlocale(LC_ALL, nullptr))); + + str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S"); + if (str.fail()) throw std::runtime_error("Parsing date failed!"); +#else + int year = 1900; + int month = 1; + int day = 1; + int hour = 0; + int minute = 0; + int second = 0; + sscanf(time.data(), "%d-%d-%d %d:%d:%d", &year, &month, &day, &hour, &minute, &second); + result.tm_year = year - 1900; + result.tm_mon = month - 1; + result.tm_mday = day; + result.tm_hour = hour; + result.tm_min = minute; + result.tm_sec = second; +#endif + result.tm_isdst = -1; // DST info not available + + return result; + } + + inline std::string to_string(std::tm const & tm) + { +#if __cplusplus > 201103L + std::ostringstream str; + str.imbue(std::locale(setlocale(LC_ALL, nullptr))); + str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); + if (str.fail()) throw std::runtime_error("Writing date failed!"); + + return str.str(); +#else + char buff[70] = {0}; + strftime(buff, sizeof(buff), "%Y-%m-%d %H:%M:%S", &tm); + return std::string(buff); +#endif + } + + inline std::string to_upper(std::string text) + { + std::transform(std::begin(text), std::end(text), + std::begin(text), [](char const c) { return static_cast(std::toupper(c)); }); + + return text; + } + + static std::vector split(CRONCPP_STRING_VIEW text, char const delimiter) + { + std::vector tokens; + std::string token; + std::istringstream tokenStream(text.data()); + while (std::getline(tokenStream, token, delimiter)) + { + tokens.push_back(token); + } + return tokens; + } + + CRONCPP_CONSTEXPTR inline bool contains(CRONCPP_STRING_VIEW text, char const ch) noexcept + { + return CRONCPP_STRING_VIEW_NPOS != text.find_first_of(ch); + } + } + + namespace detail + { + + inline cron_int to_cron_int(CRONCPP_STRING_VIEW text) + { + try + { + return static_cast(std::stoul(text.data())); + } + catch (std::exception const & ex) + { + throw bad_cronexpr(ex.what()); + } + } + + static std::string replace_ordinals( + std::string text, + std::vector const & replacement) + { + for (size_t i = 0; i < replacement.size(); ++i) + { + auto pos = text.find(replacement[i]); + if (std::string::npos != pos) + text.replace(pos, 3 ,std::to_string(i)); + } + + return text; + } + + static std::pair make_range( + CRONCPP_STRING_VIEW field, + cron_int const minval, + cron_int const maxval) + { + cron_int first = 0; + cron_int last = 0; + if (field.size() == 1 && field[0] == '*') + { + first = minval; + last = maxval; + } + else if (!utils::contains(field, '-')) + { + first = to_cron_int(field); + last = first; + } + else + { + auto parts = utils::split(field, '-'); + if (parts.size() != 2) + throw bad_cronexpr("Specified range requires two fields"); + + first = to_cron_int(parts[0]); + last = to_cron_int(parts[1]); + } + + if (first > maxval || last > maxval) + { + throw bad_cronexpr("Specified range exceeds maximum"); + } + if (first < minval || last < minval) + { + throw bad_cronexpr("Specified range is less than minimum"); + } + if (first > last) + { + throw bad_cronexpr("Specified range start exceeds range end"); + } + + return { first, last }; + } + + template + static void set_cron_field( + CRONCPP_STRING_VIEW value, + std::bitset& target, + cron_int const minval, + cron_int const maxval) + { + if(value.length() > 0 && value[value.length()-1] == ',') + throw bad_cronexpr("Value cannot end with comma"); + + auto fields = utils::split(value, ','); + if (fields.empty()) + throw bad_cronexpr("Expression parsing error"); + + for (auto const & field : fields) + { + if (!utils::contains(field, '/')) + { +#ifdef CRONCPP_IS_CPP17 + auto[first, last] = detail::make_range(field, minval, maxval); +#else + auto range = detail::make_range(field, minval, maxval); + auto first = range.first; + auto last = range.second; +#endif + for (cron_int i = first - minval; i <= last - minval; ++i) + { + target.set(i); + } + } + else + { + auto parts = utils::split(field, '/'); + if (parts.size() != 2) + throw bad_cronexpr("Incrementer must have two fields"); + +#ifdef CRONCPP_IS_CPP17 + auto[first, last] = detail::make_range(parts[0], minval, maxval); +#else + auto range = detail::make_range(parts[0], minval, maxval); + auto first = range.first; + auto last = range.second; +#endif + + if (!utils::contains(parts[0], '-')) + { + last = maxval; + } + + auto delta = detail::to_cron_int(parts[1]); + if(delta <= 0) + throw bad_cronexpr("Incrementer must be a positive value"); + + for (cron_int i = first - minval; i <= last - minval; i += delta) + { + target.set(i); + } + } + } + } + + template + static void set_cron_days_of_week( + std::string value, + std::bitset<7>& target) + { + auto days = utils::to_upper(value); + auto days_replaced = detail::replace_ordinals( + days, +#ifdef CRONCPP_IS_CPP17 + Traits::DAYS +#else + Traits::DAYS() +#endif + ); + + if (days_replaced.size() == 1 && days_replaced[0] == '?') + days_replaced[0] = '*'; + + set_cron_field( + days_replaced, + target, + Traits::CRON_MIN_DAYS_OF_WEEK, + Traits::CRON_MAX_DAYS_OF_WEEK); + } + + template + static void set_cron_days_of_month( + std::string value, + std::bitset<31>& target) + { + if (value.size() == 1 && value[0] == '?') + value[0] = '*'; + + set_cron_field( + value, + target, + Traits::CRON_MIN_DAYS_OF_MONTH, + Traits::CRON_MAX_DAYS_OF_MONTH); + } + + template + static void set_cron_month( + std::string value, + std::bitset<12>& target) + { + auto month = utils::to_upper(value); + auto month_replaced = replace_ordinals( + month, +#ifdef CRONCPP_IS_CPP17 + Traits::MONTHS +#else + Traits::MONTHS() +#endif + ); + + set_cron_field( + month_replaced, + target, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS); + } + + template + inline size_t next_set_bit( + std::bitset const & target, + size_t /*minimum*/, + size_t /*maximum*/, + size_t offset) + { + for (auto i = offset; i < N; ++i) + { + if (target.test(i)) return i; + } + + return INVALID_INDEX; + } + + inline void add_to_field( + std::tm& date, + cron_field const field, + int const val) + { + switch (field) + { + case cron_field::second: + date.tm_sec += val; + break; + case cron_field::minute: + date.tm_min += val; + break; + case cron_field::hour_of_day: + date.tm_hour += val; + break; + case cron_field::day_of_week: + case cron_field::day_of_month: + date.tm_mday += val; + date.tm_isdst = -1; + break; + case cron_field::month: + date.tm_mon += val; + date.tm_isdst = -1; + break; + case cron_field::year: + date.tm_year += val; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void set_field( + std::tm& date, + cron_field const field, + int const val) + { + switch (field) + { + case cron_field::second: + date.tm_sec = val; + break; + case cron_field::minute: + date.tm_min = val; + break; + case cron_field::hour_of_day: + date.tm_hour = val; + break; + case cron_field::day_of_week: + date.tm_wday = val; + break; + case cron_field::day_of_month: + date.tm_mday = val; + date.tm_isdst = -1; + break; + case cron_field::month: + date.tm_mon = val; + date.tm_isdst = -1; + break; + case cron_field::year: + date.tm_year = val; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void reset_field( + std::tm& date, + cron_field const field) + { + switch (field) + { + case cron_field::second: + date.tm_sec = 0; + break; + case cron_field::minute: + date.tm_min = 0; + break; + case cron_field::hour_of_day: + date.tm_hour = 0; + break; + case cron_field::day_of_week: + date.tm_wday = 0; + break; + case cron_field::day_of_month: + date.tm_mday = 1; + date.tm_isdst = -1; + break; + case cron_field::month: + date.tm_mon = 0; + date.tm_isdst = -1; + break; + case cron_field::year: + date.tm_year = 0; + break; + } + + if (INVALID_TIME == utils::tm_to_time(date)) + throw bad_cronexpr("Invalid time expression"); + } + + inline void reset_all_fields( + std::tm& date, + std::bitset<7> const & marked_fields) + { + for (size_t i = 0; i < marked_fields.size(); ++i) + { + if (marked_fields.test(i)) + reset_field(date, static_cast(i)); + } + } + + inline void mark_field( + std::bitset<7> & orders, + cron_field const field) + { + if (!orders.test(static_cast(field))) + orders.set(static_cast(field)); + } + + template + static size_t find_next( + std::bitset const & target, + std::tm& date, + unsigned int const minimum, + unsigned int const maximum, + unsigned int const value, + cron_field const field, + cron_field const next_field, + std::bitset<7> const & marked_fields) + { + auto next_value = next_set_bit(target, minimum, maximum, value); + if (INVALID_INDEX == next_value) + { + add_to_field(date, next_field, 1); + reset_field(date, field); + next_value = next_set_bit(target, minimum, maximum, 0); + } + + if (INVALID_INDEX == next_value || next_value != value) + { + set_field(date, field, static_cast(next_value)); + reset_all_fields(date, marked_fields); + } + + return next_value; + } + + template + static size_t find_next_day( + std::tm& date, + std::bitset<31> const & days_of_month, + size_t day_of_month, + std::bitset<7> const & days_of_week, + size_t day_of_week, + std::bitset<7> const & marked_fields) + { + unsigned int count = 0; + unsigned int maximum = 366; + while ( + (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) || + !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) + && count++ < maximum) + { + add_to_field(date, cron_field::day_of_month, 1); + + day_of_month = date.tm_mday; + day_of_week = date.tm_wday; + + reset_all_fields(date, marked_fields); + } + + return day_of_month; + } + + template + static bool find_next(cronexpr const & cex, + std::tm& date, + size_t const dot) + { + bool res = true; + + std::bitset<7> marked_fields{ 0 }; + std::bitset<7> empty_list{ 0 }; + + unsigned int second = date.tm_sec; + auto updated_second = find_next( + cex.seconds, + date, + Traits::CRON_MIN_SECONDS, + Traits::CRON_MAX_SECONDS, + second, + cron_field::second, + cron_field::minute, + empty_list); + + if (second == updated_second) + { + mark_field(marked_fields, cron_field::second); + } + + unsigned int minute = date.tm_min; + auto update_minute = find_next( + cex.minutes, + date, + Traits::CRON_MIN_MINUTES, + Traits::CRON_MAX_MINUTES, + minute, + cron_field::minute, + cron_field::hour_of_day, + marked_fields); + if (minute == update_minute) + { + mark_field(marked_fields, cron_field::minute); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int hour = date.tm_hour; + auto updated_hour = find_next( + cex.hours, + date, + Traits::CRON_MIN_HOURS, + Traits::CRON_MAX_HOURS, + hour, + cron_field::hour_of_day, + cron_field::day_of_week, + marked_fields); + if (hour == updated_hour) + { + mark_field(marked_fields, cron_field::hour_of_day); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int day_of_week = date.tm_wday; + unsigned int day_of_month = date.tm_mday; + auto updated_day_of_month = find_next_day( + date, + cex.days_of_month, + day_of_month, + cex.days_of_week, + day_of_week, + marked_fields); + if (day_of_month == updated_day_of_month) + { + mark_field(marked_fields, cron_field::day_of_month); + } + else + { + res = find_next(cex, date, dot); + if (!res) return res; + } + + unsigned int month = date.tm_mon; + auto updated_month = find_next( + cex.months, + date, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS, + month, + cron_field::month, + cron_field::year, + marked_fields); + if (month != updated_month) + { + if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF) + return false; + + res = find_next(cex, date, dot); + if (!res) return res; + } + + return res; + } + } + + template + static cronexpr make_cron(CRONCPP_STRING_VIEW expr) + { + cronexpr cex; + + if (expr.empty()) + throw bad_cronexpr("Invalid empty cron expression"); + + auto fields = utils::split(expr, ' '); + fields.erase( + std::remove_if(std::begin(fields), std::end(fields), + [](CRONCPP_STRING_VIEW s) {return s.empty(); }), + std::end(fields)); + if (fields.size() != 6) + throw bad_cronexpr("cron expression must have six fields"); + + detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS); + detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES); + detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS); + + detail::set_cron_days_of_week(fields[5], cex.days_of_week); + + detail::set_cron_days_of_month(fields[3], cex.days_of_month); + + detail::set_cron_month(fields[4], cex.months); + + cex.expr = expr; + + return cex; + } + + template + static std::tm cron_next(cronexpr const & cex, std::tm date) + { + time_t original = utils::tm_to_time(date); + if (INVALID_TIME == original) return {}; + + if (!detail::find_next(cex, date, date.tm_year)) + return {}; + + time_t calculated = utils::tm_to_time(date); + if (INVALID_TIME == calculated) return {}; + + if (calculated == original) + { + add_to_field(date, detail::cron_field::second, 1); + if (!detail::find_next(cex, date, date.tm_year)) + return {}; + } + + return date; + } + + template + static std::time_t cron_next(cronexpr const & cex, std::time_t const & date) + { + std::tm val; + std::tm* dt = utils::time_to_tm(&date, &val); + if (dt == nullptr) return INVALID_TIME; + + time_t original = utils::tm_to_time(*dt); + if (INVALID_TIME == original) return INVALID_TIME; + + if(!detail::find_next(cex, *dt, dt->tm_year)) + return INVALID_TIME; + + time_t calculated = utils::tm_to_time(*dt); + if (INVALID_TIME == calculated) return calculated; + + if (calculated == original) + { + add_to_field(*dt, detail::cron_field::second, 1); + if(!detail::find_next(cex, *dt, dt->tm_year)) + return INVALID_TIME; + } + + return utils::tm_to_time(*dt); + } + + template + static std::chrono::system_clock::time_point cron_next(cronexpr const & cex, std::chrono::system_clock::time_point const & time_point) { + return std::chrono::system_clock::from_time_t(cron_next(cex, std::chrono::system_clock::to_time_t(time_point))); + } + +// New implementation not found in original code // + +template +size_t previous_set_bit(const std::bitset& bits, int start) { + for (int i = start; i >= 0; --i) { + if (bits.test(i)) return i; + } + return INVALID_INDEX; +} + +void subtract_from_field(std::tm& date, cron_field field, int amount) { + switch (field) { + case cron_field::minute: + date.tm_min -= amount; + break; + case cron_field::hour: + date.tm_hour -= amount; + break; + case cron_field::mday: + date.tm_mday -= amount; + break; + case cron_field::mon: + date.tm_mon -= amount; + break; + case cron_field::year: + date.tm_year -= amount; + break; + default: + break; + } + // Normalize the date to correct underflows + mktime(&date); +} + +size_t cron_previous(std::bitset<60> const& fieldBits, std::tm& date, cron_field field, int start) { + size_t lastValid = previous_set_bit(fieldBits, start); + if (lastValid != INVALID_INDEX) { + switch (field) { + case cron_field::minute: + date.tm_min = lastValid; + break; + case cron_field::hour: + date.tm_hour = lastValid; + break; + case cron_field::mday: + date.tm_mday = lastValid; + break; + case cron_field::mon: + date.tm_mon = lastValid - 1; // month in tm is zero-indexed + break; + case cron_field::year: + date.tm_year = lastValid - 1900; // tm_year is the number of years since 1900 + break; + default: + break; + } + mktime(&date); // normalize date + return lastValid; + } + return INVALID_INDEX; +} +// End of new implementation not found in original code // + + +} \ No newline at end of file diff --git a/cfgmgr/cronhelper.cpp b/cfgmgr/cronhelper.cpp new file mode 100644 index 0000000000..56ee187e7f --- /dev/null +++ b/cfgmgr/cronhelper.cpp @@ -0,0 +1,123 @@ +#include "cronhelper.h" +#include +#include +#include +#include "logger.h" + +using namespace std; + +// CronField class implementation + +// Constructor +CronField::CronField(string field, string regex):m_field(field), m_regex(regex){ + if (!this->validate()){ + throw invalid_argument("Cron field does not match regex"); + } +} + +// Validate field +bool CronField::validate(){ + regex pattern(this->m_regex); + return regex_match(this->m_field, pattern); +} + +// CronExpression class implementation + +// Constructor +CronExpression::CronExpression(const string &expression){ + if (!this->parse(expression)){ + throw invalid_argument("Invalid cron expression"); + } +} + +// Parse expression +bool CronExpression::parse(const string &expression){ + + // Tokenize expression + vector tokens; + string token; + istringstream tokenStream(expression); + + while (getline(tokenStream, token, ' ')){ + tokens.push_back(token); + } + + // Validate number of tokens + if (tokens.size() != 5){ + return false; + } + + try + { + // Minute + CronField minute(tokens[0], m_cronMinuteRegex); + this->fields.push_back(minute); + + // Hour + CronField hour(tokens[1], m_cronHourRegex); + this->fields.push_back(hour); + + // Day of month + CronField dayOfMonth(tokens[2], m_cronDayOfMonthRegex); + this->fields.push_back(dayOfMonth); + + // Month + CronField month(tokens[3], m_cronMonthRegex); + this->fields.push_back(month); + + // Day of week + CronField dayOfWeek(tokens[4], m_cronDayOfWeekRegex); + this->fields.push_back(dayOfWeek); + + } + catch(const std::invalid_argument& e) + { + SWSS_LOG_ERROR(e.what()); + return false; + } + + return true; +} + +// Get fields +vector CronExpression::getFields(){ + return this->fields; +} + +// Get expression +string CronExpression::getExpression(){ + string expression = ""; + for (const auto &field : this->fields){ + expression += field.getExpression() + " "; + } + return expression; +} + +// CronTimeRange class implementation + +// Constructor +CronTimeRange::CronTimeRange(string name, CronExpression start, CronExpression end):expression(name), m_start(start), m_end(end){}; + +// Check if time range is active +bool CronTimeRange::isActive(){ + // Get current time + time_t now = time(0); + tm *ltm = localtime(&now); + + // Check if current time is within time range + for (const auto &field : this->m_start.getFields()){ + // Check if current time is within start time range + if (!field.validate()){ + return false; + } + } + + for (const auto &field : this->m_end.getFields()){ + // Check if current time is within end time range + if (!field.validate()){ + return false; + } + } + + return true; +} \ No newline at end of file diff --git a/cfgmgr/cronhelper.h b/cfgmgr/cronhelper.h new file mode 100644 index 0000000000..46f28abaed --- /dev/null +++ b/cfgmgr/cronhelper.h @@ -0,0 +1,50 @@ +#ifndef __CRONHELPER_H__ +#define __CRONHELPER_H__ + +#include +#include + +class CronField{ + public: + CronField(std::string field, std::string regex); + + private: + std::string m_field; + std::string m_regex; + bool validate(); +}; + +class CronExpression{ + public: + std::vector fields; + + CronExpression(const std::string &expression); + const std::vector& getFields() const; + std::string getExpression(); + + private: + // Taken from https://www.codeproject.com/Tips/5299523/Regex-for-Cron-Expressions + std::string m_cronMinuteRegex = R"((\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)))"; + std::string m_cronHourRegex = R"((\*|(?:\*|(?:\*|(?:[0-9]|1[0-9]|2[0-3])))\/(?:[0-9]|1[0-9]|2[0-3])|(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)))"; + std::string m_cronDayOfMonthRegex = R"((\*|\?|L(?:W|\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:W|\/(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)))"; + std::string m_cronMonthRegex = R"((\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)))"; + std::string m_cronDayOfWeekRegex = R"((\*|\?|[0-6](?:L|\#[1-5])?|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*)))"; + + + bool validate(); + bool parse(const std::string &expression); +}; + +class CronTimeRange{ + public: + std::string expression; + CronTimeRange(std::string name, CronExpression start, CronExpression end); + bool isActive(); + + + private: + CronExpression m_start; + CronExpression m_end; +}; + +#endif /* __CRONHELPER_H__ */ \ No newline at end of file diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index c459e38bfe..31b3a71b86 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -77,6 +77,19 @@ vector convertJsonToFieldValues(const json &jsonObj) return fieldValues; } +bool ScheduledConfigMgr::isTimeRangeActive(const string &timeRangeName) +{ + SWSS_LOG_ENTER(); + string status = ""; + + Table timeRangeStatusTable = reinterpret_cast(getExecutor(timeRangeName)); + if (!timeRangeStatusTable.hget(timeRangeName, TIME_RANGE_STATUS_STR, status)){ + SWSS_LOG_ERROR("Failed to get time range status for %s", timeRangeName.c_str()); + return false; + } + return status==TIME_RANGE_ENABLED_STR; +} + bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) { SWSS_LOG_ENTER(); @@ -198,8 +211,28 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t return task_process_status::task_failed; } + // Verify time range does not exist in the scheduledConfigurations hashmap + if scheduledConfigurations[timeRangeName].find(scheduledConfigName) != scheduledConfigurations[timeRangeName].end()) + { + SWSS_LOG_ERROR("Scheduled configuration %s already exists for time range %s", scheduledConfigName.c_str(), timeRangeName.c_str()); + return task_process_status::task_failed; + } + + // Add the configuration to the scheduledConfigurations hashmap scheduledConfigurations[timeRangeName].emplace_back(scheduledConfigName, configJson); SWSS_LOG_INFO("Successfully added %s to time range %s ", scheduledConfigName.c_str(), timeRangeName.c_str()); + + // Apply the configuration if the time range currrently is active + if (isTimeRangeActive(timeRangeName)) + { + if (task_process_status::task_success != applyConfiguration(scheduledConfigName, configJson)) + { + SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + return task_process_status::task_need_retry; + } + SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + } + } catch (const json::exception &e) { @@ -235,6 +268,18 @@ task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRang SWSS_LOG_INFO("Time range %s is being created in the local db", timeRangeName.c_str()); // Create the time range in the local db scheduledConfigurations[timeRangeName] = ConfigList{}; + + SWSS_LOG_INFO("Adding unbound configurations for time range %s", timeRangeName.c_str()); + if (unboundConfigurations.find(timeRangeName) != unboundConfigurations.end()) + { + for (const auto &configData : unboundConfigurations[timeRangeName]) + { + SWSS_LOG_NOTICE("Binding configuration %s to time range %s", configData.first.c_str(), timeRangeName.c_str()); + scheduledConfigurations[timeRangeName].emplace_back(configData); + } + unboundConfigurations.erase(timeRangeName); + } + SWSS_LOG_INFO("Time range %s created in local db, will retry to decide what to do next", timeRangeName.c_str()); return task_process_status::task_need_retry; } @@ -359,12 +404,32 @@ void ScheduledConfigMgr::doTimeRangeTask(Consumer &consumer) else { SWSS_LOG_ERROR("%s has unknown field %s", STATE_TIME_RANGE_STATUS_TABLE_NAME, fvField(i).c_str()); - // Can skip instead of returning invalid entry task_status = task_process_status::task_invalid_entry; + break; + } + } + + if (task_status != task_process_status::task_success) + { + task_status = doProcessTimeRangeStatus(timeRangeName, status); + } + } else if (op == DEL_COMMAND) + { + // Disable, and then remove the time range + if (scheduledConfigurations.find(timeRangeName) != scheduledConfigurations.end()) + { + if (task_process_status::task_success != disableTimeRange(timeRangeName)) + { + SWSS_LOG_ERROR("Could not disable time range %s", timeRangeName.c_str()); + task_status = task_process_status::task_need_retry; } + SWSS_LOG_INFO("Disabled time range %s", timeRangeName.c_str()); } + // Save configurations for future creation of time range + unboundConfigurations[timeRangeName] = scheduledConfigurations[timeRangeName]; - task_status = doProcessTimeRangeStatus(timeRangeName, status); + // Remove time range + scheduledConfigurations.erase(timeRangeName); } switch (task_status) { @@ -423,11 +488,25 @@ void ScheduledConfigMgr::doScheduledConfigurationTask(Consumer &consumer) else { SWSS_LOG_ERROR("%s has unknown field %s", CFG_SCHEDULED_CONFIGURATION_TABLE_NAME, fvField(i).c_str()); - // Can skip instead of returning invalid entry task_status = task_process_status::task_invalid_entry; } } - task_status = doProcessScheduledConfiguration(timeRangeName, scheduledConfigurationName, configuration); + if (task_status != task_process_status::task_success) + { + task_status = doProcessScheduledConfiguration(timeRangeName, scheduledConfigurationName, configuration); + } + } else if (op == DEL_COMMAND) + { + // Remove the configuration + if (scheduledConfigurations.find(timeRangeName) != scheduledConfigurations.end()) + { + if (task_process_status::task_success != removeConfiguration(scheduledConfigurationName, scheduledConfigurations[timeRangeName])) + { + SWSS_LOG_ERROR("Could not remove configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); + task_status = task_process_status::task_need_retry; + } + SWSS_LOG_INFO("Removed configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); + } } switch (task_status) diff --git a/cfgmgr/scheduledconfigmgr.h b/cfgmgr/scheduledconfigmgr.h index 162c4b8bc9..90ed04bd10 100644 --- a/cfgmgr/scheduledconfigmgr.h +++ b/cfgmgr/scheduledconfigmgr.h @@ -26,19 +26,13 @@ class ScheduledConfigMgr : public Orch private: DBConnector *m_appDb; - TimeRangeConfigMap scheduledConfigurations; + TimeRangeConfigMap scheduledConfigurations, unboundConfigurations; - // Taken from https://www.codeproject.com/Tips/5299523/Regex-for-Cron-Expressions - std::string m_cronMinuteRegex = R"((\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)))"; - std::string m_cronHourRegex = R"((\*|(?:\*|(?:\*|(?:[0-9]|1[0-9]|2[0-3])))\/(?:[0-9]|1[0-9]|2[0-3])|(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)))"; - std::string m_cronDayOfMonthRegex = R"((\*|\?|L(?:W|\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:W|\/(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)))"; - std::string m_cronMonthRegex = R"((\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)))"; - std::string m_cronDayOfWeekRegex = R"((\*|\?|[0-6](?:L|\#[1-5])?|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*)))"; - - - // Configuration functions + // Validation Functions bool validateConfiguration(const json &configJson); + bool isTimeRangeActive(const std::string &timeRangeName); + // Configuration Functions task_process_status enableTimeRange(const std::string &timeRangeName); task_process_status applyConfiguration(const std::string &configName, const json &configJson); bool applyTableConfiguration(const std::string &tableName, const json &tableKeyFields); @@ -47,13 +41,11 @@ class ScheduledConfigMgr : public Orch task_process_status removeConfiguration(const std::string &configName, const json &configJson); bool removeTableConfiguration(const std::string &tableName); - - // task processing functions + // Task Processing Functions task_process_status doProcessScheduledConfiguration(std::string timeRangeName, std::string configType, std::string configuration); task_process_status doProcessTimeRangeStatus(std::string timeRangeName, std::string status); - - // task functions + // High-level Task Functions void doTimeRangeTask(Consumer &consumer); void doScheduledConfigurationTask(Consumer &consumer); void doTask(Consumer &consumer); diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index 5d07e94e95..7aed07f13c 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -8,15 +8,31 @@ #include "timerangemgr.h" #include "tokenize.h" #include +#include "croncpp.h" using namespace std; using namespace swss; +using namespace cron; TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) { } + +bool is_time_in_range(const cronexpr& startExpr, const cronexpr& endExpr, const std::tm& currentTM) { + std::time_t currentTime = mktime(const_cast(¤tTM)); // Convert currentTM to time_t + time_t previousMinute = currentTime - 60; // Go back one minute to ensure full coverage + + // Convert back to tm + tm* prevTM = localtime(&previousMinute); + + time_t nextStartTime = cron_previous(startExpr, mktime(prevTM)); + time_t nextEndTime = cron_next(endExpr, mktime(prevTM)); + + return (currentTime >= nextStartTime && currentTime < nextEndTime); +} + task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) { string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; @@ -75,6 +91,29 @@ task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const s return task_process_status::task_success; } +task_process_status TimeRangeMgr::doTimeRangeTaskDelete(const string &rangeName) +{ + SWSS_LOG_ENTER(); + string enableCrontabName = rangeName + "-enable"; + string disableCrontabName = rangeName + "-disable"; + + // Delete the crontab files for the time range + if (remove((CRON_FILES_PATH_PREFIX_STR + enableCrontabName).c_str()) != 0) + { + SWSS_LOG_ERROR("Failed to delete crontab file for %s", enableCrontabName.c_str()); + return task_process_status::task_need_retry; + } + if (remove((CRON_FILES_PATH_PREFIX_STR + disableCrontabName).c_str()) != 0) + { + SWSS_LOG_ERROR("Failed to delete crontab file for %s", disableCrontabName.c_str()); + return task_process_status::task_need_retry; + } + + // Delete the time range status entry from the state db + m_stateTimeRangeStatusTable.del(rangeName); + + return task_process_status::task_success; +} task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) { @@ -83,11 +122,6 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const string end = ""; string runOnce = ""; - // Set time range status to disabled by default - vector stateTableFieldValues; - string key = rangeName; - stateTableFieldValues.emplace_back(FieldValueTuple(TIME_RANGE_STATUS_STR, TIME_RANGE_DISABLED_STR)); - for (const auto &i : fieldValues) { if (fvField(i) == "start") @@ -105,7 +139,6 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const else { SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); - // Can skip instead of returning invalid entry return task_process_status::task_invalid_entry; } } @@ -116,13 +149,35 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const return task_process_status::task_invalid_entry; } - // Create systemd files for time range and enable them + // Create cron files for time range and enable them // TODO sanitize inputs if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) { return task_process_status::task_need_retry; } + // Check if time range should be active by default + auto startExpr = make_cron(start); + auto endExpr = make_cron(end); + tm currentTM; + string time_range_default_status = TIME_RANGE_DISABLED_STR; + + + time_t currentTime = time(nullptr); + localtime_r(¤tTime, ¤tTM); + + + if (is_time_in_range(startExpr, endExpr, currentTM)) + { + SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); + time_range_default_status = TIME_RANGE_ENABLED_STR; + } + + // Prepare state table field-values + vector stateTableFieldValues; + string key = rangeName; + stateTableFieldValues.emplace_back(FieldValueTuple(TIME_RANGE_STATUS_STR, time_range_default_status)); + // Add time range status to range status table in state db m_stateTimeRangeStatusTable.set(key, stateTableFieldValues); @@ -153,6 +208,13 @@ void TimeRangeMgr::doTask(Consumer &consumer) task_status = doTimeRangeTask(rangeName, kfvFieldsValues(t)); } } + else if (op == DEL_COMMAND) + { + if (table_name == CFG_TIME_RANGE_TABLE_NAME) + { + task_status = doTimeRangeTaskDelete(rangeName); + } + } switch (task_status) { case task_process_status::task_failed: diff --git a/cfgmgr/timerangemgr.h b/cfgmgr/timerangemgr.h index 5df2e8bb51..4ec3f3c6d1 100644 --- a/cfgmgr/timerangemgr.h +++ b/cfgmgr/timerangemgr.h @@ -9,6 +9,8 @@ #include #include #include +#include "croncpp.h" + namespace swss { @@ -33,6 +35,8 @@ class TimeRangeMgr : public Orch task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); + bool is_time_in_range(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm currentTime); + bool is_time_in_range(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t currentTime); void doTask(Consumer &consumer); }; From 6ac24b5902dac17d62f23bc8237bdf3971281cc5 Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 19 May 2024 15:05:49 +0300 Subject: [PATCH 11/16] Implemented isTimeInRange() and removed changes added to croncpp.h --- cfgmgr/croncpp.h | 65 ----------------------------------------- cfgmgr/timerangemgr.cpp | 24 ++++++++------- cfgmgr/timerangemgr.h | 4 +-- 3 files changed, 16 insertions(+), 77 deletions(-) diff --git a/cfgmgr/croncpp.h b/cfgmgr/croncpp.h index a5b4642443..541dc1a5d3 100644 --- a/cfgmgr/croncpp.h +++ b/cfgmgr/croncpp.h @@ -914,69 +914,4 @@ namespace cron static std::chrono::system_clock::time_point cron_next(cronexpr const & cex, std::chrono::system_clock::time_point const & time_point) { return std::chrono::system_clock::from_time_t(cron_next(cex, std::chrono::system_clock::to_time_t(time_point))); } - -// New implementation not found in original code // - -template -size_t previous_set_bit(const std::bitset& bits, int start) { - for (int i = start; i >= 0; --i) { - if (bits.test(i)) return i; - } - return INVALID_INDEX; -} - -void subtract_from_field(std::tm& date, cron_field field, int amount) { - switch (field) { - case cron_field::minute: - date.tm_min -= amount; - break; - case cron_field::hour: - date.tm_hour -= amount; - break; - case cron_field::mday: - date.tm_mday -= amount; - break; - case cron_field::mon: - date.tm_mon -= amount; - break; - case cron_field::year: - date.tm_year -= amount; - break; - default: - break; - } - // Normalize the date to correct underflows - mktime(&date); -} - -size_t cron_previous(std::bitset<60> const& fieldBits, std::tm& date, cron_field field, int start) { - size_t lastValid = previous_set_bit(fieldBits, start); - if (lastValid != INVALID_INDEX) { - switch (field) { - case cron_field::minute: - date.tm_min = lastValid; - break; - case cron_field::hour: - date.tm_hour = lastValid; - break; - case cron_field::mday: - date.tm_mday = lastValid; - break; - case cron_field::mon: - date.tm_mon = lastValid - 1; // month in tm is zero-indexed - break; - case cron_field::year: - date.tm_year = lastValid - 1900; // tm_year is the number of years since 1900 - break; - default: - break; - } - mktime(&date); // normalize date - return lastValid; - } - return INVALID_INDEX; -} -// End of new implementation not found in original code // - - } \ No newline at end of file diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index 7aed07f13c..b409ae90c0 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -20,17 +20,23 @@ TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vecto } -bool is_time_in_range(const cronexpr& startExpr, const cronexpr& endExpr, const std::tm& currentTM) { +bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const std::tm& currentTM) { std::time_t currentTime = mktime(const_cast(¤tTM)); // Convert currentTM to time_t - time_t previousMinute = currentTime - 60; // Go back one minute to ensure full coverage + + // Call the other isTimeInRange function with the time_t version of current time + return this->isTimeInRange(startExpr, endExpr, currentTime); +} - // Convert back to tm - tm* prevTM = localtime(&previousMinute); +bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const std::time_t& currentTime) { + + // Find the next occurrence of the start time after the current time + std::time_t nextStartTime = cron_next(startExpr, currentTime); - time_t nextStartTime = cron_previous(startExpr, mktime(prevTM)); - time_t nextEndTime = cron_next(endExpr, mktime(prevTM)); + // Find the next occurrence of the end time after the current time + std::time_t nextEndTime = cron_next(endExpr, currentTime); - return (currentTime >= nextStartTime && currentTime < nextEndTime); + // Check if we are currently in the time range + return (nextStartTime > nextEndTime); } task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) @@ -164,10 +170,8 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const time_t currentTime = time(nullptr); - localtime_r(¤tTime, ¤tTM); - - if (is_time_in_range(startExpr, endExpr, currentTM)) + if (isTimeInRange(startExpr, endExpr, currentTime)) { SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); time_range_default_status = TIME_RANGE_ENABLED_STR; diff --git a/cfgmgr/timerangemgr.h b/cfgmgr/timerangemgr.h index 4ec3f3c6d1..e781771b69 100644 --- a/cfgmgr/timerangemgr.h +++ b/cfgmgr/timerangemgr.h @@ -35,8 +35,8 @@ class TimeRangeMgr : public Orch task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); - bool is_time_in_range(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm currentTime); - bool is_time_in_range(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t currentTime); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm currentTM); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t currentTime); void doTask(Consumer &consumer); }; From 931465714c83431516a52382c29a00252f7edc55 Mon Sep 17 00:00:00 2001 From: mazora Date: Mon, 20 May 2024 16:52:28 +0300 Subject: [PATCH 12/16] Fixed Most Compiler Errors (Still Does Not Compile) - Added license to croncpp.h - Removed static qualifier from replace_ordinals() in croncpp.h - Done in order to remove warning saying this function isn't used - change "enabled" and "disabled" to "active" and "inactive" respectively --- cfgmgr/croncpp.h | 441 ++++++++++++++++++---------------- cfgmgr/scheduledconfigmgr.cpp | 18 +- cfgmgr/timerangemgr.cpp | 11 +- cfgmgr/timerangemgr.h | 12 +- 4 files changed, 263 insertions(+), 219 deletions(-) diff --git a/cfgmgr/croncpp.h b/cfgmgr/croncpp.h index 541dc1a5d3..416a0e11b7 100644 --- a/cfgmgr/croncpp.h +++ b/cfgmgr/croncpp.h @@ -1,3 +1,28 @@ +/* File taken from https://github.com/mariusbancila/croncpp */ +/* +MIT License + +Copyright (c) 2018 Marius Bancila + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ + #pragma once #include @@ -18,16 +43,16 @@ namespace cron { #ifdef CRONCPP_IS_CPP17 - #define CRONCPP_STRING_VIEW std::string_view - #define CRONCPP_STRING_VIEW_NPOS std::string_view::npos - #define CRONCPP_CONSTEXPTR constexpr +#define CRONCPP_STRING_VIEW std::string_view +#define CRONCPP_STRING_VIEW_NPOS std::string_view::npos +#define CRONCPP_CONSTEXPTR constexpr #else - #define CRONCPP_STRING_VIEW std::string const & - #define CRONCPP_STRING_VIEW_NPOS std::string::npos - #define CRONCPP_CONSTEXPTR +#define CRONCPP_STRING_VIEW std::string const & +#define CRONCPP_STRING_VIEW_NPOS std::string::npos +#define CRONCPP_CONSTEXPTR #endif - using cron_int = uint8_t; + using cron_int = uint8_t; constexpr std::time_t INVALID_TIME = static_cast(-1); @@ -49,20 +74,19 @@ namespace cron }; template - static bool find_next(cronexpr const & cex, - std::tm& date, + static bool find_next(cronexpr const &cex, + std::tm &date, size_t const dot); } struct bad_cronexpr : public std::runtime_error { public: - explicit bad_cronexpr(CRONCPP_STRING_VIEW message) : - std::runtime_error(message.data()) - {} + explicit bad_cronexpr(CRONCPP_STRING_VIEW message) : std::runtime_error(message.data()) + { + } }; - struct cron_standard_traits { static const cron_int CRON_MIN_SECONDS = 0; @@ -86,18 +110,18 @@ namespace cron static const cron_int CRON_MAX_YEARS_DIFF = 4; #ifdef CRONCPP_IS_CPP17 - static const inline std::vector DAYS = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; - static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static const inline std::vector DAYS = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; + static const inline std::vector MONTHS = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; #else - static std::vector& DAYS() + static std::vector &DAYS() { - static std::vector days = { "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static std::vector days = {"SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; return days; } - static std::vector& MONTHS() + static std::vector &MONTHS() { - static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static std::vector months = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; return months; } #endif @@ -126,19 +150,19 @@ namespace cron static const cron_int CRON_MAX_YEARS_DIFF = 4; #ifdef CRONCPP_IS_CPP17 - static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; - static const inline std::vector MONTHS = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static const inline std::vector DAYS = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; + static const inline std::vector MONTHS = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; #else - static std::vector& DAYS() + static std::vector &DAYS() { - static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static std::vector days = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; return days; } - static std::vector& MONTHS() + static std::vector &MONTHS() { - static std::vector months = { "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static std::vector months = {"JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; return months; } #endif @@ -167,18 +191,18 @@ namespace cron static const cron_int CRON_MAX_YEARS_DIFF = 4; #ifdef CRONCPP_IS_CPP17 - static const inline std::vector DAYS = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; - static const inline std::vector MONTHS = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static const inline std::vector DAYS = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; + static const inline std::vector MONTHS = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; #else - static std::vector& DAYS() + static std::vector &DAYS() { - static std::vector days = { "NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT" }; + static std::vector days = {"NIL", "SUN", "MON", "TUE", "WED", "THU", "FRI", "SAT"}; return days; } - static std::vector& MONTHS() + static std::vector &MONTHS() { - static std::vector months = { "NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC" }; + static std::vector months = {"NIL", "JAN", "FEB", "MAR", "APR", "MAY", "JUN", "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"}; return months; } #endif @@ -194,66 +218,64 @@ namespace cron std::bitset<60> seconds; std::bitset<60> minutes; std::bitset<24> hours; - std::bitset<7> days_of_week; + std::bitset<7> days_of_week; std::bitset<31> days_of_month; std::bitset<12> months; - std::string expr; + std::string expr; - friend bool operator==(cronexpr const & e1, cronexpr const & e2); - friend bool operator!=(cronexpr const & e1, cronexpr const & e2); + friend bool operator==(cronexpr const &e1, cronexpr const &e2); + friend bool operator!=(cronexpr const &e1, cronexpr const &e2); template - friend bool detail::find_next(cronexpr const & cex, - std::tm& date, + friend bool detail::find_next(cronexpr const &cex, + std::tm &date, size_t const dot); - friend std::string to_cronstr(cronexpr const& cex); - friend std::string to_string(cronexpr const & cex); + friend std::string to_cronstr(cronexpr const &cex); + friend std::string to_string(cronexpr const &cex); template friend cronexpr make_cron(CRONCPP_STRING_VIEW expr); }; - inline bool operator==(cronexpr const & e1, cronexpr const & e2) + inline bool operator==(cronexpr const &e1, cronexpr const &e2) { - return - e1.seconds == e2.seconds && - e1.minutes == e2.minutes && - e1.hours == e2.hours && - e1.days_of_week == e2.days_of_week && - e1.days_of_month == e2.days_of_month && - e1.months == e2.months; + return e1.seconds == e2.seconds && + e1.minutes == e2.minutes && + e1.hours == e2.hours && + e1.days_of_week == e2.days_of_week && + e1.days_of_month == e2.days_of_month && + e1.months == e2.months; } - inline bool operator!=(cronexpr const & e1, cronexpr const & e2) + inline bool operator!=(cronexpr const &e1, cronexpr const &e2) { return !(e1 == e2); } - inline std::string to_string(cronexpr const & cex) + inline std::string to_string(cronexpr const &cex) { - return - cex.seconds.to_string() + " " + - cex.minutes.to_string() + " " + - cex.hours.to_string() + " " + - cex.days_of_month.to_string() + " " + - cex.months.to_string() + " " + - cex.days_of_week.to_string(); + return cex.seconds.to_string() + " " + + cex.minutes.to_string() + " " + + cex.hours.to_string() + " " + + cex.days_of_month.to_string() + " " + + cex.months.to_string() + " " + + cex.days_of_week.to_string(); } - inline std::string to_cronstr(cronexpr const& cex) + inline std::string to_cronstr(cronexpr const &cex) { return cex.expr; } namespace utils { - inline std::time_t tm_to_time(std::tm& date) + inline std::time_t tm_to_time(std::tm &date) { return std::mktime(&date); } - inline std::tm* time_to_tm(std::time_t const * date, std::tm* const out) + inline std::tm *time_to_tm(std::time_t const *date, std::tm *const out) { #ifdef _WIN32 errno_t err = localtime_s(out, date); @@ -271,7 +293,8 @@ namespace cron str.imbue(std::locale(setlocale(LC_ALL, nullptr))); str >> std::get_time(&result, "%Y-%m-%d %H:%M:%S"); - if (str.fail()) throw std::runtime_error("Parsing date failed!"); + if (str.fail()) + throw std::runtime_error("Parsing date failed!"); #else int year = 1900; int month = 1; @@ -292,13 +315,14 @@ namespace cron return result; } - inline std::string to_string(std::tm const & tm) + inline std::string to_string(std::tm const &tm) { #if __cplusplus > 201103L std::ostringstream str; str.imbue(std::locale(setlocale(LC_ALL, nullptr))); str << std::put_time(&tm, "%Y-%m-%d %H:%M:%S"); - if (str.fail()) throw std::runtime_error("Writing date failed!"); + if (str.fail()) + throw std::runtime_error("Writing date failed!"); return str.str(); #else @@ -311,7 +335,8 @@ namespace cron inline std::string to_upper(std::string text) { std::transform(std::begin(text), std::end(text), - std::begin(text), [](char const c) { return static_cast(std::toupper(c)); }); + std::begin(text), [](char const c) + { return static_cast(std::toupper(c)); }); return text; } @@ -343,30 +368,30 @@ namespace cron { return static_cast(std::stoul(text.data())); } - catch (std::exception const & ex) + catch (std::exception const &ex) { throw bad_cronexpr(ex.what()); } } - static std::string replace_ordinals( - std::string text, - std::vector const & replacement) + std::string replace_ordinals( + std::string text, + std::vector const &replacement) { for (size_t i = 0; i < replacement.size(); ++i) { auto pos = text.find(replacement[i]); if (std::string::npos != pos) - text.replace(pos, 3 ,std::to_string(i)); + text.replace(pos, 3, std::to_string(i)); } return text; } static std::pair make_range( - CRONCPP_STRING_VIEW field, - cron_int const minval, - cron_int const maxval) + CRONCPP_STRING_VIEW field, + cron_int const minval, + cron_int const maxval) { cron_int first = 0; cron_int last = 0; @@ -403,29 +428,29 @@ namespace cron throw bad_cronexpr("Specified range start exceeds range end"); } - return { first, last }; + return {first, last}; } template static void set_cron_field( - CRONCPP_STRING_VIEW value, - std::bitset& target, - cron_int const minval, - cron_int const maxval) + CRONCPP_STRING_VIEW value, + std::bitset &target, + cron_int const minval, + cron_int const maxval) { - if(value.length() > 0 && value[value.length()-1] == ',') + if (value.length() > 0 && value[value.length() - 1] == ',') throw bad_cronexpr("Value cannot end with comma"); auto fields = utils::split(value, ','); if (fields.empty()) throw bad_cronexpr("Expression parsing error"); - for (auto const & field : fields) + for (auto const &field : fields) { if (!utils::contains(field, '/')) { #ifdef CRONCPP_IS_CPP17 - auto[first, last] = detail::make_range(field, minval, maxval); + auto [first, last] = detail::make_range(field, minval, maxval); #else auto range = detail::make_range(field, minval, maxval); auto first = range.first; @@ -443,7 +468,7 @@ namespace cron throw bad_cronexpr("Incrementer must have two fields"); #ifdef CRONCPP_IS_CPP17 - auto[first, last] = detail::make_range(parts[0], minval, maxval); + auto [first, last] = detail::make_range(parts[0], minval, maxval); #else auto range = detail::make_range(parts[0], minval, maxval); auto first = range.first; @@ -456,7 +481,7 @@ namespace cron } auto delta = detail::to_cron_int(parts[1]); - if(delta <= 0) + if (delta <= 0) throw bad_cronexpr("Incrementer must be a positive value"); for (cron_int i = first - minval; i <= last - minval; i += delta) @@ -469,16 +494,16 @@ namespace cron template static void set_cron_days_of_week( - std::string value, - std::bitset<7>& target) + std::string value, + std::bitset<7> &target) { auto days = utils::to_upper(value); auto days_replaced = detail::replace_ordinals( - days, + days, #ifdef CRONCPP_IS_CPP17 - Traits::DAYS + Traits::DAYS #else - Traits::DAYS() + Traits::DAYS() #endif ); @@ -486,68 +511,69 @@ namespace cron days_replaced[0] = '*'; set_cron_field( - days_replaced, - target, - Traits::CRON_MIN_DAYS_OF_WEEK, - Traits::CRON_MAX_DAYS_OF_WEEK); + days_replaced, + target, + Traits::CRON_MIN_DAYS_OF_WEEK, + Traits::CRON_MAX_DAYS_OF_WEEK); } template static void set_cron_days_of_month( - std::string value, - std::bitset<31>& target) + std::string value, + std::bitset<31> &target) { if (value.size() == 1 && value[0] == '?') value[0] = '*'; set_cron_field( - value, - target, - Traits::CRON_MIN_DAYS_OF_MONTH, - Traits::CRON_MAX_DAYS_OF_MONTH); + value, + target, + Traits::CRON_MIN_DAYS_OF_MONTH, + Traits::CRON_MAX_DAYS_OF_MONTH); } template static void set_cron_month( - std::string value, - std::bitset<12>& target) + std::string value, + std::bitset<12> &target) { auto month = utils::to_upper(value); auto month_replaced = replace_ordinals( - month, + month, #ifdef CRONCPP_IS_CPP17 - Traits::MONTHS + Traits::MONTHS #else - Traits::MONTHS() + Traits::MONTHS() #endif ); set_cron_field( - month_replaced, - target, - Traits::CRON_MIN_MONTHS, - Traits::CRON_MAX_MONTHS); + month_replaced, + target, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS); } template inline size_t next_set_bit( - std::bitset const & target, - size_t /*minimum*/, - size_t /*maximum*/, - size_t offset) + std::bitset const &target, + size_t /*minimum*/, + size_t /*maximum*/, + size_t offset) { for (auto i = offset; i < N; ++i) { - if (target.test(i)) return i; + if (target.test(i)) + return i; } return INVALID_INDEX; } inline void add_to_field( - std::tm& date, - cron_field const field, - int const val) + std::tm &date, + cron_field const field, + int const val) { switch (field) { @@ -579,9 +605,9 @@ namespace cron } inline void set_field( - std::tm& date, - cron_field const field, - int const val) + std::tm &date, + cron_field const field, + int const val) { switch (field) { @@ -615,8 +641,8 @@ namespace cron } inline void reset_field( - std::tm& date, - cron_field const field) + std::tm &date, + cron_field const field) { switch (field) { @@ -650,8 +676,8 @@ namespace cron } inline void reset_all_fields( - std::tm& date, - std::bitset<7> const & marked_fields) + std::tm &date, + std::bitset<7> const &marked_fields) { for (size_t i = 0; i < marked_fields.size(); ++i) { @@ -661,8 +687,8 @@ namespace cron } inline void mark_field( - std::bitset<7> & orders, - cron_field const field) + std::bitset<7> &orders, + cron_field const field) { if (!orders.test(static_cast(field))) orders.set(static_cast(field)); @@ -670,14 +696,14 @@ namespace cron template static size_t find_next( - std::bitset const & target, - std::tm& date, - unsigned int const minimum, - unsigned int const maximum, - unsigned int const value, - cron_field const field, - cron_field const next_field, - std::bitset<7> const & marked_fields) + std::bitset const &target, + std::tm &date, + unsigned int const minimum, + unsigned int const maximum, + unsigned int const value, + cron_field const field, + cron_field const next_field, + std::bitset<7> const &marked_fields) { auto next_value = next_set_bit(target, minimum, maximum, value); if (INVALID_INDEX == next_value) @@ -698,19 +724,19 @@ namespace cron template static size_t find_next_day( - std::tm& date, - std::bitset<31> const & days_of_month, - size_t day_of_month, - std::bitset<7> const & days_of_week, - size_t day_of_week, - std::bitset<7> const & marked_fields) + std::tm &date, + std::bitset<31> const &days_of_month, + size_t day_of_month, + std::bitset<7> const &days_of_week, + size_t day_of_week, + std::bitset<7> const &marked_fields) { unsigned int count = 0; unsigned int maximum = 366; while ( - (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) || - !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) - && count++ < maximum) + (!days_of_month.test(day_of_month - Traits::CRON_MIN_DAYS_OF_MONTH) || + !days_of_week.test(day_of_week - Traits::CRON_MIN_DAYS_OF_WEEK)) && + count++ < maximum) { add_to_field(date, cron_field::day_of_month, 1); @@ -724,25 +750,25 @@ namespace cron } template - static bool find_next(cronexpr const & cex, - std::tm& date, + static bool find_next(cronexpr const &cex, + std::tm &date, size_t const dot) { bool res = true; - std::bitset<7> marked_fields{ 0 }; - std::bitset<7> empty_list{ 0 }; + std::bitset<7> marked_fields{0}; + std::bitset<7> empty_list{0}; unsigned int second = date.tm_sec; auto updated_second = find_next( - cex.seconds, - date, - Traits::CRON_MIN_SECONDS, - Traits::CRON_MAX_SECONDS, - second, - cron_field::second, - cron_field::minute, - empty_list); + cex.seconds, + date, + Traits::CRON_MIN_SECONDS, + Traits::CRON_MAX_SECONDS, + second, + cron_field::second, + cron_field::minute, + empty_list); if (second == updated_second) { @@ -751,14 +777,14 @@ namespace cron unsigned int minute = date.tm_min; auto update_minute = find_next( - cex.minutes, - date, - Traits::CRON_MIN_MINUTES, - Traits::CRON_MAX_MINUTES, - minute, - cron_field::minute, - cron_field::hour_of_day, - marked_fields); + cex.minutes, + date, + Traits::CRON_MIN_MINUTES, + Traits::CRON_MAX_MINUTES, + minute, + cron_field::minute, + cron_field::hour_of_day, + marked_fields); if (minute == update_minute) { mark_field(marked_fields, cron_field::minute); @@ -766,19 +792,20 @@ namespace cron else { res = find_next(cex, date, dot); - if (!res) return res; + if (!res) + return res; } unsigned int hour = date.tm_hour; auto updated_hour = find_next( - cex.hours, - date, - Traits::CRON_MIN_HOURS, - Traits::CRON_MAX_HOURS, - hour, - cron_field::hour_of_day, - cron_field::day_of_week, - marked_fields); + cex.hours, + date, + Traits::CRON_MIN_HOURS, + Traits::CRON_MAX_HOURS, + hour, + cron_field::hour_of_day, + cron_field::day_of_week, + marked_fields); if (hour == updated_hour) { mark_field(marked_fields, cron_field::hour_of_day); @@ -786,18 +813,19 @@ namespace cron else { res = find_next(cex, date, dot); - if (!res) return res; + if (!res) + return res; } unsigned int day_of_week = date.tm_wday; unsigned int day_of_month = date.tm_mday; auto updated_day_of_month = find_next_day( - date, - cex.days_of_month, - day_of_month, - cex.days_of_week, - day_of_week, - marked_fields); + date, + cex.days_of_month, + day_of_month, + cex.days_of_week, + day_of_week, + marked_fields); if (day_of_month == updated_day_of_month) { mark_field(marked_fields, cron_field::day_of_month); @@ -805,26 +833,28 @@ namespace cron else { res = find_next(cex, date, dot); - if (!res) return res; + if (!res) + return res; } unsigned int month = date.tm_mon; auto updated_month = find_next( - cex.months, - date, - Traits::CRON_MIN_MONTHS, - Traits::CRON_MAX_MONTHS, - month, - cron_field::month, - cron_field::year, - marked_fields); + cex.months, + date, + Traits::CRON_MIN_MONTHS, + Traits::CRON_MAX_MONTHS, + month, + cron_field::month, + cron_field::year, + marked_fields); if (month != updated_month) { if (date.tm_year - dot > Traits::CRON_MAX_YEARS_DIFF) return false; res = find_next(cex, date, dot); - if (!res) return res; + if (!res) + return res; } return res; @@ -841,9 +871,10 @@ namespace cron auto fields = utils::split(expr, ' '); fields.erase( - std::remove_if(std::begin(fields), std::end(fields), - [](CRONCPP_STRING_VIEW s) {return s.empty(); }), - std::end(fields)); + std::remove_if(std::begin(fields), std::end(fields), + [](CRONCPP_STRING_VIEW s) + { return s.empty(); }), + std::end(fields)); if (fields.size() != 6) throw bad_cronexpr("cron expression must have six fields"); @@ -863,16 +894,18 @@ namespace cron } template - static std::tm cron_next(cronexpr const & cex, std::tm date) + static std::tm cron_next(cronexpr const &cex, std::tm date) { time_t original = utils::tm_to_time(date); - if (INVALID_TIME == original) return {}; + if (INVALID_TIME == original) + return {}; if (!detail::find_next(cex, date, date.tm_year)) return {}; time_t calculated = utils::tm_to_time(date); - if (INVALID_TIME == calculated) return {}; + if (INVALID_TIME == calculated) + return {}; if (calculated == original) { @@ -885,33 +918,37 @@ namespace cron } template - static std::time_t cron_next(cronexpr const & cex, std::time_t const & date) + static std::time_t cron_next(cronexpr const &cex, std::time_t const &date) { std::tm val; - std::tm* dt = utils::time_to_tm(&date, &val); - if (dt == nullptr) return INVALID_TIME; + std::tm *dt = utils::time_to_tm(&date, &val); + if (dt == nullptr) + return INVALID_TIME; time_t original = utils::tm_to_time(*dt); - if (INVALID_TIME == original) return INVALID_TIME; + if (INVALID_TIME == original) + return INVALID_TIME; - if(!detail::find_next(cex, *dt, dt->tm_year)) + if (!detail::find_next(cex, *dt, dt->tm_year)) return INVALID_TIME; time_t calculated = utils::tm_to_time(*dt); - if (INVALID_TIME == calculated) return calculated; + if (INVALID_TIME == calculated) + return calculated; if (calculated == original) { add_to_field(*dt, detail::cron_field::second, 1); - if(!detail::find_next(cex, *dt, dt->tm_year)) + if (!detail::find_next(cex, *dt, dt->tm_year)) return INVALID_TIME; } return utils::tm_to_time(*dt); } - template - static std::chrono::system_clock::time_point cron_next(cronexpr const & cex, std::chrono::system_clock::time_point const & time_point) { - return std::chrono::system_clock::from_time_t(cron_next(cex, std::chrono::system_clock::to_time_t(time_point))); - } + template + static std::chrono::system_clock::time_point cron_next(cronexpr const &cex, std::chrono::system_clock::time_point const &time_point) + { + return std::chrono::system_clock::from_time_t(cron_next(cex, std::chrono::system_clock::to_time_t(time_point))); + } } \ No newline at end of file diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index 31b3a71b86..3e37341a68 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -82,12 +82,17 @@ bool ScheduledConfigMgr::isTimeRangeActive(const string &timeRangeName) SWSS_LOG_ENTER(); string status = ""; - Table timeRangeStatusTable = reinterpret_cast
(getExecutor(timeRangeName)); - if (!timeRangeStatusTable.hget(timeRangeName, TIME_RANGE_STATUS_STR, status)){ + Table* timeRangeStatusTable = dynamic_cast
(getExecutor(timeRangeName)); + if (!timeRangeStatusTable->hget(timeRangeName, "status", status)){ SWSS_LOG_ERROR("Failed to get time range status for %s", timeRangeName.c_str()); + delete timeRangeStatusTable; + timeRangeStatusTable = nullptr; return false; } - return status==TIME_RANGE_ENABLED_STR; + delete timeRangeStatusTable; + timeRangeStatusTable = nullptr; + + return status=="active"; } bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) @@ -212,7 +217,7 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t } // Verify time range does not exist in the scheduledConfigurations hashmap - if scheduledConfigurations[timeRangeName].find(scheduledConfigName) != scheduledConfigurations[timeRangeName].end()) + if (scheduledConfigurations[timeRangeName].find(scheduledConfigName) != scheduledConfigurations[timeRangeName].end()) { SWSS_LOG_ERROR("Scheduled configuration %s already exists for time range %s", scheduledConfigName.c_str(), timeRangeName.c_str()); return task_process_status::task_failed; @@ -227,10 +232,10 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t { if (task_process_status::task_success != applyConfiguration(scheduledConfigName, configJson)) { - SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigName.c_str()); return task_process_status::task_need_retry; } - SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); + SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigName.c_str()); } } @@ -349,6 +354,7 @@ task_process_status ScheduledConfigMgr::disableTimeRange(const string &timeRange SWSS_LOG_ENTER(); string configName{}; + json configJson{}; // Check if there are any configurations for the time range if (scheduledConfigurations[timeRangeName].empty()) diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index b409ae90c0..7bfc9344c0 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -8,7 +8,6 @@ #include "timerangemgr.h" #include "tokenize.h" #include -#include "croncpp.h" using namespace std; using namespace swss; @@ -70,10 +69,10 @@ task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const s string disableCrontabName = taskName + "-disable"; // Create command for enabling the task - string command_enabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ENABLED_STR + "'"; + string command_enabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ACTIVE_STR + "'"; // Create command for disabling the task - string command_disabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_DISABLED_STR + "'"; + string command_disabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_INACTIVE_STR + "'"; if (runOnce) { // Delete the time range configuration entry after the task has been disabled @@ -97,6 +96,7 @@ task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const s return task_process_status::task_success; } + task_process_status TimeRangeMgr::doTimeRangeTaskDelete(const string &rangeName) { SWSS_LOG_ENTER(); @@ -165,8 +165,7 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const // Check if time range should be active by default auto startExpr = make_cron(start); auto endExpr = make_cron(end); - tm currentTM; - string time_range_default_status = TIME_RANGE_DISABLED_STR; + string time_range_default_status = TIME_RANGE_INACTIVE_STR; time_t currentTime = time(nullptr); @@ -174,7 +173,7 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const if (isTimeInRange(startExpr, endExpr, currentTime)) { SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); - time_range_default_status = TIME_RANGE_ENABLED_STR; + time_range_default_status = TIME_RANGE_ACTIVE_STR; } // Prepare state table field-values diff --git a/cfgmgr/timerangemgr.h b/cfgmgr/timerangemgr.h index e781771b69..d813579b0e 100644 --- a/cfgmgr/timerangemgr.h +++ b/cfgmgr/timerangemgr.h @@ -14,8 +14,8 @@ namespace swss { -#define TIME_RANGE_ENABLED_STR "enabled" -#define TIME_RANGE_DISABLED_STR "disabled" +#define TIME_RANGE_ACTIVE_STR "active" +#define TIME_RANGE_INACTIVE_STR "inactive" #define TIME_RANGE_STATUS_STR "status" @@ -34,10 +34,12 @@ class TimeRangeMgr : public Orch task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); - task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); - bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm currentTM); - bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t currentTime); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm& currentTM); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t& currentTime); + void doTask(Consumer &consumer); + task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); + task_process_status doTimeRangeTaskDelete(const std::string &rangeName); }; } From 74f29376d13737455025fdcae7a61bb5bc9de52b Mon Sep 17 00:00:00 2001 From: mazora Date: Wed, 22 May 2024 14:18:48 +0300 Subject: [PATCH 13/16] Changed Data Structure ScheduledConfig DB (Compiles - not tested) - Changed DB to use a nested unordered_map instead of a vector of pairs - Surrounded replace_ordinals() in croncpp.h to remove unused fxn warning - Surrounded make_cron() usage with try-catch --- cfgmgr/croncpp.h | 8 +++++--- cfgmgr/scheduledconfigmgr.cpp | 8 ++++---- cfgmgr/scheduledconfigmgr.h | 7 +++---- cfgmgr/timerangemgr.cpp | 21 ++++++++++++++------- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/cfgmgr/croncpp.h b/cfgmgr/croncpp.h index 416a0e11b7..dc9fb652c5 100644 --- a/cfgmgr/croncpp.h +++ b/cfgmgr/croncpp.h @@ -373,8 +373,9 @@ namespace cron throw bad_cronexpr(ex.what()); } } - - std::string replace_ordinals( +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-function" + static std::string replace_ordinals( std::string text, std::vector const &replacement) { @@ -387,6 +388,7 @@ namespace cron return text; } +#pragma GCC diagnostic pop static std::pair make_range( CRONCPP_STRING_VIEW field, @@ -538,7 +540,7 @@ namespace cron std::bitset<12> &target) { auto month = utils::to_upper(value); - auto month_replaced = replace_ordinals( + auto month_replaced = detail::replace_ordinals( month, #ifdef CRONCPP_IS_CPP17 Traits::MONTHS diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index 3e37341a68..c62c9040f8 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -224,7 +224,7 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t } // Add the configuration to the scheduledConfigurations hashmap - scheduledConfigurations[timeRangeName].emplace_back(scheduledConfigName, configJson); + scheduledConfigurations[timeRangeName][scheduledConfigName] = configJson; SWSS_LOG_INFO("Successfully added %s to time range %s ", scheduledConfigName.c_str(), timeRangeName.c_str()); // Apply the configuration if the time range currrently is active @@ -271,8 +271,8 @@ task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRang if (scheduledConfigurations.find(timeRangeName) == scheduledConfigurations.end()) { SWSS_LOG_INFO("Time range %s is being created in the local db", timeRangeName.c_str()); - // Create the time range in the local db - scheduledConfigurations[timeRangeName] = ConfigList{}; + // Create the time range in the local db with default value + scheduledConfigurations[timeRangeName]; SWSS_LOG_INFO("Adding unbound configurations for time range %s", timeRangeName.c_str()); if (unboundConfigurations.find(timeRangeName) != unboundConfigurations.end()) @@ -280,7 +280,7 @@ task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRang for (const auto &configData : unboundConfigurations[timeRangeName]) { SWSS_LOG_NOTICE("Binding configuration %s to time range %s", configData.first.c_str(), timeRangeName.c_str()); - scheduledConfigurations[timeRangeName].emplace_back(configData); + scheduledConfigurations[timeRangeName].insert(configData); } unboundConfigurations.erase(timeRangeName); } diff --git a/cfgmgr/scheduledconfigmgr.h b/cfgmgr/scheduledconfigmgr.h index 90ed04bd10..7fe011c5d0 100644 --- a/cfgmgr/scheduledconfigmgr.h +++ b/cfgmgr/scheduledconfigmgr.h @@ -10,11 +10,10 @@ #include using json = nlohmann::json; + // Define a type alias for the configuration data structure -using ConfigData = std::pair; // Pair of configName and configuration JSON object -// using ConfigData = std::tuple; // Tuple of configName, configType, and configuration JSON object -using ConfigList = std::vector; // A list of configurations -using TimeRangeConfigMap = std::unordered_map; // Maps time range names to lists of configurations +using ConfigData = std::unordered_map; // Maps configName to JSON object +using TimeRangeConfigMap = std::unordered_map; // Maps time range names to map of config data namespace swss { diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index 7bfc9344c0..7205fc3f4f 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -128,6 +128,8 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const string end = ""; string runOnce = ""; + cron::detail::replace_ordinals("", vector{}); + for (const auto &i : fieldValues) { if (fvField(i) == "start") @@ -163,17 +165,22 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const } // Check if time range should be active by default - auto startExpr = make_cron(start); - auto endExpr = make_cron(end); string time_range_default_status = TIME_RANGE_INACTIVE_STR; + try + { + auto startExpr = make_cron(start); + auto endExpr = make_cron(end); + time_t currentTime = time(nullptr); - time_t currentTime = time(nullptr); - - if (isTimeInRange(startExpr, endExpr, currentTime)) + if (isTimeInRange(startExpr, endExpr, currentTime)) + { + SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); + time_range_default_status = TIME_RANGE_ACTIVE_STR; + } + } catch (bad_cronexpr const & ex) { - SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); - time_range_default_status = TIME_RANGE_ACTIVE_STR; + SWSS_LOG_WARN("%s", ex.what()); } // Prepare state table field-values From f24b99be6169270a88e8308588ca30090b04d59c Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 2 Jun 2024 17:03:41 +0300 Subject: [PATCH 14/16] Finished Basic Functionality of Feature Will now correctly add and remove configurations when time range becomes active/inactive. Time range status will be updated to active immediately if the current time is within the newly set time range. Added internal database to keep track of active/inactive scheduled configurations in case of delete. --- cfgmgr/scheduledconfigmgr.cpp | 136 +++++++++++++++++++++++++--------- cfgmgr/scheduledconfigmgr.h | 8 +- cfgmgr/timerangemgr.cpp | 11 ++- 3 files changed, 116 insertions(+), 39 deletions(-) diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index c62c9040f8..9fc5a76072 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -19,6 +19,35 @@ ScheduledConfigMgr::ScheduledConfigMgr(vector &connectors, DBCon m_appDb = appDb; } +string ScheduledConfigMgr::findTimeRangeByConfiguration(string scheduledConfigurationName) { + for (const auto& pair : scheduledConfigurations) { + const std::string& timeRangeName = pair.first; + const ConfigData& configDataMap = pair.second; + + // Check if the scheduledConfigurationName exists in the inner ConfigData map + if (configDataMap.find(scheduledConfigurationName) != configDataMap.end()) { + return timeRangeName; + } + } + return ""; // Return an empty string if not found +} + +DBConnector* ScheduledConfigMgr::getDBConnector(const string &tableName){ + + Consumer* tableConsumer = static_cast(getExecutor(tableName)); + if (!tableConsumer) + { + SWSS_LOG_ERROR("Failed to get consumer for %s", tableName.c_str()); + return nullptr; + } + DBConnector* tableDBConnector = const_cast(tableConsumer->getDbConnector()); + if (!tableDBConnector) { + SWSS_LOG_ERROR("Failed to get DB connector for %s", tableName.c_str()); + return nullptr; + } + return tableDBConnector; +} + string join(const json &jsonArray, const string &delimiter) { string result; @@ -80,19 +109,20 @@ vector convertJsonToFieldValues(const json &jsonObj) bool ScheduledConfigMgr::isTimeRangeActive(const string &timeRangeName) { SWSS_LOG_ENTER(); - string status = ""; + shared_ptr statusPtr{}; + string key = ""; + DBConnector* timeRangeStatusDBConnector = getDBConnector(STATE_TIME_RANGE_STATUS_TABLE_NAME); + if (!timeRangeStatusDBConnector) + return false; - Table* timeRangeStatusTable = dynamic_cast
(getExecutor(timeRangeName)); - if (!timeRangeStatusTable->hget(timeRangeName, "status", status)){ + key = STATE_TIME_RANGE_STATUS_TABLE_NAME + SonicDBConfig::getSeparator(timeRangeStatusDBConnector) + timeRangeName; + statusPtr = timeRangeStatusDBConnector->hget(key, "status"); + if (!statusPtr){ SWSS_LOG_ERROR("Failed to get time range status for %s", timeRangeName.c_str()); - delete timeRangeStatusTable; - timeRangeStatusTable = nullptr; return false; } - delete timeRangeStatusTable; - timeRangeStatusTable = nullptr; - return status=="active"; + return *statusPtr=="active"; } bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, const json &tableKeyFields) @@ -123,7 +153,7 @@ bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, c return true; } -bool ScheduledConfigMgr::removeTableConfiguration(const std::string &tableName) +bool ScheduledConfigMgr::removeTableConfiguration(const string &tableName, const string &key) { SWSS_LOG_ENTER(); @@ -131,11 +161,11 @@ bool ScheduledConfigMgr::removeTableConfiguration(const std::string &tableName) ProducerStateTable tableObj(m_appDb, tableName); // Create a Table object and set the field values - tableObj.clear(); + tableObj.del(key); return true; } -task_process_status ScheduledConfigMgr::applyConfiguration(const std::string &configName, const json &configJson) +task_process_status ScheduledConfigMgr::applyConfiguration(const string &configName, const json &configJson) { SWSS_LOG_ENTER(); @@ -154,18 +184,35 @@ task_process_status ScheduledConfigMgr::applyConfiguration(const std::string &co return task_process_status::task_success; } -task_process_status ScheduledConfigMgr::removeConfiguration(const std::string &configName, const json &configJson) +task_process_status ScheduledConfigMgr::removeConfiguration(const string &configName, const json &configJson) { SWSS_LOG_ENTER(); - string tableName = ""; - - for (const auto &tableEntry : configJson.items()) - { - tableName = tableEntry.key(); + std::string tableName; + std::string key; + + for (const auto &tableEntry : configJson.items()) { + tableName = tableEntry.key(); // e.g., "ACL_TABLE_TABLE" or "ACL_TABLE_TABLE:ACL_TABLE_NAME" + const json &innerObject = tableEntry.value(); + + // Check if the outer key already contains the entire table name and key + size_t pos = tableName.find(':'); + if (pos != std::string::npos) { + key = tableName.substr(pos + 1); // Extract the key part after ':' + tableName = tableName.substr(0, pos); // Extract the table name part before ':' + } else if (innerObject.is_object()) { + // Iterate through the inner object to get the key + for (const auto &innerEntry : innerObject.items()) { + key = innerEntry.key(); // e.g., "ACL_TABLE_NAME" + break; // We only need the first key for this function + } + } else { + SWSS_LOG_ERROR("Expected JSON object for key: %s", tableName.c_str()); + return task_process_status::task_failed; + } - if (!removeTableConfiguration(tableName)) - { - SWSS_LOG_ERROR("Failed to remove configuration %s for table: %s", configName.c_str(), tableName.c_str()); + // Call removeTableConfiguration with the parsed tableName and key + if (!removeTableConfiguration(tableName, key)) { + SWSS_LOG_ERROR("Failed to remove configuration %s for table: %s with key: %s", configName.c_str(), tableName.c_str(), key.c_str()); return task_process_status::task_failed; } } @@ -195,7 +242,6 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t try { - // Parse the configuration string into a JSON object for validation // Assuming the configuration is in a JSON string format SWSS_LOG_DEBUG("===JSON CONFIGURATION STRING BEFORE PROCESS==="); @@ -227,7 +273,7 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t scheduledConfigurations[timeRangeName][scheduledConfigName] = configJson; SWSS_LOG_INFO("Successfully added %s to time range %s ", scheduledConfigName.c_str(), timeRangeName.c_str()); - // Apply the configuration if the time range currrently is active + // Apply the configuration if the time range currently is active if (isTimeRangeActive(timeRangeName)) { if (task_process_status::task_success != applyConfiguration(scheduledConfigName, configJson)) @@ -235,9 +281,13 @@ task_process_status ScheduledConfigMgr::doProcessScheduledConfiguration(string t SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigName.c_str()); return task_process_status::task_need_retry; } + // Add the configuration to the scheduledConfigurationStatus hashmap with status true + scheduledConfigurationStatus[scheduledConfigName] = true; SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigName.c_str()); + } else { + // Add the configuration to the scheduledConfigurationStatus hashmap with status false + scheduledConfigurationStatus[scheduledConfigName] = false; } - } catch (const json::exception &e) { @@ -290,9 +340,10 @@ task_process_status ScheduledConfigMgr::doProcessTimeRangeStatus(string timeRang } // If the time range exists, apply the configuration based on the status - if (status == "enabled") + if (status == "active"){ task_status = enableTimeRange(timeRangeName); - else if (status == "disabled") + } + else if (status == "inactive") { task_status = disableTimeRange(timeRangeName); } @@ -344,6 +395,7 @@ task_process_status ScheduledConfigMgr::enableTimeRange(const string &timeRangeN SWSS_LOG_ERROR("Could not apply configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); return task_process_status::task_need_retry; } + scheduledConfigurationStatus[configName] = true; SWSS_LOG_INFO("Applied configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); } return task_process_status::task_success; @@ -377,6 +429,7 @@ task_process_status ScheduledConfigMgr::disableTimeRange(const string &timeRange SWSS_LOG_ERROR("Could not remove configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); return task_process_status::task_need_retry; } + scheduledConfigurationStatus[configName] = false; SWSS_LOG_INFO("Removed configuration for time range %s, configName: %s", timeRangeName.c_str(), configName.c_str()); } return task_process_status::task_success; @@ -415,7 +468,7 @@ void ScheduledConfigMgr::doTimeRangeTask(Consumer &consumer) } } - if (task_status != task_process_status::task_success) + if (task_status == task_process_status::task_success) { task_status = doProcessTimeRangeStatus(timeRangeName, status); } @@ -497,21 +550,33 @@ void ScheduledConfigMgr::doScheduledConfigurationTask(Consumer &consumer) task_status = task_process_status::task_invalid_entry; } } - if (task_status != task_process_status::task_success) + if (task_status == task_process_status::task_success) { task_status = doProcessScheduledConfiguration(timeRangeName, scheduledConfigurationName, configuration); } } else if (op == DEL_COMMAND) { - // Remove the configuration - if (scheduledConfigurations.find(timeRangeName) != scheduledConfigurations.end()) - { - if (task_process_status::task_success != removeConfiguration(scheduledConfigurationName, scheduledConfigurations[timeRangeName])) - { - SWSS_LOG_ERROR("Could not remove configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); - task_status = task_process_status::task_need_retry; + if (scheduledConfigurationStatus.find(scheduledConfigurationName) != scheduledConfigurationStatus.end()){ + if (scheduledConfigurationStatus[scheduledConfigurationName]){ + // Get scheduled configuration time range name + timeRangeName = findTimeRangeByConfiguration(scheduledConfigurationName); + + // Remove the configuration + if (scheduledConfigurations.find(timeRangeName) != scheduledConfigurations.end()) + { + if (task_process_status::task_success != removeConfiguration(scheduledConfigurationName, scheduledConfigurations[timeRangeName][scheduledConfigurationName])) + { + SWSS_LOG_ERROR("Could not remove configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); + task_status = task_process_status::task_need_retry; + } + scheduledConfigurationStatus.erase(scheduledConfigurationName); + scheduledConfigurations[timeRangeName].erase(scheduledConfigurationName); + SWSS_LOG_INFO("Removed configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); + } } - SWSS_LOG_INFO("Removed configuration for time range %s, configName: %s", timeRangeName.c_str(), scheduledConfigurationName.c_str()); + } else { + SWSS_LOG_ERROR("Scheduled configuration %s does not exist", scheduledConfigurationName.c_str()); + task_status = task_process_status::task_failed; } } @@ -519,6 +584,7 @@ void ScheduledConfigMgr::doScheduledConfigurationTask(Consumer &consumer) { case task_process_status::task_failed: SWSS_LOG_ERROR("Failed to process table update"); + it = consumer.m_toSync.erase(it); return; case task_process_status::task_need_retry: SWSS_LOG_INFO("Unable to process table update. Will retry..."); diff --git a/cfgmgr/scheduledconfigmgr.h b/cfgmgr/scheduledconfigmgr.h index 7fe011c5d0..3e8974dad5 100644 --- a/cfgmgr/scheduledconfigmgr.h +++ b/cfgmgr/scheduledconfigmgr.h @@ -12,6 +12,7 @@ using json = nlohmann::json; // Define a type alias for the configuration data structure +using ConfigStatus = std::unordered_map; // Maps configName to config status boolean using ConfigData = std::unordered_map; // Maps configName to JSON object using TimeRangeConfigMap = std::unordered_map; // Maps time range names to map of config data @@ -26,6 +27,11 @@ class ScheduledConfigMgr : public Orch private: DBConnector *m_appDb; TimeRangeConfigMap scheduledConfigurations, unboundConfigurations; + ConfigStatus scheduledConfigurationStatus; + + // Helper Functions + DBConnector* getDBConnector(const std::string &tableName); + std::string findTimeRangeByConfiguration(std::string scheduledConfigurationName); // Validation Functions bool validateConfiguration(const json &configJson); @@ -38,7 +44,7 @@ class ScheduledConfigMgr : public Orch task_process_status disableTimeRange(const std::string &timeRangeName); task_process_status removeConfiguration(const std::string &configName, const json &configJson); - bool removeTableConfiguration(const std::string &tableName); + bool removeTableConfiguration(const std::string &tableName, const std::string &key); // Task Processing Functions task_process_status doProcessScheduledConfiguration(std::string timeRangeName, std::string configType, std::string configuration); diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index 7205fc3f4f..b0beff20e5 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -168,8 +168,13 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const string time_range_default_status = TIME_RANGE_INACTIVE_STR; try { - auto startExpr = make_cron(start); - auto endExpr = make_cron(end); + // croncpp.h uses nonstandard "seconds" field. Add "0 " to the beginning of the cron expression. + // This is a workaround to avoid using seconds field. + // TODO To make croncpp more efficient for standard cron use, remove seconds field from croncpp.h + auto startSeconds = string("0 ") + start; + auto endSeconds = string("0 ") + end; + auto startExpr = make_cron(startSeconds); + auto endExpr = make_cron(endSeconds); time_t currentTime = time(nullptr); @@ -243,4 +248,4 @@ void TimeRangeMgr::doTask(Consumer &consumer) break; } } -} +} \ No newline at end of file From a17d233cd0ef97d4ecf6f322e8502adf4cc9fe38 Mon Sep 17 00:00:00 2001 From: mazora Date: Tue, 4 Jun 2024 16:41:04 +0300 Subject: [PATCH 15/16] Implemnted Year Functionality and Removed Seconds from croncpp.h --- cfgmgr/croncpp.h | 75 ++++++++------------------------- cfgmgr/timerangemgr.cpp | 93 +++++++++++++++++++++++++++-------------- cfgmgr/timerangemgr.h | 8 ++-- 3 files changed, 84 insertions(+), 92 deletions(-) diff --git a/cfgmgr/croncpp.h b/cfgmgr/croncpp.h index dc9fb652c5..f0a898524f 100644 --- a/cfgmgr/croncpp.h +++ b/cfgmgr/croncpp.h @@ -64,7 +64,6 @@ namespace cron { enum class cron_field { - second, minute, hour_of_day, day_of_week, @@ -89,9 +88,6 @@ namespace cron struct cron_standard_traits { - static const cron_int CRON_MIN_SECONDS = 0; - static const cron_int CRON_MAX_SECONDS = 59; - static const cron_int CRON_MIN_MINUTES = 0; static const cron_int CRON_MAX_MINUTES = 59; @@ -129,9 +125,6 @@ namespace cron struct cron_oracle_traits { - static const cron_int CRON_MIN_SECONDS = 0; - static const cron_int CRON_MAX_SECONDS = 59; - static const cron_int CRON_MIN_MINUTES = 0; static const cron_int CRON_MAX_MINUTES = 59; @@ -170,9 +163,6 @@ namespace cron struct cron_quartz_traits { - static const cron_int CRON_MIN_SECONDS = 0; - static const cron_int CRON_MAX_SECONDS = 59; - static const cron_int CRON_MIN_MINUTES = 0; static const cron_int CRON_MAX_MINUTES = 59; @@ -215,7 +205,6 @@ namespace cron class cronexpr { - std::bitset<60> seconds; std::bitset<60> minutes; std::bitset<24> hours; std::bitset<7> days_of_week; @@ -240,8 +229,7 @@ namespace cron inline bool operator==(cronexpr const &e1, cronexpr const &e2) { - return e1.seconds == e2.seconds && - e1.minutes == e2.minutes && + return e1.minutes == e2.minutes && e1.hours == e2.hours && e1.days_of_week == e2.days_of_week && e1.days_of_month == e2.days_of_month && @@ -255,8 +243,7 @@ namespace cron inline std::string to_string(cronexpr const &cex) { - return cex.seconds.to_string() + " " + - cex.minutes.to_string() + " " + + return cex.minutes.to_string() + " " + cex.hours.to_string() + " " + cex.days_of_month.to_string() + " " + cex.months.to_string() + " " + @@ -579,9 +566,6 @@ namespace cron { switch (field) { - case cron_field::second: - date.tm_sec += val; - break; case cron_field::minute: date.tm_min += val; break; @@ -613,9 +597,6 @@ namespace cron { switch (field) { - case cron_field::second: - date.tm_sec = val; - break; case cron_field::minute: date.tm_min = val; break; @@ -648,9 +629,6 @@ namespace cron { switch (field) { - case cron_field::second: - date.tm_sec = 0; - break; case cron_field::minute: date.tm_min = 0; break; @@ -679,7 +657,7 @@ namespace cron inline void reset_all_fields( std::tm &date, - std::bitset<7> const &marked_fields) + std::bitset<6> const &marked_fields) { for (size_t i = 0; i < marked_fields.size(); ++i) { @@ -689,7 +667,7 @@ namespace cron } inline void mark_field( - std::bitset<7> &orders, + std::bitset<6> &orders, cron_field const field) { if (!orders.test(static_cast(field))) @@ -705,7 +683,7 @@ namespace cron unsigned int const value, cron_field const field, cron_field const next_field, - std::bitset<7> const &marked_fields) + std::bitset<6> const &marked_fields) { auto next_value = next_set_bit(target, minimum, maximum, value); if (INVALID_INDEX == next_value) @@ -731,7 +709,7 @@ namespace cron size_t day_of_month, std::bitset<7> const &days_of_week, size_t day_of_week, - std::bitset<7> const &marked_fields) + std::bitset<6> const &marked_fields) { unsigned int count = 0; unsigned int maximum = 366; @@ -758,24 +736,8 @@ namespace cron { bool res = true; - std::bitset<7> marked_fields{0}; - std::bitset<7> empty_list{0}; - - unsigned int second = date.tm_sec; - auto updated_second = find_next( - cex.seconds, - date, - Traits::CRON_MIN_SECONDS, - Traits::CRON_MAX_SECONDS, - second, - cron_field::second, - cron_field::minute, - empty_list); - - if (second == updated_second) - { - mark_field(marked_fields, cron_field::second); - } + std::bitset<6> marked_fields{0}; + std::bitset<6> empty_list{0}; unsigned int minute = date.tm_min; auto update_minute = find_next( @@ -877,18 +839,17 @@ namespace cron [](CRONCPP_STRING_VIEW s) { return s.empty(); }), std::end(fields)); - if (fields.size() != 6) - throw bad_cronexpr("cron expression must have six fields"); + if (fields.size() != 5) + throw bad_cronexpr("cron expression must have five fields"); - detail::set_cron_field(fields[0], cex.seconds, Traits::CRON_MIN_SECONDS, Traits::CRON_MAX_SECONDS); - detail::set_cron_field(fields[1], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES); - detail::set_cron_field(fields[2], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS); + detail::set_cron_field(fields[0], cex.minutes, Traits::CRON_MIN_MINUTES, Traits::CRON_MAX_MINUTES); + detail::set_cron_field(fields[1], cex.hours, Traits::CRON_MIN_HOURS, Traits::CRON_MAX_HOURS); - detail::set_cron_days_of_week(fields[5], cex.days_of_week); + detail::set_cron_days_of_week(fields[4], cex.days_of_week); - detail::set_cron_days_of_month(fields[3], cex.days_of_month); + detail::set_cron_days_of_month(fields[2], cex.days_of_month); - detail::set_cron_month(fields[4], cex.months); + detail::set_cron_month(fields[3], cex.months); cex.expr = expr; @@ -911,7 +872,7 @@ namespace cron if (calculated == original) { - add_to_field(date, detail::cron_field::second, 1); + add_to_field(date, detail::cron_field::minute, 1); if (!detail::find_next(cex, date, date.tm_year)) return {}; } @@ -940,7 +901,7 @@ namespace cron if (calculated == original) { - add_to_field(*dt, detail::cron_field::second, 1); + add_to_field(*dt, detail::cron_field::minute, 1); if (!detail::find_next(cex, *dt, dt->tm_year)) return INVALID_TIME; } @@ -953,4 +914,4 @@ namespace cron { return std::chrono::system_clock::from_time_t(cron_next(cex, std::chrono::system_clock::to_time_t(time_point))); } -} \ No newline at end of file +} diff --git a/cfgmgr/timerangemgr.cpp b/cfgmgr/timerangemgr.cpp index b0beff20e5..dd25e191bc 100644 --- a/cfgmgr/timerangemgr.cpp +++ b/cfgmgr/timerangemgr.cpp @@ -19,26 +19,47 @@ TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vecto } -bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const std::tm& currentTM) { - std::time_t currentTime = mktime(const_cast(¤tTM)); // Convert currentTM to time_t +bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const tm& currentTM, const string& startYear = "", const string& endYear = "") { + time_t currentTime = mktime(const_cast(¤tTM)); // Convert currentTM to time_t // Call the other isTimeInRange function with the time_t version of current time - return this->isTimeInRange(startExpr, endExpr, currentTime); + return this->isTimeInRange(startExpr, endExpr, currentTime, startYear, endYear); } -bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const std::time_t& currentTime) { +bool TimeRangeMgr::isTimeInRange(const cronexpr& startExpr, const cronexpr& endExpr, const time_t& currentTime, const string& startYear = "", const string& endYear = "") { + // Check if the current year is within the start and end year range + bool startYearExists = (startYear != ""); + bool endYearExists = (endYear != ""); + + if (startYearExists || endYearExists) + { + // Get the current year + tm currentTM = *localtime(¤tTime); + int currentYear = currentTM.tm_year + 1900; // tm_year is years since 1900 + + // Check if the current year is within the start and end year range + if (startYearExists && currentYear < stoi(startYear)) + { + return false; + } + if (endYearExists && currentYear > stoi(endYear)) + { + return false; + } + } + // Find the next occurrence of the start time after the current time - std::time_t nextStartTime = cron_next(startExpr, currentTime); + time_t nextStartTime = cron_next(startExpr, currentTime); // Find the next occurrence of the end time after the current time - std::time_t nextEndTime = cron_next(endExpr, currentTime); + time_t nextEndTime = cron_next(endExpr, currentTime); // Check if we are currently in the time range return (nextStartTime > nextEndTime); } -task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) +task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command) { string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; ofstream crontabFile{cronFileName}; @@ -51,10 +72,6 @@ task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const crontabFile << schedule << " "; crontabFile << CRON_USERNAME_STR << " "; crontabFile << command; - if (deleteSelfAfterCompletion) - { - crontabFile << " ; rm " << cronFileName; - } crontabFile << endl; crontabFile.close(); @@ -63,31 +80,42 @@ task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const } // TODO add rollback mechanism -task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) +task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, const string &startYear = "", const string &endYear = "") { string enableCrontabName = taskName + "-enable"; string disableCrontabName = taskName + "-disable"; + // Create year check string + string yearCheck = ""; + if (startYear != "") + { + yearCheck = "[ $(date +\"%Y\") -ge " + startYear + " ]"; + } + if (endYear != "") + { + if (startYear != "") + { + yearCheck += " && "; + } + yearCheck += "[ $(date +\"%Y\") -le " + endYear + " ]"; + } + // Create command for enabling the task string command_enabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ACTIVE_STR + "'"; + command_enabled = yearCheck + " && " + command_enabled; // Create command for disabling the task string command_disabled = string("/usr/bin/redis-cli -n ") + to_string(STATE_DB) + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_INACTIVE_STR + "'"; - if (runOnce) - { - // Delete the time range configuration entry after the task has been disabled - // writeCrontabFile() will delete the crontab file itself after the task has been executed - command_disabled += " ; /usr/bin/redis-cli -n " + to_string(CONFIG_DB) + " del '" + CFG_TIME_RANGE_TABLE_NAME + "|" + taskName + "'"; - } + command_disabled = yearCheck + " && " + command_disabled; // Service file for enabling the task - if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) + if (writeCrontabFile(enableCrontabName, startTime, command_enabled) != task_process_status::task_success) { return task_process_status::task_need_retry; } // Service file for disabling the task - if (writeCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) + if (writeCrontabFile(disableCrontabName, endTime, command_disabled) != task_process_status::task_success) { return task_process_status::task_need_retry; } @@ -126,9 +154,8 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const SWSS_LOG_ENTER(); string start = ""; string end = ""; - string runOnce = ""; - - cron::detail::replace_ordinals("", vector{}); + string start_year = ""; + string end_year = ""; for (const auto &i : fieldValues) { @@ -140,9 +167,13 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const { end = fvValue(i); } - else if (fvField(i) == "runOnce") + else if (fvField(i) == "start_year") + { + start_year = fvValue(i); + } + else if (fvField(i) == "end_year") { - runOnce = fvValue(i); + end_year = fvValue(i); } else { @@ -159,7 +190,7 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const // Create cron files for time range and enable them // TODO sanitize inputs - if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) + if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, start_year, end_year)) { return task_process_status::task_need_retry; } @@ -171,14 +202,14 @@ task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const // croncpp.h uses nonstandard "seconds" field. Add "0 " to the beginning of the cron expression. // This is a workaround to avoid using seconds field. // TODO To make croncpp more efficient for standard cron use, remove seconds field from croncpp.h - auto startSeconds = string("0 ") + start; - auto endSeconds = string("0 ") + end; - auto startExpr = make_cron(startSeconds); - auto endExpr = make_cron(endSeconds); + // auto startSeconds = string("0 ") + start; + // auto endSeconds = string("0 ") + end; + auto startExpr = make_cron(start); + auto endExpr = make_cron(end); time_t currentTime = time(nullptr); - if (isTimeInRange(startExpr, endExpr, currentTime)) + if (isTimeInRange(startExpr, endExpr, currentTime, start_year, end_year)) { SWSS_LOG_INFO("Time range %s is active", rangeName.c_str()); time_range_default_status = TIME_RANGE_ACTIVE_STR; diff --git a/cfgmgr/timerangemgr.h b/cfgmgr/timerangemgr.h index d813579b0e..a97c166fb7 100644 --- a/cfgmgr/timerangemgr.h +++ b/cfgmgr/timerangemgr.h @@ -32,10 +32,10 @@ class TimeRangeMgr : public Orch private: Table m_stateTimeRangeStatusTable; - task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command, bool deleteSelfAfterCompletion); - task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, bool runOnce); - bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm& currentTM); - bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t& currentTime); + task_process_status writeCrontabFile(const std::string& fileName, const std::string& schedule, const std::string& command); + task_process_status createCronjobs(const std::string& rangeName, const std::string& start, const std::string& end, const std::string& startYear, const std::string& endYear); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::tm& currentTM, const std::string& startYear, const std::string& endYear); + bool isTimeInRange(const cron::cronexpr& startExpr, const cron::cronexpr& endExpr, const std::time_t& currentTime, const std::string& startYear, const std::string& endYear); void doTask(Consumer &consumer); task_process_status doTimeRangeTask(const std::string& rangeName, const std::vector& fieldValues); From 92414aaeb7328a1c40bd66f7fcdbac52d36bee11 Mon Sep 17 00:00:00 2001 From: mazora Date: Sun, 30 Jun 2024 17:33:20 +0300 Subject: [PATCH 16/16] Remove Unecessary Files --- cfgmgr/cronhelper.cpp | 123 ---------------------- cfgmgr/cronhelper.h | 50 --------- cfgmgr/scheduledconfigmgr.cpp | 2 + cfgmgr/schedulermgr.cpp | 188 ---------------------------------- 4 files changed, 2 insertions(+), 361 deletions(-) delete mode 100644 cfgmgr/cronhelper.cpp delete mode 100644 cfgmgr/cronhelper.h delete mode 100644 cfgmgr/schedulermgr.cpp diff --git a/cfgmgr/cronhelper.cpp b/cfgmgr/cronhelper.cpp deleted file mode 100644 index 56ee187e7f..0000000000 --- a/cfgmgr/cronhelper.cpp +++ /dev/null @@ -1,123 +0,0 @@ -#include "cronhelper.h" -#include -#include -#include -#include "logger.h" - -using namespace std; - -// CronField class implementation - -// Constructor -CronField::CronField(string field, string regex):m_field(field), m_regex(regex){ - if (!this->validate()){ - throw invalid_argument("Cron field does not match regex"); - } -} - -// Validate field -bool CronField::validate(){ - regex pattern(this->m_regex); - return regex_match(this->m_field, pattern); -} - -// CronExpression class implementation - -// Constructor -CronExpression::CronExpression(const string &expression){ - if (!this->parse(expression)){ - throw invalid_argument("Invalid cron expression"); - } -} - -// Parse expression -bool CronExpression::parse(const string &expression){ - - // Tokenize expression - vector tokens; - string token; - istringstream tokenStream(expression); - - while (getline(tokenStream, token, ' ')){ - tokens.push_back(token); - } - - // Validate number of tokens - if (tokens.size() != 5){ - return false; - } - - try - { - // Minute - CronField minute(tokens[0], m_cronMinuteRegex); - this->fields.push_back(minute); - - // Hour - CronField hour(tokens[1], m_cronHourRegex); - this->fields.push_back(hour); - - // Day of month - CronField dayOfMonth(tokens[2], m_cronDayOfMonthRegex); - this->fields.push_back(dayOfMonth); - - // Month - CronField month(tokens[3], m_cronMonthRegex); - this->fields.push_back(month); - - // Day of week - CronField dayOfWeek(tokens[4], m_cronDayOfWeekRegex); - this->fields.push_back(dayOfWeek); - - } - catch(const std::invalid_argument& e) - { - SWSS_LOG_ERROR(e.what()); - return false; - } - - return true; -} - -// Get fields -vector CronExpression::getFields(){ - return this->fields; -} - -// Get expression -string CronExpression::getExpression(){ - string expression = ""; - for (const auto &field : this->fields){ - expression += field.getExpression() + " "; - } - return expression; -} - -// CronTimeRange class implementation - -// Constructor -CronTimeRange::CronTimeRange(string name, CronExpression start, CronExpression end):expression(name), m_start(start), m_end(end){}; - -// Check if time range is active -bool CronTimeRange::isActive(){ - // Get current time - time_t now = time(0); - tm *ltm = localtime(&now); - - // Check if current time is within time range - for (const auto &field : this->m_start.getFields()){ - // Check if current time is within start time range - if (!field.validate()){ - return false; - } - } - - for (const auto &field : this->m_end.getFields()){ - // Check if current time is within end time range - if (!field.validate()){ - return false; - } - } - - return true; -} \ No newline at end of file diff --git a/cfgmgr/cronhelper.h b/cfgmgr/cronhelper.h deleted file mode 100644 index 46f28abaed..0000000000 --- a/cfgmgr/cronhelper.h +++ /dev/null @@ -1,50 +0,0 @@ -#ifndef __CRONHELPER_H__ -#define __CRONHELPER_H__ - -#include -#include - -class CronField{ - public: - CronField(std::string field, std::string regex); - - private: - std::string m_field; - std::string m_regex; - bool validate(); -}; - -class CronExpression{ - public: - std::vector fields; - - CronExpression(const std::string &expression); - const std::vector& getFields() const; - std::string getExpression(); - - private: - // Taken from https://www.codeproject.com/Tips/5299523/Regex-for-Cron-Expressions - std::string m_cronMinuteRegex = R"((\*|(?:\*|(?:[0-9]|(?:[1-5][0-9])))\/(?:[0-9]|(?:[1-5][0-9]))|(?:[0-9]|(?:[1-5][0-9]))(?:(?:\-[0-9]|\-(?:[1-5][0-9]))?|(?:\,(?:[0-9]|(?:[1-5][0-9])))*)))"; - std::string m_cronHourRegex = R"((\*|(?:\*|(?:\*|(?:[0-9]|1[0-9]|2[0-3])))\/(?:[0-9]|1[0-9]|2[0-3])|(?:[0-9]|1[0-9]|2[0-3])(?:(?:\-(?:[0-9]|1[0-9]|2[0-3]))?|(?:\,(?:[0-9]|1[0-9]|2[0-3]))*)))"; - std::string m_cronDayOfMonthRegex = R"((\*|\?|L(?:W|\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:W|\/(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:[1-9]|(?:[12][0-9])|3[01])(?:(?:\-(?:[1-9]|(?:[12][0-9])|3[01]))?|(?:\,(?:[1-9]|(?:[12][0-9])|3[01]))*)))"; - std::string m_cronMonthRegex = R"((\*|(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC)(?:(?:\-(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))?|(?:\,(?:[1-9]|1[012]|JAN|FEB|MAR|APR|MAY|JUN|JUL|AUG|SEP|OCT|NOV|DEC))*)))"; - std::string m_cronDayOfWeekRegex = R"((\*|\?|[0-6](?:L|\#[1-5])?|(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT)(?:(?:\-(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))?|(?:\,(?:[0-6]|SUN|MON|TUE|WED|THU|FRI|SAT))*)))"; - - - bool validate(); - bool parse(const std::string &expression); -}; - -class CronTimeRange{ - public: - std::string expression; - CronTimeRange(std::string name, CronExpression start, CronExpression end); - bool isActive(); - - - private: - CronExpression m_start; - CronExpression m_end; -}; - -#endif /* __CRONHELPER_H__ */ \ No newline at end of file diff --git a/cfgmgr/scheduledconfigmgr.cpp b/cfgmgr/scheduledconfigmgr.cpp index 9fc5a76072..6b755866c8 100644 --- a/cfgmgr/scheduledconfigmgr.cpp +++ b/cfgmgr/scheduledconfigmgr.cpp @@ -50,6 +50,7 @@ DBConnector* ScheduledConfigMgr::getDBConnector(const string &tableName){ string join(const json &jsonArray, const string &delimiter) { + // Join the elements of the JSON array into a single string string result; for (auto it = jsonArray.begin(); it != jsonArray.end(); ++it) @@ -132,6 +133,7 @@ bool ScheduledConfigMgr::applyTableConfiguration(const std::string &tableName, c // Create a Table object for the given tableName ProducerStateTable tableObj(m_appDb, tableName); + // Extract the key and fieldValues from the JSON object for (auto it = tableKeyFields.begin(); it != tableKeyFields.end(); ++it) { diff --git a/cfgmgr/schedulermgr.cpp b/cfgmgr/schedulermgr.cpp deleted file mode 100644 index 067be35c79..0000000000 --- a/cfgmgr/schedulermgr.cpp +++ /dev/null @@ -1,188 +0,0 @@ -#include -#include -#include -#include -#include "logger.h" -#include "dbconnector.h" -#include "timer.h" -#include "timerangemgr.h" - -using namespace std; -using namespace swss; - -TimeRangeMgr::TimeRangeMgr(DBConnector *cfgDb, DBConnector *stateDb, const vector &tableNames) : Orch(cfgDb, tableNames), - m_cfgTimeRangeTable(cfgDb, CFG_TIME_RANGE_TABLE_NAME), - m_stateTimeRangeStatusTable(stateDb, STATE_TIME_RANGE_STATUS_TABLE_NAME) -{ -} - -task_process_status TimeRangeMgr::writeCrontabFile(const string &fileName, const string &schedule, const string &command, bool deleteSelfAfterCompletion) -{ - string cronFileName = CRON_FILES_PATH_PREFIX_STR + fileName; - ofstream crontabFile{cronFileName}; - - if (crontabFile.fail()) - { - SWSS_LOG_ERROR("Failed to create crontab file for %s", fileName.c_str()); - return task_process_status::task_need_retry; - } - crontabFile << schedule << " "; - crontabFile << CRON_USERNAME_STR << " "; - crontabFile << command; - if (deleteSelfAfterCompletion) - { - crontabFile << " ; rm " << cronFileName; - } - crontabFile << endl; - crontabFile.close(); - - SWSS_LOG_DEBUG("Crontab file for %s has been created", fileName.c_str()); - return task_process_status::task_success; -} - -// TODO add rollback mechanism -task_process_status TimeRangeMgr::createCronjobs(const string &taskName, const string &startTime, const string &endTime, bool runOnce) -{ - string enableCrontabName = taskName + "-enable"; - string disableCrontabName = taskName + "-disable"; - - // Create command for enabling the task - string command_enabled = string("/usr/bin/redis-cli -n ") + STATE_DB + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_ENABLED_STR + "'"; - // { - // stringstream ss; - // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_ENABLED_STR << "'"; - // command_enabled = ss.str(); - // } - - // Create command for disabling the task - string command_disabled = string("/usr/bin/redis-cli -n ") + STATE_DB + " HSET '" + STATE_TIME_RANGE_STATUS_TABLE_NAME + "|" + taskName + "' '" + TIME_RANGE_STATUS_STR + "' '" + TIME_RANGE_DISABLED_STR + "'"; - if (runOnce) - { - // Delete the time range configuration entry after the task has been disabled - // writeCrontabFile() will delete the crontab file itself after the task has been executed - command_disabled += " ; /usr/bin/redis-cli -n " + CONFIG_DB + " del '" + CFG_TIME_RANGE_TABLE_NAME + "|" + taskName + "'"; - } - // { - // stringstream ss; - // ss << "/usr/bin/redis-cli -n " << STATE_DB << " HSET '" << STATE_TIME_RANGE_STATUS_TABLE_NAME << "|" << taskName << "' '" << TIME_RANGE_STATUS_STR << "' '" << TIME_RANGE_DISABLED_STR << "'"; - // if (runOnce){ - // // Delete the time range configuration entry after the task has been disabled - // // writeCrontabFile() will delete the crontab file itself after the task has been executed - // ss << " ; /usr/bin/redis-cli -n " << CONFIG_DB << " del '" << CFG_TIME_RANGE_TABLE_NAME << "|" << taskName << "'"; - // } - // command_disabled = ss.str(); - // } - - // Service file for enabling the task - if (writeCrontabFile(enableCrontabName, startTime, command_enabled, runOnce) != task_process_status::task_success) - { - return task_process_status::task_need_retry; - } - - // Service file for disabling the task - if (writeCrontabFile(disableCrontabName, endTime, command_disabled, runOnce) != task_process_status::task_success) - { - return task_process_status::task_need_retry; - } - - SWSS_LOG_INFO("Succesfully created crontab files for %s", taskName.c_str()); - - return task_process_status::task_success; -} - -task_process_status TimeRangeMgr::doTimeRangeTask(const string &rangeName, const vector &fieldValues) -{ - SWSS_LOG_ENTER(); - string start = ""; - string end = ""; - string runOnce = ""; - - // Set time range status to disabled by default - vector stateTableFieldValues; - string key = rangeName; - stateTableFieldValues.emplace_back(FieldValueTuple(TIME_RANGE_STATUS_STR, TIME_RANGE_DISABLED_STR)); - - for (const auto &i : fieldValues) - { - if (fvField(i) == "start") - { - start = fvValue(i); - } - else if (fvField(i) == "end") - { - end = fvValue(i); - } - else if (fvField(i) == "runOnce") - { - runOnce = fvValue(i); - } - else - { - SWSS_LOG_ERROR("Time range %s has unknown field %s", rangeName.c_str(), fvField(i).c_str()); - // Can skip instead of returning invalid entry - return task_process_status::task_invalid_entry; - } - } - - if (start == "" || end == "") - { - SWSS_LOG_ERROR("Time range %s is missing start or end time", rangeName.c_str()); - return task_process_status::task_invalid_entry; - } - - // Create systemd files for time range and enable them - // TODO sanitize inputs - if (task_process_status::task_need_retry == createCronjobs(rangeName, start, end, (runOnce == "true"))) - { - return task_process_status::task_need_retry; - } - - // Add time range status to range status table in state db - m_stateTimeRangeStatusTable.set(key, stateTableFieldValues); - - return task_process_status::task_success; -} - -void TimeRangeMgr::doTask(Consumer &consumer) -{ - SWSS_LOG_ENTER(); - - string table_name = consumer.getTableName(); - - auto it = consumer.m_toSync.begin(); - while (it != consumer.m_toSync.end()) - { - KeyOpFieldsValuesTuple t = it->second; - - string keySeparator = CONFIGDB_KEY_SEPARATOR; - vector keys = tokenize(kfvKey(t), keySeparator[0]); - string rangeName(keys[0]); - - string op = kfvOp(t); - task_process_status task_status = task_process_status::task_success; - if (op == SET_COMMAND) - { - if (table_name == CFG_TIME_RANGE_TABLE_NAME) - { - task_status = doTimeRangeTask(rangeName, kfvFieldsValues(t)); - } - } - switch (task_status) - { - case task_process_status::task_failed: - SWSS_LOG_ERROR("Failed to process table update"); - return; - case task_process_status::task_need_retry: - SWSS_LOG_INFO("Unable to process table update. Will retry..."); - ++it; - break; - case task_process_status::task_invalid_entry: - SWSS_LOG_ERROR("Failed to process invalid entry, drop it"); - it = consumer.m_toSync.erase(it); - break; - default: - it = consumer.m_toSync.erase(it); - break; - } - } -}