summaryrefslogtreecommitdiffstats
path: root/src/hooks/dhcp/high_availability/ha_service.h
blob: 288a03671fbbbdad9a9558721c4397cc1bccb804 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
// Copyright (C) 2018-2021 Internet Systems Consortium, Inc. ("ISC")
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.

#ifndef HA_SERVICE_H
#define HA_SERVICE_H

#include <communication_state.h>
#include <ha_config.h>
#include <ha_server_type.h>
#include <lease_update_backlog.h>
#include <query_filter.h>
#include <asiolink/asio_wrapper.h>
#include <asiolink/io_service.h>
#include <asiolink/tls_socket.h>
#include <cc/data.h>
#include <config/cmd_http_listener.h>
#include <dhcp/pkt4.h>
#include <http/response.h>
#include <dhcpsrv/lease.h>
#include <dhcpsrv/network_state.h>
#include <hooks/parking_lots.h>
#include <http/client.h>
#include <util/state_model.h>

#include <boost/noncopyable.hpp>
#include <boost/shared_ptr.hpp>
#include <functional>
#include <map>
#include <mutex>
#include <vector>

namespace isc {
namespace ha {

/// @brief High availability service.
///
/// This class derives from the @c util::StateModel and implements a
/// state machine for the high availability service in the Kea DHCP
/// server instance.
class HAService : public boost::noncopyable, public util::StateModel {
public:

    /// Finished heartbeat command.
    static const int HA_HEARTBEAT_COMPLETE_EVT = SM_DERIVED_EVENT_MIN + 1;

    /// Finished lease updates commands.
    static const int HA_LEASE_UPDATES_COMPLETE_EVT = SM_DERIVED_EVENT_MIN + 2;

    /// Lease database synchronization failed.
    static const int HA_SYNCING_FAILED_EVT = SM_DERIVED_EVENT_MIN + 3;

    /// Lease database synchronization succeeded.
    static const int HA_SYNCING_SUCCEEDED_EVT = SM_DERIVED_EVENT_MIN + 4;

    /// ha-maintenance-notify command received.
    static const int HA_MAINTENANCE_NOTIFY_EVT = SM_DERIVED_EVENT_MIN + 5;

    /// ha-maintenance-start command received.
    static const int HA_MAINTENANCE_START_EVT = SM_DERIVED_EVENT_MIN + 6;

    /// ha-maintenance-cancel command received.
    static const int HA_MAINTENANCE_CANCEL_EVT = SM_DERIVED_EVENT_MIN + 7;

    /// The heartbeat command failed after receiving ha-sync-complete-notify
    /// command from the partner.
    static const int HA_SYNCED_PARTNER_UNAVAILABLE_EVT = SM_DERIVED_EVENT_MIN + 8;

    /// Control result returned in response to ha-maintenance-notify.
    static const int HA_CONTROL_RESULT_MAINTENANCE_NOT_ALLOWED = 1001;

protected:

    /// @brief Callback invoked when request was sent and a response received
    /// or an error occurred.
    ///
    /// The first argument indicates if the operation passed (when true).
    /// The second argument holds error message.
    /// The third argument holds control status returned.
   typedef std::function<void(const bool, const std::string&, const int)> PostRequestCallback;

    /// @brief Callback invoked when lease database synchronization is complete.
    ///
    /// The first argument indicates if the operation passed (when true).
    /// The second argument holds error message.
    /// The third argument indicates whether the synchronization resulted in
    /// disabling DHCP service on the partner server and has to be
    /// re-enabled.
    typedef std::function<void(const bool, const std::string&, const bool)> PostSyncCallback;

public:

    /// @brief Constructor.
    ///
    /// It clears the DHCP state using origin HA internal command and starts the
    /// state model in waiting state.  Creates and starts the client and the
    /// listener (if one).
    ///
    /// @param io_service Pointer to the IO service used by the DHCP server.
    /// @param config Parsed HA hook library configuration.
    /// @param network_state Object holding state of the DHCP service
    /// (enabled/disabled).
    /// @param server_type Server type, i.e. DHCPv4 or DHCPv6 server.
    HAService(const asiolink::IOServicePtr& io_service,
              const dhcp::NetworkStatePtr& network_state,
              const HAConfigPtr& config,
              const HAServerType& server_type = HAServerType::DHCPv4);

    /// @brief Destructor.
    ///
    /// Stops the client and listener (if one). It clears the DHCP
    /// state using origin HA internal command.
    virtual ~HAService();

    /// @brief Returns HA server type used in object construction.
    HAServerType getServerType() const {
        return (server_type_);
    }

    /// @brief Defines events used by the HA service.
    virtual void defineEvents();

    /// @brief Verifies events used by the HA service.
    virtual void verifyEvents();

    /// @brief Defines states of the HA service.
    virtual void defineStates();

    /// @brief Handler for the "backup" state.
    ///
    /// This is the normal operation state for a backup server. Only
    /// the backup server can be transitioned to this state. The
    /// DHCP service is disabled in this state and the server merely
    /// receives lease updates from the active servers. The backup
    /// server may be manually instructed to enable DHCP service and
    /// serve selected scopes, e.g. when both primary and secondary
    /// (or standby) servers are down.
    ///
    /// This handler disables DHCP service on the first pass. It is
    /// no-op during all subsequent passes.
    void backupStateHandler();

    /// @brief Handler for the "communication-recovery" state.
    ///
    /// This is a handler invoked for the active servers running in
    /// the load-balancing mode. A primary or secondary server may
    /// transition into this state when it detects that the
    /// communication with its partner is failing.
    ///
    /// If the communication is resumed before the server transitions
    /// to the partner-down state, the server will transition back to
    /// the load-balancing state.
    ///
    /// In the communication-recovery state the server remains
    /// responsive to the DHCP clients but does not send lease updates
    /// to the partner. Instead, it collects the lease updates and
    /// tries to send them in bulk when it returns to the
    /// load-balancing state.
    ///
    /// Transitioning into this state is only enabled when delayed-updates-limit
    /// is non-zero.
    ///
    /// A server running in the hot-standby mode is never allowed to
    /// enter this state. In this mode, even a short communication failure
    /// may cause the primary server to transition to the partner-down
    /// state. Consequently, two servers would be responding to
    /// DHCP queries, possibly allocating the same addresses to two
    /// different clients. This doesn't occur in load-balancing mode
    /// because the address pools are required to be split.
    void communicationRecoveryHandler();

