blob: dcf35c7871d7c91c829fe2c6f799d0f67cc02af5 [file] [log] [blame]
Rolf Badorek8324d022019-09-17 16:47:20 +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 <gtest/gtest.h>
18#include <arpa/inet.h>
19#include <sdl/asyncstorage.hpp>
20#include "private/createlogger.hpp"
21#include "private/hostandport.hpp"
22#include "private/timer.hpp"
23#include "private/redis/asyncsentineldatabasediscovery.hpp"
24#include "private/tst/asynccommanddispatchermock.hpp"
25#include "private/tst/contentsbuildermock.hpp"
26#include "private/tst/enginemock.hpp"
27#include "private/tst/replymock.hpp"
28#include "private/tst/wellknownerrorcode.hpp"
29
30using namespace shareddatalayer;
31using namespace shareddatalayer::redis;
32using namespace shareddatalayer::tst;
33using namespace testing;
34
35namespace
36{
37 class AsyncSentinelDatabaseDiscoveryBaseTest: public testing::Test
38 {
39 public:
40 std::unique_ptr<AsyncSentinelDatabaseDiscovery> asyncSentinelDatabaseDiscovery;
41 std::shared_ptr<StrictMock<EngineMock>> engineMock;
42 std::shared_ptr<StrictMock<AsyncCommandDispatcherMock>> dispatcherMock;
43 std::shared_ptr<StrictMock<ContentsBuilderMock>> contentsBuilderMock;
44 std::shared_ptr<Logger> logger;
45 Contents contents;
46 AsyncCommandDispatcher::ConnectAck dispatcherConnectAck;
47 AsyncCommandDispatcher::CommandCb savedCommandCb;
48 ReplyMock replyMock;
49 std::string someHost;
50 uint16_t somePort;
51 Reply::DataItem hostDataItem;
52 Reply::DataItem portDataItem;
53 std::shared_ptr<ReplyMock> masterInquiryReplyHost;
54 std::shared_ptr<ReplyMock> masterInquiryReplyPort;
55 Reply::ReplyVector masterInquiryReply;
56 Timer::Duration expectedMasterInquiryRetryTimerDuration;
57 Timer::Callback savedConnectionRetryTimerCallback;
58
59 AsyncSentinelDatabaseDiscoveryBaseTest():
60 engineMock(std::make_shared<StrictMock<EngineMock>>()),
61 dispatcherMock(std::make_shared<StrictMock<AsyncCommandDispatcherMock>>()),
62 contentsBuilderMock(std::make_shared<StrictMock<ContentsBuilderMock>>(AsyncStorage::SEPARATOR)),
63 logger(createLogger(SDL_LOG_PREFIX)),
64 contents({{"aaa","bbb"},{3,3}}),
65 someHost("somehost"),
66 somePort(1234),
67 hostDataItem({someHost,ReplyStringLength(someHost.length())}),
68 portDataItem({std::to_string(somePort),ReplyStringLength(std::to_string(somePort).length())}),
69 masterInquiryReplyHost(std::make_shared<ReplyMock>()),
70 masterInquiryReplyPort(std::make_shared<ReplyMock>()),
71 expectedMasterInquiryRetryTimerDuration(std::chrono::seconds(1))
72 {
73 masterInquiryReply.push_back(masterInquiryReplyHost);
74 masterInquiryReply.push_back(masterInquiryReplyPort);
75 }
76
77 virtual ~AsyncSentinelDatabaseDiscoveryBaseTest()
78 {
79 }
80
81 std::shared_ptr<AsyncCommandDispatcher> asyncCommandDispatcherCreator(Engine&,
82 const DatabaseInfo&,
83 std::shared_ptr<ContentsBuilder>)
84 {
85 // @TODO Add database info checking when configuration support for sentinel is added.
86 newDispatcherCreated();
87 return dispatcherMock;
88 }
89
90 MOCK_METHOD0(newDispatcherCreated, void());
91
92 void expectNewDispatcherCreated()
93 {
94 EXPECT_CALL(*this, newDispatcherCreated())
95 .Times(1);
96 }
97
98 void expectDispatcherWaitConnectedAsync()
99 {
100 EXPECT_CALL(*dispatcherMock, waitConnectedAsync(_))
101 .Times(1)
102 .WillOnce(Invoke([this](const AsyncCommandDispatcher::ConnectAck& connectAck)
103 {
104 dispatcherConnectAck = connectAck;
105 }));
106 }
107
108 void expectContentsBuild(const std::string& string,
109 const std::string& string2,
110 const std::string& string3)
111 {
112 EXPECT_CALL(*contentsBuilderMock, build(string, string2, string3))
113 .Times(1)
114 .WillOnce(Return(contents));
115 }
116
117 void expectDispatchAsync()
118 {
119 EXPECT_CALL(*dispatcherMock, dispatchAsync(_, _, contents))
120 .Times(1)
121 .WillOnce(SaveArg<0>(&savedCommandCb));
122 }
123
124 void expectMasterInquiry()
125 {
126 expectContentsBuild("SENTINEL", "get-master-addr-by-name", "mymaster");
127 expectDispatchAsync();
128 }
129
130 MOCK_METHOD1(stateChangedCb, void(const DatabaseInfo&));
131
132 void expectStateChangedCb()
133 {
134 EXPECT_CALL(*this, stateChangedCb(_))
135 .Times(1)
136 .WillOnce(Invoke([this](const DatabaseInfo& databaseInfo)
137 {
138 EXPECT_THAT(DatabaseConfiguration::Addresses({ HostAndPort(someHost, htons(somePort)) }),
139 ContainerEq(databaseInfo.hosts));
140 EXPECT_EQ(DatabaseInfo::Type::SINGLE, databaseInfo.type);
141 EXPECT_EQ(boost::none, databaseInfo.ns);
142 EXPECT_EQ(DatabaseInfo::Discovery::SENTINEL, databaseInfo.discovery);
143 }));
144 }
145
146 void expectGetReplyType(ReplyMock& mock, const Reply::Type& type)
147 {
148 EXPECT_CALL(mock, getType())
149 .Times(1)
150 .WillOnce(Return(type));
151 }
152
153 void expectGetReplyArray_ReturnMasterInquiryReply()
154 {
155 EXPECT_CALL(replyMock, getArray())
156 .Times(1)
157 .WillOnce(Return(&masterInquiryReply));
158 }
159
160 void expectGetReplyString(ReplyMock& mock, const Reply::DataItem& item)
161 {
162 EXPECT_CALL(mock, getString())
163 .Times(1)
164 .WillOnce(Return(&item));
165 }
166
167 void expectMasterIquiryReply()
168 {
169 expectGetReplyType(replyMock, Reply::Type::ARRAY);
170 expectGetReplyArray_ReturnMasterInquiryReply();
171 expectGetReplyType(*masterInquiryReplyHost, Reply::Type::STRING);
172 expectGetReplyString(*masterInquiryReplyHost, hostDataItem);
173 expectGetReplyType(*masterInquiryReplyPort, Reply::Type::STRING);
174 expectGetReplyString(*masterInquiryReplyPort, portDataItem);
175 }
176
177 void expectMasterInquiryRetryTimer()
178 {
179 EXPECT_CALL(*engineMock, armTimer(_, expectedMasterInquiryRetryTimerDuration, _))
180 .Times(1)
181 .WillOnce(SaveArg<2>(&savedConnectionRetryTimerCallback));
182 }
183
184 void setDefaultResponsesForMasterInquiryReplyParsing()
185 {
186 ON_CALL(replyMock, getType())
187 .WillByDefault(Return(Reply::Type::ARRAY));
188 ON_CALL(replyMock, getArray())
189 .WillByDefault(Return(&masterInquiryReply));
190 ON_CALL(*masterInquiryReplyHost, getType())
191 .WillByDefault(Return(Reply::Type::STRING));
192 ON_CALL(*masterInquiryReplyHost, getString())
193 .WillByDefault(Return(&hostDataItem));
194 ON_CALL(*masterInquiryReplyPort, getType())
195 .WillByDefault(Return(Reply::Type::STRING));
196 ON_CALL(*masterInquiryReplyHost, getString())
197 .WillByDefault(Return(&portDataItem));
198 }
199 };
200
201 class AsyncSentinelDatabaseDiscoveryTest: public AsyncSentinelDatabaseDiscoveryBaseTest
202 {
203 public:
204 AsyncSentinelDatabaseDiscoveryTest()
205 {
206 expectNewDispatcherCreated();
207 asyncSentinelDatabaseDiscovery.reset(
208 new AsyncSentinelDatabaseDiscovery(
209 engineMock,
210 logger,
211 std::bind(&AsyncSentinelDatabaseDiscoveryBaseTest::asyncCommandDispatcherCreator,
212 this,
213 std::placeholders::_1,
214 std::placeholders::_2,
215 std::placeholders::_3),
216 contentsBuilderMock));
217 }
218 };
219
220 using AsyncSentinelDatabaseDiscoveryDeathTest = AsyncSentinelDatabaseDiscoveryTest;
221}
222
223TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, IsNotCopyable)
224{
225 InSequence dummy;
226 EXPECT_FALSE(std::is_copy_constructible<AsyncSentinelDatabaseDiscovery>::value);
227 EXPECT_FALSE(std::is_copy_assignable<AsyncSentinelDatabaseDiscovery>::value);
228}
229
230TEST_F(AsyncSentinelDatabaseDiscoveryBaseTest, ImplementsAsyncDatabaseDiscovery)
231{
232 InSequence dummy;
233 EXPECT_TRUE((std::is_base_of<AsyncDatabaseDiscovery, AsyncSentinelDatabaseDiscovery>::value));
234}
235
236TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterIsInquiredFromSentinel)
237{
238 InSequence dummy;
239 expectDispatcherWaitConnectedAsync();
240 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
241 this,
242 std::placeholders::_1));
243 expectMasterInquiry();
244 dispatcherConnectAck();
245 expectMasterIquiryReply();
246 expectStateChangedCb();
247 savedCommandCb(std::error_code(), replyMock);
248}
249
250TEST_F(AsyncSentinelDatabaseDiscoveryTest, RedisMasterInquiryErrorTriggersRetry)
251{
252 InSequence dummy;
253 expectDispatcherWaitConnectedAsync();
254 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
255 this,
256 std::placeholders::_1));
257 expectMasterInquiry();
258 dispatcherConnectAck();
259 expectMasterInquiryRetryTimer();
260 savedCommandCb(getWellKnownErrorCode(), replyMock);
261 expectMasterInquiry();
262 savedConnectionRetryTimerCallback();
263 expectMasterIquiryReply();
264 expectStateChangedCb();
265 savedCommandCb(std::error_code(), replyMock);
266}
267
268TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidReplyType)
269{
270 InSequence dummy;
271 expectDispatcherWaitConnectedAsync();
272 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
273 this,
274 std::placeholders::_1));
275 expectMasterInquiry();
276 dispatcherConnectAck();
277 ON_CALL(replyMock, getType())
278 .WillByDefault(Return(Reply::Type::NIL));
279 EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
280}
281
282TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidHostElementType)
283{
284 InSequence dummy;
285 expectDispatcherWaitConnectedAsync();
286 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
287 this,
288 std::placeholders::_1));
289 expectMasterInquiry();
290 dispatcherConnectAck();
291 setDefaultResponsesForMasterInquiryReplyParsing();
292 ON_CALL(*masterInquiryReplyHost, getType())
293 .WillByDefault(Return(Reply::Type::NIL));
294 EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
295}
296
297TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_InvalidPortElementType)
298{
299 InSequence dummy;
300 expectDispatcherWaitConnectedAsync();
301 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
302 this,
303 std::placeholders::_1));
304 expectMasterInquiry();
305 dispatcherConnectAck();
306 setDefaultResponsesForMasterInquiryReplyParsing();
307 ON_CALL(*masterInquiryReplyPort, getType())
308 .WillByDefault(Return(Reply::Type::NIL));
309 EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
310}
311
312TEST_F(AsyncSentinelDatabaseDiscoveryDeathTest, MasterInquiryParsingErrorAborts_PortCantBeCastedToInt)
313{
314 InSequence dummy;
315 expectDispatcherWaitConnectedAsync();
316 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
317 this,
318 std::placeholders::_1));
319 expectMasterInquiry();
320 dispatcherConnectAck();
321 setDefaultResponsesForMasterInquiryReplyParsing();
322 std::string invalidPort("invalidPort");
323 Reply::DataItem invalidPortDataItem({invalidPort,ReplyStringLength(invalidPort.length())});
324 ON_CALL(*masterInquiryReplyPort, getString())
325 .WillByDefault(Return(&invalidPortDataItem));
326 EXPECT_EXIT(savedCommandCb(std::error_code(), replyMock), KilledBySignal(SIGABRT), ".*Master inquiry reply parsing error");
327}
328
329TEST_F(AsyncSentinelDatabaseDiscoveryTest, CallbackIsNotCalledAfterCleared)
330{
331 InSequence dummy;
332 expectDispatcherWaitConnectedAsync();
333 asyncSentinelDatabaseDiscovery->setStateChangedCb(std::bind(&AsyncSentinelDatabaseDiscoveryTest::stateChangedCb,
334 this,
335 std::placeholders::_1));
336 expectMasterInquiry();
337 dispatcherConnectAck();
338 expectMasterInquiryRetryTimer();
339 savedCommandCb(getWellKnownErrorCode(), replyMock);
340 expectMasterInquiry();
341 savedConnectionRetryTimerCallback();
342 expectMasterIquiryReply();
343 asyncSentinelDatabaseDiscovery->clearStateChangedCb();
344 EXPECT_CALL(*this, stateChangedCb(_))
345 .Times(0);
346 savedCommandCb(std::error_code(), replyMock);
347}