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 if (startsWith("READONLY", rr->str, static_cast<size_t>(rr->len)))
74 return AsyncRedisCommandDispatcherErrorCode::WRITING_TO_SLAVE;
76 std::ostringstream oss;
77 oss << "redis reply error: " << std::string(rr->str, static_cast<size_t>(rr->len));
78 logErrorOnce(oss.str());
79 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
82 AsyncRedisCommandDispatcherErrorCode mapRedisContextErrorToSdlError(int redisContextErr, const char* redisContextErrstr)
84 switch (redisContextErr)
86 /* From hiredis/read.h:
87 * When an error occurs, the err flag in a context is set to hold the type of
88 * error that occurred. REDIS_ERR_IO means there was an I/O error and you
89 * should use the "errno" variable to find out what is wrong.
90 * For other values, the "errstr" field will hold a description. */
92 if (errno == ECONNRESET)
93 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
94 logErrorOnce("redis io error. Errno: " + std::to_string(errno));
95 return AsyncRedisCommandDispatcherErrorCode::IO_ERROR;
97 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
98 case REDIS_ERR_PROTOCOL:
99 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
101 return AsyncRedisCommandDispatcherErrorCode::OUT_OF_MEMORY;
103 /* hiredis_vip returns CLUSTER_ERROR_NOT_CONNECTED when cluster node is disconnected
104 * but failover handling has not started yet (node_timeout not elapsed yet). In
105 * this situation hiredis_vip does not send request to redis (as it is clear that
106 * request cannot succeed), therefore we can map this error to NOT_CONNECTED error
107 * as it best descripes this situation.
109 case CLUSTER_ERROR_NOT_CONNECTED:
110 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
111 /* hiredis_vip returns CLUSTER_ERROR_CONNECTION_LOST when connection is lost while
112 * hiredis is waiting for reply from redis.
114 case CLUSTER_ERROR_CONNECTION_LOST:
115 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
118 std::ostringstream oss;
119 oss << "redis error: "
120 << redisContextErrstr
121 << " (" << redisContextErr << ")";
122 logErrorOnce(oss.str());
123 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
128 namespace shareddatalayer
132 std::string formatToClusterSyntax(const DatabaseConfiguration::Addresses& addresses)
134 std::ostringstream oss;
135 for (auto i(addresses.begin()); i != addresses.end(); ++i)
137 oss << i->getHost() << ':' << ntohs(i->getPort());
138 if (i == --addresses.end())
146 const std::set<std::string>& getRequiredRedisModuleCommands()
148 static const std::set<std::string> requiredRedisModuleCommands({"msetpub","setie","setiepub","setnxpub","delpub","delie","deliepub"});
149 return requiredRedisModuleCommands;
152 std::error_code getRedisError(int redisContextErr, const char* redisContextErrstr, const redisReply* rr)
156 if (rr->type != REDIS_REPLY_ERROR)
157 return std::error_code();
159 return std::error_code(mapRedisReplyErrorToSdlError(rr));
162 return std::error_code(mapRedisContextErrorToSdlError(redisContextErr, redisContextErrstr));
165 std::set<std::string> parseCommandListReply(const redis::Reply& reply)
167 std::set<std::string> availableCommands;
168 auto replyArray(reply.getArray());
169 for (const auto& j : *replyArray)
171 auto element = j->getArray();
172 auto command = element->front()->getString();
173 availableCommands.insert(command->str);
175 return availableCommands;
178 bool checkRedisModuleCommands(const std::set<std::string>& availableCommands)
180 std::set<std::string> missingCommands;
182 for (const auto& i : getRequiredRedisModuleCommands())
184 const auto it = availableCommands.find(i);
185 if (it == availableCommands.end())
186 missingCommands.insert(i);
188 if (!missingCommands.empty())
190 logErrorOnce("Missing Redis module extension commands:");
191 for (const auto& i : missingCommands)
194 return missingCommands.empty();