    /// @brief Handler for the "hot-standby" and "load-balancing"
    /// states.
    ///
    /// This is a handler invoked for the servers running in the
    /// hot-standby or load-balancing mode, both for the primary
    /// and the standby (or secondary) server.
    ///
    /// In the hot-standby mode, the primary server responds to all
    /// DHCP queries from the clients. The standby server receives
    /// lease updates from the primary, but it doesn't respond to any
    /// DHCP queries. Both servers exchange heartbeats to monitor
    /// each other states. If any of the servers detects a failure
    /// of its partner, it transitions to the "partner-down" state.
    ///
    /// In the load-balancing mode, both servers respond to the DHCP
    /// queries and exchange the heartbeats and lease updates.
    /// If any of the servers detects a failure of its partner,
    /// it transitions to the "partner-down" state.
    ///
    /// If any of the servers in the "hot-standby" or "load-balancing"
    /// state detects that its partner is in the "partner-down" state,
    /// the server transitions to the "waiting" state. Such situation
    /// may occur if the Control Agent of this server crashes but the
    /// DHCP daemon continues to run. The partner will transition to
    /// the "partner-down" state if the failure detection algorithm
    /// (based on "secs" field or "elapsed time" option monitoring)
    /// and this server is considered to be offline based solely on
    /// the fact that it doesn't respond to heartbeats.
    void normalStateHandler();

    /// @brief Handler for the "in-maintenance" state.
    ///
    /// This is a handler invoked when one of the servers detected that
    /// its partner is in the "partner-in-maintenance" state. The server
    /// in this state is awaiting the shutdown by the administrator.
    /// The administrator shuts down the server to perform some planned
    /// maintenance. Meanwhile, the partner in the "partner-in-maintenance"
    /// state responds to all DHCP queries. The server in the
    /// "in-maintenance" state responds to no DHCP queries.
    void inMaintenanceStateHandler();

    /// @brief Handler for "partner-down" state.
    ///
    /// This is a handler invoked for the server which detected a failure
    /// of its partner. The partner was not responding to heartbeats and
    /// did not respond to a number of DHCP queries directed to it. In
    /// some configurations, the server may transition to this state when
    /// the server is not responding to the heartbeats, without checking
    /// whether it responds to DHCP queries ("max-unacked-clients" parameter
    /// is set to 0).
    ///
    /// In the "partner-down" state the server responds to all DHCP queries,
    /// i.e. the queries it would normally respond to and to the queries
    /// to which its partner would respond.
    ///
    /// The backup server would never transition to this state.
    ///
    /// The server will transition from the "partner-down" state to the
    /// "load-balancing" or "hot-standby" state if its partner is in the
    /// "ready" state. In this state, the partner indicates that it has
    /// synchronized its database and is ready to enable its DHCP service.
    ///
    /// If this server finds that the partner is in an unexpected state,
    /// i.e. "load-balancing", "hot-standby" or "partner-down", it transitions
    /// to the "waiting" state to try to resolve the conflict with the partner.
    void partnerDownStateHandler();

    /// @brief Handler for "partner-in-maintenance" state.
    ///
    /// This is a handler invoked for the server which was administratively
    /// transitioned to the "partner-in-maintenance" state. This is the case
    /// when the partner needs to be shutdown for some planned maintenance.
    ///
    /// The server receiving ha-maintenance-start command transitions to this
    /// state. It sends the ha-maintenance-notify command to the partner to cause
    /// the partner to stop responding to the DHCP queries. Next, this server
    /// starts responding to all DHCP queries. This allows the server
    /// administrator to safely shutdown the partner as it is no longer
    /// responsible for any portion of the DHCP traffic.
    ///
    /// The server in the "partner-in-maintenance" state remains in this state
    /// until the first unsuccessful lease update, ha-heartbeat or any
    /// other command send to the partner due to the issues with communication.
    /// In that case the server assumes that the partner has been shutdown
    /// and transitions to the "partner-down" state in which it still responds
    /// to all DHCP queries but doesn't attempt to send lease updates to the
    /// offline partner.
    void partnerInMaintenanceStateHandler();

    /// @brief Handler for "passive-backup" state.
    ///
    /// This handler is invoked for the server entering the "passive-backup"
    /// state. The primary server enters this state in the "passive-backup"
    /// mode of operation in which there is one server responding to the
    /// DHCP queries and zero, one or more backup servers which receive
    /// lease updates from this server.
    void passiveBackupStateHandler();

    /// @brief Handler for "ready" state.
    ///
    /// This a handler invoked for the server which finished synchronizing
    /// its lease database with the partner and is indicating readiness to
    /// start normal operation, i.e. load balancing or hot standby. The
    /// partner in the "partner-down" state will transition to the
    /// "load-balancing" or "hot-standby" state. The "ready" server will
    /// also transition to one of these states following the transition
    /// of the partner.
    ///
    /// If both servers appear to be in the "ready" state, the primary
    /// server transitions to the "load-balancing" or "hot-standby" state
    /// first.
    ///
    /// The server in the "ready" state is not responding to the DHCP queries.
    void readyStateHandler();

    /// @brief Handler for "syncing" state.
    ///
    /// This is a handler invoked for the server in the "syncing" state.
    /// The server in this state is trying to retrieve leases from
    /// the partner's database and update its local database. Every
    /// primary, secondary and standby server must transition via this
    /// state to retrieve up to date lease information from the active
    /// partner. If the partner is offline the server will eventually
    /// transition to the "partner-down" state without synchronizing
    /// the lease database.
    ///
    /// The lease database synchronization is performed synchronously,
    /// i.e. the handler doesn't return until the synchronization completes
    /// or a communication failure occurs.
    ///
    /// The server in the "syncing" state is not responding to the DHCP queries.
    void syncingStateHandler();

    /// @brief Handler for "terminated" state.
    ///
    /// This is a handler invoked for the server in the "terminated" state.
    /// This indicates that the HA service is disabled, typically as a result
    /// of an unrecoverable error such as detecting that clocks skew between
    /// the active HA servers being too large. This situation requires
    /// manual intervention of an administrator. When the problem is corrected,
    /// the HA service needs to be restarted.
    ///
    /// @note Currently, restarting the HA service requires restarting the
    /// DHCP server. In the future, we will provide a command to restart
    /// the HA service.
    ///
    /// The server in the "terminated" state will respond to DHCP clients
    /// as if it was in a hot-standby or load-balancing state. However, it will
    /// neither send nor receive lease updates. It also won't send heartbeats
    /// to the partner.
    void terminatedStateHandler();

