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