Add extra line about src files are part of RIC platform project
[ric-plt/sdl.git] / src / redis / redisgeneral.cpp
1 /*
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 /*
18  * This source code is part of the near-RT RIC (RAN Intelligent Controller)
19  * platform project (RICP).
20 */
21
22 #include "config.h"
23 #include "private/createlogger.hpp"
24 #include "private/error.hpp"
25 #include "private/redis/redisgeneral.hpp"
26 #include <arpa/inet.h>
27 #include <cstring>
28 #if HAVE_HIREDIS_VIP
29 #include <hircluster.h>
30 #endif
31 #include <hiredis/hiredis.h>
32 #include <sstream>
33
34 using namespace shareddatalayer;
35 using namespace shareddatalayer::redis;
36
37 namespace
38 {
39     bool equals(const std::string& s1, const char* s2, size_t s2Len)
40     {
41         if (s2 == nullptr)
42         {
43             logErrorOnce("redisGeneral: null pointer passed to equals function");
44             return false;
45         }
46
47         return ((s1.size() == s2Len) && (std::memcmp(s1.data(), s2, s2Len) == 0));
48     }
49
50     bool startsWith(const std::string& s1, const char* s2, size_t s2Len)
51     {
52         if (s2 == nullptr)
53         {
54             logErrorOnce("redisGeneral: null pointer passed to startsWith function");
55             return false;
56         }
57
58         return ((s1.size() <= s2Len) && (std::memcmp(s1.data(), s2, s1.size()) == 0));
59     }
60
61     AsyncRedisCommandDispatcherErrorCode mapRedisReplyErrorToSdlError(const redisReply* rr)
62     {
63         if (equals("LOADING Redis is loading the dataset in memory", rr->str, static_cast<size_t>(rr->len)))
64             return AsyncRedisCommandDispatcherErrorCode::DATASET_LOADING;
65
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.
71          */
72         if (startsWith("CLUSTERDOWN", rr->str, static_cast<size_t>(rr->len)))
73             return AsyncRedisCommandDispatcherErrorCode::NOT_CONNECTED;
74
75         if (startsWith("ERR Protocol error", rr->str, static_cast<size_t>(rr->len)))
76             return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
77
78         if (startsWith("READONLY", rr->str, static_cast<size_t>(rr->len)))
79             return AsyncRedisCommandDispatcherErrorCode::WRITING_TO_SLAVE;
80
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;
85     }
86
87     AsyncRedisCommandDispatcherErrorCode mapRedisContextErrorToSdlError(int redisContextErr, const char* redisContextErrstr)
88     {
89         switch (redisContextErr)
90         {
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. */
96             case REDIS_ERR_IO:
97                 if (errno == ECONNRESET)
98                     return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
99                 logErrorOnce("redis io error. Errno: " + std::to_string(errno));
100                 return AsyncRedisCommandDispatcherErrorCode::IO_ERROR;
101             case REDIS_ERR_EOF:
102                 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
103             case REDIS_ERR_PROTOCOL:
104                 return AsyncRedisCommandDispatcherErrorCode::PROTOCOL_ERROR;
105             case REDIS_ERR_OOM:
106                 return AsyncRedisCommandDispatcherErrorCode::OUT_OF_MEMORY;
107 #if HAVE_HIREDIS_VIP
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.
113              */
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.
118               */
119             case CLUSTER_ERROR_CONNECTION_LOST:
120                 return AsyncRedisCommandDispatcherErrorCode::CONNECTION_LOST;
121 #endif
122             default:
123                 std::ostringstream oss;
124                 oss << "redis error: "
125                     << redisContextErrstr
126                     << " (" << redisContextErr << ")";
127                 logErrorOnce(oss.str());
128                 return AsyncRedisCommandDispatcherErrorCode::UNKNOWN_ERROR;
129         }
130     }
131 }
132
133 namespace shareddatalayer
134 {
135     namespace redis
136     {
137         std::string formatToClusterSyntax(const DatabaseConfiguration::Addresses& addresses)
138         {
139             std::ostringstream oss;
140             for (auto i(addresses.begin()); i != addresses.end(); ++i)
141             {
142                 oss << i->getHost() << ':' << ntohs(i->getPort());
143                 if (i == --addresses.end())
144                     break;
145                 else
146                     oss << ',';
147             }
148             return oss.str();
149         }
150
151         const std::set<std::string>& getRequiredRedisModuleCommands()
152         {
153             static const std::set<std::string> requiredRedisModuleCommands({"msetpub","setie","setiepub","setnxpub","delpub","delie","deliepub"});
154             return requiredRedisModuleCommands;
155         }
156
157         std::error_code getRedisError(int redisContextErr, const char* redisContextErrstr, const redisReply* rr)
158         {
159             if (rr != nullptr)
160             {
161                 if (rr->type != REDIS_REPLY_ERROR)
162                     return std::error_code();
163
164                 return std::error_code(mapRedisReplyErrorToSdlError(rr));
165             }
166
167             return std::error_code(mapRedisContextErrorToSdlError(redisContextErr, redisContextErrstr));
168         }
169
170         std::set<std::string> parseCommandListReply(const redis::Reply& reply)
171         {
172             std::set<std::string> availableCommands;
173             auto replyArray(reply.getArray());
174             for (const auto& j : *replyArray)
175             {
176                 auto element = j->getArray();
177                 auto command = element->front()->getString();
178                 availableCommands.insert(command->str);
179             }
180             return availableCommands;
181         }
182
183         bool checkRedisModuleCommands(const std::set<std::string>& availableCommands)
184         {
185             std::set<std::string> missingCommands;
186
187             for (const auto& i : getRequiredRedisModuleCommands())
188             {
189                 const auto it = availableCommands.find(i);
190                 if (it == availableCommands.end())
191                     missingCommands.insert(i);
192             }
193             if (!missingCommands.empty())
194             {
195                 logErrorOnce("Missing Redis module extension commands:");
196                 for (const auto& i : missingCommands)
197                     logErrorOnce(i);
198             }
199             return missingCommands.empty();
200         }
201     }
202 }