    /// @brief Handler for "waiting" state.
    ///
    /// This is a handler invoked for the server in the "waiting" state.
    /// This is the first state of every server after its startup. The
    /// server initiates a heartbeat to learn the state of its partner.
    /// If the partner is operating (e.g. is in the "partner-down" state),
    /// the server will transition to the "syncing" state to fetch
    /// lease information from the partner. If leases synchronization is
    /// administratively disabled with 'sync-leases' parameter, the server
    /// will transition directly to the "ready" state. If both servers are
    /// in the "waiting" state the primary transitions to the "syncing" or
    /// "ready" state first. If the partner is in the "syncing" state,
    /// this server will remain in the "waiting" state until the partner
    /// completes synchronization.
    ///
    /// If the server starts, but the partner appears to be offline, the
    /// server transitions to the "partner-down" state.
    ///
    /// A backup server transitions from the "waiting" to the "backup"
    /// state directly.
    ///
    /// The server in the "waiting" state is not responding to the DHCP
    /// queries.
    void waitingStateHandler();

    /// @brief Returns last known state of the partner.
    /// @ref CommunicationState::getPartnerState.
    int getPartnerState() const {
        return (communication_state_->getPartnerState());
    }

protected:

    /// @brief Transitions to a desired state and logs it.
    ///
    /// @param state the new value to assign to the current state.
    void verboseTransition(const unsigned state);

    /// @brief Returns normal operation state for the current configuration.
    ///
    /// @return "load-balancing" for active servers in load balancing mode,
    /// "hot-standby" for active servers in hot-standby mode, "backup" for
    /// backup servers and "passive-backup" for primary server in the
    /// "passive-backup" mode.
    int getNormalState() const;

public:

    /// @brief Unpauses the HA state machine with logging.
    ///
    /// It un-pauses the state machine if it is paused and logs an informational
    /// message. It doesn't log the message if the state machine is not paused.
    ///
    /// @return true if the state machine was unpaused, false if the state
    /// machine was not paused when this method was invoked.
    bool unpause();

protected:

    /// @brief Logs if the server is paused in the current state.
    ///
    /// This method is internally called by the state handlers upon
    /// entry to a new state.
    void conditionalLogPausedState() const;

public:

    /// @brief Instructs the HA service to serve default scopes.
    ///
    /// This method is mostly useful for unit testing. The scopes need to be
    /// enabled to test @c inScope methods invoked via @c HAImpl class.
    void serveDefaultScopes();

    /// @brief Checks if the DHCPv4 query should be processed by this server.
    ///
    /// It also associates the DHCPv4 query with required classes appropriate
    /// to the server that should process the packet and increments counters
    /// of unanswered DHCP queries when in communications interrupted state.
    ///
    /// @param [out] query4 pointer to the DHCPv4 query received. A client class
    /// will be appended to this query instance, appropriate for the server to
    /// process this query, e.g. "HA_server1" if the "server1" should process
    /// the query etc.
    ///
    /// @return true if DHCPv4 query should be processed by this server instance,
    /// false otherwise.
    bool inScope(dhcp::Pkt4Ptr& query4);

    /// @brief Checks if the DHCPv6 query should be processed by this server.
    ///
    /// It also associates the DHCPv6 query with required classes appropriate
    /// to the server that should process the packet and increments counters
    /// of unanswered DHCP queries when in communications interrupted state.
    ///
    /// @param [out] query6 pointer to the DHCPv6 query received. A client class
    /// will be appended to this query instance, appropriate for the server to
    /// process this query, e.g. "HA_server1" if the "server1" should process
    /// the query etc.
    ///
    /// @return true if DHCPv6 query should be processed by this server instance,
    /// false otherwise.
    bool inScope(dhcp::Pkt6Ptr& query6);

private:

    /// @brief Checks if the DHCP query should be processed by this server.
    ///
    /// This is a generic implementation of the public @c inScope method
    /// variants.
    ///
    /// @tparam QueryPtrType type of the pointer to the DHCP query.
    /// @param [out] query6 pointer to the DHCP query received. A client class
    /// will be appended to this query instance, appropriate for the server to
    /// process this query, e.g. "HA_server1" if the "server1" should process
    /// the query etc.
    ///
    /// @return true if DHCP query should be processed by this server instance,
    /// false otherwise.
    template<typename QueryPtrType>
    bool inScopeInternal(QueryPtrType& query);

public:

    /// @brief Enables or disables network state depending on the served scopes.
    ///
    /// This method is called in each HA state to enable/disable DHCP service
    /// as appropriate for that state.
    void adjustNetworkState();

protected:

    /// @brief Indicates if the server should transition to the partner down
    /// state.
    ///
    /// It indicates that the server should transition to the partner down
    /// state when the communications is interrupted (over the control channel)
    /// and the partner is not answering DHCP queries in the load balancing
    /// case and in the hot standby case, when this server is a secondary.
    ///
    /// In the hot standby case, when the server is primary, the communications
    /// interrupted is enough to transition to the partner down state.
    ///
    /// @return true if the server should transition to the partner down state,
    /// false otherwise.
    bool shouldPartnerDown() const;

    /// @brief Indicates if the server should transition to the terminated
    /// state as a result of high clock skew.
    ///
    /// It indicates that the server should transition to the terminated
    /// state because of the clock skew being too high. If the clock skew is
    /// is higher than 30 seconds but lower than 60 seconds this method
    /// only logs a warning. In case, the clock skew exceeds 60 seconds, this
    /// method logs a warning and returns true.
    ///
    /// @return true if the server should transition to the terminated state,
    /// false otherwise.
    bool shouldTerminate() const;

    /// @brief Convenience method checking if the current state is a result
    /// of canceling the maintenance.
    ///
    /// @return true if the maintenance was canceled, false otherwise.
    bool isMaintenanceCanceled() const;

    /// @brief Indicates if the partner's state is invalid.
    ///
    /// Partner's state is invalid from the local server's perspective when the
    /// remote server can't transition to this state if the configuration is
    /// consistent with the local server's configuration.
    ///
    /// The following cases are currently checked:
    /// - partner in communication-recovery state but this server not in the
    ///   load balancing mode,
    /// - partner in the hot-standby state but this server not in the hot standby
    ///   mode,
    /// - partner in the load-balancing state but this server not in the
    ///   load balancing mode.
    ///
    /// @return true if the partner's state is invalid, false otherwise.
    bool isPartnerStateInvalid() const;

public:

