Redfish:Dump offload handler implementation using nbd-proxy
This handler transfers data between nbd-client and nbd-server.
basically it invokes nbd-proxy and reads data from socket
and writes on to nbd-client and vice-versa
Change-Id: I429393a5e056647333bf4e148c0df2a5695b2a47
Signed-off-by: Ravi Teja <raviteja28031990@gmail.com>
diff --git a/include/dump_offload.hpp b/include/dump_offload.hpp
new file mode 100644
index 0000000..e628ec6
--- /dev/null
+++ b/include/dump_offload.hpp
@@ -0,0 +1,305 @@
+#pragma once
+
+#include <signal.h>
+#include <sys/select.h>
+
+#include <boost/beast/core/flat_static_buffer.hpp>
+#include <boost/beast/http.hpp>
+#include <boost/process.hpp>
+#include <cstdio>
+#include <cstdlib>
+
+namespace crow
+{
+namespace obmc_dump
+{
+
+inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
+ const std::string& entryId);
+inline void resetHandler();
+
+// The max network block device buffer size is 128kb plus 16bytes
+// for the message header
+static constexpr auto nbdBufferSize = 131088;
+
+/** class Handler
+ * handles data transfer between nbd-client and nbd-server.
+ * This handler invokes nbd-proxy and reads data from socket
+ * and writes on to nbd-client and vice-versa
+ */
+class Handler : public std::enable_shared_from_this<Handler>
+{
+ public:
+ Handler(const std::string& media, boost::asio::io_context& ios,
+ const std::string& entryID) :
+ pipeOut(ios),
+ pipeIn(ios), media(media), entryID(entryID), doingWrite(false),
+ negotiationDone(false), writeonnbd(false),
+ outputBuffer(std::make_unique<
+ boost::beast::flat_static_buffer<nbdBufferSize>>()),
+ inputBuffer(
+ std::make_unique<boost::beast::flat_static_buffer<nbdBufferSize>>())
+ {
+ }
+
+ ~Handler()
+ {
+ }
+
+ /**
+ * @brief Invokes InitiateOffload method of dump manager which
+ * directs pldm to start writing on the nbd device.
+ *
+ * @return void
+ */
+ void initiateOffloadOnNbdDevice()
+ {
+ crow::connections::systemBus->async_method_call(
+ [this,
+ self(shared_from_this())](const boost::system::error_code ec) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "DBUS response error: " << ec;
+ resetBuffers();
+ resetHandler();
+ return;
+ }
+ },
+ "xyz.openbmc_project.Dump.Manager",
+ "/xyz/openbmc_project/dump/entry/" + entryID,
+ "xyz.openbmc_project.Dump.Entry", "InitiateOffload");
+ }
+
+ /**
+ * @brief Kills nbd-proxy
+ *
+ * @return void
+ */
+ void doClose()
+ {
+ int rc = kill(proxy.id(), SIGTERM);
+ if (rc)
+ {
+ return;
+ }
+ proxy.wait();
+ }
+
+ /**
+ * @brief Starts nbd-proxy
+ *
+ * @return void
+ */
+ void connect()
+ {
+ std::error_code ec;
+ proxy = boost::process::child("/usr/sbin/nbd-proxy", media,
+ boost::process::std_out > pipeOut,
+ boost::process::std_in < pipeIn, ec);
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Couldn't connect to nbd-proxy: "
+ << ec.message();
+ resetHandler();
+ return;
+ }
+ doRead();
+ }
+
+ /**
+ * @brief Wait for data on tcp socket from nbd-server.
+ *
+ * @return void
+ */
+ void waitForMessageOnSocket()
+ {
+
+ std::size_t bytes = inputBuffer->capacity() - inputBuffer->size();
+
+ (*stream).async_read_some(
+ inputBuffer->prepare(bytes),
+ [this,
+ self(shared_from_this())](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "Error while reading on socket";
+ doClose();
+ resetBuffers();
+ resetHandler();
+ return;
+ }
+
+ inputBuffer->commit(bytes_transferred);
+ doWrite();
+ });
+ }
+
+ /**
+ * @brief Writes data on input pipe of nbd-client.
+ *
+ * @return void
+ */
+ void doWrite()
+ {
+
+ if (doingWrite)
+ {
+ BMCWEB_LOG_DEBUG << "Already writing. Bailing out";
+ return;
+ }
+
+ if (inputBuffer->size() == 0)
+ {
+ BMCWEB_LOG_DEBUG << "inputBuffer empty. Bailing out";
+ return;
+ }
+
+ doingWrite = true;
+ boost::asio::async_write(
+ pipeIn, inputBuffer->data(),
+ [this, self(shared_from_this())](const boost::beast::error_code& ec,
+ std::size_t bytesWritten) {
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "VM socket port closed";
+ doClose();
+ resetBuffers();
+ resetHandler();
+ return;
+ }
+
+ doingWrite = false;
+
+ if (negotiationDone == false)
+ {
+ // "gDf" is NBD reply magic
+ std::string reply_magic("gDf");
+ std::string reply_string(
+ static_cast<char*>(inputBuffer->data().data()),
+ bytesWritten);
+ std::size_t found = reply_string.find(reply_magic);
+ if (found != std::string::npos)
+ {
+ negotiationDone = true;
+ writeonnbd = true;
+ }
+ }
+
+ inputBuffer->consume(bytesWritten);
+ waitForMessageOnSocket();
+ if (writeonnbd)
+ {
+ // NBD Negotiation Complete!!!!. Notify Dump manager to
+ // start dumping the actual data over NBD device
+ initiateOffloadOnNbdDevice();
+ writeonnbd = false;
+ }
+ });
+ }
+
+ /**
+ * @brief Reads data on output pipe of nbd-client and write on
+ * tcp socket.
+ *
+ * @return void
+ */
+ void doRead()
+ {
+ std::size_t bytes = outputBuffer->capacity() - outputBuffer->size();
+
+ pipeOut.async_read_some(
+ outputBuffer->prepare(bytes),
+ [this, self(shared_from_this())](
+ const boost::system::error_code& ec, std::size_t bytesRead) {
+ if (ec)
+ {
+ BMCWEB_LOG_ERROR << "Couldn't read from VM port: " << ec;
+ doClose();
+ resetBuffers();
+ resetHandler();
+ return;
+ }
+
+ outputBuffer->commit(bytesRead);
+
+ boost::asio::async_write(
+ *stream, outputBuffer->data(),
+ [this](const boost::system::error_code& ec,
+ std::size_t bytes_transferred) {
+ if (ec)
+ {
+ BMCWEB_LOG_DEBUG << "Error while writing on socket";
+ doClose();
+ resetBuffers();
+ resetHandler();
+ return;
+ }
+
+ outputBuffer->consume(bytes_transferred);
+ doRead();
+ });
+ });
+ }
+
+ /**
+ * @brief Resets input and output buffers.
+ * @return void
+ */
+ void resetBuffers()
+ {
+#if BOOST_VERSION >= 107000
+ this->inputBuffer->clear();
+ this->outputBuffer->clear();
+#else
+ this->inputBuffer->reset();
+ this->outputBuffer->reset();
+#endif
+ }
+
+ boost::process::async_pipe pipeOut;
+ boost::process::async_pipe pipeIn;
+ boost::process::child proxy;
+ std::string media;
+ std::string entryID;
+ bool doingWrite;
+ bool negotiationDone;
+ bool writeonnbd;
+ std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
+ outputBuffer;
+ std::unique_ptr<boost::beast::flat_static_buffer<nbdBufferSize>>
+ inputBuffer;
+ std::shared_ptr<crow::Request::Adaptor> stream;
+};
+
+static std::shared_ptr<Handler> handler;
+inline void resetHandler()
+{
+
+ handler.reset();
+}
+inline void handleDumpOffloadUrl(const crow::Request& req, crow::Response& res,
+ const std::string& entryId)
+{
+
+ // Run only one instance of Handler, one dump offload can happen at a time
+ if (handler != nullptr)
+ {
+ BMCWEB_LOG_ERROR << "Handler already running";
+ res.result(boost::beast::http::status::service_unavailable);
+ res.jsonValue["Description"] = "Service is already being used";
+ res.end();
+ return;
+ }
+
+ const char* media = "1";
+ boost::asio::io_context* io_con = req.ioService;
+
+ handler = std::make_shared<Handler>(media, *io_con, entryId);
+ handler->stream =
+ std::make_shared<crow::Request::Adaptor>(std::move(req.socket()));
+ handler->connect();
+ handler->waitForMessageOnSocket();
+}
+} // namespace obmc_dump
+} // namespace crow