Browse Source

Merge #2768

The rpcCall method in C++
Michal 'vorner' Vaner 12 years ago
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
 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
 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_INSTANCE_WILDCARD = "*";
 // Reply codes
-const int CC_REPLY_SUCCESS = 0;
 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,
                        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));
     if (result) {
         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 <cc/data.h>
-#include <module_spec.h>
+#include <config/module_spec.h>
 #include <cc/session.h>
 #include <exceptions/exceptions.h>
 
@@ -857,5 +857,27 @@ ModuleCCSession::cancelAsyncRecv(const AsyncRecvRequestID& 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/data.h>
+#include <cc/proto_defs.h>
 
 #include <string>
 #include <list>
@@ -146,6 +147,34 @@ public:
         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,
 /// holds configuration information, and handles messages from
@@ -335,13 +364,15 @@ public:
      * \param group see isc::cc::Session::group_sendmsg()
      * \param instance 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()
      */
     int groupSendMsg(isc::data::ConstElementPtr msg,
                      std::string group,
                      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));
     };
 
+    /// \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.
     ///
     /// 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
 There was an error opening the given file. The reason for the failure
 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.
         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() {
         isc::log::setRootLoggerName(root_name);
     }
@@ -65,6 +87,36 @@ protected:
     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) {
     ConstElementPtr answer;
     answer = createAnswer();

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

@@ -183,12 +183,12 @@ FakeSession::unsubscribe(std::string group, std::string instance) {
 
 int
 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_) {
         isc_throw(Exception, "Throw on send is set in FakeSession");
     }
-    addMessage(msg, group, to);
+    addMessage(msg, group, to, -1, want_answer);
     return (1);
 }
 
@@ -231,13 +231,16 @@ FakeSession::getFirstMessage(std::string& group, std::string& to) const {
 
 void
 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();
     m_el->add(Element::create(group));
     m_el->add(Element::create(to));
     m_el->add(msg);
     m_el->add(Element::create(seq));
+    if (want_answer) {
+        m_el->add(Element::create(want_answer));
+    }
     if (!msg_queue_) {
         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,
                                                std::string& to) const;
     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,
                           const std::string& instance);
     bool haveSubscription(const isc::data::ConstElementPtr group,