    /// @brief Schedules asynchronous IPv4 leases updates.
    ///
    /// This method schedules asynchronous lease updates as a result of the
    /// "leases4_committed" callout. The lease updates are transmitted over
    /// HTTP to the peers specified in the configuration (except self).
    /// If the server is in the partner-down state the lease updates are not
    /// sent to the partner but they are sent to all backup servers.
    /// In other states in which the server responds to DHCP queries, the
    /// lease updates are sent to all servers. The scheduled lease updates
    /// are performed after the callouts return. The server parks the
    /// processed DHCP packet and runs IO service shared between the server
    /// and the hook library.
    ////
    /// If the lease update to the partner (primary, secondary or standby)
    /// fails, the parked packet is dropped. If the lease update to any of
    /// the backup server fails, an error message is logged but the DHCP
    /// packet is not dropped.
    ///
    /// This method must be called only if there is at least one lease
    /// altered.
    ///
    /// @param query Pointer to the processed DHCP client message.
    /// @param leases Pointer to a collection of the newly allocated or
    /// updated leases.
    /// @param deleted_leases Pointer to a collection of the released leases.
    /// @param [out] parking_lot Pointer to the parking lot handle available
    /// for the "leases4_committed" hook point. This is where the DHCP client
    /// message is parked. This method calls @c unpark() on this object when
    /// the asynchronous updates are completed.
    ///
    /// @return Number of peers to whom lease updates have been scheduled
    /// to be sent and from which we expect a response prior to unparking
    /// the packet and sending a response to the DHCP client.
    size_t asyncSendLeaseUpdates(const dhcp::Pkt4Ptr& query,
                                 const dhcp::Lease4CollectionPtr& leases,
                                 const dhcp::Lease4CollectionPtr& deleted_leases,
                                 const hooks::ParkingLotHandlePtr& parking_lot);

    /// @brief Schedules asynchronous IPv6 lease updates.
    ///
    /// This method schedules asynchronous IPv6 lease updates as a result of the
    /// "leases6_committed" callout. It works analogously to the IPv4 version of
    /// this function.
    ///
    /// @param query Pointer to the processed DHCP client message.
    /// @param leases Pointer to a collection of the newly allocated or
    /// updated leases.
    /// @param deleted_leases Pointer to a collection of the released leases.
    /// @param [out] parking_lot Pointer to the parking lot handle available
    /// for the "leases6_committed" hook point. This is where the DHCP client
    /// message is parked. This method calls @c unpark() on this object when
    /// the asynchronous updates are completed.
    ///
    /// @return Number of peers to whom lease updates have been scheduled
    /// to be sent and from which we expect a response prior to unparking
    /// the packet and sending a response to the DHCP client.
    size_t asyncSendLeaseUpdates(const dhcp::Pkt6Ptr& query,
                                 const dhcp::Lease6CollectionPtr& leases,
                                 const dhcp::Lease6CollectionPtr& deleted_leases,
                                 const hooks::ParkingLotHandlePtr& parking_lot);

protected:

    /// @brief Asynchronously sends lease update to the peer.
    ///
    /// @param query Pointer to the DHCP client's query.
    /// @param config Pointer to the configuration of the server to which the
    /// command should be sent.
    /// @param command Pointer to the command to be sent.
    /// @param [out] parking_lot Parking lot where the query is parked.
    /// This method uses this handle to unpark the packet when all asynchronous
    /// requests have been completed.
    /// @tparam QueryPtrType Type of the pointer to the DHCP client's message,
    /// i.e. Pkt4Ptr or Pkt6Ptr.
    /// @throw Unexpected when an unexpected error occurs.
    template<typename QueryPtrType>
    void asyncSendLeaseUpdate(const QueryPtrType& query,
                              const HAConfig::PeerConfigPtr& config,
                              const data::ConstElementPtr& command,
                              const hooks::ParkingLotHandlePtr& parking_lot);

    /// @brief Log failed lease updates.
    ///
    /// Logs failed lease updates included in the "failed-deleted-leases"
    /// and/or "failed-leases" carried in the response to the
    /// @c lease6-bulk-apply command.
    ///
    /// @param query Pointer to the DHCP client's query.
    /// @param args Arguments of the response. It may be null, in which
    /// case the function simply returns.
    void logFailedLeaseUpdates(const dhcp::PktPtr& query,
                               const data::ConstElementPtr& args) const;

    /// @brief Checks if the lease updates should be sent as result of leases
    /// allocation or release.
    ///
    /// This method checks if the lease updates should be sent by the server
    /// while this server is in the given state. Note that the backup server
    /// will never send lease updates.
    ///
    /// @param peer_config pointer to the configuration of the peer to which
    /// the updates are to be sent.
    /// @return true if the server should send lease updates, false otherwise.
    bool shouldSendLeaseUpdates(const HAConfig::PeerConfigPtr& peer_config) const;

    /// @brief Checks if the lease updates should be queued.
    ///
    /// If lease updates should be sent to the partner but the server is in
    /// the communication-recovery state (temporarily unavailable) the lease
    /// updates should be queued and later sent when the communication is
    /// re-established. This function checks if the server is in the state
    /// in which lease updates should be queued.
    ///
    /// @param peer_config pointer to the configuration of the peer to which
    /// the updates are to be sent.
    /// @return true if the server should queue lease updates, false otherwise.
    bool shouldQueueLeaseUpdates(const HAConfig::PeerConfigPtr& peer_config) const;

public:

    /// @brief Processes ha-heartbeat command and returns a response.
    ///
    /// This method processes a ha-heartbeat command sent by a peer. This
    /// command is sent periodically to the server to detect its state. The
    /// servers use the heartbeat mechanism to detect peers' failures and to
    /// synchronize their operations when they start up after the failure or
    /// a restart.
    ///
    /// The ha-heartbeat command takes no arguments. The response contains
    /// a server state, served scopes and timestamp in the following format:
    ///
    /// @code
    /// {
    ///     "arguments": {
    ///         "date-time": "Thu, 01 Feb 2018 21:18:26 GMT",
    ///         "scopes": [ "server1" ],
    ///         "state": "waiting"
    ///     },
    ///     "result": 0,
    ///     "text": "HA peer status returned."
    /// }
    /// @endcode
    ///
    /// @return Pointer to the response to the heartbeat.
    data::ConstElementPtr processHeartbeat();

    /// @brief Processes status-get command and returns a response.
    ///
    /// @c HAImpl::commandProcessed calls this to add information about the
    /// HA servers status into the status-get response.
    data::ConstElementPtr processStatusGet() const;

