commit c83585452819b6e06008aba49237f46977a73a9a Author: Mattias Walström Date: Wed Jun 26 11:04:36 2024 +0200 Add optional timeout option Since rousette will fail applying a new configuration on really slow systems and a lot of sysrepo callbacks. Change-Id: I61d92a1f64e4c3617587e544cfd9d6a4269e0d35 diff --git a/src/restconf/Server.cpp b/src/restconf/Server.cpp index 5327b52..685bf78 100644 --- a/src/restconf/Server.cpp +++ b/src/restconf/Server.cpp @@ -202,14 +202,13 @@ DataFormat chooseDataEncoding(const nghttp2::asio_http2::header_map& headers) return {resContentType, resAccept ? *resAccept : libyang::DataFormat::JSON}; } -bool dataExists(sysrepo::Session session, const std::string& path) +bool dataExists(sysrepo::Session session, const std::string& path, std::chrono::milliseconds timeout) { - if (auto data = session.getData(path)) { - if (data->findPath(path)) { + if (auto data = session.getData(path, 0, sysrepo::GetOptions::Default,timeout)) { + if (data->findPath(path)) return true; } - } - return false; + return false; } /** @brief Checks if node is a key node in a maybeList node list */ @@ -261,7 +260,7 @@ struct RequestContext { std::string payload; }; -void processActionOrRPC(std::shared_ptr requestCtx) +void processActionOrRPC(std::shared_ptr requestCtx, std::chrono::milliseconds timeout) { requestCtx->sess.switchDatastore(sysrepo::Datastore::Operational); auto ctx = requestCtx->sess.getContext(); @@ -276,7 +275,7 @@ void processActionOrRPC(std::shared_ptr requestCtx) if (rpcSchemaNode.nodeType() == libyang::NodeType::Action) { // FIXME: This is race-prone: we check for existing action data node but before we send the RPC the node may be gone auto [pathToParent, pathSegment] = asLibyangPathSplit(ctx, requestCtx->req.uri().path); - if (!dataExists(requestCtx->sess, pathToParent)) { + if (!dataExists(requestCtx->sess, pathToParent,timeout)) { throw ErrorResponse(400, "application", "operation-failed", "Action data node '" + requestCtx->lyPathOriginal + "' does not exist."); } } @@ -287,7 +286,7 @@ void processActionOrRPC(std::shared_ptr requestCtx) rpcNode->parseOp(requestCtx->payload, *requestCtx->dataFormat.request, libyang::OperationType::RpcRestconf); } - auto rpcReply = requestCtx->sess.sendRPC(*rpcNode); + auto rpcReply = requestCtx->sess.sendRPC(*rpcNode, timeout); if (rpcReply.immediateChildren().empty()) { requestCtx->res.write_head(204, {{"access-control-allow-origin", {"*", false}}}); @@ -330,7 +329,7 @@ void processActionOrRPC(std::shared_ptr requestCtx) } } -void processPost(std::shared_ptr requestCtx) +void processPost(std::shared_ptr requestCtx, std::chrono::milliseconds timeout) { try { auto ctx = requestCtx->sess.getContext(); @@ -374,7 +373,7 @@ void processPost(std::shared_ptr requestCtx) createdNodes.begin()->newMeta(*mod, "operation", "create"); // FIXME: check no other nc:operations in the tree requestCtx->sess.editBatch(*edit, sysrepo::DefaultOperation::Merge); - requestCtx->sess.applyChanges(); + requestCtx->sess.applyChanges(timeout); requestCtx->res.write_head(201, { @@ -402,7 +401,7 @@ void processPost(std::shared_ptr requestCtx) } } -void processPut(std::shared_ptr requestCtx) +void processPut(std::shared_ptr requestCtx, std::chrono::milliseconds timeout) { try { auto ctx = requestCtx->sess.getContext(); @@ -410,7 +409,7 @@ void processPut(std::shared_ptr requestCtx) // PUT / means replace everything if (requestCtx->lyPathOriginal == "/") { auto edit = ctx.parseData(requestCtx->payload, *requestCtx->dataFormat.request, libyang::ParseOptions::Strict | libyang::ParseOptions::NoState | libyang::ParseOptions::ParseOnly); - requestCtx->sess.replaceConfig(edit); + requestCtx->sess.replaceConfig(edit, std::nullopt, timeout); requestCtx->res.write_head(edit ? 201 : 204, { @@ -422,7 +421,7 @@ void processPut(std::shared_ptr requestCtx) } auto mod = ctx.getModuleImplemented("ietf-netconf"); - bool nodeExisted = dataExists(requestCtx->sess, requestCtx->lyPathOriginal); + bool nodeExisted = dataExists(requestCtx->sess, requestCtx->lyPathOriginal, timeout); std::optional edit; std::optional replacementNode; @@ -471,7 +470,7 @@ void processPut(std::shared_ptr requestCtx) replacementNode->newMeta(*mod, "operation", "replace"); // FIXME: check no other nc:operations in the tree requestCtx->sess.editBatch(*edit, sysrepo::DefaultOperation::Merge); - requestCtx->sess.applyChanges(); + requestCtx->sess.applyChanges(timeout); requestCtx->res.write_head(nodeExisted ? 204 : 201, { @@ -551,7 +550,7 @@ Server::~Server() server->join(); } -Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port) +Server::Server(sysrepo::Connection conn, const std::string& address, const std::string& port, long timeout_seconds) : nacm(conn) , server{std::make_unique()} , dwdmEvents{std::make_unique(conn.sessionStart())} @@ -562,6 +561,8 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: } } + std::chrono::milliseconds timeout(timeout_seconds * 1000); + // RFC 8527, we must implement at least ietf-yang-library@2019-01-04 and support operational DS if (auto mod = conn.sessionStart().getContext().getModuleImplemented("ietf-yang-library"); !mod || mod->revision() < "2019-01-04") { throw std::runtime_error("Module ietf-yang-library of revision at least 2019-01-04 is not implemented"); @@ -627,7 +628,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: }); server->handle(restconfRoot, - [conn /* intentionally by value, otherwise conn gets destroyed when the ctor returns */, this](const auto& req, const auto& res) mutable { + [conn /* intentionally by value, otherwise conn gets destroyed when the ctor returns */, this, timeout](const auto& req, const auto& res) mutable { const auto& peer = http::peer_from_request(req); spdlog::info("{}: {} {}", peer, req.method(), req.uri().raw_path); @@ -665,7 +666,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: case RestconfRequest::Type::GetData: sess.switchDatastore(restconfRequest.datastore.value_or(sysrepo::Datastore::Operational)); - if (auto data = sess.getData(restconfRequest.path); data) { + if (auto data = sess.getData(restconfRequest.path, 0, sysrepo::GetOptions::Default,timeout); data) { res.write_head( 200, { @@ -692,16 +693,16 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: auto requestCtx = std::make_shared(req, res, dataFormat, sess, restconfRequest.path); - req.on_data([requestCtx, restconfRequest /* intentional copy */](const uint8_t* data, std::size_t length) { + req.on_data([requestCtx, restconfRequest /* intentional copy */, timeout](const uint8_t* data, std::size_t length) { if (length > 0) { // there are still some data to be read requestCtx->payload.append(reinterpret_cast(data), length); return; } if (restconfRequest.type == RestconfRequest::Type::CreateOrReplaceThisNode) { - processPut(requestCtx); + processPut(requestCtx, timeout); } else { - processPost(requestCtx); + processPost(requestCtx, timeout); } }); break; @@ -725,7 +726,7 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: } sess.editBatch(*edit, sysrepo::DefaultOperation::Merge); - sess.applyChanges(); + sess.applyChanges(timeout); } catch (const sysrepo::ErrorWithCode& e) { if (e.code() == sysrepo::ErrorCode::Unauthorized) { throw ErrorResponse(403, "application", "access-denied", "Access denied.", restconfRequest.path); @@ -747,11 +748,11 @@ Server::Server(sysrepo::Connection conn, const std::string& address, const std:: case RestconfRequest::Type::Execute: { auto requestCtx = std::make_shared(req, res, dataFormat, sess, restconfRequest.path); - req.on_data([requestCtx](const uint8_t* data, std::size_t length) { + req.on_data([requestCtx,timeout](const uint8_t* data, std::size_t length) { if (length > 0) { requestCtx->payload.append(reinterpret_cast(data), length); } else { - processActionOrRPC(requestCtx); + processActionOrRPC(requestCtx,timeout); } }); break; diff --git a/src/restconf/Server.h b/src/restconf/Server.h index bf6fecc..b2c42f4 100644 --- a/src/restconf/Server.h +++ b/src/restconf/Server.h @@ -24,11 +24,10 @@ class OpticalEvents; namespace restconf { std::optional as_subtree_path(const std::string& path); - /** @short A RESTCONF-ish server */ class Server { public: - explicit Server(sysrepo::Connection conn, const std::string& address, const std::string& port); + explicit Server(sysrepo::Connection conn, const std::string& address, const std::string& port, long timeout); ~Server(); private: diff --git a/src/restconf/main.cpp b/src/restconf/main.cpp index 2bc9a86..e295f63 100644 --- a/src/restconf/main.cpp +++ b/src/restconf/main.cpp @@ -27,9 +27,10 @@ static const char usage[] = R"(Rousette - RESTCONF server Usage: - rousette [--syslog] [--help] + rousette [--syslog] [--timeout ] [--help] Options: -h --help Show this screen. + -t --timeout Change default timeout in sysrepo (if not set, use sysrepo internal). --syslog Log to syslog. )"; #ifdef HAVE_SYSTEMD @@ -74,6 +75,10 @@ public: int main(int argc, char* argv []) { auto args = docopt::docopt(usage, {argv + 1, argv + argc}, true,""/* version */, true); + long timeout = 0; + + if (args["--timeout"]) + timeout = args["--timeout"].asLong(); if (args["--syslog"].asBool()) { auto syslog_sink = std::make_shared("rousette", LOG_PID, LOG_USER, true); @@ -100,8 +105,7 @@ int main(int argc, char* argv []) } auto conn = sysrepo::Connection{}; - auto server = rousette::restconf::Server{conn, "::1", "10080"}; - + auto server = rousette::restconf::Server{conn, "::1", "10080", timeout}; signal(SIGTERM, [](int) {}); signal(SIGINT, [](int) {}); pause();