Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,45 @@ BMC applications.
meson setup builddir
meson compile -C builddir
```

### Signal-Triggered Watched Paths Logging (DataSync Debug Utility)

Inspect watched paths using the `SIGUSR1` signal. This feature is helpful for
verifying which paths are currently being monitored by the phosphor-data-sync.

#### To check whether certain files or directories are currently being monitored

If you want to check if some particular paths are being monitored or not, create
a file `/tmp/data_sync_paths_info.lsv` and enter the paths line by line. Then
send the signal (Refer below), you can see the output in the same file.

```bash
$ cat data_sync_paths_info.lsv
rtest
/var/lib
$ kill -SIGUSR1 <pid_of_phosphor-rbmc-data-sync-mgr>
$ cat data_sync_paths_info.lsv
Individual user entered paths results:
Not Watching path: rtest
Watching path: /var/lib
```

If the user input file is not present, then on receiving the signal, it will
dump all the currently monitored paths to `/tmp/data_sync_paths_info.json`.
Refer below for example output

```bash
$ cat data_sync_paths_info.json
[
"/var/lib",
"/var/lib/phosphor-state-manager"
]
```

To trigger the utility, send a `SIGUSR1` signal to the
`phosphor-rbmc-data-sync-mgr` process:

```bash
pgrep -f phosphor-rbmc-data-sync-mgr
kill -SIGUSR1 <pid_of_phosphor-rbmc-data-sync-mgr>
```
5 changes: 5 additions & 0 deletions src/data_watcher.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ DataWatcher::DataWatcher(sdbusplus::async::context& ctx, const int inotifyFlags,
std::make_unique<sdbusplus::async::fdio>(ctx, _inotifyFileDescriptor()))
{
createWatchers(_dataPathToWatch);
std::lock_guard<std::mutex> lock(getMutex());
getAllWatchers().push_back(this);
}

DataWatcher::~DataWatcher()
Expand All @@ -33,6 +35,9 @@ DataWatcher::~DataWatcher()
}
});
}
auto& instances = getAllWatchers();
instances.erase(std::remove(instances.begin(), instances.end(), this),
instances.end());
}

int DataWatcher::inotifyInit() const
Expand Down
37 changes: 37 additions & 0 deletions src/data_watcher.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <filesystem>
#include <map>
#include <mutex>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To be honest, I’d prefer not to complicate the DataWatcher class. It should be driven by the Manager class, which can provide all the necessary debug details.

At the moment, I don’t have a concrete solution in mind—we’ll need to revisit this and find a cleaner approach. Any thoughts?

I was considering whether the Manager could maintain a container of DataWatcher objects, but that seems to violate the structured concurrency model of sender/receiver. So, I think we need to explore this further.


namespace data_sync::watch::inotify
{
Expand Down Expand Up @@ -131,6 +132,42 @@ class DataWatcher
*/
sdbusplus::async::task<DataOperations> onDataChange();

/**
* @brief API to get all the instances of the DataWatcher
*
* @returns std::vector<DataWatcher*> - A reference to the vector containing
* all instances of this class
*/
static std::vector<DataWatcher*>& getAllWatchers()
{
static std::vector<DataWatcher*> instances;
return instances;
}

/**
* @brief API to get the reference to the static mutex to guard
* concurrent access to shared data (i.e., the vector of DataWatcher
* instances)
*
* @returns std::mutex& - A reference to the static mutex
*/
static std::mutex& getMutex()
{
static std::mutex mtx;
return mtx;
}

/**
* @brief API to get the file descriptor of the inotify instance
*
* @returns std::map<WD, fs::path>& - A reference to the map of WD and their
* associated file paths.
*/
const std::map<WD, fs::path>& getWatchDescriptors() const
{
return _watchDescriptors;
}

private:
/**
* @brief inotify flags
Expand Down
77 changes: 77 additions & 0 deletions src/manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include <nlohmann/json.hpp>
#include <phosphor-logging/lg2.hpp>

#include <csignal>
#include <cstdlib>
#include <exception>
#include <fstream>
Expand Down Expand Up @@ -46,9 +47,85 @@ sdbusplus::async::task<> Manager::init()
co_await startFullSync();
}

std::signal(SIGUSR1, logSignalHandler);
co_return co_await startSyncEvents();
}

std::unordered_set<std::string> Manager::collectAllWatchedPaths()
{
std::unordered_set<std::string> watchedPaths;
for (const auto* watcher : watch::inotify::DataWatcher::getAllWatchers())
{
std::ranges::transform(
watcher->getWatchDescriptors(),
std::inserter(watchedPaths, watchedPaths.end()),
[](const auto& wdPathPair) { return wdPathPair.second.string(); });
}
return watchedPaths;
}

void Manager::saveWatchedPathsToFile()
{
std::string storeWatchedPath = "/tmp/data_sync_paths_info.json";
try
{
data_sync::persist::util::writeFile(
nlohmann::json(collectAllWatchedPaths()), storeWatchedPath);
lg2::info("Wrote all watched paths to file: {FILE}", "FILE",
storeWatchedPath);
}
catch (const std::exception& e)
{
lg2::error("Error writing all watched paths to file: {ERROR}", "ERROR",
e);
}
}

bool Manager::watchedPathCheck(std::string& filePath)
{
std::ifstream infile{filePath};
if (!infile)
{
return false;
}
std::vector<std::string> lines(std::istream_iterator<std::string>{infile},
{});
std::unordered_set<std::string> watchedPaths = collectAllWatchedPaths();

if (std::ofstream outfile{std::string(filePath), std::ios::trunc}; outfile)
{
outfile << "Individual user entered paths results:\n";
for (const auto& line : lines)
{
outfile << (watchedPaths.contains(line) ? "Watching"
: "Not Watching")
<< " path: " << line << '\n';
}
return true;
}
else
{
return false;
}
}

void Manager::logSignalHandler(int signal)
{
lg2::info("Received signal: {SIGNAL}", "SIGNAL", signal);
std::string checkForWatchedPaths = "/tmp/data_sync_paths_info.lsv";
if (watchedPathCheck(checkForWatchedPaths))
{
lg2::info("Checked and wrote each path in file {FILE}", "FILE",
checkForWatchedPaths);
}
else
{
lg2::info("Unable to open user input {FILE}, logging all watched paths",
"FILE", checkForWatchedPaths);
saveWatchedPathsToFile();
}
}

// NOLINTNEXTLINE
sdbusplus::async::task<> Manager::parseConfiguration()
{
Expand Down
37 changes: 37 additions & 0 deletions src/manager.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#include <filesystem>
#include <ranges>
#include <unordered_set>
#include <vector>

namespace data_sync
Expand Down Expand Up @@ -202,6 +203,42 @@ class Manager
*/
bool isSyncEligible(const config::DataSyncConfig& dataSyncCfg);

/**
* @brief A helper function which saves all currently watched paths to a
* JSON file.
*/
static void saveWatchedPathsToFile();

/**
* @brief A helper function which reads paths from the given input file
* stream and compares each path against currently watched paths, then
* writes the result to same path `/tmp/data_sync_paths_info.lsv`.
*
* @param[in] filePath - Input file path containing one path per line to
* check.
*
* @return True if filePath present and succesfully wrote to it; otherwise
* False.
*/
static bool watchedPathCheck(std::string& filePath);

/**
* @brief A helper function to handle logging when a SIGUSR1 signal is
* received.
*
* @param[in] signal - The signal value that triggered the handler.
*/
static void logSignalHandler(int signal);

/**
* @brief A helper API to collect all the paths currently being watched for
* immediate sync.
*
* @returns std::unordered_set<std::string> - A set containing all the paths
* being watched.
*/
static std::unordered_set<std::string> collectAllWatchedPaths();

/**
* @brief The async context object used to perform operations asynchronously
* as required.
Expand Down
117 changes: 117 additions & 0 deletions test/manager_test.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// SPDX-License-Identifier: Apache-2.0
#include "manager_test.hpp"

#include <csignal>

std::filesystem::path ManagerTest::dataSyncCfgDir;
std::filesystem::path ManagerTest::tmpDataSyncDataDir;
nlohmann::json ManagerTest::commonJsonData;
Expand Down Expand Up @@ -208,3 +210,118 @@ TEST_F(ManagerTest, testDBusDataPersistency)

ctx.run();
}

TEST_F(ManagerTest, testSignalReceiverLogic)
{
using namespace std::literals;
namespace ed = data_sync::ext_data;

std::unique_ptr<ed::ExternalDataIFaces> extDataIface =
std::make_unique<ed::MockExternalDataIFaces>();

ed::MockExternalDataIFaces* mockExtDataIfaces =
dynamic_cast<ed::MockExternalDataIFaces*>(extDataIface.get());

ON_CALL(*mockExtDataIfaces, fetchBMCRedundancyMgrProps())
// NOLINTNEXTLINE
.WillByDefault([&mockExtDataIfaces]() -> sdbusplus::async::task<> {
mockExtDataIfaces->setBMCRole(ed::BMCRole::Active);
mockExtDataIfaces->setBMCRedundancy(true);
co_return;
});

EXPECT_CALL(*mockExtDataIfaces, fetchSiblingBmcIP())
// NOLINTNEXTLINE
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });

EXPECT_CALL(*mockExtDataIfaces, fetchRbmcCredentials())
// NOLINTNEXTLINE
.WillRepeatedly([]() -> sdbusplus::async::task<> { co_return; });

nlohmann::json jsonData = {
{"Files",
{
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile1"},
{"DestinationPath",
ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"},
{"Description", "FullSync from Active to Passive bmc"},
{"SyncDirection", "Active2Passive"},
{"SyncType", "Immediate"}},
{{"Path", ManagerTest::tmpDataSyncDataDir.string() + "/srcFile2"},
{"DestinationPath",
ManagerTest::tmpDataSyncDataDir.string() + "/destDir/"},
{"Description", "FullSync from Active to Passive bmc"},
{"SyncDirection", "Active2Passive"},
{"SyncType", "Immediate"}},
}}};

fs::path srcFile1{jsonData["Files"][0]["Path"]};
fs::path srcFile2{jsonData["Files"][1]["Path"]};

fs::path destDir1{jsonData["Files"][0]["DestinationPath"]};
fs::path destDir2{jsonData["Files"][1]["DestinationPath"]};

writeConfig(jsonData);
std::filesystem::remove("/tmp/data_sync_paths_info.lsv");
sdbusplus::async::context ctx;

std::string data1{"Data written on the file1\n"};
std::string data2{"Data written on the file2\n"};

ManagerTest::writeData(srcFile1, data1);
ManagerTest::writeData(srcFile2, data2);

ASSERT_EQ(ManagerTest::readData(srcFile1), data1);
ASSERT_EQ(ManagerTest::readData(srcFile2), data2);

data_sync::Manager manager{ctx, std::move(extDataIface),
ManagerTest::dataSyncCfgDir};

auto waitingForFullSyncToFinish =
// NOLINTNEXTLINE
[&](sdbusplus::async::context& ctx) -> sdbusplus::async::task<void> {
auto status = manager.getFullSyncStatus();

while (status != FullSyncStatus::FullSyncCompleted &&
status != FullSyncStatus::FullSyncFailed)
{
co_await sdbusplus::async::sleep_for(ctx,
std::chrono::milliseconds(50));
status = manager.getFullSyncStatus();
}

EXPECT_EQ(status, FullSyncStatus::FullSyncCompleted)
<< "FullSync status is not Completed!";

// Raising signal after full sync completes, so that sync events starts
// and the watchers are being created.
sdbusplus::async::sleep_for(ctx, 0.1s);
// Raising a signal to trigger the signal handler logic.
std::raise(SIGUSR1);
// waiting to write all the watched paths to a file as per
// signal handler logic.
sdbusplus::async::sleep_for(ctx, 0.5s);

ctx.request_stop();

// Forcing to trigger inotify events so that all running immediate
// sync tasks will resume and stop since the context is requested to
// stop in the above.
ManagerTest::writeData(srcFile1, data1);
ManagerTest::writeData(srcFile2, data2);

// Reading the file where all the watched paths are written by signal
// handler logic.
auto watchersF =
ManagerTest::readData("/tmp/data_sync_paths_info.json");
auto parsed = nlohmann::json::parse(watchersF);
EXPECT_TRUE(parsed.is_array());
// Checking if the srcFile1 and srcFile2 are present in the parsed json
EXPECT_NE(watchersF.find(srcFile1), std::string::npos);
EXPECT_NE(watchersF.find(srcFile2), std::string::npos);
co_return;
};

ctx.spawn(waitingForFullSyncToFinish(ctx));
ctx.run();
}