    /// @brief Processes ha-reset command and returns a response.
    ///
    /// This method processes ha-reset command which instructs the server to
    /// transition to the waiting state. A partner may send this command when
    /// the communication is re-established between the servers in the
    /// communication-recovery state and full lease database synchronization is
    /// required. This command may also be sent by an operator if the server's
    /// state is invalid and the reset operation may help correct the situation.
    ///
    /// The ha-reset takes no arguments.
    ///
    /// @return Pointer to a response to the ha-reset command.
    data::ConstElementPtr processHAReset();

protected:

    /// @brief Starts asynchronous heartbeat to a peer.
    void asyncSendHeartbeat();

    /// @brief Schedules asynchronous heartbeat to a peer if it is not scheduled.
    ///
    /// The heartbeat will be sent according to the value of the heartbeat-delay
    /// setting in the HA configuration. This is one shot heartbeat. The callback
    /// will reschedule it.
    void scheduleHeartbeat();

    /// @brief Unconditionally starts one heartbeat to a peer.
    void startHeartbeat();

    /// @brief Schedules asynchronous "dhcp-disable" command to the specified
    /// server.
    ///
    /// @param http_client reference to the client to be used to communicate
    /// with the other server.
    /// @param server_name name of the server to which the command should be
    /// sent.
    /// @param max_period maximum number of seconds for which the DHCP service
    /// should be disabled.
    /// @param post_request_action pointer to the function to be executed when
    /// the request is completed.
    void asyncDisableDHCPService(http::HttpClient& http_client,
                                 const std::string& server_name,
                                 const unsigned int max_period,
                                 PostRequestCallback post_request_action);

    /// @brief Schedules asynchronous "dhcp-enable" command to the specified
    /// server.
    ///
    /// @param http_client reference to the client to be used to communicate
    /// with the other server.
    /// @param server_name name of the server to which the command should be
    /// sent.
    /// @param post_request_action pointer to the function to be executed when
    /// the request is completed.
    void asyncEnableDHCPService(http::HttpClient& http_client,
                                const std::string& server_name,
                                PostRequestCallback post_request_action);

    /// @brief Disables local DHCP service.
    void localDisableDHCPService();

    /// @brief Enables local DHCP service.
    void localEnableDHCPService();

    /// @brief Asynchronously reads leases from a peer and updates local
    /// lease database.
    ///
    /// This method asynchronously sends lease4-get-page command to fetch
    /// leases from the HA peer database. When the response is received, the
    /// callback function iterates over the returned leases and inserts those
    /// that are not present in the local database and replaces any existing
    /// leases if the fetched lease instance is newer (based on cltt) than
    /// the instance in the local lease database.
    ///
    /// If there is an error while inserting or updating any of the leases
    /// a warning message is logged and the process continues for the
    /// remaining leases.
    ///
    /// This method variant uses default HTTP client for communication.
    void asyncSyncLeases();

    /// @brief Asynchronously reads leases from a peer and updates local
    /// lease database using a provided client instance.
    ///
    /// This method first sends dhcp-disable command to the server from which
    /// it will be fetching leases to disable its DHCP function while database
    /// synchronization is in progress. If the command is successful, it then
    /// sends lease4-get-page command to fetch a page of leases from the
    /// partner's database. Depending on the configured page size, it may
    /// be required to send multiple lease4-get-page or lease6-get-page
    /// commands to fetch all leases. If the lease database is large,
    /// the database synchronization may even take several minutes.
    /// Therefore, dhcp-disable command is sent prior to fetching each page,
    /// in order to reset the timeout for automatic re-enabling of the
    /// DHCP service on the remote server. Such timeout must only occur
    /// if there was no communication from the synchronizing server for
    /// longer period of time. If the synchronization is progressing the
    /// timeout must be deferred.
    ///
    /// The @c asyncSyncLeases method calls itself (recurses) when the previous
    /// @c lease4-get-page or @c lease6-get-page command has completed
    /// successfully. If the last page of leases was fetched or if any
    /// error occurred, the synchronization is terminated and the
    /// @c post_sync_action callback is invoked.
    ///
    /// The last parameter passed to the @c post_sync_action callback indicates
    /// whether this server has successfully disabled DHCP service on
    /// the partner server at least once. If that's the case, the DHCP
    /// service must be re-enabled by sending dhcp-enable command. This
    /// is done in the @c HAService::synchronize method.
    ///
    /// If there is an error while inserting or updating any of the leases
    /// a warning message is logged and the process continues for the
    /// remaining leases.
    ///
    /// @param http_client reference to the client to be used to communicate
    /// with the other server.
    /// @param server_name name of the server to fetch leases from.
    /// @param max_period maximum number of seconds to disable DHCP service
    /// @param last_lease Pointer to the last lease returned on the previous
    /// page of leases. This lease is used to set the value of the "from"
    /// parameter in the @c lease4-get-page and @c lease6-get-page commands. If this
    /// command is sent to fetch the first page, the @c last_lease parameter
    /// should be set to null.
    /// @param post_sync_action pointer to the function to be executed when
    /// lease database synchronization is complete. If this is null, no
    /// post synchronization action is invoked.
    /// @param dhcp_disabled Boolean flag indicating if the remote DHCP
    /// server is disabled. This flag propagates down to the
    /// @c post_sync_action to indicate whether the DHCP service has to
    /// be enabled after the leases synchronization.
    void asyncSyncLeases(http::HttpClient& http_client,
                         const std::string& server_name,
                         const unsigned int max_period,
                         const dhcp::LeasePtr& last_lease,
                         PostSyncCallback post_sync_action,
                         const bool dhcp_disabled = false);

