blob: f031a771eae552819bc5bb7325160660be28bf5a [file] [log] [blame]
Rolf Badorekef2bf512019-08-20 11:17:15 +03001/*
2 Copyright (c) 2018-2019 Nokia.
3
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
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
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.
15*/
16
17#include "config.h"
18#include "private/createlogger.hpp"
19#include "private/error.hpp"
20#include "private/redis/redisgeneral.hpp"
21#include <arpa/inet.h>
22#include <cstring>
23#if HAVE_HIREDIS_VIP
24#include <hircluster.h>
25#endif
26#include <hiredis/hiredis.h>
27#include <sstream>
28
29using namespace shareddatalayer;
30using namespace shareddatalayer::redis;
31
32namespace
33{
34 bool equals(const std::string& s1, const char* s2, size_t s2Len)
35 {
36 if (s2 == nullptr)
37 {
38 logErrorOnce("redisGeneral: null pointer passed to equals function");
39 return false;
40 }
41
42 return ((s1.size() == s2Len) && (std::memcmp(s1.data(), s2, s2Len) == 0));
43 }
44
45 bool startsWith(const std::string& s1, const char* s2, size_t s2Len)
46 {
47 if (s2 == nullptr)
48 {
49 logErrorOnce("redisGeneral: null pointer passed to startsWith function");
50 return false;
51 }
52
53 return ((s1.size() <= s2Len) && (std::memcmp(s1.data(), s2, s1.size()) == 0));
54 }
55
56 AsyncRedisCommandDispatcherErrorCode mapRedisReplyErrorToSdlError(const redisReply* rr)
57 {
58 if (equals("LOADING Redis is loading the dataset in memory", rr->str, static_cast<size_t>(rr->len)))
59 return AsyncRedisCommandDispatcherErrorCode::DATASET_LOADING;
60
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.
66 */
67 if (startsWith("CLUSTERDOWN", rr->str, static_cast<size_t>(rr->len)))
68 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
69
70 if (startsWith("ERR Protocol error", rr->str, static_cast<size_t>(rr->len)))
71 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
72
Rolf Badorekb7f49712019-09-23 14:14:56 +030073 if (startsWith("READONLY", rr->str, static_cast<size_t>(rr->len)))
74 return AsyncRedisCommandDispatcherErrorCode::WRITING_TO_SLAVE;
75
Rolf Badorekef2bf512019-08-20 11:17:15 +030076 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;
80 }
81
82 AsyncRedisCommandDispatcherErrorCode mapRedisContextErrorToSdlError(int redisContextErr, const char* redisContextErrstr)
83 {
84 switch (redisContextErr)
85 {
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. */
91 case REDIS_ERR_IO:
92 if (errno == ECONNRESET)
93 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
94 logErrorOnce("redis io error. Errno: " + std::to_string(errno));
95 return AsyncRedisCommandDispatcherErrorCode::IO_ERROR;
96 case REDIS_ERR_EOF:
97 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
98 case REDIS_ERR_PROTOCOL:
99 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
100 case REDIS_ERR_OOM:
101 return AsyncRedisCommandDispatcherErrorCode::OUT_OF_MEMORY;
102#if HAVE_HIREDIS_VIP
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.
108 */
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.
113 */
114 case CLUSTER_ERROR_CONNECTION_LOST:
115 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
116#endif
117 default:
118 std::ostringstream oss;
119 oss << "redis error: "
120 << redisContextErrstr
121 << " (" << redisContextErr << ")";
122 logErrorOnce(oss.str());
123 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
124 }
125 }
126}
127
128namespace shareddatalayer
129{
130 namespace redis
131 {
132 std::string formatToClusterSyntax(const DatabaseConfiguration::Addresses& addresses)
133 {
134 std::ostringstream oss;
135 for (auto i(addresses.begin()); i != addresses.end(); ++i)
136 {
137 oss << i->getHost() << ':' << ntohs(i->getPort());
138 if (i == --addresses.end())
139 break;
140 else
141 oss << ',';
142 }
143 return oss.str();
144 }
145
146 const std::set<std::string>& getRequiredRedisModuleCommands()
147 {
148 static const std::set<std::string> requiredRedisModuleCommands({"msetpub","setie","setiepub","setnxpub","delpub","delie","deliepub"});
149 return requiredRedisModuleCommands;
150 }
151
152 std::error_code getRedisError(int redisContextErr, const char* redisContextErrstr, const redisReply* rr)
153 {
154 if (rr != nullptr)
155 {
156 if (rr->type != REDIS_REPLY_ERROR)
157 return std::error_code();
158
159 return std::error_code(mapRedisReplyErrorToSdlError(rr));
160 }
161
162 return std::error_code(mapRedisContextErrorToSdlError(redisContextErr, redisContextErrstr));
163 }
164
165 std::set<std::string> parseCommandListReply(const redis::Reply& reply)
166 {
167 std::set<std::string> availableCommands;
168 auto replyArray(reply.getArray());
169 for (const auto& j : *replyArray)
170 {
171 auto element = j->getArray();
172 auto command = element->front()->getString();
173 availableCommands.insert(command->str);
174 }
175 return availableCommands;
176 }
177
178 bool checkRedisModuleCommands(const std::set<std::string>& availableCommands)
179 {
180 std::set<std::string> missingCommands;
181
182 for (const auto& i : getRequiredRedisModuleCommands())
183 {
184 const auto it = availableCommands.find(i);
185 if (it == availableCommands.end())
186 missingCommands.insert(i);
187 }
188 if (!missingCommands.empty())
189 {
190 logErrorOnce("Missing Redis module extension commands:");
191 for (const auto& i : missingCommands)
192 logErrorOnce(i);
193 }
194 return missingCommands.empty();
195 }
196 }
197}