1 // Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC")
2 //
3 // This Source Code Form is subject to the terms of the Mozilla Public
4 // License, v. 2.0. If a copy of the MPL was not distributed with this
5 // file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7 #include <config.h>
8
9 #include <asiolink/asio_wrapper.h>
10 #include <ha_test.h>
11 #include <asiolink/interval_timer.h>
12 #include <cc/command_interpreter.h>
13 #include <cc/data.h>
14 #include <dhcp/dhcp4.h>
15 #include <dhcp/dhcp6.h>
16 #include <dhcp/duid.h>
17 #include <dhcp/hwaddr.h>
18 #include <dhcp/option.h>
19 #include <dhcp/option_int.h>
20 #include <dhcpsrv/cfgmgr.h>
21 #include <hooks/hooks.h>
22 #include <hooks/hooks_manager.h>
23 #include <util/range_utilities.h>
24 #include <functional>
25 #include <utility>
26 #include <vector>
27
28 using namespace isc::asiolink;
29 using namespace isc::data;
30 using namespace isc::dhcp;
31 using namespace isc::hooks;
32
33 namespace {
34
35 /// @brief Structure that holds registered hook indexes.
36 struct TestHooks {
37 int hooks_index_dhcp4_srv_configured_;
38
39 /// Constructor that registers hook points for the tests.
TestHooks__anon9bcbbbbb0111::TestHooks40 TestHooks() {
41 hooks_index_dhcp4_srv_configured_ = HooksManager::registerHook("dhcp4_srv_configured");
42 }
43 };
44
45 TestHooks Hooks;
46
47 }
48
49 namespace isc {
50 namespace ha {
51 namespace test {
52
HATest()53 HATest::HATest()
54 : io_service_(new IOService()),
55 network_state_(new NetworkState(NetworkState::DHCPv4)) {
56 }
57
~HATest()58 HATest::~HATest() {
59 }
60
61 void
startHAService()62 HATest::startHAService() {
63 if (HooksManager::calloutsPresent(Hooks.hooks_index_dhcp4_srv_configured_)) {
64 CalloutHandlePtr callout_handle = HooksManager::createCalloutHandle();
65 callout_handle->setArgument("io_context", io_service_);
66 callout_handle->setArgument("network_state", network_state_);
67 HooksManager::callCallouts(Hooks.hooks_index_dhcp4_srv_configured_,
68 *callout_handle);
69 }
70 }
71
72 void
runIOService(long ms)73 HATest::runIOService(long ms) {
74 io_service_->get_io_service().reset();
75 IntervalTimer timer(*io_service_);
76 timer.setup(std::bind(&IOService::stop, io_service_), ms,
77 IntervalTimer::ONE_SHOT);
78 io_service_->run();
79 timer.cancel();
80 }
81
82 void
runIOService(long ms,std::function<bool ()> stop_condition)83 HATest::runIOService(long ms, std::function<bool()> stop_condition) {
84 io_service_->get_io_service().reset();
85 IntervalTimer timer(*io_service_);
86 bool timeout = false;
87 timer.setup(std::bind(&HATest::stopIOServiceHandler, this, std::ref(timeout)),
88 ms, IntervalTimer::ONE_SHOT);
89
90 while (!stop_condition() && !timeout) {
91 io_service_->run_one();
92 }
93
94 timer.cancel();
95 }
96
97 boost::shared_ptr<std::thread>
runIOServiceInThread()98 HATest::runIOServiceInThread() {
99 io_service_->get_io_service().reset();
100
101 bool running = false;
102 std::mutex mutex;
103 std::condition_variable condvar;
104
105 io_service_->post(std::bind(&HATest::signalServiceRunning, this, std::ref(running),
106 std::ref(mutex), std::ref(condvar)));
107 boost::shared_ptr<std::thread>
108 th(new std::thread(std::bind(&IOService::run, io_service_.get())));
109
110 std::unique_lock<std::mutex> lock(mutex);
111 while (!running) {
112 condvar.wait(lock);
113 }
114
115 return (th);
116 }
117
118 void
testSynchronousCommands(std::function<void ()> commands)119 HATest::testSynchronousCommands(std::function<void()> commands) {
120 // Run IO service in thread.
121 auto thread = runIOServiceInThread();
122
123 // Run desired commands.
124 commands();
125
126 // Stop the IO service. This should cause the thread to terminate.
127 io_service_->stop();
128 thread->join();
129 }
130
131 void
signalServiceRunning(bool & running,std::mutex & mutex,std::condition_variable & condvar)132 HATest::signalServiceRunning(bool& running, std::mutex& mutex,
133 std::condition_variable& condvar) {
134 std::lock_guard<std::mutex> lock(mutex);
135 running = true;
136 condvar.notify_one();
137 }
138
139 void
stopIOServiceHandler(bool & stop_flag)140 HATest::stopIOServiceHandler(bool& stop_flag) {
141 stop_flag = true;
142 }
143
144 ConstElementPtr
createValidJsonConfiguration(const HAConfig::HAMode & ha_mode) const145 HATest::createValidJsonConfiguration(const HAConfig::HAMode& ha_mode) const {
146 std::ostringstream config_text;
147 config_text <<
148 "["
149 " {"
150 " \"this-server-name\": \"server1\","
151 " \"mode\": " << (ha_mode == HAConfig::LOAD_BALANCING ?
152 "\"load-balancing\"" : "\"hot-standby\"") << ","
153 " \"sync-page-limit\": 3,"
154 " \"heartbeat-delay\": 1000,"
155 " \"max-response-delay\": 1000,"
156 " \"max-ack-delay\": 10000,"
157 " \"max-unacked-clients\": 10,"
158 " \"wait-backup-ack\": false,"
159 " \"peers\": ["
160 " {"
161 " \"name\": \"server1\","
162 " \"url\": \"http://127.0.0.1:18123/\","
163 " \"role\": \"primary\","
164 " \"auto-failover\": true"
165 " },"
166 " {"
167 " \"name\": \"server2\","
168 " \"url\": \"http://127.0.0.1:18124/\","
169 " \"role\": " << (ha_mode == HAConfig::LOAD_BALANCING ?
170 "\"secondary\"" : "\"standby\"") << ","
171 " \"auto-failover\": true"
172 " },"
173 " {"
174 " \"name\": \"server3\","
175 " \"url\": \"http://127.0.0.1:18125/\","
176 " \"role\": \"backup\","
177 " \"auto-failover\": false"
178 " }"
179 " ]"
180 " }"
181 "]";
182
183 return (Element::fromJSON(config_text.str()));
184 }
185
186 ConstElementPtr
createValidPassiveBackupJsonConfiguration() const187 HATest::createValidPassiveBackupJsonConfiguration() const {
188 std::ostringstream config_text;
189 config_text <<
190 "["
191 " {"
192 " \"this-server-name\": \"server1\","
193 " \"mode\": \"passive-backup\","
194 " \"wait-backup-ack\": false,"
195 " \"peers\": ["
196 " {"
197 " \"name\": \"server1\","
198 " \"url\": \"http://127.0.0.1:18123/\","
199 " \"role\": \"primary\""
200 " },"
201 " {"
202 " \"name\": \"server2\","
203 " \"url\": \"http://127.0.0.1:18124/\","
204 " \"role\": \"backup\""
205 " },"
206 " {"
207 " \"name\": \"server3\","
208 " \"url\": \"http://127.0.0.1:18125/\","
209 " \"role\": \"backup\""
210 " }"
211 " ]"
212 " }"
213 "]";
214
215 return (Element::fromJSON(config_text.str()));
216 }
217
218
219 HAConfigPtr
createValidConfiguration(const HAConfig::HAMode & ha_mode) const220 HATest::createValidConfiguration(const HAConfig::HAMode& ha_mode) const {
221 HAConfigPtr config_storage(new HAConfig());
222 HAConfigParser parser;
223
224 parser.parse(config_storage, createValidJsonConfiguration(ha_mode));
225 return (config_storage);
226 }
227
228 HAConfigPtr
createValidPassiveBackupConfiguration() const229 HATest::createValidPassiveBackupConfiguration() const {
230 HAConfigPtr config_storage(new HAConfig());
231 HAConfigParser parser;
232
233 parser.parse(config_storage, createValidPassiveBackupJsonConfiguration());
234 return (config_storage);
235 }
236
237 void
checkAnswer(const isc::data::ConstElementPtr & answer,const int exp_status,const std::string & exp_txt)238 HATest::checkAnswer(const isc::data::ConstElementPtr& answer,
239 const int exp_status,
240 const std::string& exp_txt) {
241 int rcode = 0;
242 isc::data::ConstElementPtr comment;
243 comment = isc::config::parseAnswer(rcode, answer);
244
245 EXPECT_EQ(exp_status, rcode)
246 << "Expected status code " << exp_status
247 << " but received " << rcode << ", comment: "
248 << (comment ? comment->str() : "(none)");
249
250 // Ok, parseAnswer interface is weird. If there are no arguments,
251 // it returns content of text. But if there is an argument,
252 // it returns the argument and it's not possible to retrieve
253 // "text" (i.e. comment).
254 if (comment->getType() != Element::string) {
255 comment = answer->get("text");
256 }
257
258 if (!exp_txt.empty()) {
259 EXPECT_EQ(exp_txt, comment->stringValue());
260 }
261 }
262
263 std::vector<uint8_t>
randomKey(const size_t key_size) const264 HATest::randomKey(const size_t key_size) const {
265 std::vector<uint8_t> key(key_size);
266 util::fillRandom(key.begin(), key.end());
267 return (key);
268 }
269
270
271 Pkt4Ptr
createMessage4(const uint8_t msg_type,const uint8_t hw_address_seed,const uint8_t client_id_seed,const uint16_t secs) const272 HATest::createMessage4(const uint8_t msg_type, const uint8_t hw_address_seed,
273 const uint8_t client_id_seed, const uint16_t secs) const {
274 Pkt4Ptr message(new Pkt4(msg_type, 0x1234));
275
276 HWAddrPtr hw_address(new HWAddr(std::vector<uint8_t>(6, hw_address_seed),
277 HTYPE_ETHER));
278 message->setHWAddr(hw_address);
279 message->setSecs(secs);
280
281 if (client_id_seed > 0) {
282 OptionPtr opt_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
283 std::vector<uint8_t>(6, client_id_seed)));
284 message->addOption(opt_client_id);
285 }
286
287 return (message);
288 }
289
290 Pkt4Ptr
createQuery4(const std::string & hw_address_text) const291 HATest::createQuery4(const std::string& hw_address_text) const {
292 Pkt4Ptr query4(new Pkt4(DHCPDISCOVER, 0x1234));
293 HWAddr hwaddr = HWAddr::fromText(hw_address_text);
294 query4->setHWAddr(HWAddrPtr(new HWAddr(hwaddr.hwaddr_, HTYPE_ETHER)));
295 return (query4);
296 }
297
298 Pkt4Ptr
createQuery4(const std::vector<uint8_t> & hw_address,const std::vector<uint8_t> & client_id) const299 HATest::createQuery4(const std::vector<uint8_t>& hw_address,
300 const std::vector<uint8_t>& client_id) const {
301 Pkt4Ptr query4(new Pkt4(DHCPDISCOVER, 0x1234));
302 query4->setHWAddr(HWAddrPtr(new HWAddr(hw_address, HTYPE_ETHER)));
303 if (!client_id.empty()) {
304 OptionPtr opt_client_id(new Option(Option::V4, DHO_DHCP_CLIENT_IDENTIFIER,
305 client_id));
306 query4->addOption(opt_client_id);
307 }
308 return (query4);
309 }
310
311 Pkt6Ptr
createQuery6(const std::vector<uint8_t> & duid) const312 HATest::createQuery6(const std::vector<uint8_t>& duid) const {
313 Pkt6Ptr query6(new Pkt6(DHCPV6_SOLICIT, 0x1234));
314 OptionPtr opt_duid(new Option(Option::V6, D6O_CLIENTID, duid));
315 query6->addOption(opt_duid);
316 return (query6);
317 }
318
319 Pkt6Ptr
createMessage6(const uint8_t msg_type,const uint8_t duid_seed,const uint16_t elapsed_time) const320 HATest::createMessage6(const uint8_t msg_type, const uint8_t duid_seed,
321 const uint16_t elapsed_time) const {
322 Pkt6Ptr message(new Pkt6(msg_type, 0x1234));
323
324 OptionPtr opt_duid(new Option(Option::V6, D6O_CLIENTID, OptionBuffer(8, duid_seed)));
325 message->addOption(opt_duid);
326
327 OptionUint16Ptr opt_elapsed_time(new OptionUint16(Option::V6, D6O_ELAPSED_TIME,
328 elapsed_time));
329 message->addOption(opt_elapsed_time);
330
331 return (message);
332 }
333
334 Pkt6Ptr
createQuery6(const std::string & duid_text) const335 HATest::createQuery6(const std::string& duid_text) const {
336 Pkt6Ptr query6(new Pkt6(DHCPV6_SOLICIT, 0x1234));
337 DUID duid = DUID::fromText(duid_text);
338 OptionPtr client_id(new Option(Option::V6, D6O_CLIENTID, duid.getDuid()));
339 query6->addOption(client_id);
340 return (query6);
341 }
342
343 void
setDHCPMultiThreadingConfig(bool enabled,uint32_t thread_pool_size,uint32_t packet_queue_size)344 HATest::setDHCPMultiThreadingConfig(bool enabled, uint32_t thread_pool_size,
345 uint32_t packet_queue_size) {
346 ElementPtr mt_config = Element::createMap();
347 mt_config->set("enable-multi-threading", Element::create(enabled));
348 mt_config->set("thread-pool-size",
349 Element::create(static_cast<int>(thread_pool_size)));
350 mt_config->set("queue-size",
351 Element::create(static_cast<int>(packet_queue_size)));
352 CfgMgr::instance().getStagingCfg()->setDHCPMultiThreading(mt_config);
353 }
354
355 std::string
makeHAMtJson(bool enable_multi_threading,bool http_dedicated_listener,uint32_t http_listener_threads,uint32_t http_client_threads)356 HATest::makeHAMtJson(bool enable_multi_threading, bool http_dedicated_listener,
357 uint32_t http_listener_threads, uint32_t http_client_threads) {
358 std::stringstream ss;
359 ss << "\"multi-threading\": {"
360 << " \"enable-multi-threading\": "
361 << (enable_multi_threading ? "true" : "false") << ","
362 << " \"http-dedicated-listener\": "
363 << (http_dedicated_listener ? "true" : "false") << ","
364 << " \"http-listener-threads\": " << http_listener_threads << ","
365 << " \"http-client-threads\": " << http_client_threads << "}";
366 return (ss.str());
367 }
368
369 } // end of namespace isc::ha::test
370 } // end of namespace isc::ha
371 } // end of namespace isc
372