    /// @brief Implements fetching one page of leases during synchronization.
    ///
    /// This method implements the actual lease fetching from the partner
    /// and synchronization of the database. It excludes sending @c dhcp-disable
    /// command. This command is sent by @c HAService::asyncSyncLeases.
    ///
    /// When the page of leases is successfully synchronized, this method
    /// will call @c HAService::asyncSyncLeases to schedule synchronization of
    /// the next page of leases.
    ///
    /// @param http_client reference to the client to be used to communicate
    /// with the other server.
    /// @param server_name name of the server to fetch leases from.
    /// @param max_period maximum number of seconds to disable DHCP service
    /// @param last_lease Pointer to the last lease returned on the previous
    /// page of leases. This lease is used to set the value of the "from"
    /// parameter in the lease4-get-page and lease6-get-page commands. If this
    /// command is sent to fetch the first page, the @c last_lease parameter
    /// should be set to null.
    /// @param post_sync_action pointer to the function to be executed when
    /// lease database synchronization is complete. If this is null, no
    /// post synchronization action is invoked.
    /// @param dhcp_disabled Boolean flag indicating if the remote DHCP
    /// server is disabled. This flag propagates down to the
    /// @c post_sync_action to indicate whether the DHCP service has to
    /// be enabled after the leases synchronization.
    void asyncSyncLeasesInternal(http::HttpClient& http_client,
                                 const std::string& server_name,
                                 const unsigned int max_period,
                                 const dhcp::LeasePtr& last_lease,
                                 PostSyncCallback post_sync_action,
                                 const bool dhcp_disabled);

public:

    /// @brief Processes ha-sync command and returns a response.
    ///
    /// This method processes ha-sync command. It instructs the server
    /// to disable the DHCP service on the HA peer, fetch all leases from
    /// the peer and update the local lease database. Leases synchronization
    /// is usually performed automatically by the server which starts up for
    /// the first time or after a failure. However, the ha-sync command can
    /// also be triggered manually by the server administrator to force
    /// synchronization of the lease database in cases when manual recovery
    /// is required. One of the possible cases is when the lease database has
    /// to be recovered from the backup server, e.g. when both primary and
    /// secondary (or standby) servers have crashed.
    ///
    /// @param server_name name of the server to fetch leases from.
    /// @param max_period maximum number of seconds to disable DHCP service
    /// of the peer. This value is used in dhcp-disable command issued to
    /// the peer before the lease4-get-page command.
    ///
    /// @return Pointer to the response to the ha-sync command.
    data::ConstElementPtr processSynchronize(const std::string& server_name,
                                             const unsigned int max_period);

protected:

    /// @brief Synchronizes lease database with a partner.
    ///
    /// It instructs the server to disable the DHCP service on the HA peer,
    /// fetch all leases from the peer and update the local lease database.
    /// It sends ha-sync-complete-notify command to the partner when the
    /// synchronization completes successfully. If the partner does not
    /// support this command, it sends dhcp-enable command to enable
    /// the DHCP service on the partner.
    ///
    /// This method creates its own instances of the HttpClient and IOService and
    /// invokes IOService::run().
    ///
    /// @param [out] status_message status message in textual form.
    /// @param server_name name of the server to fetch leases from.
    /// @param max_period maximum number of seconds to disable DHCP service
    /// of the peer. This value is used in dhcp-disable command issued to
    /// the peer before the lease4-get-page command.
    ///
    /// @return Synchronization result according to the status codes returned
    /// in responses to control commands.
    int synchronize(std::string& status_message, const std::string& server_name,
                    const unsigned int max_period);

    /// @brief Sends lease updates from backlog to partner asynchronously.
    ///
    /// This method checks if there are any outstanding DHCPv4 or DHCPv6 leases
    /// in the backlog and schedules asynchronous sends of these leases. In
    /// DHCPv6 case it sends a single lease6-bulk-apply command with all
    /// outstanding leases. In DHCPv4 case, it sends lease4-update or lease4-delete
    /// commands recursively (when one lease update completes successfully it
    /// schedules sending next lease update).
    ///
    /// If there are no lease updates in the backlog it calls @c post_request_action
    /// callback.
    ///
    /// This method is called from @c sendLeaseUpdatesFromBacklog.
    ///
    /// @param http_client reference to the HTTP client to be used for communication.
    /// @param remote_config pointer to the remote server's configuration.
    /// @param post_request_action callback to be invoked when the operation
    /// completes. It can be used for handling errors.
    void asyncSendLeaseUpdatesFromBacklog(http::HttpClient& http_client,
                                          const HAConfig::PeerConfigPtr& remote_config,
                                          PostRequestCallback post_request_action);

    /// @brief Attempts to send all lease updates from the backlog synchronously.
    ///
    /// This method is called upon exiting communication-recovery state and before
    /// entering the load-balancing state. It ensures that all outstanding lease
    /// updates are sent to the partner before the server can continue normal
    /// operation in the load-balancing state. In order to prevent collisions
    /// between new allocations and outstanding updates this method is synchronous.
    ///
    /// This method creates its own instances of the HttpClient and IOService and
    /// invokes IOService::run().
    ///
    /// @return boolean value indicating that the lease updates were delivered
    /// successfully (when true) or unsuccessfully (when false).
    bool sendLeaseUpdatesFromBacklog();

    /// @brief Sends ha-reset command to partner asynchronously.
    ///
    /// @param http_client reference to the HTTP client to be used for communication.
    /// @param remote_config pointer to the remote server's configuration.
    /// @param post_request_action callback to be invoked when the operation
    /// completes. It can be used for handling errors.
    void asyncSendHAReset(http::HttpClient& http_client,
                          const HAConfig::PeerConfigPtr& remote_config,
                          PostRequestCallback post_request_action);

    /// @brief Sends ha-reset command to partner synchronously.
    ///
    /// This method attempts to send ha-reset command to the active partner
    /// synchronously. It may be invoked when the communication with the partner
    /// is re-established after temporary failure. It causes the partner to
    /// transition the partner to the waiting state. This effectively means that
    /// the partner will synchronize its lease database with this server.
    ///
    /// This method creates its own instances of the HttpClient and IOService and
    /// invokes IOService::run().
    ///
    /// @return true if the command was sent successfully, false otherwise.
    bool sendHAReset();

public:

    /// @brief Processes ha-scopes command and returns a response.
    ///
    /// @param scopes vector of scope names to be enabled.
    ///
    /// @return Pointer to the response to the ha-scopes command.
    data::ConstElementPtr processScopes(const std::vector<std::string>& scopes);

    /// @brief Processes ha-continue command and returns a response.
    ///
    /// @return Pointer to the response to the ha-continue command.
    data::ConstElementPtr processContinue();

    /// @brief Processes ha-maintenance-notify command and returns a response.
    ///
    /// This command attempts to transition the server to the in-maintenance state
    /// if the cancel flag is set to false. Such transition is not allowed if
    /// the server is currently in one of the following states:
    /// - backup: because maintenance is not supported for backup servers,
    /// - partner-in-maintenance: because only one server is in maintenance while
    ///   the partner must be in partner-in-maintenance state,
    /// - terminated: because the only way to resume HA service is by shutting
    ///   down the server, fixing the clock skew and restarting.
    ///
    /// If the cancel flag is set to true, the server will be transitioned from
    /// the in-maintenance state to the previous state it was in before entering
    /// the in-maintenance state.
    ///
    /// @param cancel boolean value indicating if the maintenance is being
    /// canceled with this operation. If it is set to false the maintenance
    /// is being started.
    ///
    /// @return Pointer to the response to the ha-maintenance-notify.
    data::ConstElementPtr processMaintenanceNotify(const bool cancel);

