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 <communication_state.h>
8 #include <ha_config.h>
9 #include <ha_config_parser.h>
10 #include <asiolink/io_service.h>
11 #include <cc/data.h>
12 #include <dhcp/duid.h>
13 #include <dhcp/hwaddr.h>
14 #include <dhcp/pkt4.h>
15 #include <dhcp/pkt6.h>
16 #include <dhcpsrv/network_state.h>
17 #include <hooks/libinfo.h>
18 #include <boost/shared_ptr.hpp>
19 #include <gtest/gtest.h>
20 #include <cstdint>
21 #include <functional>
22 #include <string>
23 #include <vector>
24 #include <mutex>
25 #include <condition_variable>
26 #include <thread>
27 
28 namespace isc {
29 namespace ha {
30 namespace test {
31 
32 /// @brief Derivation of the @c CommunicationState class which allows
33 /// for modifications of poke time.
34 ///
35 /// @tparam @c CommunicationState4 or @c CommunicationState6.
36 template<typename StateType>
37 class NakedCommunicationState : public StateType {
38 public:
39 
40     /// @brief Constructor.
41     ///
42     /// @param io_service pointer to the IO service object.
NakedCommunicationState(const asiolink::IOServicePtr & io_service,const HAConfigPtr & config)43     explicit NakedCommunicationState(const asiolink::IOServicePtr& io_service,
44                                      const HAConfigPtr& config)
45         : StateType(io_service, config) {
46     }
47 
48     /// @brief Checks if the object was poked recently.
49     ///
50     /// @return true if the object was poked less than 5 seconds ago,
51     /// false otherwise.
isPoked()52     bool isPoked() const {
53         return (StateType::getDurationInMillisecs() < 5000);
54     }
55 
56     using StateType::config_;
57     using StateType::timer_;
58     using StateType::clock_skew_;
59     using StateType::last_clock_skew_warn_;
60     using StateType::my_time_at_skew_;
61     using StateType::partner_time_at_skew_;
62     using StateType::unsent_update_count_;
63 };
64 
65 /// @brief Type of the NakedCommunicationState for DHCPv4.
66 typedef NakedCommunicationState<CommunicationState4> NakedCommunicationState4;
67 
68 /// @brief Type of the pointer to the @c NakedCommunicationState4.
69 typedef boost::shared_ptr<NakedCommunicationState4> NakedCommunicationState4Ptr;
70 
71 /// @brief Type of the NakedCommunicationState for DHCPv6.
72 typedef NakedCommunicationState<CommunicationState6> NakedCommunicationState6;
73 
74 /// @brief Type of the pointer to the @c NakedCommunicationState6.
75 typedef boost::shared_ptr<NakedCommunicationState6> NakedCommunicationState6Ptr;
76 
77 /// @brief General test fixture class for all HA unittests.
78 ///
79 /// It provides basic functions to load and unload HA hooks library.
80 /// All test classes should derive from this class.
81 class HATest : public ::testing::Test {
82 public:
83 
84     /// @brief Constructor.
85     HATest();
86 
87     /// @brief Destructor.
88     virtual ~HATest();
89 
90     /// @brief Calls dhcp4_srv_configured callout to set IO service pointer.
91     void startHAService();
92 
93     /// @brief Runs IO service for a specified amount of time.
94     ///
95     /// @param ms number of milliseconds for which the IO service should be
96     /// run.
97     void runIOService(long ms);
98 
99     /// @brief Runs IO service until timeout occurs or until provided method
100     /// returns true.
101     ///
102     /// @param ms number of milliseconds for which the IO service should be
103     /// run.
104     /// @param stop_condition pointer to the function which returns true if
105     /// when the IO service should be stopped.
106     void runIOService(long ms, std::function<bool()> stop_condition);
107 
108     /// @brief Runs IO service in a thread.
109     ///
110     /// @return Shared pointer to the thread.
111     boost::shared_ptr<std::thread> runIOServiceInThread();
112 
113     /// @brief Executes commands while running IO service in a thread.
114     ///
115     /// @param commands pointer to a function to be executed while IO service
116     /// is run in thread.
117     void testSynchronousCommands(std::function<void()> commands);
118 
119 protected:
120 
121     /// @brief Signals that the IO service is running.
122     ///
123     /// @param running reference to the flag which is set to true when the
124     /// IO service starts running and executes this function.
125     /// @param mutex reference to the mutex used for synchronization.
126     /// @param condvar reference to condition variable used for synchronization.
127     void signalServiceRunning(bool& running, std::mutex& mutex,
128                               std::condition_variable& condvar);
129 
130 public:
131 
132     /// @brief Handler for timeout during runIOService invocation.
133     ///
134     /// @param [out] stop_flag set to true when the handler is invoked.
135     void stopIOServiceHandler(bool& stop_flag);
136 
137     /// @brief Return HA configuration with three servers in JSON format.
138     ///
139     /// @param ha_mode HA operation mode (default is load balancing).
140     /// @return Pointer to the unparsed configuration.
141     data::ConstElementPtr
142     createValidJsonConfiguration(const HAConfig::HAMode& ha_mode =
143                                  HAConfig::LOAD_BALANCING) const;
144 
145     /// @brief Return HA configuration with one primary and two backup
146     /// servers in the JSON format.
147     ///
148     /// @return Pointer to the unparsed configuration.
149     data::ConstElementPtr
150     createValidPassiveBackupJsonConfiguration() const;
151 
152     /// @brief Return HA configuration with three servers.
153     ///
154     /// @param ha_mode HA operation mode (default is load balancing).
155     /// @return Pointer to the parsed configuration.
156     HAConfigPtr createValidConfiguration(const HAConfig::HAMode& ha_mode =
157                                          HAConfig::LOAD_BALANCING) const;
158 
159     /// @brief Return passive-backup configuration.
160     ///
161     /// @return Pointer to the parsed configuration.
162     HAConfigPtr createValidPassiveBackupConfiguration() const;
163 
164     /// @brief Checks the status code and message against expected values.
165     ///
166     /// @param answer Element set containing an integer response and string
167     /// comment.
168     /// @param exp_status Status code against which to compare the status.
169     /// @param exp_txt Expected text (not checked if empty)
170     void checkAnswer(const isc::data::ConstElementPtr& answer,
171                      const int exp_status,
172                      const std::string& exp_txt = "");
173 
174     /// @brief Creates an identifier of arbitrary size with random values.
175     ///
176     /// This function is useful in generating random client identifiers and
177     /// HW addresses for load balancing tests.
178     ///
179     /// @param key_size Size of the generated random identifier.
180     std::vector<uint8_t> randomKey(const size_t key_size) const;
181 
182     /// @brief Generates simple DHCPv4 message.
183     ///
184     /// @param msg_type DHCPv4 message type to be created.
185     /// @param hw_address_seed value from which HW address will be generated.
186     /// @param client_id_seed value from which client identifier will be
187     /// generated.
188     /// @param secs value to be stored in the "secs" field of the DHCPv4 message.
189     ///
190     /// @return Pointer to the created message.
191     dhcp::Pkt4Ptr createMessage4(const uint8_t msg_type,
192                                  const uint8_t hw_address_seed,
193                                  const uint8_t client_id_seed,
194                                  const uint16_t secs) const;
195 
196     /// @brief Creates test DHCPv4 query instance.
197     ///
198     /// @param hw_address_text HW address to be included in the query. It is
199     /// used in load balancing.
200     ///
201     /// @return Pointer to the DHCPv4 query instance.
202     dhcp::Pkt4Ptr createQuery4(const std::string& hw_address_text) const;
203 
204     /// @brief Creates test DHCPv4 query instance.
205     ///
206     /// @param hw_address HW address to be included in the query. It is used
207     /// in load balancing.
208     /// @param client_id optional client identifier.
209     dhcp::Pkt4Ptr createQuery4(const std::vector<uint8_t>& hw_address,
210                                const std::vector<uint8_t>& client_id =
211                                std::vector<uint8_t>()) const;
212 
213     /// @brief Creates test DHCPv6 query instance.
214     ///
215     /// @param duid DUI to be included in the query. It is used in load balancing.
216     dhcp::Pkt6Ptr createQuery6(const std::vector<uint8_t>& duid) const;
217 
218     /// @brief Generates simple DHCPv6 message.
219     ///
220     /// @param msg_type DHCPv6 message type to be created.
221     /// @param duid value from which DUID will be generated.
222     /// @param elapsed_time value of the Elapsed Time option.
223     ///
224     /// @return Pointer to the created message.
225     dhcp::Pkt6Ptr createMessage6(const uint8_t msg_type,
226                                  const uint8_t duid_seed,
227                                  const uint16_t elapsed_time) const;
228 
229     /// @brief Sets the DHCP multi-threading configuration in staging SrvConfig.
230     ///
231     /// @param enable_multi_threading value that maps to enable-multi-threading.
232     /// @param thread_pool_size value that maps to thread-pool-size.
233     /// @param queue_size value that maps to queue-size.
234     void setDHCPMultiThreadingConfig(bool enable_multi_threading,
235                                      uint32_t thread_pool_size = 0,
236                                      uint32_t queue_size = 16);
237 
238     /// @brief Constructs JSON string for HA "multi-threading" element.
239     ///
240     /// Constructs a JSON string with the following content:
241     ///
242     /// ```
243     /// "multi-threading" {
244     ///     "enable-multi-threading": <bool>,
245     ///     "dedicated-http-listener": <bool>,
246     ///     "http-listener-threads": <int>,
247     ///     "http-client-threads": <int>
248     /// }"
249     /// ```
250     ///
251     /// @param enable_multi_threading value for enable-multi-threading.
252     /// @param http_dedicated_listener value for dedicated-http-listener.
253     /// @param http_listener_threads value for http-listener-threads
254     /// @param http_client_threads value for http-client-threads
255     ///
256     /// @return JSON string
257     std::string makeHAMtJson(bool enable_multi_threading,
258                              bool http_dedicated_listener,
259                              uint32_t http_listener_threads,
260                              uint32_t http_client_threads);
261 
262     /// @brief Creates test DHCPv6 query instance.
263     ///
264     /// @param duid_text DUID to be included in the query. It is used in load
265     /// balancing.
266     ///
267     /// @return Pointer to the DHCPv6 query instance.
268     dhcp::Pkt6Ptr createQuery6(const std::string& duid_text) const;
269 
270     /// @brief Pointer to the IO service used in the tests.
271     asiolink::IOServicePtr io_service_;
272 
273     /// @brief Object holding a state of the DHCP service.
274     dhcp::NetworkStatePtr network_state_;
275 };
276 
277 } // end of namespace isc::ha::test
278 } // end of namespace isc::ha
279 } // end of namespace isc
280