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 * This source code is part of the near-RT RIC (RAN Intelligent Controller)
19 * platform project (RICP).
23 #include "private/createlogger.hpp"
24 #include "private/error.hpp"
25 #include "private/redis/redisgeneral.hpp"
26 #include <arpa/inet.h>
29 #include <hircluster.h>
31 #include <hiredis/hiredis.h>
34 using namespace shareddatalayer;
35 using namespace shareddatalayer::redis;
39 bool equals(const std::string& s1, const char* s2, size_t s2Len)
43 logErrorOnce("redisGeneral: null pointer passed to equals function");
47 return ((s1.size() == s2Len) && (std::memcmp(s1.data(), s2, s2Len) == 0));
50 bool startsWith(const std::string& s1, const char* s2, size_t s2Len)
54 logErrorOnce("redisGeneral: null pointer passed to startsWith function");
58 return ((s1.size() <= s2Len) && (std::memcmp(s1.data(), s2, s1.size()) == 0));
61 AsyncRedisCommandDispatcherErrorCode mapRedisReplyErrorToSdlError(const redisReply* rr)
63 if (equals("LOADING Redis is loading the dataset in memory", rr->str, static_cast<size_t>(rr->len)))
64 return AsyncRedisCommandDispatcherErrorCode::DATASET_LOADING;
66 /* This error reply comes when some cluster node(s) is down and rest of the cluster
67 * nodes cannot operate due to that. This error reply typically comes from nodes
68 * which are working but cannot handle requests because other node(s) are down.
69 * Nodes which are actually down (under failover handling) typically return
70 * CLUSTER_ERROR_NOT_CONNECTED or CLUSTER_ERROR_CONNECTION_LOST.
72 if (startsWith("CLUSTERDOWN", rr->str, static_cast<size_t>(rr->len)))
73 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
75 if (startsWith("ERR Protocol error", rr->str, static_cast<size_t>(rr->len)))
76 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
78 if (startsWith("READONLY", rr->str, static_cast<size_t>(rr->len)))
79 return AsyncRedisCommandDispatcherErrorCode::WRITING_TO_SLAVE;
81 std::ostringstream oss;
82 oss << "redis reply error: " << std::string(rr->str, static_cast<size_t>(rr->len));
83 logErrorOnce(oss.str());
84 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
87 AsyncRedisCommandDispatcherErrorCode mapRedisContextErrorToSdlError(int redisContextErr, const char* redisContextErrstr)
89 switch (redisContextErr)
91 /* From hiredis/read.h:
92 * When an error occurs, the err flag in a context is set to hold the type of
93 * error that occurred. REDIS_ERR_IO means there was an I/O error and you
94 * should use the "errno" variable to find out what is wrong.
95 * For other values, the "errstr" field will hold a description. */
97 if (errno == ECONNRESET)
98 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
99 logErrorOnce("redis io error. Errno: " + std::to_string(errno));
100 return AsyncRedisCommandDispatcherErrorCode::IO_ERROR;
102 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
103 case REDIS_ERR_PROTOCOL:
104 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
106 return AsyncRedisCommandDispatcherErrorCode::OUT_OF_MEMORY;
108 /* hiredis_vip returns CLUSTER_ERROR_NOT_CONNECTED when cluster node is disconnected
109 * but failover handling has not started yet (node_timeout not elapsed yet). In
110 * this situation hiredis_vip does not send request to redis (as it is clear that
111 * request cannot succeed), therefore we can map this error to NOT_CONNECTED error
112 * as it best descripes this situation.
114 case CLUSTER_ERROR_NOT_CONNECTED:
115 return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
116 /* hiredis_vip returns CLUSTER_ERROR_CONNECTION_LOST when connection is lost while
117 * hiredis is waiting for reply from redis.
119 case CLUSTER_ERROR_CONNECTION_LOST:
120 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
123 std::ostringstream oss;
124 oss << "redis error: "
125 << redisContextErrstr
126 << " (" << redisContextErr << ")";
127 logErrorOnce(oss.str());
128 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
133 namespace shareddatalayer
137 std::string formatToClusterSyntax(const DatabaseConfiguration::Addresses& addresses)
139 std::ostringstream oss;
140 for (auto i(addresses.begin()); i != addresses.end(); ++i)
142 oss << i->getHost() << ':' << ntohs(i->getPort());
143 if (i == --addresses.end())
151 const std::set<std::string>& getRequiredRedisModuleCommands()
153 static const std::set<std::string> requiredRedisModuleCommands({"msetpub","setie","setiepub","setnxpub","delpub","delie","deliepub"});
154 return requiredRedisModuleCommands;
157 std::error_code getRedisError(int redisContextErr, const char* redisContextErrstr, const redisReply* rr)
161 if (rr->type != REDIS_REPLY_ERROR)
162 return std::error_code();
164 return std::error_code(mapRedisReplyErrorToSdlError(rr));
167 return std::error_code(mapRedisContextErrorToSdlError(redisContextErr, redisContextErrstr));
170 std::set<std::string> parseCommandListReply(const redis::Reply& reply)
172 std::set<std::string> availableCommands;
173 auto replyArray(reply.getArray());
174 for (const auto& j : *replyArray)
176 auto element = j->getArray();
177 auto command = element->front()->getString();
178 availableCommands.insert(command->str);
180 return availableCommands;
183 bool checkRedisModuleCommands(const std::set<std::string>& availableCommands)
185 std::set<std::string> missingCommands;
187 for (const auto& i : getRequiredRedisModuleCommands())
189 const auto it = availableCommands.find(i);
190 if (it == availableCommands.end())
191 missingCommands.insert(i);
193 if (!missingCommands.empty())
195 logErrorOnce("Missing Redis module extension commands:");
196 for (const auto& i : missingCommands)
199 return missingCommands.empty();