    /// @brief Processes ha-maintenance-start command and returns a response.
    ///
    /// The server receiving this command will try to send the
    /// ha-maintenance-notify command to the partner to instruct the partner
    /// to transition to the in-maintenance state. In this state the partner will
    /// not respond to any DHCP queries. Next, this server will transition to
    /// the partner-in-maintenance state and therefore will start responding
    /// to all DHCP queries. If the partner responds to the ha-maintenance-notify
    /// with an error, this server won't transition to the partner-in-maintenance
    /// state and signal an error to the caller. If the partner is unavailable,
    /// this server will directly transition to the partner-down state.
    ///
    /// This method creates its own instances of the HttpClient and IOService and
    /// invokes IOService::run().
    ///
    /// @return Pointer to the response to the ha-maintenance-start.
    data::ConstElementPtr processMaintenanceStart();

    /// @brief Processes ha-maintenance-cancel command and returns a response.
    ///
    /// The server receiving this command will try to revert the partner's
    /// state from the in-maintenance to the previous state, and also it will
    /// try to revert its own state from the partner-in-maintenance to the
    /// previous state. It effectively means canceling the request for
    /// maintenance signaled with the ha-maintenance-start command.
    ///
    /// In some cases canceling the maintenance is no longer possible, e.g.
    /// if the server has already got into the partner-down state. Generally,
    /// canceling the maintenance is only possible if this server is in the
    /// partner-in-maintenance state and the partner is in the in-maintenance
    /// state.
    ///
    /// @return Pointer to the response to the ha-maintenance-cancel.
    data::ConstElementPtr processMaintenanceCancel();

    /// @brief Check client and(or) listener current thread permissions to
    /// perform thread pool state transition.
    ///
    /// @throw MultiThreadingInvalidOperation if the state transition is done on
    /// any of the worker threads.
    void checkPermissionsClientAndListener();

protected:

    /// @brief Schedules asynchronous "ha-sync-complete-notify" command to the
    /// specified server.
    ///
    /// @param http_client reference to the client to be used to communicate
    /// with the other server.
    /// @param server_name name of the server to which the command should be
    /// sent.
    /// @param post_request_action pointer to the function to be executed when
    /// the request is completed.
    void asyncSyncCompleteNotify(http::HttpClient& http_client,
                                 const std::string& server_name,
                                 PostRequestCallback post_request_action);

public:

    /// @brief Process ha-sync-complete-notify command and returns a response.
    ///
    /// A server finishing a lease database synchronization may notify its
    /// partner about it with this command. This function implements reception
    /// and processing of the command.
    ///
    /// It enables DHCP service unless the server is in the partner-down state.
    /// In this state, the server will first have to check connectivity with
    /// the partner and transition to a state in which it will send lease updates.
    ///
    /// @return Pointer to the response to the ha-sync-complete-notify command.
    data::ConstElementPtr processSyncCompleteNotify();

    /// @brief Start the client and(or) listener instances.
    ///
    /// When HA+MT is enabled it starts the client's thread pool
    /// and the dedicated listener thread pool, if the listener exists.
    /// It registers pauseClientAndListener() and resumeClientAndListener()
    /// as the MultiThreading critical section entry and exit callbacks,
    /// respectively.
    void startClientAndListener();

    /// @brief Pauses client and(or) listener thread pool operations.
    ///
    /// Suspends the client and listener thread pool event processing.
    /// Has no effect in single-threaded mode or if thread pools are
    /// not currently running.  Serves as the MultiThreading critical
    /// section entry callback.
    void pauseClientAndListener();

    /// @brief Resumes client and(or) listener thread pool operations.
    ///
    /// Resumes the client and listener thread pool event processing.
    /// Has no effect in single-threaded mode or if thread pools are
    /// not currently paused. Serves as the MultiThreading critical
    /// section exit callback.
    void resumeClientAndListener();

    /// @brief Stop the client and(or) listener instances.
    ///
    /// It unregisters the MultiThreading critical section callbacks,
    /// closes all connections and stops the thread pools for the client
    /// and listener, if they exist.
    void stopClientAndListener();

protected:

    /// @brief Checks if the response is valid or contains an error.
    ///
    /// The response must be non-null, must contain a JSON body and must
    /// contain a success status code.
    ///
    /// @param response pointer to the received response.
    /// @param [out] rcode result found in the response.
    /// @return Pointer to the response arguments.
    /// @throw CtrlChannelError if response is invalid or contains an error.
    /// @throw CommandUnsupportedError if sent command is unsupported.
    data::ConstElementPtr verifyAsyncResponse(const http::HttpResponsePtr& response,
                                              int& rcode);

    /// @brief HttpClient connect callback handler
    ///
    /// Passed into HttpClient calls to allow registration of client's TCP socket
    /// with an external monitor (such as IfaceMgr's  main-thread select()).
    ///
    /// @param ec Error status of the ASIO connect
    /// @param tcp_native_fd socket descriptor to register
    /// @return always true. Registration cannot fail, and if ec indicates a real
    /// error we want Connection logic to process it.
    bool clientConnectHandler(const boost::system::error_code& ec, int tcp_native_fd);

    /// @brief HttpClient handshake callback handler
    ///
    /// Currently is never called and does nothing.
    ///
    /// @return always true.
    bool clientHandshakeHandler(const boost::system::error_code&) {
        return (true);
    }

    /// @brief IfaceMgr external socket ready callback handler
    ///
    /// IfaceMgr invokes this call back when a registered socket has been
    /// flagged as ready to read.   It is installed by the invocation to
    /// register the socket with IfaceMgr made in @ref clientConnectHandler.
    ///
    /// The handler calls @c http::HttpClient::closeIfOutOfBand() to catch
    /// and close any sockets that have gone ready outside of transactions.
    ///
    /// We do this in case the other peer closed the socket (e.g. idle timeout),
    /// as this will cause the socket to appear ready to read to the
    /// IfaceMgr::select(). If this happens while no transactions are
    /// in progress, we won't have anything to deal with the socket event.
    /// This causes IfaceMgr::select() to endlessly interrupt on the socket.
    ///
    /// @param tcp_native_fd socket descriptor of the ready socket
    void socketReadyHandler(int tcp_native_fd);

