Parcourir la source

Merge #2768

The rpcCall method in C++
Michal 'vorner' Vaner il y a 12 ans
Parent
commit
95218da8e5

+ 1 - 1
src/lib/cc/cc_messages.mes

@@ -37,7 +37,7 @@ socket listed in the output.
 This debug message indicates that the connection was successfully made, this
 This debug message indicates that the connection was successfully made, this
 should follow CC_ESTABLISH.
 should follow CC_ESTABLISH.
 
 
-% CC_GROUP_RECEIVE trying to receive a message
+% CC_GROUP_RECEIVE trying to receive a message with seq %1
 Debug message, noting that a message is expected to come over the command
 Debug message, noting that a message is expected to come over the command
 channel.
 channel.
 
 

+ 1 - 1
src/lib/cc/proto_defs.cc

@@ -38,8 +38,8 @@ const char* const CC_COMMAND_SEND = "send";
 const char* const CC_TO_WILDCARD = "*";
 const char* const CC_TO_WILDCARD = "*";
 const char* const CC_INSTANCE_WILDCARD = "*";
 const char* const CC_INSTANCE_WILDCARD = "*";
 // Reply codes
 // Reply codes
-const int CC_REPLY_SUCCESS = 0;
 const int CC_REPLY_NO_RECPT = -1;
 const int CC_REPLY_NO_RECPT = -1;
+const int CC_REPLY_SUCCESS = 0;
 
 
 }
 }
 }
 }

+ 1 - 1
src/lib/cc/session.cc

@@ -498,7 +498,7 @@ bool
 Session::group_recvmsg(ConstElementPtr& envelope, ConstElementPtr& msg,
 Session::group_recvmsg(ConstElementPtr& envelope, ConstElementPtr& msg,
                        bool nonblock, int seq)
                        bool nonblock, int seq)
 {
 {
-    LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE);
+    LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVE).arg(seq);
     bool result(recvmsg(envelope, msg, nonblock, seq));
     bool result(recvmsg(envelope, msg, nonblock, seq));
     if (result) {
     if (result) {
         LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVED).
         LOG_DEBUG(logger, DBG_TRACE_DETAILED, CC_GROUP_RECEIVED).

+ 23 - 1
src/lib/config/ccsession.cc

@@ -32,7 +32,7 @@
 #include <boost/foreach.hpp>
 #include <boost/foreach.hpp>
 
 
 #include <cc/data.h>
 #include <cc/data.h>
-#include <module_spec.h>
+#include <config/module_spec.h>
 #include <cc/session.h>
 #include <cc/session.h>
 #include <exceptions/exceptions.h>
 #include <exceptions/exceptions.h>
 
 
@@ -857,5 +857,27 @@ ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& id) {
     async_recv_requests_.erase(id);
     async_recv_requests_.erase(id);
 }
 }
 
 
+ConstElementPtr
+ModuleCCSession::rpcCall(const std::string &command, const std::string &group,
+                         const std::string &instance, const std::string &to,
+                         const ConstElementPtr &params)
+{
+    ConstElementPtr command_el(createCommand(command, params));
+    const int seq = groupSendMsg(command_el, group, instance, to, true);
+    ConstElementPtr env, answer;
+    LOG_DEBUG(config_logger, DBGLVL_TRACE_DETAIL, CONFIG_RPC_SEQ).arg(command).
+        arg(group).arg(seq);
+    groupRecvMsg(env, answer, true, seq);
+    int rcode;
+    const ConstElementPtr result(parseAnswer(rcode, answer));
+    if (rcode == isc::cc::CC_REPLY_NO_RECPT) {
+        isc_throw(RPCRecipientMissing, result);
+    } else if (rcode != isc::cc::CC_REPLY_SUCCESS) {
+        isc_throw_1(RPCError, result, rcode);
+    } else {
+        return (result);
+    }
+}
+
 }
 }
 }
 }

+ 77 - 2
src/lib/config/ccsession.h

@@ -20,6 +20,7 @@
 
 
 #include <cc/session.h>
 #include <cc/session.h>
 #include <cc/data.h>
 #include <cc/data.h>
+#include <cc/proto_defs.h>
 
 
 #include <string>
 #include <string>
 #include <list>
 #include <list>
@@ -146,6 +147,34 @@ public:
         isc::Exception(file, line, what) {}
         isc::Exception(file, line, what) {}
 };
 };
 
 
+/// \brief Exception thrown when there's a problem with the remote call.
+///
+/// This usually means either the command couldn't be called or the remote
+/// side sent an error as a response.
+class RPCError: public CCSessionError {
+public:
+    RPCError(const char* file, size_t line, const char* what, int rcode) :
+        CCSessionError(file, line, what),
+        rcode_(rcode)
+    {}
+
+    /// \brief The error code for the error.
+    int rcode() const {
+        return (rcode_);
+    }
+private:
+    const int rcode_;
+};
+
+/// \brief Specific version of RPCError for the case the recipient of command
+///     doesn't exist.
+class RPCRecipientMissing: public RPCError {
+public:
+    RPCRecipientMissing(const char* file, size_t line, const char* what) :
+        RPCError(file, line, what, isc::cc::CC_REPLY_NO_RECPT)
+    {}
+};
+
 ///
 ///
 /// \brief This module keeps a connection to the command channel,
 /// \brief This module keeps a connection to the command channel,
 /// holds configuration information, and handles messages from
 /// holds configuration information, and handles messages from
