Skip to content

Commit 7ef170b

Browse files
DataSync: Added signal-triggered watched paths logging utility
- Introduced a mechanism to handle SIGUSR1 signal for debugging purposes. - Added support to read a list of paths from a predefined file and log, via journalctl, whether each path is currently being watched or not. - On receiving the SIGUSR1 signal, the system logs all currently watched paths to a file for inspection. - Helps in live debugging and verification of inotify watch status without restarting services. Tested - Added test cases. - Verified on simics. Example JSON file generated: ```bash $ kill -10 1156 $ cat /tmp/current_watched_paths.json [ "/var/lib", "/var/lib/phosphor-state-manager" ] ``` Change-Id: I251fcf15c5307a2ceca5e464d0bd2ba1b98f7974 Signed-off-by: Harsh Agarwal <[email protected]>
1 parent a9374eb commit 7ef170b

File tree

5 files changed

+212
-0
lines changed

5 files changed

+212
-0
lines changed

src/data_watcher.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ DataWatcher::DataWatcher(sdbusplus::async::context& ctx, const int inotifyFlags,
1818
_fdioInstance(
1919
std::make_unique<sdbusplus::async::fdio>(ctx, _inotifyFileDescriptor()))
2020
{
21+
getAllWatchers().push_back(this);
2122
createWatchers(_dataPathToWatch);
2223
}
2324

@@ -32,6 +33,9 @@ DataWatcher::~DataWatcher()
3233
}
3334
});
3435
}
36+
auto& instances = getAllWatchers();
37+
instances.erase(std::remove(instances.begin(), instances.end(), this),
38+
instances.end());
3539
}
3640

3741
int DataWatcher::inotifyInit() const

src/data_watcher.hpp

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,29 @@ class DataWatcher
127127
*/
128128
sdbusplus::async::task<DataOperations> onDataChange();
129129

130+
/**
131+
* @brief API to get all the instances of the DataWatcher
132+
*
133+
* @returns std::vector<DataWatcher*> - A reference to the vector containing
134+
* all instances of this class
135+
*/
136+
static std::vector<DataWatcher*>& getAllWatchers()
137+
{
138+
static std::vector<DataWatcher*> instances;
139+
return instances;
140+
}
141+
142+
/**
143+
* @brief API to get the file descriptor of the inotify instance
144+
*
145+
* @returns std::map<WD, fs::path>& - A reference to the map of WD and their
146+
* associated file paths.
147+
*/
148+
const std::map<WD, fs::path>& getWatchDescriptors() const
149+
{
150+
return _watchDescriptors;
151+
}
152+
130153
private:
131154
/**
132155
* @brief inotify flags

src/manager.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <nlohmann/json.hpp>
88
#include <phosphor-logging/lg2.hpp>
99

10+
#include <csignal>
1011
#include <cstdlib>
1112
#include <exception>
1213
#include <fstream>
@@ -46,9 +47,59 @@ sdbusplus::async::task<> Manager::init()
4647
co_await startFullSync();
4748
}
4849

50+
std::signal(SIGUSR1, logSignalHandler);
4951
co_return co_await startSyncEvents();
5052
}
5153

54+
std::unordered_set<std::string> Manager::collectAllWatchedPaths()
55+
{
56+
std::unordered_set<std::string> watchedPaths;
57+
for (const auto* watcher : watch::inotify::DataWatcher::getAllWatchers())
58+
{
59+
std::ranges::transform(
60+
watcher->getWatchDescriptors(),
61+
std::inserter(watchedPaths, watchedPaths.end()),
62+
[](const auto& wdPathPair) { return wdPathPair.second.string(); });
63+
}
64+
return watchedPaths;
65+
}
66+
67+
void Manager::logSignalHandler(int signal)
68+
{
69+
lg2::info("Received signal: {SIGNAL}", "SIGNAL", signal);
70+
std::unordered_set<std::string> watchedPaths = collectAllWatchedPaths();
71+
72+
std::string checkForWatchedPaths = "/tmp/check_watched_paths.txt";
73+
if (std::ifstream infile{checkForWatchedPaths}; infile)
74+
{
75+
for (std::string line; std::getline(infile, line);)
76+
{
77+
std::string_view status =
78+
watchedPaths.contains(line) ? "Watching" : "Not Watching";
79+
lg2::info("{STATUS} path: {PATH}", "STATUS", status, "PATH", line);
80+
}
81+
}
82+
else
83+
{
84+
lg2::info("Failed to open {FILE}", "FILE", checkForWatchedPaths);
85+
}
86+
87+
// write all the paths to a file
88+
try
89+
{
90+
std::string storeWatchedPath = "/tmp/current_watched_paths.json";
91+
data_sync::persist::util::writeFile(nlohmann::json(watchedPaths),
92+
storeWatchedPath);
93+
lg2::info("Wrote all watched paths to file: {FILE}", "FILE",
94+
storeWatchedPath);
95+
}
96+
catch (const std::exception& e)
97+
{
98+
lg2::error("Error writing all watched paths to file: {ERROR}", "ERROR",
99+
e);
100+
}
101+
}
102+
52103
// NOLINTNEXTLINE
53104
sdbusplus::async::task<> Manager::parseConfiguration()
54105
{

src/manager.hpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
#include <filesystem>
1111
#include <ranges>
12+
#include <unordered_set>
1213
#include <vector>
1314

1415
namespace data_sync
@@ -201,6 +202,23 @@ class Manager
201202
*/
202203
bool isSyncEligible(const config::DataSyncConfig& dataSyncCfg);
203204

