2 Copyright (c) 2018-2019 Nokia.
4 Licensed under the Apache License, Version 2.0 (the "License");
5 you may not use this file except in compliance with the License.
6 You may obtain a copy of the License at
8 http://www.apache.org/licenses/LICENSE-2.0
10 Unless required by applicable law or agreed to in writing, software
11 distributed under the License is distributed on an "AS IS" BASIS,
12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 See the License for the specific language governing permissions and
14 limitations under the License.
18 #include "private/createlogger.hpp"
19 #include "private/error.hpp"
20 #include "private/redis/redisgeneral.hpp"
21 #include <arpa/inet.h>
24 #include <hircluster.h>
26 #include <hiredis/hiredis.h>
29 using namespace shareddatalayer;
30 using namespace shareddatalayer::redis;
34 bool equals(const std::string& s1, const char* s2, size_t s2Len)
38 logErrorOnce("redisGeneral: null pointer passed to equals function");
42 return ((s1.size() == s2Len) && (std::memcmp(s1.data(), s2, s2Len) == 0));
45 bool startsWith(const std::string& s1, const char* s2, size_t s2Len)
49 logErrorOnce("redisGeneral: null pointer passed to startsWith function");
53 return ((s1.size() <= s2Len) && (std::memcmp(s1.data(), s2, s1.size()) == 0));
56 AsyncRedisCommandDispatcherErrorCode mapRedisReplyErrorToSdlError(const redisReply* rr)
58 if (equals("LOADING Redis is loading the dataset in memory", rr->str, static_cast<size_t>(rr->len)))
59 return AsyncRedisCommandDispatcherErrorCode::DATASET_LOADING;
61 /* This error reply comes when some cluster node(s) is down and rest of the cluster
62 * nodes cannot operate due to that. This error reply typically comes from nodes
63 * which are working but cannot handle requests because other node(s) are down.
64 * Nodes which are actually down (under failover handling) typically return
65 * CLUSTER_ERROR_NOT_CONNECTED or CLUSTER_ERROR_CONNECTION_LOST.
67 if (startsWith("CLUSTERDOWN", rr->str, static_cast<size_t>(rr->len)))
68 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
70 if (startsWith("ERR Protocol error", rr->str, static_cast<size_t>(rr->len)))
71 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
73 std::ostringstream oss;
74 oss << "redis reply error: " << std::string(rr->str, static_cast<size_t>(rr->len));
75 logErrorOnce(oss.str());
76 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
79 AsyncRedisCommandDispatcherErrorCode mapRedisContextErrorToSdlError(int redisContextErr, const char* redisContextErrstr)
81 switch (redisContextErr)
83 /* From hiredis/read.h:
84 * When an error occurs, the err flag in a context is set to hold the type of
85 * error that occurred. REDIS_ERR_IO means there was an I/O error and you
86 * should use the "errno" variable to find out what is wrong.
87 * For other values, the "errstr" field will hold a description. */
89 if (errno == ECONNRESET)
90 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
91 logErrorOnce("redis io error. Errno: " + std::to_string(errno));
92 return AsyncRedisCommandDispatcherErrorCode::IO_ERROR;
94 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
95 case REDIS_ERR_PROTOCOL:
96 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
98 return AsyncRedisCommandDispatcherErrorCode::OUT_OF_MEMORY;
100 /* hiredis_vip returns CLUSTER_ERROR_NOT_CONNECTED when cluster node is disconnected
101 * but failover handling has not started yet (node_timeout not elapsed yet). In
102 * this situation hiredis_vip does not send request to redis (as it is clear that
103 * request cannot succeed), therefore we can map this error to NOT_CONNECTED error
104 * as it best descripes this situation.
106 case CLUSTER_ERROR_NOT_CONNECTED:
107 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
108 /* hiredis_vip returns CLUSTER_ERROR_CONNECTION_LOST when connection is lost while
109 * hiredis is waiting for reply from redis.
111 case CLUSTER_ERROR_CONNECTION_LOST:
112 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
115 std::ostringstream oss;
116 oss << "redis error: "
117 << redisContextErrstr
118 << " (" << redisContextErr << ")";
119 logErrorOnce(oss.str());
120 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
125 namespace shareddatalayer
129 std::string formatToClusterSyntax(const DatabaseConfiguration::Addresses& addresses)
131 std::ostringstream oss;
132 for (auto i(addresses.begin()); i != addresses.end(); ++i)
134 oss << i->getHost() << ':' << ntohs(i->getPort());
135 if (i == --addresses.end())
143 const std::set<std::string>& getRequiredRedisModuleCommands()
145 static const std::set<std::string> requiredRedisModuleCommands({"msetpub","setie","setiepub","setnxpub","delpub","delie","deliepub"});
146 return requiredRedisModuleCommands;
149 std::error_code getRedisError(int redisContextErr, const char* redisContextErrstr, const redisReply* rr)
153 if (rr->type != REDIS_REPLY_ERROR)
154 return std::error_code();
156 return std::error_code(mapRedisReplyErrorToSdlError(rr));
159 return std::error_code(mapRedisContextErrorToSdlError(redisContextErr, redisContextErrstr));
162 std::set<std::string> parseCommandListReply(const redis::Reply& reply)
164 std::set<std::string> availableCommands;
165 auto replyArray(reply.getArray());
166 for (const auto& j : *replyArray)
168 auto element = j->getArray();
169 auto command = element->front()->getString();
170 availableCommands.insert(command->str);
172 return availableCommands;
175 bool checkRedisModuleCommands(const std::set<std::string>& availableCommands)
177 std::set<std::string> missingCommands;
179 for (const auto& i : getRequiredRedisModuleCommands())
181 const auto it = availableCommands.find(i);
182 if (it == availableCommands.end())
183 missingCommands.insert(i);
185 if (!missingCommands.empty())
187 logErrorOnce("Missing Redis module extension commands:");
188 for (const auto& i : missingCommands)
191 return missingCommands.empty();