@@ -335,13 +364,15 @@ public:
      * \param group see isc::cc::Session::group_sendmsg()
      * \param group see isc::cc::Session::group_sendmsg()
      * \param instance see isc::cc::Session::group_sendmsg()
      * \param instance see isc::cc::Session::group_sendmsg()
      * \param to see isc::cc::Session::group_sendmsg()
      * \param to see isc::cc::Session::group_sendmsg()
+     * \param want_answer see isc::cc::Session::group_sendmsg()
      * \return see isc::cc::Session::group_sendmsg()
      * \return see isc::cc::Session::group_sendmsg()
      */
      */
     int groupSendMsg(isc::data::ConstElementPtr msg,
     int groupSendMsg(isc::data::ConstElementPtr msg,
                      std::string group,
                      std::string group,
                      std::string instance = "*",
                      std::string instance = "*",
-                     std::string to = "*") {
-        return (session_.group_sendmsg(msg, group, instance, to));
+                     std::string to = "*",
+                     bool want_answer = false) {
+        return (session_.group_sendmsg(msg, group, instance, to, want_answer));
     };
     };
 
 
     /**
     /**
@@ -361,6 +392,50 @@ public:
         return (session_.group_recvmsg(envelope, msg, nonblock, seq));
         return (session_.group_recvmsg(envelope, msg, nonblock, seq));
     };
     };
 
 
+    /// \brief Send a command message and wait for the answer.
+    ///
+    /// This is mostly a convenience wrapper around groupSendMsg
+    /// and groupRecvMsg, with some error handling.
+    ///
+    /// \param command Name of the command to call.
+    /// \param group Name of the remote module to call the command on.
+    /// \param instance Instance part of recipient address.
+    /// \param to The lname to send it to. Can be used to override the
+    ///     addressing and use a direct recipient.
+    /// \param params Parameters for the command. Can be left NULL if
+    ///     no parameters are needed.
+    /// \return Return value of the successfull remote call. It can be
+    ///     NULL if the remote command is void function (returns nothing).
+    /// \throw RPCError if the call fails (for example when the other
+    ///     side responds with error code).
+    /// \throw RPCRecipientMissing if the recipient doesn't exist.
+    /// \throw CCSessionError if some lower-level error happens (eg.
+    ///     the response was malformed).
+    isc::data::ConstElementPtr rpcCall(const std::string& command,
+                                       const std::string& group,
+                                       const std::string& instance =
+                                           isc::cc::CC_INSTANCE_WILDCARD,
+                                       const std::string& to =
+                                           isc::cc::CC_TO_WILDCARD,
+                                       const isc::data::ConstElementPtr&
+                                           params =
+                                           isc::data::ConstElementPtr());
+
+    /// \brief Convenience version of rpcCall
+    ///
+    /// This is exactly the same as the previous version of rpcCall, except
+    /// that the instance and to parameters are at their default. This
+    /// allows to sending a command with parameters to a named module
+    /// without long typing of the parameters.
+    isc::data::ConstElementPtr rpcCall(const std::string& command,
+                                       const std::string& group,
+                                       const isc::data::ConstElementPtr&
+                                           params)
+    {
+        return rpcCall(command, group, isc::cc::CC_INSTANCE_WILDCARD,
+                       isc::cc::CC_TO_WILDCARD, params);
+    }
+
     /// \brief Forward declaration of internal data structure.
     /// \brief Forward declaration of internal data structure.
     ///
     ///
     /// This holds information about one asynchronous request to receive
     /// This holds information about one asynchronous request to receive

+ 4 - 0
src/lib/config/config_messages.mes

@@ -94,3 +94,7 @@ manager.
 % CONFIG_OPEN_FAIL error opening %1: %2
 % CONFIG_OPEN_FAIL error opening %1: %2
 There was an error opening the given file. The reason for the failure
 There was an error opening the given file. The reason for the failure
 is included in the message.
 is included in the message.
+
+% CONFIG_RPC_SEQ RPC call %1 to %2 with seq %3
+Debug message, saying there's a RPC call of given command to given module. It
+has internal sequence number as listed in the message.

+ 52 - 0
src/lib/config/tests/ccsession_unittests.cc

@@ -58,6 +58,28 @@ protected:
         // ok answer.
         // ok answer.
         session.getMessages()->add(createAnswer());
         session.getMessages()->add(createAnswer());
     }
     }
+    ConstElementPtr rpcCheck(const std::string& reply) {
+        ModuleCCSession mccs(ccspecfile("spec1.spec"), session, NULL, NULL,
+                             false, false);
+        // Prepare the answer beforehand, it'll block until it gets one
+        const ConstElementPtr reply_el(el(reply));
+        session.getMessages()->add(reply_el);
+        const ConstElementPtr
+            result(mccs.rpcCall("test", "Spec2",
+                                el("{\"param1\": \"Param 1\","
+                                   "\"param2\": \"Param 2\"}")));
+        const ConstElementPtr
+            request(el("[\"Spec2\", \"*\", {"
+                       "  \"command\": [\"test\", {"
+                       "    \"param1\": \"Param 1\","
+                       "    \"param2\": \"Param 2\""
+                       "}]}, -1, true]"));
+        // The 0th one is from the initialization, to ConfigManager.
+        // our is the 1st.
+        EXPECT_TRUE(request->equals(*session.getMsgQueue()->get(1))) <<
+            session.getMsgQueue()->get(1)->toWire();
+        return (result);
+    }
     ~CCSessionTest() {
     ~CCSessionTest() {
         isc::log::setRootLoggerName(root_name);
         isc::log::setRootLoggerName(root_name);
     }
     }
@@ -65,6 +87,36 @@ protected:
     const std::string root_name;
     const std::string root_name;
 };
 };
 
 
+// Test we can send an RPC (command) and get an answer. The answer is success
+// in this case.
+TEST_F(CCSessionTest, rpcCallSuccess) {
+    const ConstElementPtr result =
+        rpcCheck("{\"result\": [0, {\"Hello\": \"a\"}]}");
+    EXPECT_TRUE(el("{\"Hello\": \"a\"}")->equals(*result));
+}
+
+// Test success of RPC, but the answer is empty (eg. a void function on the
+// remote side).
+TEST_F(CCSessionTest, rpcCallSuccessNone) {
+    EXPECT_FALSE(rpcCheck("{\"result\": [0]}"));
+}
+
+// Test it successfully raises CCSessionError if the answer is malformed.
+TEST_F(CCSessionTest, rpcCallMalformedAnswer) {
+    EXPECT_THROW(rpcCheck("[\"Nonsense\"]"), CCSessionError);
+}
+
+// Test it raises exception when the remote side reports an error
+TEST_F(CCSessionTest, rpcCallError) {
+    EXPECT_THROW(rpcCheck("{\"result\": [1, \"Error\"]}"), RPCError);
+}
+
+// Test it raises exception when the remote side doesn't exist
+TEST_F(CCSessionTest, rpcNoRecpt) {
+    EXPECT_THROW(rpcCheck("{\"result\": [-1, \"Error\"]}"),
+                 RPCRecipientMissing);
+}
+
 TEST_F(CCSessionTest, createAnswer) {
 TEST_F(CCSessionTest, createAnswer) {
     ConstElementPtr answer;
     ConstElementPtr answer;
     answer = createAnswer();
     answer = createAnswer();

+ 6 - 3
src/lib/config/tests/fake_session.cc

@@ -183,12 +183,12 @@ FakeSession::unsubscribe(std::string group, std::string instance) {
 
 
 int
 int
 FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
 FakeSession::group_sendmsg(ConstElementPtr msg, std::string group,
-                           std::string to, std::string, bool)
+                           std::string to, std::string, bool want_answer)
 {
 {
     if (throw_on_send_) {
     if (throw_on_send_) {
         isc_throw(Exception, "Throw on send is set in FakeSession");
         isc_throw(Exception, "Throw on send is set in FakeSession");
     }
     }
-    addMessage(msg, group, to);
+    addMessage(msg, group, to, -1, want_answer);
     return (1);
     return (1);
 }
 }
 
 
@@ -231,13 +231,16 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
 
 
 void
 void
 FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
 FakeSession::addMessage(ConstElementPtr msg, const std::string& group,
-                        const std::string& to, int seq)
+                        const std::string& to, int seq, bool want_answer)
 {
 {
     ElementPtr m_el = Element::createList();
     ElementPtr m_el = Element::createList();
     m_el->add(Element::create(group));
     m_el->add(Element::create(group));
     m_el->add(Element::create(to));
     m_el->add(Element::create(to));
     m_el->add(msg);
     m_el->add(msg);
     m_el->add(Element::create(seq));
     m_el->add(Element::create(seq));
+    if (want_answer) {
+        m_el->add(Element::create(want_answer));
+    }
     if (!msg_queue_) {
     if (!msg_queue_) {
         msg_queue_ = Element::createList();
         msg_queue_ = Element::createList();
     }
     }

+ 2 - 1
src/lib/config/tests/fake_session.h

@@ -75,7 +75,8 @@ public:
     isc::data::ConstElementPtr getFirstMessage(std::string& group,
     isc::data::ConstElementPtr getFirstMessage(std::string& group,
                                                std::string& to) const;
                                                std::string& to) const;
     void addMessage(isc::data::ConstElementPtr, const std::string& group,
     void addMessage(isc::data::ConstElementPtr, const std::string& group,
-                    const std::string& to, int seq = -1);
+                    const std::string& to, int seq = -1,
+                    bool want_answer = false);
     bool haveSubscription(const std::string& group,
     bool haveSubscription(const std::string& group,
                           const std::string& instance);
                           const std::string& instance);
     bool haveSubscription(const isc::data::ConstElementPtr group,
     bool haveSubscription(const isc::data::ConstElementPtr group,