205+
/**
206+
* @brief A helper function to handle logging when a SIGUSR1 signal is
207+
* received.
208+
*
209+
* @param[in] signal - The signal value that triggered the handler.
210+
*/
211+
static void logSignalHandler(int signal);
212+
213+
/**
214+
* @brief A helper API to collect all the paths currently being watched for
215+
* immediate sync.
216+
*
217+
* @returns std::unordered_set<std::string> - A set containing all the paths
218+
* being watched.
219+
*/
220+
static std::unordered_set<std::string> collectAllWatchedPaths();
221+
204222
/**
205223
* @brief The async context object used to perform operations asynchronously
206224
* as required.

test/manager_test.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// SPDX-License-Identifier: Apache-2.0
22
#include "manager_test.hpp"
33

4+
#include <csignal>
5+
46
std::filesystem::path ManagerTest::dataSyncCfgDir;
57
std::filesystem::path ManagerTest::tmpDataSyncDataDir;
68
nlohmann::json ManagerTest::commonJsonData;
@@ -204,3 +206,117 @@ TEST_F(ManagerTest, testDBusDataPersistency)
204206

205207
ctx.run();
206208
}
209+
210+
TEST_F(ManagerTest, testSignalReceiverLogic)
211+
{
212+
using namespace std::literals;
213+
namespace ed = data_sync::ext_data;
214+
215+
std::unique_ptr<ed::ExternalDataIFaces> extDataIface =
216+
std::make_unique<ed::MockExternalDataIFaces>();
217+
218+
ed::MockExternalDataIFaces* mockExtDataIfaces =
219+
dynamic_cast<ed::MockExternalDataIFaces*>(extDataIface.get());
220+
221+
ON_CALL(*mockExtDataIfaces, fetchBMCRedundancyMgrProps())
222+
// NOLINTNEXTLINE
223+
.WillByDefault([&mockExtDataIfaces]() -> sdbusplus::async::task<> {
224+
mockExtDataIfaces->setBMCRole(ed::BMCRole::Active);
225+
mockExtDataIfaces->setBMCRedundancy(true);
226+
co_return;
227+
});
228+
229+
EXPECT_CALL(*mockExtDataIfaces, fetchSiblingBmcIP())
230+
// NOLINTNEXTLINE
231+
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });
232+
233+
EXPECT_CALL(*mockExtDataIfaces, fetchRbmcCredentials())
234+
// NOLINTNEXTLINE
235+
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });
236+
237+
nlohmann::json jsonData = {
238+
{"Files",
239+
{
240+
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile1"},
241+
{"DestinationPath",
242+
ManagerTest::tmpDataSyncDataDir.string() + "/destFile1"},
243+
{"Description", "FullSync from Active to Passive bmc"},
244+
{"SyncDirection", "Active2Passive"},
245+
{"SyncType", "Immediate"}},
246+
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile2"},
247+
{"DestinationPath",
248+
ManagerTest::tmpDataSyncDataDir.string() + "/destFile2"},
249+
{"Description", "FullSync from Active to Passive bmc"},
250+
{"SyncDirection", "Active2Passive"},
251+
{"SyncType", "Immediate"}},
252+
}}};
253+
254+
std::string srcFile1{jsonData["Files"][0]["Path"]};
255+
std::string srcFile2{jsonData["Files"][1]["Path"]};
256+
257+
std::string destFile1{jsonData["Files"][0]["DestinationPath"]};
258+
std::string destFile2{jsonData["Files"][1]["DestinationPath"]};
259+
260+
writeConfig(jsonData);
261+
sdbusplus::async::context ctx;
262+
263+
std::string data1{"Data written on the file1\n"};
264+
std::string data2{"Data written on the file2\n"};
265+
266+
ManagerTest::writeData(srcFile1, data1);
267+
ManagerTest::writeData(srcFile2, data2);
268+
269+
ASSERT_EQ(ManagerTest::readData(srcFile1), data1);
270+
ASSERT_EQ(ManagerTest::readData(srcFile2), data2);
271+
272+
data_sync::Manager manager{ctx, std::move(extDataIface),
273+
ManagerTest::dataSyncCfgDir};
274+
275+
auto waitingForFullSyncToFinish =
276+
// NOLINTNEXTLINE
277+
[&](sdbusplus::async::context& ctx) -> sdbusplus::async::task<void> {
278+
auto status = manager.getFullSyncStatus();
279+
280+
while (status != FullSyncStatus::FullSyncCompleted &&
281+
status != FullSyncStatus::FullSyncFailed)
282+
{
283+
co_await sdbusplus::async::sleep_for(ctx,
284+
std::chrono::milliseconds(50));
285+
status = manager.getFullSyncStatus();
286+
}
287+
288+
EXPECT_EQ(status, FullSyncStatus::FullSyncCompleted)
289+
<< "FullSync status is not Completed!";
290+
291+
// Raising signal after full sync completes, so that sync events starts
292+
// and the watchers are being created.
293+
sdbusplus::async::sleep_for(ctx, 0.1s);
294+
// Raising a signal to trigger the signal handler logic. This should
295+
// write all the watched paths to a file.
296+
std::raise(SIGUSR1);
297+
298+
ctx.request_stop();
299+
300+
// Forcing to trigger inotify events so that all running immediate
301+
// sync tasks will resume and stop since the context is requested to
302+
// stop in the above.
303+
ManagerTest::writeData(srcFile1, data1);
304+
ManagerTest::writeData(srcFile2, data2);
305+
306+
// Reading the file where all the watched paths are written by signal
307+
// handler logic.
308+
auto watchersF =
309+
ManagerTest::readData("/tmp/current_watched_paths.json");
310+
auto parsed = nlohmann::json::parse(watchersF);
311+
EXPECT_TRUE(parsed.is_array());
312+
// Checking if the srcFile1 and srcFile2 are present in the parsed json
313+
EXPECT_NE(std::find(parsed.begin(), parsed.end(), srcFile1),
314+
parsed.end());
315+
EXPECT_NE(std::find(parsed.begin(), parsed.end(), srcFile2),
316+
parsed.end());
317+
co_return;
318+
};
319+
320+
ctx.spawn(waitingForFullSyncToFinish(ctx));
321+
ctx.run();
322+
}

0 commit comments

Comments
 (0)