    /// @brief HttpClient close callback handler
    ///
    /// Passed into HttpClient calls to allow unregistration of client's
    /// TCP socket with an external monitor (such as IfaceMgr's
    /// main-thread select()).
    ///
    /// @param tcp_native_fd socket descriptor to register
    void clientCloseHandler(int tcp_native_fd);

    /// @brief Pointer to the IO service object shared between this hooks
    /// library and the DHCP server.
    asiolink::IOServicePtr io_service_;

    /// @brief Pointer to the state of the DHCP service (enabled/disabled).
    dhcp::NetworkStatePtr network_state_;

    /// @brief Pointer to the HA hooks library configuration.
    HAConfigPtr config_;

    /// @brief DHCP server type.
    HAServerType server_type_;

    /// @brief HTTP client instance used to send HA commands and lease updates.
    http::HttpClientPtr client_;

    /// @brief HTTP listener instance used to receive and respond to HA commands
    /// and lease updates.
    config::CmdHttpListenerPtr listener_;

    /// @brief Holds communication state with a peer.
    CommunicationStatePtr communication_state_;

    /// @brief Selects queries to be processed/dropped.
    QueryFilter query_filter_;

    /// @brief Handle last pending request for this query.
    ///
    /// Search if there are pending requests for this query:
    ///  - if there are decrement the count
    ///  - if there were at least two return false
    ///  - if there was none or one unpark the query
    ///  - if there was one remove the query from the map
    ///  - return true
    ///
    /// @tparam QueryPtrType Type of the pointer to the DHCP client's message,
    /// i.e. Pkt4Ptr or Pkt6Ptr.
    /// @param query Pointer to the DHCP client's query.
    /// @param [out] parking_lot Parking lot where the query is parked.
    /// This method uses this handle to unpark the packet when all asynchronous
    /// requests have been completed.
    /// @return When all lease updates are complete returns true, false otherwise.
    template<typename QueryPtrType>
    bool leaseUpdateComplete(QueryPtrType& query,
                             const hooks::ParkingLotHandlePtr& parking_lot);

    /// @brief Update pending request counter for this query.
    ///
    /// @tparam QueryPtrType Type of the pointer to the DHCP client's message,
    /// i.e. Pkt4Ptr or Pkt6Ptr.
    /// @param query Pointer to the DHCP client's query.
    template<typename QueryPtrType>
    void updatePendingRequest(QueryPtrType& query);

    /// @brief Get the number of entries in the pending request map.
    ///
    /// @note Currently for testing purposes only.
    /// @return Number of entries in the pending request map.
    size_t pendingRequestSize();

    /// @brief Get the number of scheduled requests for a given query.
    ///
    /// @note Currently for testing purposes only.
    ///
    /// If there is an entry in the pending request map for the given
    /// query the entry is returned else zero is returned.
    ///
    /// @param query Pointer to the DHCP client's query.
    /// @return Number of scheduled requests for the query or zero.
    template<typename QueryPtrType>
    int getPendingRequest(const QueryPtrType& query);

private:
    /// @brief Handle last pending request for this query.
    ///
    /// Search if there are pending requests for this query:
    ///  - if there are decrement the count
    ///  - if there were at least two return false
    ///  - if there was none or one unpark the query
    ///  - if there was one remove the query from the map
    ///  - return true
    ///
    /// Should be called in a thread safe context.
    ///
    /// @tparam QueryPtrType Type of the pointer to the DHCP client's message,
    /// i.e. Pkt4Ptr or Pkt6Ptr.
    /// @param query Pointer to the DHCP client's query.
    /// @param [out] parking_lot Parking lot where the query is parked.
    /// This method uses this handle to unpark the packet when all asynchronous
    /// requests have been completed.
    /// @return When all lease updates are complete returns true, false otherwise.
    template<typename QueryPtrType>
    bool leaseUpdateCompleteInternal(QueryPtrType& query,
                                     const hooks::ParkingLotHandlePtr& parking_lot);

    /// @brief Update pending request counter for this query.
    ///
    /// Should be called in a thread safe context.
    ///
    /// @tparam QueryPtrType Type of the pointer to the DHCP client's message,
    /// i.e. Pkt4Ptr or Pkt6Ptr.
    /// @param query Pointer to the DHCP client's query.
    template<typename QueryPtrType>
    void updatePendingRequestInternal(QueryPtrType& query);

    /// @brief Get the number of scheduled requests for a given query.
    /// @note Currently for testing purposes only.
    ///
    /// If there is an entry in the pending request map for the given
    /// query the entry is returned else zero is returned.
    ///
    /// Should be called in a thread safe context.
    ///
    /// @param query Pointer to the DHCP client's query.
    /// @return Number of scheduled requests for the query or zero.
    template<typename QueryPtrType>
    int getPendingRequestInternal(const QueryPtrType& query);

    /// @brief Mutex to protect the internal state.
    std::mutex mutex_;

    /// @brief Map holding a number of scheduled requests for a given packet.
    ///
    /// A single callout may send multiple requests at the same time, e.g.
    /// "leases4_committed" may provide multiple deleted leases and multiple
    /// newly allocated leases. The parked packet may be unparked when all
    /// requests have been delivered. Therefore, it is required to count
    /// the number of responses received so far and unpark the packet when
    /// all responses have been received. That's what this map is used for.
    std::map<boost::shared_ptr<dhcp::Pkt>, int> pending_requests_;

protected:

    /// @brief Backlog of DHCP lease updates.
    ///
    /// Unsent lease updates are stored in this queue when the server is in
    /// the communication-recovery state and is temporarily unable to send
    /// lease updates to the partner.
    LeaseUpdateBacklog lease_update_backlog_;

    /// @brief An indicator that a partner sent ha-sync-complete-notify command.
    ///
    /// This indicator is set when the partner finished synchronization. It blocks
    /// enabling DHCP service in the partner-down state. The server will first
    /// send heartbeat to the partner to ensure that the communication is
    /// re-established. If the communication remains broken, the server clears
    /// this flag and enables DHCP service to continue the service.
    bool sync_complete_notified_;
};

/// @brief Pointer to the @c HAService class.
typedef boost::shared_ptr<HAService> HAServicePtr;

} // end of namespace isc::ha
} // end of namespace isc

#endif