Kea 2.7.1
ha_service.cc
Go to the documentation of this file.
1// Copyright (C) 2018-2024 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 <command_creator.h>
10#include <ha_log.h>
11#include <ha_service.h>
12#include <ha_service_states.h>
14#include <cc/data.h>
16#include <config/timeouts.h>
17#include <dhcp/iface_mgr.h>
18#include <dhcpsrv/cfgmgr.h>
19#include <dhcpsrv/lease_mgr.h>
22#include <http/date_time.h>
23#include <http/response_json.h>
26#include <util/stopwatch.h>
27#include <boost/pointer_cast.hpp>
28#include <boost/make_shared.hpp>
29#include <boost/weak_ptr.hpp>
30#include <functional>
31#include <sstream>
32
33using namespace isc::asiolink;
34using namespace isc::config;
35using namespace isc::data;
36using namespace isc::dhcp;
37using namespace isc::hooks;
38using namespace isc::http;
39using namespace isc::log;
40using namespace isc::util;
41namespace ph = std::placeholders;
42
43namespace {
44
46class CommandUnsupportedError : public CtrlChannelError {
47public:
48 CommandUnsupportedError(const char* file, size_t line, const char* what) :
49 CtrlChannelError(file, line, what) {}
50};
51
53class ConflictError : public CtrlChannelError {
54public:
55 ConflictError(const char* file, size_t line, const char* what) :
56 CtrlChannelError(file, line, what) {}
57};
58
59}
60
61namespace isc {
62namespace ha {
63
74
75HAService::HAService(const unsigned int id, const IOServicePtr& io_service,
76 const NetworkStatePtr& network_state, const HAConfigPtr& config,
77 const HAServerType& server_type)
78 : id_(id), io_service_(io_service), network_state_(network_state), config_(config),
79 server_type_(server_type), client_(), listener_(), communication_state_(),
80 query_filter_(config), lease_sync_filter_(server_type, config), mutex_(),
81 pending_requests_(), lease_update_backlog_(config->getDelayedUpdatesLimit()),
82 sync_complete_notified_(false) {
83
84 if (server_type == HAServerType::DHCPv4) {
86
87 } else {
89 }
90
91 network_state_->enableService(getLocalOrigin());
92
94
95 // Create the client and(or) listener as appropriate.
96 if (!config_->getEnableMultiThreading()) {
97 // Not configured for multi-threading, start a client in ST mode.
98 client_.reset(new HttpClient(io_service_, false));
99 } else {
100 // Create an MT-mode client.
101 client_.reset(new HttpClient(io_service_, true,
102 config_->getHttpClientThreads(), true));
103
104 // If we're configured to use our own listener create and start it.
105 if (config_->getHttpDedicatedListener()) {
106 // Get the server address and port from this server's URL.
107 auto my_url = config_->getThisServerConfig()->getUrl();
108 IOAddress server_address(IOAddress::IPV4_ZERO_ADDRESS());
109 try {
110 // Since we do not currently support hostname resolution,
111 // we need to make sure we have an IP address here.
112 server_address = IOAddress(my_url.getStrippedHostname());
113 } catch (const std::exception& ex) {
114 isc_throw(Unexpected, "server Url:" << my_url.getStrippedHostname()
115 << " is not a valid IP address");
116 }
117
118 // Fetch how many threads the listener will use.
119 uint32_t listener_threads = config_->getHttpListenerThreads();
120
121 // Fetch the TLS context.
122 auto tls_context = config_->getThisServerConfig()->getTlsContext();
123
124 // Instantiate the listener.
125 listener_.reset(new CmdHttpListener(server_address, my_url.getPort(),
126 listener_threads, tls_context));
127 // Set the command filter when enabled.
128 if (config_->getRestrictCommands()) {
129 if (server_type == HAServerType::DHCPv4) {
132 } else {
135 }
136 }
137 }
138 }
139
141 .arg(config_->getThisServerName())
142 .arg(HAConfig::HAModeToString(config->getHAMode()))
143 .arg(HAConfig::PeerConfig::roleToString(config->getThisServerConfig()->getRole()));
144}
145
147 // Stop client and/or listener.
149
150 network_state_->enableService(getLocalOrigin());
151}
152
153std::string
154HAService::getCSCallbacksSetName() const {
155 std::ostringstream s;
156 s << "HA_MT_" << id_;
157 return (s.str());
158}
159
160void
161HAService::defineEvents() {
163
164 defineEvent(HA_HEARTBEAT_COMPLETE_EVT, "HA_HEARTBEAT_COMPLETE_EVT");
165 defineEvent(HA_LEASE_UPDATES_COMPLETE_EVT, "HA_LEASE_UPDATES_COMPLETE_EVT");
166 defineEvent(HA_SYNCING_FAILED_EVT, "HA_SYNCING_FAILED_EVT");
167 defineEvent(HA_SYNCING_SUCCEEDED_EVT, "HA_SYNCING_SUCCEEDED_EVT");
168 defineEvent(HA_MAINTENANCE_NOTIFY_EVT, "HA_MAINTENANCE_NOTIFY_EVT");
169 defineEvent(HA_MAINTENANCE_START_EVT, "HA_MAINTENANCE_START_EVT");
170 defineEvent(HA_MAINTENANCE_CANCEL_EVT, "HA_MAINTENANCE_CANCEL_EVT");
171 defineEvent(HA_SYNCED_PARTNER_UNAVAILABLE_EVT, "HA_SYNCED_PARTNER_UNAVAILABLE_EVT");
172}
173
174void
175HAService::verifyEvents() {
177
186}
187
188void
189HAService::defineStates() {
191
193 std::bind(&HAService::backupStateHandler, this),
194 config_->getStateMachineConfig()->getStateConfig(HA_BACKUP_ST)->getPausing());
195
197 std::bind(&HAService::communicationRecoveryHandler, this),
198 config_->getStateMachineConfig()->getStateConfig(HA_COMMUNICATION_RECOVERY_ST)->getPausing());
199
201 std::bind(&HAService::normalStateHandler, this),
202 config_->getStateMachineConfig()->getStateConfig(HA_HOT_STANDBY_ST)->getPausing());
203
205 std::bind(&HAService::normalStateHandler, this),
206 config_->getStateMachineConfig()->getStateConfig(HA_LOAD_BALANCING_ST)->getPausing());
207
209 std::bind(&HAService::inMaintenanceStateHandler, this),
210 config_->getStateMachineConfig()->getStateConfig(HA_IN_MAINTENANCE_ST)->getPausing());
211
213 std::bind(&HAService::partnerDownStateHandler, this),
214 config_->getStateMachineConfig()->getStateConfig(HA_PARTNER_DOWN_ST)->getPausing());
215
217 std::bind(&HAService::partnerInMaintenanceStateHandler, this),
218 config_->getStateMachineConfig()->getStateConfig(HA_PARTNER_IN_MAINTENANCE_ST)->getPausing());
219
221 std::bind(&HAService::passiveBackupStateHandler, this),
222 config_->getStateMachineConfig()->getStateConfig(HA_PASSIVE_BACKUP_ST)->getPausing());
223
225 std::bind(&HAService::readyStateHandler, this),
226 config_->getStateMachineConfig()->getStateConfig(HA_READY_ST)->getPausing());
227
229 std::bind(&HAService::syncingStateHandler, this),
230 config_->getStateMachineConfig()->getStateConfig(HA_SYNCING_ST)->getPausing());
231
233 std::bind(&HAService::terminatedStateHandler, this),
234 config_->getStateMachineConfig()->getStateConfig(HA_TERMINATED_ST)->getPausing());
235
237 std::bind(&HAService::waitingStateHandler, this),
238 config_->getStateMachineConfig()->getStateConfig(HA_WAITING_ST)->getPausing());
239}
240
241void
242HAService::backupStateHandler() {
243 if (doOnEntry()) {
246
247 // Log if the state machine is paused.
249 }
250
251 // There is nothing to do in that state. This server simply receives
252 // lease updates from the partners.
254}
255
256void
257HAService::communicationRecoveryHandler() {
258 if (doOnEntry()) {
261
262 // Log if the state machine is paused.
264 }
265
267
270
271 // Check if the clock skew is still acceptable. If not, transition to
272 // the terminated state.
273 } else if (shouldTerminate()) {
275
276 } else if (isPartnerStateInvalid()) {
278
279 } else {
280
281 // Transitions based on the partner's state.
282 switch (communication_state_->getPartnerState()) {
285 break;
286
289 break;
290
293 break;
294
295 case HA_TERMINATED_ST:
297 break;
298
300 if (shouldPartnerDown()) {
302
303 } else {
305 }
306 break;
307
308 case HA_WAITING_ST:
309 case HA_SYNCING_ST:
310 case HA_READY_ST:
311 // The partner seems to be waking up, perhaps after communication-recovery.
312 // If our backlog queue is overflown we need to synchronize our lease database.
313 // There is no need to send ha-reset to the partner because the partner is
314 // already synchronizing its lease database.
315 if (!communication_state_->isCommunicationInterrupted() &&
318 } else {
319 // Backlog was not overflown, so there is no need to synchronize our
320 // lease database. Let's wait until our partner completes synchronization
321 // and transitions to the load-balancing state.
323 }
324 break;
325
326 default:
327 // If the communication is still interrupted, let's continue sitting
328 // in this state until it is resumed or until the transition to the
329 // partner-down state, depending on what happens first.
330 if (communication_state_->isCommunicationInterrupted()) {
332 break;
333 }
334
335 // The communication has been resumed. The partner server must be in a state
336 // in which it can receive outstanding lease updates we collected. The number of
337 // outstanding lease updates must not exceed the configured limit. Finally, the
338 // lease updates must be successfully sent. If that all works, we will transition
339 // to the normal operation.
340 if ((communication_state_->getPartnerState() == getNormalState()) ||
341 (communication_state_->getPartnerState() == HA_COMMUNICATION_RECOVERY_ST)) {
343 // If our lease backlog was overflown or we were unable to send lease
344 // updates to the partner we should notify the partner that it should
345 // synchronize the lease database. We do it by sending ha-reset command.
346 if (sendHAReset()) {
348 }
349 break;
350 }
351 // The backlog was not overflown and we successfully sent our lease updates.
352 // We can now transition to the normal operation state. If the partner
353 // fails to send his outstanding lease updates to us it should send the
354 // ha-reset command to us.
356 break;
357 }
358
359 // The partner appears to be in unexpected state, we have exceeded the number
360 // of lease updates in a backlog or an attempt to send lease updates failed.
361 // In all these cases we follow plan B and transition to the waiting state.
362 // The server will then attempt to synchronize the entire lease database.
364 }
365 }
366
367 // When exiting this state we must ensure that lease updates backlog is cleared.
368 if (doOnExit()) {
370 }
371}
372
373void
374HAService::normalStateHandler() {
375 // If we are transitioning from another state, we have to define new
376 // serving scopes appropriate for the new state. We don't do it if
377 // we remain in this state.
378 if (doOnEntry()) {
381
382 // Log if the state machine is paused.
384 }
385
387
390 return;
391 }
392
393 // Check if the clock skew is still acceptable. If not, transition to
394 // the terminated state.
395 if (shouldTerminate()) {
397 return;
398 }
399
400 // Check if the partner state is valid per current configuration. If it is
401 // in an invalid state let's transition to the waiting state and stay there
402 // until the configuration is corrected.
403 if (isPartnerStateInvalid()) {
405 return;
406 }
407
408 switch (communication_state_->getPartnerState()) {
411 break;
412
415 break;
416
419 break;
420
421 case HA_TERMINATED_ST:
423 break;
424
426 if (shouldPartnerDown()) {
428
429 } else if (config_->amAllowingCommRecovery()) {
431
432 } else {
434 }
435 break;
436
437 default:
439 }
440
441 if (doOnExit()) {
442 // Do nothing here but doOnExit() call clears the "on exit" flag
443 // when transitioning to the communication-recovery state. In that
444 // state we need this flag to be cleared.
445 }
446}
447
448void
449HAService::inMaintenanceStateHandler() {
450 // If we are transitioning from another state, we have to define new
451 // serving scopes appropriate for the new state. We don't do it if
452 // we remain in this state.
453 if (doOnEntry()) {
454 // In this state the server remains silent and waits for being
455 // shutdown.
458
459 // Log if the state machine is paused.
461
463 .arg(config_->getThisServerName());
464 }
465
467
468 // We don't transition out of this state unless explicitly mandated
469 // by the administrator via a dedicated command which cancels
470 // the maintenance.
472}
473
474void
475HAService::partnerDownStateHandler() {
476 // If we are transitioning from another state, we have to define new
477 // serving scopes appropriate for the new state. We don't do it if
478 // we remain in this state.
479 if (doOnEntry()) {
480
481 bool maintenance = (getLastEvent() == HA_MAINTENANCE_START_EVT);
482
483 // It may be administratively disabled to handle partner's scope
484 // in case of failure. If this is the case we'll just handle our
485 // default scope (or no scope at all). The user will need to
486 // manually enable this server to handle partner's scope.
487 // If we're in the maintenance mode we serve all scopes because
488 // it is not a failover situation.
489 if (maintenance || config_->getThisServerConfig()->isAutoFailover()) {
491 } else {
493 }
495 communication_state_->clearRejectedLeaseUpdates();
496
497 // Log if the state machine is paused.
499
500 if (maintenance) {
501 // If we ended up in the partner-down state as a result of
502 // receiving the ha-maintenance-start command let's log it.
504 .arg(config_->getThisServerName());
505 }
506
508 // Partner sent the ha-sync-complete-notify command to indicate that
509 // it has successfully synchronized its lease database but this server
510 // was unable to send heartbeat to this server. Enable the DHCP service
511 // and continue serving the clients in the partner-down state until the
512 // communication with the partner is fixed.
514 }
515
517
520 return;
521 }
522
523 // Check if the clock skew is still acceptable. If not, transition to
524 // the terminated state.
525 if (shouldTerminate()) {
527 return;
528 }
529
530 // Check if the partner state is valid per current configuration. If it is
531 // in an invalid state let's transition to the waiting state and stay there
532 // until the configuration is corrected.
533 if (isPartnerStateInvalid()) {
535 return;
536 }
537
538 switch (communication_state_->getPartnerState()) {
543 break;
544
545 case HA_READY_ST:
546 // If partner allocated new leases for which it didn't send lease updates
547 // to us we should synchronize our database.
548 if (communication_state_->hasPartnerNewUnsentUpdates()) {
550 } else {
551 // We did not miss any lease updates. There is no need to synchronize
552 // the database.
554 }
555 break;
556
557 case HA_TERMINATED_ST:
559 break;
560
561 default:
563 }
564}
565
566void
567HAService::partnerInMaintenanceStateHandler() {
568 // If we are transitioning from another state, we have to define new
569 // serving scopes appropriate for the new state. We don't do it if
570 // we remain in this state.
571 if (doOnEntry()) {
573
575
576 // Log if the state machine is paused.
578
580 .arg(config_->getThisServerName());
581 }
582
584
585 if (isModelPaused()) {
587 return;
588 }
589
590 // Check if the clock skew is still acceptable. If not, transition to
591 // the terminated state.
592 if (shouldTerminate()) {
594 return;
595 }
596
597 switch (communication_state_->getPartnerState()) {
600 break;
601 default:
603 }
604}
605
606void
607HAService::passiveBackupStateHandler() {
608 // If we are transitioning from another state, we have to define new
609 // serving scopes appropriate for the new state. We don't do it if
610 // we remain in this state.
611 if (doOnEntry()) {
614
615 // In the passive-backup state we don't send heartbeat.
616 communication_state_->stopHeartbeat();
617
618 // Log if the state machine is paused.
620 }
622}
623
624void
625HAService::readyStateHandler() {
626 // If we are transitioning from another state, we have to define new
627 // serving scopes appropriate for the new state. We don't do it if
628 // we remain in this state.
629 if (doOnEntry()) {
632 communication_state_->clearRejectedLeaseUpdates();
633
634 // Log if the state machine is paused.
636 }
637
639
642 return;
643 }
644
645 // Check if the clock skew is still acceptable. If not, transition to
646 // the terminated state.
647 if (shouldTerminate()) {
649 return;
650 }
651
652 // Check if the partner state is valid per current configuration. If it is
653 // in an invalid state let's transition to the waiting state and stay there
654 // until the configuration is corrected.
655 if (isPartnerStateInvalid()) {
657 return;
658 }
659
660 switch (communication_state_->getPartnerState()) {
665 break;
666
669 break;
670
673 break;
674
675 case HA_READY_ST:
676 // If both servers are ready, the primary server "wins" and is
677 // transitioned first.
678 if (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::PRIMARY) {
681 } else {
683 }
684 break;
685
686 case HA_TERMINATED_ST:
688 break;
689
691 if (shouldPartnerDown()) {
693
694 } else {
696 }
697 break;
698
699 default:
701 }
702}
703
704void
705HAService::syncingStateHandler() {
706 // If we are transitioning from another state, we have to define new
707 // serving scopes appropriate for the new state. We don't do it if
708 // we remain in this state.
709 if (doOnEntry()) {
712 communication_state_->clearRejectedLeaseUpdates();
713
714 // Log if the state machine is paused.
716 }
717
720 return;
721 }
722
723 // Check if the clock skew is still acceptable. If not, transition to
724 // the terminated state.
725 if (shouldTerminate()) {
727 return;
728 }
729
730 // Check if the partner state is valid per current configuration. If it is
731 // in an invalid state let's transition to the waiting state and stay there
732 // until the configuration is corrected.
733 if (isPartnerStateInvalid()) {
735 return;
736 }
737
738 // We don't want to perform synchronous attempt to synchronize with
739 // a partner until we know that the partner is responding. Therefore,
740 // we wait for the heartbeat to complete successfully before we
741 // initiate the synchronization.
742 switch (communication_state_->getPartnerState()) {
743 case HA_TERMINATED_ST:
745 return;
746
748 // If the partner appears to be offline, let's transition to the partner
749 // down state. Otherwise, we'd be stuck trying to synchronize with a
750 // dead partner.
751 if (shouldPartnerDown()) {
753
754 } else {
756 }
757 break;
758
759 default:
760 // We don't want the heartbeat to interfere with the synchronization,
761 // so let's temporarily stop it.
762 communication_state_->stopHeartbeat();
763
764 // Timeout is configured in milliseconds. Need to convert to seconds.
765 unsigned int dhcp_disable_timeout =
766 static_cast<unsigned int>(config_->getSyncTimeout() / 1000);
767 if (dhcp_disable_timeout == 0) {
768 ++dhcp_disable_timeout;
769 }
770
771 // Perform synchronous leases update.
772 std::string status_message;
773 int sync_status = synchronize(status_message,
774 config_->getFailoverPeerConfig(),
775 dhcp_disable_timeout);
776
777 // If the leases synchronization was successful, let's transition
778 // to the ready state.
779 if (sync_status == CONTROL_RESULT_SUCCESS) {
781
782 } else {
783 // If the synchronization was unsuccessful we're back to the
784 // situation that the partner is unavailable and therefore
785 // we stay in the syncing state.
787 }
788 }
789
790 // Make sure that the heartbeat is re-enabled.
792}
793
794void
795HAService::terminatedStateHandler() {
796 // If we are transitioning from another state, we have to define new
797 // serving scopes appropriate for the new state. We don't do it if
798 // we remain in this state.
799 if (doOnEntry()) {
802 communication_state_->clearRejectedLeaseUpdates();
803
804 // In the terminated state we don't send heartbeat.
805 communication_state_->stopHeartbeat();
806
807 // Log if the state machine is paused.
809
811 .arg(config_->getThisServerName());
812 }
813
815}
816
817void
818HAService::waitingStateHandler() {
819 // If we are transitioning from another state, we have to define new
820 // serving scopes appropriate for the new state. We don't do it if
821 // we remain in this state.
822 if (doOnEntry()) {
825 communication_state_->clearRejectedLeaseUpdates();
826
827 // Log if the state machine is paused.
829 }
830
831 // Only schedule the heartbeat for non-backup servers.
832 if ((config_->getHAMode() != HAConfig::PASSIVE_BACKUP) &&
833 (config_->getThisServerConfig()->getRole() != HAConfig::PeerConfig::BACKUP)) {
835 }
836
839 return;
840 }
841
842 // Backup server must remain in its own state.
843 if (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::BACKUP) {
845 return;
846 }
847
848 // We're not a backup server, so we're either primary or secondary. If this is
849 // a passive-backup mode of operation, we're primary and we should transition
850 // to the passive-backup state.
851 if (config_->getHAMode() == HAConfig::PASSIVE_BACKUP) {
853 return;
854 }
855
856 // Check if the clock skew is still acceptable. If not, transition to
857 // the terminated state.
858 if (shouldTerminate()) {
860 return;
861 }
862
863 // Check if the partner state is valid per current configuration. If it is
864 // in an invalid state let's sit in the waiting state until the configuration
865 // is corrected.
866 if (isPartnerStateInvalid()) {
868 return;
869 }
870
871 switch (communication_state_->getPartnerState()) {
878 case HA_READY_ST:
879 // If we're configured to not synchronize lease database, proceed directly
880 // to the "ready" state.
881 verboseTransition(config_->amSyncingLeases() ? HA_SYNCING_ST : HA_READY_ST);
882 break;
883
884 case HA_SYNCING_ST:
886 break;
887
888 case HA_TERMINATED_ST: {
889 auto partner_in_terminated = communication_state_->getDurationSincePartnerStateTime();
890 if (!partner_in_terminated.is_not_a_date_time() &&
891 (partner_in_terminated.total_seconds()) / 60 >= HA_WAITING_TO_TERMINATED_ST_DELAY_MINUTES) {
893 .arg(config_->getThisServerName())
896 break;
897 }
898
899 // We have checked above whether the clock skew is exceeding the threshold
900 // and we should terminate. If we're here, it means that the clock skew
901 // is acceptable. The partner may be still in the terminated state because
902 // it hasn't been restarted yet. Probably, this server is the first one
903 // being restarted after syncing the clocks. Let's just sit in the waiting
904 // state until the partner gets restarted.
906 .arg(config_->getThisServerName());
908 break;
909 }
910 case HA_WAITING_ST:
911 // If both servers are waiting, the primary server 'wins' and is
912 // transitioned to the next state first.
913 if (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::PRIMARY) {
914 // If we're configured to not synchronize lease database, proceed directly
915 // to the "ready" state.
916 verboseTransition(config_->amSyncingLeases() ? HA_SYNCING_ST : HA_READY_ST);
917
918 } else {
920 }
921 break;
922
924 if (shouldPartnerDown()) {
926
927 } else {
929 }
930 break;
931
932 default:
934 }
935}
936
937void
938HAService::verboseTransition(const unsigned state) {
939 // Get current and new state name.
940 std::string current_state_name = getStateLabel(getCurrState());
941 std::string new_state_name = getStateLabel(state);
942
943 // Turn them to upper case so as they are better visible in the logs.
944 boost::to_upper(current_state_name);
945 boost::to_upper(new_state_name);
946
947 if (config_->getHAMode() != HAConfig::PASSIVE_BACKUP) {
948 // If this is load-balancing or hot-standby mode we also want to log
949 // partner's state.
950 auto partner_state = communication_state_->getPartnerState();
951 std::string partner_state_name = getStateLabel(partner_state);
952 boost::to_upper(partner_state_name);
953
954 // Log the transition.
956 .arg(config_->getThisServerName())
957 .arg(current_state_name)
958 .arg(new_state_name)
959 .arg(partner_state_name);
960
961 } else {
962 // In the passive-backup mode we don't know the partner's state.
964 .arg(config_->getThisServerName())
965 .arg(current_state_name)
966 .arg(new_state_name);
967 }
968
969 // If we're transitioning directly from the "waiting" to "ready"
970 // state it indicates that the database synchronization is
971 // administratively disabled. Let's remind the user about this
972 // configuration setting.
973 if ((state == HA_READY_ST) && (getCurrState() == HA_WAITING_ST)) {
975 .arg(config_->getThisServerName());
976 }
977
978 // Do the actual transition.
979 transition(state, getNextEvent());
980
981 // Inform the administrator whether or not lease updates are generated.
982 // Updates are never generated by a backup server so it doesn't make
983 // sense to log anything for the backup server.
984 if ((config_->getHAMode() != HAConfig::PASSIVE_BACKUP) &&
985 (config_->getThisServerConfig()->getRole() != HAConfig::PeerConfig::BACKUP)) {
986 if (shouldSendLeaseUpdates(config_->getFailoverPeerConfig())) {
988 .arg(config_->getThisServerName())
989 .arg(new_state_name);
990
991 } else if (!config_->amSendingLeaseUpdates()) {
992 // Lease updates are administratively disabled.
994 .arg(config_->getThisServerName())
995 .arg(new_state_name);
996
997 } else {
998 // Lease updates are not administratively disabled, but they
999 // are not issued because this is the backup server or because
1000 // in this state the server should not generate lease updates.
1002 .arg(config_->getThisServerName())
1003 .arg(new_state_name);
1004 }
1005 }
1006}
1007
1008int
1010 if (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::BACKUP) {
1011 return (HA_BACKUP_ST);
1012 }
1013
1014 switch (config_->getHAMode()) {
1016 return (HA_LOAD_BALANCING_ST);
1018 return (HA_HOT_STANDBY_ST);
1019 default:
1020 return (HA_PASSIVE_BACKUP_ST);
1021 }
1022}
1023
1024bool
1026 if (isModelPaused()) {
1028 .arg(config_->getThisServerName());
1029 unpauseModel();
1030 return (true);
1031 }
1032 return (false);
1033}
1034
1035void
1037 // Inform the administrator if the state machine is paused.
1038 if (isModelPaused()) {
1039 std::string state_name = stateToString(getCurrState());
1040 boost::to_upper(state_name);
1042 .arg(config_->getThisServerName())
1043 .arg(state_name);
1044 }
1045}
1046
1047void
1051
1052void
1056
1057bool
1059 return (inScopeInternal(query4));
1060}
1061
1062bool
1064 return (inScopeInternal(query6));
1065}
1066
1067template<typename QueryPtrType>
1068bool
1069HAService::inScopeInternal(QueryPtrType& query) {
1070 // Check if the query is in scope (should be processed by this server).
1071 std::string scope_class;
1072 const bool in_scope = query_filter_.inScope(query, scope_class);
1073 // Whether or not the query is going to be processed by this server,
1074 // we associate the query with the appropriate class.
1075 query->addClass(dhcp::ClientClass(scope_class));
1076 // The following is the part of the server failure detection algorithm.
1077 // If the query should be processed by the partner we need to check if
1078 // the partner responds. If the number of unansweered queries exceeds a
1079 // configured threshold, we will consider the partner to be offline.
1080 if (!in_scope && communication_state_->isCommunicationInterrupted()) {
1081 communication_state_->analyzeMessage(query);
1082 }
1083 // Indicate if the query is in scope.
1084 return (in_scope);
1085}
1086
1087void
1089 std::string current_state_name = getStateLabel(getCurrState());
1090 boost::to_upper(current_state_name);
1091
1092 // DHCP service should be enabled in the following states.
1093 const bool should_enable = ((getCurrState() == HA_COMMUNICATION_RECOVERY_ST) ||
1100
1101 if (!should_enable && network_state_->isServiceEnabled()) {
1102 current_state_name = getStateLabel(getCurrState());
1103 boost::to_upper(current_state_name);
1105 .arg(config_->getThisServerName())
1106 .arg(current_state_name);
1107 network_state_->disableService(getLocalOrigin());
1108
1109 } else if (should_enable && !network_state_->isServiceEnabled()) {
1110 current_state_name = getStateLabel(getCurrState());
1111 boost::to_upper(current_state_name);
1113 .arg(config_->getThisServerName())
1114 .arg(current_state_name);
1115 network_state_->enableService(getLocalOrigin());
1116 }
1117}
1118
1119bool
1121 // Checking whether the communication with the partner is OK is the
1122 // first step towards verifying if the server is up.
1123 if (communication_state_->isCommunicationInterrupted()) {
1124 // If the communication is interrupted, we also have to check
1125 // whether the partner answers DHCP requests. The only cases
1126 // when we don't (can't) do it are: the hot standby configuration
1127 // in which this server is a primary and when the DHCP service is
1128 // disabled so we can't analyze incoming traffic. Note that the
1129 // primary server can't check delayed responses to the partner
1130 // because the partner doesn't respond to any queries in this
1131 // configuration.
1132 if (network_state_->isServiceEnabled() &&
1133 ((config_->getHAMode() == HAConfig::LOAD_BALANCING) ||
1134 (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::STANDBY))) {
1135 return (communication_state_->failureDetected());
1136 }
1137
1138 // Hot standby / primary case.
1139 return (true);
1140 }
1141
1142 // Shouldn't transition to the partner down state.
1143 return (false);
1144}
1145
1146bool
1148 // Check if skew is fatally large.
1149 bool should_terminate = communication_state_->clockSkewShouldTerminate();
1150
1151 // If not issue a warning if it's getting large.
1152 if (!should_terminate) {
1153 communication_state_->clockSkewShouldWarn();
1154 // Check if we should terminate because the number of rejected leases
1155 // has been exceeded.
1156 should_terminate = communication_state_->rejectedLeaseUpdatesShouldTerminate();
1157 }
1158
1159 return (should_terminate);
1160}
1161
1162bool
1166
1167bool
1169 switch (communication_state_->getPartnerState()) {
1171 if (config_->getHAMode() != HAConfig::LOAD_BALANCING) {
1173 .arg(config_->getThisServerName());
1174 return (true);
1175 }
1176 break;
1177
1178 case HA_HOT_STANDBY_ST:
1179 if (config_->getHAMode() != HAConfig::HOT_STANDBY) {
1181 .arg(config_->getThisServerName());
1182 return (true);
1183 }
1184 break;
1185
1187 if (config_->getHAMode() != HAConfig::LOAD_BALANCING) {
1189 .arg(config_->getThisServerName());
1190 return (true);
1191 }
1192 break;
1193
1194 default:
1195 ;
1196 }
1197 return (false);
1198}
1199
1200size_t
1202 const dhcp::Lease4CollectionPtr& leases,
1203 const dhcp::Lease4CollectionPtr& deleted_leases,
1204 const hooks::ParkingLotHandlePtr& parking_lot) {
1205
1206 // Get configurations of the peers. Exclude this instance.
1207 HAConfig::PeerConfigMap peers_configs = config_->getOtherServersConfig();
1208
1209 size_t sent_num = 0;
1210
1211 // Schedule sending lease updates to each peer.
1212 for (auto const& p : peers_configs) {
1213 HAConfig::PeerConfigPtr conf = p.second;
1214
1215 // Check if the lease updates should be queued. This is the case when the
1216 // server is in the communication-recovery state. Queued lease updates may
1217 // be sent when the communication is re-established.
1218 if (shouldQueueLeaseUpdates(conf)) {
1219 // Lease updates for deleted leases.
1220 for (auto const& l : *deleted_leases) {
1221 // If a released lease is preserved in the database send the lease
1222 // update to the partner. Otherwise, delete the lease.
1223 if (l->state_ == Lease4::STATE_RELEASED) {
1225 } else {
1227 }
1228 }
1229
1230 // Lease updates for new allocations and updated leases.
1231 for (auto const& l : *leases) {
1233 }
1234
1235 continue;
1236 }
1237
1238 // Check if the lease update should be sent to the server. If we're in
1239 // the partner-down state we don't send lease updates to the partner.
1240 if (!shouldSendLeaseUpdates(conf)) {
1241 // If we decide to not send the lease updates to an active partner, we
1242 // should make a record of it in the communication state. The partner
1243 // can check if there were any unsent lease updates when he determines
1244 // whether it should synchronize its database or not when it recovers
1245 // from the partner-down state.
1246 if (conf->getRole() != HAConfig::PeerConfig::BACKUP) {
1247 communication_state_->increaseUnsentUpdateCount();
1248 }
1249 continue;
1250 }
1251
1252 // Lease updates for deleted leases.
1253 for (auto const& l : *deleted_leases) {
1254 // If a released lease is preserved in the database send the lease
1255 // update to the partner. Otherwise, delete the lease.
1256 if (l->state_ == Lease4::STATE_RELEASED) {
1258 parking_lot);
1259 } else {
1261 parking_lot);
1262 }
1263 }
1264
1265 // Lease updates for new allocations and updated leases.
1266 for (auto const& l : *leases) {
1268 parking_lot);
1269 }
1270
1271 // If we're contacting a backup server from which we don't expect a
1272 // response prior to responding to the DHCP client we don't count
1273 // it.
1274 if ((config_->amWaitingBackupAck() || (conf->getRole() != HAConfig::PeerConfig::BACKUP))) {
1275 ++sent_num;
1276 }
1277 }
1278
1279 return (sent_num);
1280}
1281
1282size_t
1284 const dhcp::Lease4Ptr& lease,
1285 const hooks::ParkingLotHandlePtr& parking_lot) {
1287 leases->push_back(lease);
1288 Lease4CollectionPtr deleted_leases(new Lease4Collection());
1289
1290 return (asyncSendLeaseUpdates(query, leases, deleted_leases, parking_lot));
1291}
1292
1293size_t
1295 const dhcp::Lease6CollectionPtr& leases,
1296 const dhcp::Lease6CollectionPtr& deleted_leases,
1297 const hooks::ParkingLotHandlePtr& parking_lot) {
1298
1299 // Get configurations of the peers. Exclude this instance.
1300 HAConfig::PeerConfigMap peers_configs = config_->getOtherServersConfig();
1301
1302 size_t sent_num = 0;
1303
1304 // Schedule sending lease updates to each peer.
1305 for (auto const& p : peers_configs) {
1306 HAConfig::PeerConfigPtr conf = p.second;
1307
1308 // Check if the lease updates should be queued. This is the case when the
1309 // server is in the communication-recovery state. Queued lease updates may
1310 // be sent when the communication is re-established.
1311 if (shouldQueueLeaseUpdates(conf)) {
1312 for (auto const& l : *deleted_leases) {
1313 // If a released lease is preserved in the database send the lease
1314 // update to the partner. Otherwise, delete the lease.
1315 if (l->state_ == Lease4::STATE_RELEASED) {
1317 } else {
1319 }
1320 }
1321
1322 // Lease updates for new allocations and updated leases.
1323 for (auto const& l : *leases) {
1325 }
1326
1327 continue;
1328 }
1329
1330 // Check if the lease update should be sent to the server. If we're in
1331 // the partner-down state we don't send lease updates to the partner.
1332 if (!shouldSendLeaseUpdates(conf)) {
1333 // If we decide to not send the lease updates to an active partner, we
1334 // should make a record of it in the communication state. The partner
1335 // can check if there were any unsent lease updates when he determines
1336 // whether it should synchronize its database or not when it recovers
1337 // from the partner-down state.
1338 if (conf->getRole() != HAConfig::PeerConfig::BACKUP) {
1339 communication_state_->increaseUnsentUpdateCount();
1340 }
1341 continue;
1342 }
1343
1344 // If we're contacting a backup server from which we don't expect a
1345 // response prior to responding to the DHCP client we don't count
1346 // it.
1347 if (config_->amWaitingBackupAck() || (conf->getRole() != HAConfig::PeerConfig::BACKUP)) {
1348 ++sent_num;
1349 }
1350
1351 // Send new/updated leases and deleted leases in one command.
1352 asyncSendLeaseUpdate(query, conf, CommandCreator::createLease6BulkApply(leases, deleted_leases),
1353 parking_lot);
1354 }
1355
1356 return (sent_num);
1357}
1358
1359template<typename QueryPtrType>
1360bool
1362 const ParkingLotHandlePtr& parking_lot) {
1363 if (MultiThreadingMgr::instance().getMode()) {
1364 std::lock_guard<std::mutex> lock(mutex_);
1365 return (leaseUpdateCompleteInternal(query, parking_lot));
1366 } else {
1367 return (leaseUpdateCompleteInternal(query, parking_lot));
1368 }
1369}
1370
1371template<typename QueryPtrType>
1372bool
1373HAService::leaseUpdateCompleteInternal(QueryPtrType& query,
1374 const ParkingLotHandlePtr& parking_lot) {
1375 auto it = pending_requests_.find(query);
1376
1377 // If there are no more pending requests for this query, let's unpark
1378 // the DHCP packet.
1379 if (it == pending_requests_.end() || (--pending_requests_[query] <= 0)) {
1380 if (parking_lot) {
1381 parking_lot->unpark(query);
1382 }
1383
1384 // If we have unparked the packet we can clear pending requests for
1385 // this query.
1386 if (it != pending_requests_.end()) {
1387 pending_requests_.erase(it);
1388 }
1389 return (true);
1390 }
1391 return (false);
1392}
1393
1394template<typename QueryPtrType>
1395void
1397 if (MultiThreadingMgr::instance().getMode()) {
1398 std::lock_guard<std::mutex> lock(mutex_);
1399 updatePendingRequestInternal(query);
1400 } else {
1401 updatePendingRequestInternal(query);
1402 }
1403}
1404
1405template<typename QueryPtrType>
1406void
1407HAService::updatePendingRequestInternal(QueryPtrType& query) {
1408 if (pending_requests_.count(query) == 0) {
1409 pending_requests_[query] = 1;
1410 } else {
1411 ++pending_requests_[query];
1412 }
1413}
1414
1415template<typename QueryPtrType>
1416void
1417HAService::asyncSendLeaseUpdate(const QueryPtrType& query,
1418 const HAConfig::PeerConfigPtr& config,
1419 const ConstElementPtr& command,
1420 const ParkingLotHandlePtr& parking_lot) {
1421 // Create HTTP/1.1 request including our command.
1422 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
1423 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
1424 HostHttpHeader(config->getUrl().getStrippedHostname()));
1425 config->addBasicAuthHttpHeader(request);
1426 request->setBodyAsJson(command);
1427 request->finalize();
1428
1429 // Response object should also be created because the HTTP client needs
1430 // to know the type of the expected response.
1431 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
1432
1433 // When possible we prefer to pass weak pointers to the queries, rather
1434 // than shared pointers, to avoid memory leaks in case cross reference
1435 // between the pointers.
1436 boost::weak_ptr<typename QueryPtrType::element_type> weak_query(query);
1437
1438 // Schedule asynchronous HTTP request.
1439 client_->asyncSendRequest(config->getUrl(), config->getTlsContext(),
1440 request, response,
1441 [this, weak_query, parking_lot, config]
1442 (const boost::system::error_code& ec,
1443 const HttpResponsePtr& response,
1444 const std::string& error_str) {
1445 // Get the shared pointer of the query. The server should keep the
1446 // pointer to the query and then park it. Therefore, we don't really
1447 // expect it to be null. If it is null, something is really wrong.
1448 QueryPtrType query = weak_query.lock();
1449 if (!query) {
1450 isc_throw(Unexpected, "query is null while receiving response from"
1451 " HA peer. This is programmatic error");
1452 }
1453
1454 // There are four possible groups of errors during the lease update.
1455 // One is the IO error causing issues in communication with the peer.
1456 // Another one is an HTTP parsing error. The third type occurs when
1457 // the partner receives the command but it is invalid or there is
1458 // an internal processing error. Finally, the forth type is when the
1459 // conflict status code is returned in the response indicating that
1460 // the lease update does not match the partner's configuration.
1461
1462 bool lease_update_success = true;
1463 bool lease_update_conflict = false;
1464
1465 // Handle first two groups of errors.
1466 if (ec || !error_str.empty()) {
1468 .arg(config_->getThisServerName())
1469 .arg(query->getLabel())
1470 .arg(config->getLogLabel())
1471 .arg(ec ? ec.message() : error_str);
1472
1473 // Communication error, so let's drop parked packet. The DHCP
1474 // response will not be sent.
1475 lease_update_success = false;
1476
1477 } else {
1478
1479 try {
1480 int rcode = 0;
1481 auto args = verifyAsyncResponse(response, rcode);
1482 // In the v6 case the server may return a list of failed lease
1483 // updates and we should log them.
1484 logFailedLeaseUpdates(query, args);
1485
1486 } catch (const ConflictError& ex) {
1487 // Handle forth group of errors.
1488 lease_update_conflict = true;
1489 lease_update_success = false;
1490 communication_state_->reportRejectedLeaseUpdate(query);
1491
1493 .arg(config_->getThisServerName())
1494 .arg(query->getLabel())
1495 .arg(config->getLogLabel())
1496 .arg(ex.what());
1497
1498 } catch (const std::exception& ex) {
1499 // Handle third group of errors.
1501 .arg(config_->getThisServerName())
1502 .arg(query->getLabel())
1503 .arg(config->getLogLabel())
1504 .arg(ex.what());
1505
1506 // Error while doing an update. The DHCP response will not be sent.
1507 lease_update_success = false;
1508 }
1509 }
1510
1511 // We don't care about the result of the lease update to the backup server.
1512 // It is a best effort update.
1513 if (config->getRole() != HAConfig::PeerConfig::BACKUP) {
1514 // If the lease update was unsuccessful we may need to set the partner
1515 // state as unavailable.
1516 if (!lease_update_success) {
1517 // Do not set it as unavailable if it was a conflict because the
1518 // partner actually responded.
1519 if (!lease_update_conflict) {
1520 // If we were unable to communicate with the partner we set partner's
1521 // state as unavailable.
1522 communication_state_->setPartnerUnavailable();
1523 }
1524 } else {
1525 // Lease update successful and we may need to clear some previously
1526 // rejected lease updates.
1527 communication_state_->reportSuccessfulLeaseUpdate(query);
1528 }
1529 }
1530
1531 // It is possible to configure the server to not wait for a response from
1532 // the backup server before we unpark the packet and respond to the client.
1533 // Here we check if we're dealing with such situation.
1534 if (config_->amWaitingBackupAck() || (config->getRole() != HAConfig::PeerConfig::BACKUP)) {
1535 // We're expecting a response from the backup server or it is not
1536 // a backup server and the lease update was unsuccessful. In such
1537 // case the DHCP exchange fails.
1538 if (!lease_update_success) {
1539 if (parking_lot) {
1540 parking_lot->drop(query);
1541 }
1542 }
1543 } else {
1544 // This was a response from the backup server and we're configured to
1545 // not wait for their acknowledgments, so there is nothing more to do.
1546 return;
1547 }
1548
1549 if (leaseUpdateComplete(query, parking_lot)) {
1550 // If we have finished sending the lease updates we need to run the
1551 // state machine until the state machine finds that additional events
1552 // are required, such as next heartbeat or a lease update. The runModel()
1553 // may transition to another state, schedule asynchronous tasks etc.
1554 // Then it returns control to the DHCP server.
1556 }
1557 },
1559 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
1560 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
1561 std::bind(&HAService::clientCloseHandler, this, ph::_1)
1562 );
1563
1564 // The number of pending requests is the number of requests for which we
1565 // expect an acknowledgment prior to responding to the DHCP clients. If
1566 // we're configured to wait for the acks from the backups or it is not
1567 // a backup increase the number of pending requests.
1568 if (config_->amWaitingBackupAck() || (config->getRole() != HAConfig::PeerConfig::BACKUP)) {
1569 // Request scheduled, so update the request counters for the query.
1570 updatePendingRequest(query);
1571 }
1572}
1573
1574bool
1575HAService::shouldSendLeaseUpdates(const HAConfig::PeerConfigPtr& peer_config) const {
1576 // Never send lease updates if they are administratively disabled.
1577 if (!config_->amSendingLeaseUpdates()) {
1578 return (false);
1579 }
1580
1581 // Always send updates to the backup server.
1582 if (peer_config->getRole() == HAConfig::PeerConfig::BACKUP) {
1583 return (true);
1584 }
1585
1586 // Never send updates if this is a backup server.
1587 if (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::BACKUP) {
1588 return (false);
1589 }
1590
1591 // In other case, whether we send lease updates or not depends on our
1592 // state.
1593 switch (getCurrState()) {
1594 case HA_HOT_STANDBY_ST:
1597 return (true);
1598
1599 default:
1600 ;
1601 }
1602
1603 return (false);
1604}
1605
1606bool
1607HAService::shouldQueueLeaseUpdates(const HAConfig::PeerConfigPtr& peer_config) const {
1608 if (!config_->amSendingLeaseUpdates()) {
1609 return (false);
1610 }
1611
1612 if (peer_config->getRole() == HAConfig::PeerConfig::BACKUP) {
1613 return (false);
1614 }
1615
1616 return (getCurrState() == HA_COMMUNICATION_RECOVERY_ST);
1617}
1618
1619void
1620HAService::logFailedLeaseUpdates(const PktPtr& query,
1621 const ConstElementPtr& args) const {
1622 // If there are no arguments, it means that the update was successful.
1623 if (!args || (args->getType() != Element::map)) {
1624 return;
1625 }
1626
1627 // Instead of duplicating the code between the failed-deleted-leases and
1628 // failed-leases, let's just have one function that does it for both.
1629 auto log_proc = [](const PktPtr query, const ConstElementPtr& args,
1630 const std::string& param_name, const log::MessageID& mesid) {
1631
1632 // Check if there are any failed leases.
1633 auto failed_leases = args->get(param_name);
1634
1635 // The failed leases must be a list.
1636 if (failed_leases && (failed_leases->getType() == Element::list)) {
1637 // Go over the failed leases and log each of them.
1638 for (int i = 0; i < failed_leases->size(); ++i) {
1639 auto lease = failed_leases->get(i);
1640 if (lease->getType() == Element::map) {
1641
1642 // ip-address
1643 auto ip_address = lease->get("ip-address");
1644
1645 // lease type
1646 auto lease_type = lease->get("type");
1647
1648 // error-message
1649 auto error_message = lease->get("error-message");
1650
1651 LOG_INFO(ha_logger, mesid)
1652 .arg(query->getLabel())
1653 .arg(lease_type && (lease_type->getType() == Element::string) ?
1654 lease_type->stringValue() : "(unknown)")
1655 .arg(ip_address && (ip_address->getType() == Element::string) ?
1656 ip_address->stringValue() : "(unknown)")
1657 .arg(error_message && (error_message->getType() == Element::string) ?
1658 error_message->stringValue() : "(unknown)");
1659 }
1660 }
1661 }
1662 };
1663
1664 // Process "failed-deleted-leases"
1665 log_proc(query, args, "failed-deleted-leases", HA_LEASE_UPDATE_DELETE_FAILED_ON_PEER);
1666
1667 // Process "failed-leases".
1668 log_proc(query, args, "failed-leases", HA_LEASE_UPDATE_CREATE_UPDATE_FAILED_ON_PEER);
1669}
1670
1672HAService::processStatusGet() const {
1673 ElementPtr ha_servers = Element::createMap();
1674
1675 // Local part
1678 role = config_->getThisServerConfig()->getRole();
1679 std::string role_txt = HAConfig::PeerConfig::roleToString(role);
1680 local->set("role", Element::create(role_txt));
1681 int state = getCurrState();
1682 try {
1683 local->set("state", Element::create(stateToString(state)));
1684
1685 } catch (...) {
1686 // Empty string on error.
1687 local->set("state", Element::create(std::string()));
1688 }
1689 std::set<std::string> scopes = query_filter_.getServedScopes();
1691 for (auto const& scope : scopes) {
1692 list->add(Element::create(scope));
1693 }
1694 local->set("scopes", list);
1695 local->set("server-name", Element::create(config_->getThisServerName()));
1696 ha_servers->set("local", local);
1697
1698 // Do not include remote server information if this is a backup server or
1699 // we're in the passive-backup mode.
1700 if ((config_->getHAMode() == HAConfig::PASSIVE_BACKUP) ||
1701 (config_->getThisServerConfig()->getRole() == HAConfig::PeerConfig::BACKUP)) {
1702 return (ha_servers);
1703 }
1704
1705 // Remote part
1706 ElementPtr remote = communication_state_->getReport();
1707
1708 try {
1709 role = config_->getFailoverPeerConfig()->getRole();
1710 role_txt = HAConfig::PeerConfig::roleToString(role);
1711 remote->set("role", Element::create(role_txt));
1712
1713 } catch (...) {
1714 remote->set("role", Element::create(std::string()));
1715 }
1716 remote->set("server-name", Element::create(config_->getFailoverPeerConfig()->getName()));
1717 ha_servers->set("remote", remote);
1718
1719 return (ha_servers);
1720}
1721
1723HAService::processHeartbeat() {
1724 ElementPtr arguments = Element::createMap();
1725 std::string state_label = getState(getCurrState())->getLabel();
1726 arguments->set("state", Element::create(state_label));
1727
1728 std::string date_time = HttpDateTime().rfc1123Format();
1729 arguments->set("date-time", Element::create(date_time));
1730
1731 auto scopes = query_filter_.getServedScopes();
1732 ElementPtr scopes_list = Element::createList();
1733 for (auto const& scope : scopes) {
1734 scopes_list->add(Element::create(scope));
1735 }
1736 arguments->set("scopes", scopes_list);
1737
1738 arguments->set("unsent-update-count",
1739 Element::create(static_cast<int64_t>(communication_state_->getUnsentUpdateCount())));
1740
1741 return (createAnswer(CONTROL_RESULT_SUCCESS, "HA peer status returned.",
1742 arguments));
1743}
1744
1746HAService::processHAReset() {
1747 if (getCurrState() == HA_WAITING_ST) {
1748 return (createAnswer(CONTROL_RESULT_SUCCESS, "HA state machine already in WAITING state."));
1749 }
1750 verboseTransition(HA_WAITING_ST);
1751 runModel(NOP_EVT);
1752 return (createAnswer(CONTROL_RESULT_SUCCESS, "HA state machine reset."));
1753}
1754
1755void
1756HAService::asyncSendHeartbeat() {
1757 HAConfig::PeerConfigPtr partner_config = config_->getFailoverPeerConfig();
1758
1759 // If the sync_complete_notified_ is true it means that the partner
1760 // notified us that it had completed lease database synchronization.
1761 // We confirm that the partner is operational by sending the heartbeat
1762 // to it. Regardless if the partner responds to our heartbeats or not,
1763 // we should clear this flag. But, since we need the current value in
1764 // the async call handler, we save it in the local variable before
1765 // clearing it.
1766 bool sync_complete_notified = sync_complete_notified_;
1767 sync_complete_notified_ = false;
1768
1769 // Create HTTP/1.1 request including our command.
1770 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
1771 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
1772 HostHttpHeader(partner_config->getUrl().getStrippedHostname()));
1773 partner_config->addBasicAuthHttpHeader(request);
1774 request->setBodyAsJson(CommandCreator::createHeartbeat(config_->getThisServerName(),
1775 server_type_));
1776 request->finalize();
1777
1778 // Response object should also be created because the HTTP client needs
1779 // to know the type of the expected response.
1780 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
1781
1782 // Schedule asynchronous HTTP request.
1783 client_->asyncSendRequest(partner_config->getUrl(),
1784 partner_config->getTlsContext(),
1785 request, response,
1786 [this, partner_config, sync_complete_notified]
1787 (const boost::system::error_code& ec,
1788 const HttpResponsePtr& response,
1789 const std::string& error_str) {
1790
1791 // There are three possible groups of errors during the heartbeat.
1792 // One is the IO error causing issues in communication with the peer.
1793 // Another one is an HTTP parsing error. The last type of error is
1794 // when non-success error code is returned in the response carried
1795 // in the HTTP message or if the JSON response is otherwise broken.
1796
1797 bool heartbeat_success = true;
1798
1799 // Handle first two groups of errors.
1800 if (ec || !error_str.empty()) {
1801 LOG_WARN(ha_logger, HA_HEARTBEAT_COMMUNICATIONS_FAILED)
1802 .arg(config_->getThisServerName())
1803 .arg(partner_config->getLogLabel())
1804 .arg(ec ? ec.message() : error_str);
1805 heartbeat_success = false;
1806
1807 } else {
1808
1809 // Handle third group of errors.
1810 try {
1811 // Response must contain arguments and the arguments must
1812 // be a map.
1813 int rcode = 0;
1814 ConstElementPtr args = verifyAsyncResponse(response, rcode);
1815 if (!args || args->getType() != Element::map) {
1816 isc_throw(CtrlChannelError, "returned arguments in the response"
1817 " must be a map");
1818 }
1819 // Response must include partner's state.
1820 ConstElementPtr state = args->get("state");
1821 if (!state || state->getType() != Element::string) {
1822 isc_throw(CtrlChannelError, "server state not returned in response"
1823 " to a ha-heartbeat command or it is not a string");
1824 }
1825 // Remember the partner's state. This may throw if the returned
1826 // state is invalid.
1827 communication_state_->setPartnerState(state->stringValue());
1828
1829 ConstElementPtr date_time = args->get("date-time");
1830 if (!date_time || date_time->getType() != Element::string) {
1831 isc_throw(CtrlChannelError, "date-time not returned in response"
1832 " to a ha-heartbeat command or it is not a string");
1833 }
1834 // Note the time returned by the partner to calculate the clock skew.
1835 communication_state_->setPartnerTime(date_time->stringValue());
1836
1837 // Remember the scopes served by the partner.
1838 try {
1839 auto scopes = args->get("scopes");
1840 communication_state_->setPartnerScopes(scopes);
1841
1842 } catch (...) {
1843 // We don't want to fail if the scopes are missing because
1844 // this would be incompatible with old HA hook library
1845 // versions. We may make it mandatory one day, but during
1846 // upgrades of existing HA setup it would be a real issue
1847 // if we failed here.
1848 }
1849
1850 // unsent-update-count was not present in earlier HA versions.
1851 // Let's check if the partner has sent the parameter. We initialized
1852 // the counter to 0, and it remains 0 if the partner doesn't send it.
1853 // It effectively means that we don't track partner's unsent updates
1854 // as in the earlier HA versions.
1855 auto unsent_update_count = args->get("unsent-update-count");
1856 if (unsent_update_count) {
1857 if (unsent_update_count->getType() != Element::integer) {
1858 isc_throw(CtrlChannelError, "unsent-update-count returned in"
1859 " the ha-heartbeat response is not an integer");
1860 }
1861 communication_state_->setPartnerUnsentUpdateCount(static_cast<uint64_t>
1862 (unsent_update_count->intValue()));
1863 }
1864
1865 } catch (const std::exception& ex) {
1867 .arg(config_->getThisServerName())
1868 .arg(partner_config->getLogLabel())
1869 .arg(ex.what());
1870 heartbeat_success = false;
1871 }
1872 }
1873
1874 // If heartbeat was successful, let's mark the connection with the
1875 // peer as healthy.
1876 if (heartbeat_success) {
1877 communication_state_->poke();
1878
1879 } else {
1880 // We were unable to retrieve partner's state, so let's mark it
1881 // as unavailable.
1882 communication_state_->setPartnerUnavailable();
1883 // Log if the communication is interrupted.
1884 if (communication_state_->isCommunicationInterrupted()) {
1885 LOG_WARN(ha_logger, HA_COMMUNICATION_INTERRUPTED)
1886 .arg(config_->getThisServerName())
1887 .arg(partner_config->getName());
1888 }
1889 }
1890
1891 startHeartbeat();
1892 // Even though the partner notified us about the synchronization completion,
1893 // we still can't communicate with the partner. Let's continue serving
1894 // the clients until the link is fixed.
1895 if (sync_complete_notified && !heartbeat_success) {
1896 postNextEvent(HA_SYNCED_PARTNER_UNAVAILABLE_EVT);
1897 }
1898 // Whatever the result of the heartbeat was, the state machine needs
1899 // to react to this. Let's run the state machine until the state machine
1900 // finds that some new events are required, i.e. next heartbeat or
1901 // lease update. The runModel() may transition to another state, schedule
1902 // asynchronous tasks etc. Then it returns control to the DHCP server.
1903 runModel(HA_HEARTBEAT_COMPLETE_EVT);
1904 },
1906 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
1907 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
1908 std::bind(&HAService::clientCloseHandler, this, ph::_1)
1909 );
1910}
1911
1912void
1913HAService::scheduleHeartbeat() {
1914 if (!communication_state_->isHeartbeatRunning()) {
1915 startHeartbeat();
1916 }
1917}
1918
1919void
1920HAService::startHeartbeat() {
1921 if (config_->getHeartbeatDelay() > 0) {
1922 communication_state_->startHeartbeat(config_->getHeartbeatDelay(),
1923 std::bind(&HAService::asyncSendHeartbeat,
1924 this));
1925 }
1926}
1927
1928void
1929HAService::asyncDisableDHCPService(HttpClient& http_client,
1930 const HAConfig::PeerConfigPtr& remote_config,
1931 const unsigned int max_period,
1932 PostRequestCallback post_request_action) {
1933 // Create HTTP/1.1 request including our command.
1934 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
1935 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
1936 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
1937
1938 remote_config->addBasicAuthHttpHeader(request);
1939 request->setBodyAsJson(CommandCreator::createDHCPDisable(getRemoteOrigin(),
1940 max_period,
1941 server_type_));
1942 request->finalize();
1943
1944 // Response object should also be created because the HTTP client needs
1945 // to know the type of the expected response.
1946 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
1947
1948 // Schedule asynchronous HTTP request.
1949 http_client.asyncSendRequest(remote_config->getUrl(),
1950 remote_config->getTlsContext(),
1951 request, response,
1952 [this, remote_config, post_request_action]
1953 (const boost::system::error_code& ec,
1954 const HttpResponsePtr& response,
1955 const std::string& error_str) {
1956
1957 // There are three possible groups of errors during the heartbeat.
1958 // One is the IO error causing issues in communication with the peer.
1959 // Another one is an HTTP parsing error. The last type of error is
1960 // when non-success error code is returned in the response carried
1961 // in the HTTP message or if the JSON response is otherwise broken.
1962
1963 int rcode = 0;
1964 std::string error_message;
1965
1966 // Handle first two groups of errors.
1967 if (ec || !error_str.empty()) {
1968 error_message = (ec ? ec.message() : error_str);
1969 LOG_ERROR(ha_logger, HA_DHCP_DISABLE_COMMUNICATIONS_FAILED)
1970 .arg(config_->getThisServerName())
1971 .arg(remote_config->getLogLabel())
1972 .arg(error_message);
1973
1974 } else {
1975
1976 // Handle third group of errors.
1977 try {
1978 static_cast<void>(verifyAsyncResponse(response, rcode));
1979
1980 } catch (const std::exception& ex) {
1981 error_message = ex.what();
1983 .arg(config_->getThisServerName())
1984 .arg(remote_config->getLogLabel())
1985 .arg(error_message);
1986 }
1987 }
1988
1989 // If there was an error communicating with the partner, mark the
1990 // partner as unavailable.
1991 if (!error_message.empty()) {
1992 communication_state_->setPartnerUnavailable();
1993 }
1994
1995 // Invoke post request action if it was specified.
1996 if (post_request_action) {
1997 post_request_action(error_message.empty(),
1998 error_message,
1999 rcode);
2000 }
2001 },
2003 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
2004 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
2005 std::bind(&HAService::clientCloseHandler, this, ph::_1)
2006 );
2007}
2008
2009void
2010HAService::asyncEnableDHCPService(HttpClient& http_client,
2011 const HAConfig::PeerConfigPtr& remote_config,
2012 PostRequestCallback post_request_action) {
2013 // Create HTTP/1.1 request including our command.
2014 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2015 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2016 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
2017 remote_config->addBasicAuthHttpHeader(request);
2018 request->setBodyAsJson(CommandCreator::createDHCPEnable(getRemoteOrigin(),
2019 server_type_));
2020 request->finalize();
2021
2022 // Response object should also be created because the HTTP client needs
2023 // to know the type of the expected response.
2024 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2025
2026 // Schedule asynchronous HTTP request.
2027 http_client.asyncSendRequest(remote_config->getUrl(),
2028 remote_config->getTlsContext(),
2029 request, response,
2030 [this, remote_config, post_request_action]
2031 (const boost::system::error_code& ec,
2032 const HttpResponsePtr& response,
2033 const std::string& error_str) {
2034
2035 // There are three possible groups of errors during the heartbeat.
2036 // One is the IO error causing issues in communication with the peer.
2037 // Another one is an HTTP parsing error. The last type of error is
2038 // when non-success error code is returned in the response carried
2039 // in the HTTP message or if the JSON response is otherwise broken.
2040
2041 int rcode = 0;
2042 std::string error_message;
2043
2044 // Handle first two groups of errors.
2045 if (ec || !error_str.empty()) {
2046 error_message = (ec ? ec.message() : error_str);
2047 LOG_ERROR(ha_logger, HA_DHCP_ENABLE_COMMUNICATIONS_FAILED)
2048 .arg(config_->getThisServerName())
2049 .arg(remote_config->getLogLabel())
2050 .arg(error_message);
2051
2052 } else {
2053
2054 // Handle third group of errors.
2055 try {
2056 static_cast<void>(verifyAsyncResponse(response, rcode));
2057
2058 } catch (const std::exception& ex) {
2059 error_message = ex.what();
2061 .arg(config_->getThisServerName())
2062 .arg(remote_config->getLogLabel())
2063 .arg(error_message);
2064 }
2065 }
2066
2067 // If there was an error communicating with the partner, mark the
2068 // partner as unavailable.
2069 if (!error_message.empty()) {
2070 communication_state_->setPartnerUnavailable();
2071 }
2072
2073 // Invoke post request action if it was specified.
2074 if (post_request_action) {
2075 post_request_action(error_message.empty(),
2076 error_message,
2077 rcode);
2078 }
2079 },
2081 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
2082 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
2083 std::bind(&HAService::clientCloseHandler, this, ph::_1)
2084 );
2085}
2086
2087void
2088HAService::localDisableDHCPService() {
2089 network_state_->disableService(getLocalOrigin());
2090}
2091
2092void
2093HAService::localEnableDHCPService() {
2094 network_state_->enableService(getLocalOrigin());
2095}
2096
2097void
2098HAService::asyncSyncLeases() {
2099 PostSyncCallback null_action;
2100
2101 // Timeout is configured in milliseconds. Need to convert to seconds.
2102 unsigned int dhcp_disable_timeout =
2103 static_cast<unsigned int>(config_->getSyncTimeout() / 1000);
2104 if (dhcp_disable_timeout == 0) {
2105 // Ensure that we always use at least 1 second timeout.
2106 dhcp_disable_timeout = 1;
2107 }
2108
2109 lease_sync_filter_.apply();
2110 asyncSyncLeases(*client_, config_->getFailoverPeerConfig(),
2111 dhcp_disable_timeout, LeasePtr(), null_action);
2112}
2113
2114void
2115HAService::asyncSyncLeases(http::HttpClient& http_client,
2116 const HAConfig::PeerConfigPtr& remote_config,
2117 const unsigned int max_period,
2118 const dhcp::LeasePtr& last_lease,
2119 PostSyncCallback post_sync_action,
2120 const bool dhcp_disabled) {
2121 // Synchronization starts with a command to disable DHCP service of the
2122 // peer from which we're fetching leases. We don't want the other server
2123 // to allocate new leases while we fetch from it. The DHCP service will
2124 // be disabled for a certain amount of time and will be automatically
2125 // re-enabled if we die during the synchronization.
2126 asyncDisableDHCPService(http_client, remote_config, max_period,
2127 [this, &http_client, remote_config, max_period, last_lease,
2128 post_sync_action, dhcp_disabled]
2129 (const bool success, const std::string& error_message, const int) {
2130
2131 // If we have successfully disabled the DHCP service on the peer,
2132 // we can start fetching the leases.
2133 if (success) {
2134 // The last argument indicates that disabling the DHCP
2135 // service on the partner server was successful.
2136 asyncSyncLeasesInternal(http_client, remote_config, max_period,
2137 last_lease, post_sync_action, true);
2138
2139 } else {
2140 post_sync_action(success, error_message, dhcp_disabled);
2141 }
2142 });
2143}
2144
2145void
2146HAService::asyncSyncLeasesInternal(http::HttpClient& http_client,
2147 const HAConfig::PeerConfigPtr& remote_config,
2148 const unsigned int max_period,
2149 const dhcp::LeasePtr& last_lease,
2150 PostSyncCallback post_sync_action,
2151 const bool dhcp_disabled) {
2152 // Create HTTP/1.1 request including our command.
2153 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2154 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2155 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
2156 remote_config->addBasicAuthHttpHeader(request);
2157 if (server_type_ == HAServerType::DHCPv4) {
2158 request->setBodyAsJson(CommandCreator::createLease4GetPage(
2159 boost::dynamic_pointer_cast<Lease4>(last_lease), config_->getSyncPageLimit()));
2160
2161 } else {
2162 request->setBodyAsJson(CommandCreator::createLease6GetPage(
2163 boost::dynamic_pointer_cast<Lease6>(last_lease), config_->getSyncPageLimit()));
2164 }
2165 request->finalize();
2166
2167 // Response object should also be created because the HTTP client needs
2168 // to know the type of the expected response.
2169 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2170
2171 // Schedule asynchronous HTTP request.
2172 http_client.asyncSendRequest(remote_config->getUrl(),
2173 remote_config->getTlsContext(),
2174 request, response,
2175 [this, remote_config, post_sync_action, &http_client, max_period, dhcp_disabled]
2176 (const boost::system::error_code& ec,
2177 const HttpResponsePtr& response,
2178 const std::string& error_str) {
2179
2180 // Holds last lease received on the page of leases. If the last
2181 // page was hit, this value remains null.
2182 LeasePtr last_lease;
2183
2184 // There are three possible groups of errors during the heartbeat.
2185 // One is the IO error causing issues in communication with the peer.
2186 // Another one is an HTTP parsing error. The last type of error is
2187 // when non-success error code is returned in the response carried
2188 // in the HTTP message or if the JSON response is otherwise broken.
2189
2190 std::string error_message;
2191
2192 // Handle first two groups of errors.
2193 if (ec || !error_str.empty()) {
2194 error_message = (ec ? ec.message() : error_str);
2195 LOG_ERROR(ha_logger, HA_LEASES_SYNC_COMMUNICATIONS_FAILED)
2196 .arg(config_->getThisServerName())
2197 .arg(remote_config->getLogLabel())
2198 .arg(error_message);
2199
2200 } else {
2201 // Handle third group of errors.
2202 try {
2203 int rcode = 0;
2204 ConstElementPtr args = verifyAsyncResponse(response, rcode);
2205
2206 // Arguments must be a map.
2207 if (args && (args->getType() != Element::map)) {
2208 isc_throw(CtrlChannelError,
2209 "arguments in the received response must be a map");
2210 }
2211
2212 ConstElementPtr leases = args->get("leases");
2213 if (!leases || (leases->getType() != Element::list)) {
2214 isc_throw(CtrlChannelError,
2215 "server response does not contain leases argument or this"
2216 " argument is not a list");
2217 }
2218
2219 // Iterate over the leases and update the database as appropriate.
2220 auto const& leases_element = leases->listValue();
2221
2222 LOG_INFO(ha_logger, HA_LEASES_SYNC_LEASE_PAGE_RECEIVED)
2223 .arg(config_->getThisServerName())
2224 .arg(leases_element.size())
2225 .arg(remote_config->getLogLabel());
2226
2227 // Count actually applied leases.
2228 uint64_t applied_lease_count = 0;
2229 for (auto l = leases_element.begin(); l != leases_element.end(); ++l) {
2230 try {
2231
2232 if (server_type_ == HAServerType::DHCPv4) {
2233 Lease4Ptr lease = Lease4::fromElement(*l);
2234
2235 // If we're not on the last page and we're processing final lease on
2236 // this page, let's record the lease as input to the next
2237 // lease4-get-page command.
2238 if ((leases_element.size() >= config_->getSyncPageLimit()) &&
2239 (l + 1 == leases_element.end())) {
2240 last_lease = boost::dynamic_pointer_cast<Lease>(lease);
2241 }
2242
2243 if (!lease_sync_filter_.shouldSync(lease)) {
2244 continue;
2245 }
2246
2247 // Check if there is such lease in the database already.
2248 Lease4Ptr existing_lease = LeaseMgrFactory::instance().getLease4(lease->addr_);
2249 if (!existing_lease) {
2250 // There is no such lease, so let's add it.
2251 LeaseMgrFactory::instance().addLease(lease);
2252 ++applied_lease_count;
2253
2254 } else if (existing_lease->cltt_ < lease->cltt_) {
2255 // If the existing lease is older than the fetched lease, update
2256 // the lease in our local database.
2257 // Update lease current expiration time with value received from the
2258 // database. Some database backends reject operations on the lease if
2259 // the current expiration time value does not match what is stored.
2260 Lease::syncCurrentExpirationTime(*existing_lease, *lease);
2261 LeaseMgrFactory::instance().updateLease4(lease);
2262 ++applied_lease_count;
2263
2264 } else {
2265 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASE_SYNC_STALE_LEASE4_SKIP)
2266 .arg(config_->getThisServerName())
2267 .arg(lease->addr_.toText())
2268 .arg(lease->subnet_id_);
2269 }
2270
2271 } else {
2272 Lease6Ptr lease = Lease6::fromElement(*l);
2273
2274 // If we're not on the last page and we're processing final lease on
2275 // this page, let's record the lease as input to the next
2276 // lease6-get-page command.
2277 if ((leases_element.size() >= config_->getSyncPageLimit()) &&
2278 (l + 1 == leases_element.end())) {
2279 last_lease = boost::dynamic_pointer_cast<Lease>(lease);
2280 }
2281
2282 if (!lease_sync_filter_.shouldSync(lease)) {
2283 continue;
2284 }
2285
2286 // Check if there is such lease in the database already.
2287 Lease6Ptr existing_lease = LeaseMgrFactory::instance().getLease6(lease->type_,
2288 lease->addr_);
2289 if (!existing_lease) {
2290 // There is no such lease, so let's add it.
2291 LeaseMgrFactory::instance().addLease(lease);
2292 ++applied_lease_count;
2293
2294 } else if (existing_lease->cltt_ < lease->cltt_) {
2295 // If the existing lease is older than the fetched lease, update
2296 // the lease in our local database.
2297 // Update lease current expiration time with value received from the
2298 // database. Some database backends reject operations on the lease if
2299 // the current expiration time value does not match what is stored.
2300 Lease::syncCurrentExpirationTime(*existing_lease, *lease);
2301 LeaseMgrFactory::instance().updateLease6(lease);
2302 ++applied_lease_count;
2303
2304 } else {
2305 LOG_DEBUG(ha_logger, DBGLVL_TRACE_BASIC, HA_LEASE_SYNC_STALE_LEASE6_SKIP)
2306 .arg(config_->getThisServerName())
2307 .arg(lease->addr_.toText())
2308 .arg(lease->subnet_id_);
2309 }
2310 }
2311
2312 } catch (const std::exception& ex) {
2313 LOG_WARN(ha_logger, HA_LEASE_SYNC_FAILED)
2314 .arg(config_->getThisServerName())
2315 .arg((*l)->str())
2316 .arg(ex.what());
2317 }
2318 }
2319
2320 LOG_INFO(ha_logger, HA_LEASES_SYNC_APPLIED_LEASES)
2321 .arg(config_->getThisServerName())
2322 .arg(applied_lease_count);
2323
2324 } catch (const std::exception& ex) {
2325 error_message = ex.what();
2327 .arg(config_->getThisServerName())
2328 .arg(remote_config->getLogLabel())
2329 .arg(error_message);
2330 }
2331 }
2332
2333 // If there was an error communicating with the partner, mark the
2334 // partner as unavailable.
2335 if (!error_message.empty()) {
2336 communication_state_->setPartnerUnavailable();
2337
2338 } else if (last_lease) {
2339 // This indicates that there are more leases to be fetched.
2340 // Therefore, we have to send another leaseX-get-page command.
2341 asyncSyncLeases(http_client, remote_config, max_period, last_lease,
2342 post_sync_action, dhcp_disabled);
2343 return;
2344 }
2345
2346 // Invoke post synchronization action if it was specified.
2347 if (post_sync_action) {
2348 post_sync_action(error_message.empty(),
2349 error_message,
2350 dhcp_disabled);
2351 }
2352 },
2353 HttpClient::RequestTimeout(config_->getSyncTimeout()),
2354 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
2355 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
2356 std::bind(&HAService::clientCloseHandler, this, ph::_1)
2357 );
2358
2359}
2360
2362HAService::processSynchronize(const std::string& server_name,
2363 const unsigned int max_period) {
2364 HAConfig::PeerConfigPtr remote_config;
2365 try {
2366 remote_config = config_->getPeerConfig(server_name);
2367 } catch (const std::exception& ex) {
2368 return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
2369 }
2370 // We must not synchronize with self.
2371 if (remote_config->getName() == config_->getThisServerName()) {
2372 return (createAnswer(CONTROL_RESULT_ERROR, "'" + remote_config->getName()
2373 + "' points to local server but should point to a partner"));
2374 }
2375 std::string answer_message;
2376 int sync_status = synchronize(answer_message, remote_config, max_period);
2377 return (createAnswer(sync_status, answer_message));
2378}
2379
2380int
2381HAService::synchronize(std::string& status_message,
2382 const HAConfig::PeerConfigPtr& remote_config,
2383 const unsigned int max_period) {
2384 lease_sync_filter_.apply();
2385
2386 IOServicePtr io_service(new IOService());
2387 HttpClient client(io_service, false);
2388
2389 asyncSyncLeases(client, remote_config, max_period, Lease4Ptr(),
2390 [&](const bool success, const std::string& error_message,
2391 const bool dhcp_disabled) {
2392 // If there was a fatal error while fetching the leases, let's
2393 // log an error message so as it can be included in the response
2394 // to the controlling client.
2395 if (!success) {
2396 status_message = error_message;
2397 }
2398
2399 // Whether or not there was an error while fetching the leases,
2400 // we need to re-enable the DHCP service on the peer if the
2401 // DHCP service was disabled in the course of synchronization.
2402 if (dhcp_disabled) {
2403 // If the synchronization was completed successfully let's
2404 // try to send the ha-sync-complete-notify command to the
2405 // partner.
2406 if (success) {
2407 asyncSyncCompleteNotify(client, remote_config,
2408 [&](const bool success,
2409 const std::string& error_message,
2410 const int rcode) {
2411 // This command may not be supported by the partner when it
2412 // runs an older Kea version. In that case, send the dhcp-enable
2413 // command as in previous Kea version.
2415 asyncEnableDHCPService(client, remote_config,
2416 [&](const bool success,
2417 const std::string& error_message,
2418 const int) {
2419 // It is possible that we have already recorded an error
2420 // message while synchronizing the lease database. Don't
2421 // override the existing error message.
2422 if (!success && status_message.empty()) {
2423 status_message = error_message;
2424 }
2425
2426 // The synchronization process is completed, so let's break
2427 // the IO service so as we can return the response to the
2428 // controlling client.
2429 io_service->stop();
2430 });
2431
2432 } else {
2433 // ha-sync-complete-notify command was delivered to the partner.
2434 // The synchronization process ends here.
2435 if (!success && status_message.empty()) {
2436 status_message = error_message;
2437 }
2438
2439 io_service->stop();
2440 }
2441 });
2442
2443 } else {
2444 // Synchronization was unsuccessful. Send the dhcp-enable command to
2445 // re-enable the DHCP service. Note, that we don't send the
2446 // ha-sync-complete-notify command in this case. It is only sent in
2447 // the case when synchronization ends successfully.
2448 asyncEnableDHCPService(client, remote_config,
2449 [&](const bool success,
2450 const std::string& error_message,
2451 const int) {
2452 if (!success && status_message.empty()) {
2453 status_message = error_message;
2454 }
2455
2456 // The synchronization process is completed, so let's break
2457 // the IO service so as we can return the response to the
2458 // controlling client.
2459 io_service->stop();
2460
2461 });
2462 }
2463
2464 } else {
2465 // Also stop IO service if there is no need to enable DHCP
2466 // service.
2467 io_service->stop();
2468 }
2469 });
2470
2472 .arg(config_->getThisServerName())
2473 .arg(remote_config->getLogLabel());
2474
2475 // Measure duration of the synchronization.
2476 Stopwatch stopwatch;
2477
2478 // Run the IO service until it is stopped by any of the callbacks. This
2479 // makes it synchronous.
2480 io_service->run();
2481
2482 // End measuring duration.
2483 stopwatch.stop();
2484
2485 client.stop();
2486
2487 io_service->stopAndPoll();
2488
2489 // If an error message has been recorded, return an error to the controlling
2490 // client.
2491 if (!status_message.empty()) {
2492 postNextEvent(HA_SYNCING_FAILED_EVT);
2493
2495 .arg(config_->getThisServerName())
2496 .arg(remote_config->getLogLabel())
2497 .arg(status_message);
2498
2499 return (CONTROL_RESULT_ERROR);
2500
2501 }
2502
2503 // Everything was fine, so let's return a success.
2504 status_message = "Lease database synchronization complete.";
2505 postNextEvent(HA_SYNCING_SUCCEEDED_EVT);
2506
2508 .arg(config_->getThisServerName())
2509 .arg(remote_config->getLogLabel())
2510 .arg(stopwatch.logFormatLastDuration());
2511
2512 return (CONTROL_RESULT_SUCCESS);
2513}
2514
2515void
2516HAService::asyncSendLeaseUpdatesFromBacklog(HttpClient& http_client,
2517 const HAConfig::PeerConfigPtr& config,
2518 PostRequestCallback post_request_action) {
2519 if (lease_update_backlog_.size() == 0) {
2520 post_request_action(true, "", CONTROL_RESULT_SUCCESS);
2521 return;
2522 }
2523
2524 ConstElementPtr command;
2525 if (server_type_ == HAServerType::DHCPv4) {
2527 Lease4Ptr lease = boost::dynamic_pointer_cast<Lease4>(lease_update_backlog_.pop(op_type));
2528 if (op_type == LeaseUpdateBacklog::ADD) {
2529 command = CommandCreator::createLease4Update(*lease);
2530 } else {
2531 command = CommandCreator::createLease4Delete(*lease);
2532 }
2533
2534 } else {
2535 command = CommandCreator::createLease6BulkApply(lease_update_backlog_);
2536 }
2537
2538 // Create HTTP/1.1 request including our command.
2539 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2540 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2541 HostHttpHeader(config->getUrl().getStrippedHostname()));
2542 config->addBasicAuthHttpHeader(request);
2543 request->setBodyAsJson(command);
2544 request->finalize();
2545
2546 // Response object should also be created because the HTTP client needs
2547 // to know the type of the expected response.
2548 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2549
2550 http_client.asyncSendRequest(config->getUrl(), config->getTlsContext(),
2551 request, response,
2552 [this, &http_client, config, post_request_action]
2553 (const boost::system::error_code& ec,
2554 const HttpResponsePtr& response,
2555 const std::string& error_str) {
2556
2557 int rcode = 0;
2558 std::string error_message;
2559
2560 if (ec || !error_str.empty()) {
2561 error_message = (ec ? ec.message() : error_str);
2562 LOG_WARN(ha_logger, HA_LEASES_BACKLOG_COMMUNICATIONS_FAILED)
2563 .arg(config_->getThisServerName())
2564 .arg(config->getLogLabel())
2565 .arg(ec ? ec.message() : error_str);
2566
2567 } else {
2568 // Handle third group of errors.
2569 try {
2570 auto args = verifyAsyncResponse(response, rcode);
2571 } catch (const std::exception& ex) {
2572 error_message = ex.what();
2574 .arg(config_->getThisServerName())
2575 .arg(config->getLogLabel())
2576 .arg(ex.what());
2577 }
2578 }
2579
2580 // Recursively send all outstanding lease updates or break when an
2581 // error occurs. In DHCPv6, this is a single iteration because we use
2582 // lease6-bulk-apply, which combines many lease updates in a single
2583 // transaction. In the case of DHCPv4, each update is sent in its own
2584 // transaction.
2585 if (error_message.empty()) {
2586 asyncSendLeaseUpdatesFromBacklog(http_client, config, post_request_action);
2587 } else {
2588 post_request_action(error_message.empty(), error_message, rcode);
2589 }
2590 });
2591}
2592
2593bool
2594HAService::sendLeaseUpdatesFromBacklog() {
2595 auto num_updates = lease_update_backlog_.size();
2596 if (num_updates == 0) {
2598 .arg(config_->getThisServerName());
2599 return (true);
2600 }
2601
2602 IOServicePtr io_service(new IOService());
2603 HttpClient client(io_service, false);
2604 auto remote_config = config_->getFailoverPeerConfig();
2605 bool updates_successful = true;
2606
2608 .arg(config_->getThisServerName())
2609 .arg(num_updates)
2610 .arg(remote_config->getName());
2611
2612 asyncSendLeaseUpdatesFromBacklog(client, remote_config,
2613 [&](const bool success, const std::string&, const int) {
2614 io_service->stop();
2615 updates_successful = success;
2616 });
2617
2618 // Measure duration of the updates.
2619 Stopwatch stopwatch;
2620
2621 // Run the IO service until it is stopped by the callback. This makes it synchronous.
2622 io_service->run();
2623
2624 // End measuring duration.
2625 stopwatch.stop();
2626
2627 client.stop();
2628
2629 io_service->stopAndPoll();
2630
2631 if (updates_successful) {
2633 .arg(config_->getThisServerName())
2634 .arg(remote_config->getName())
2635 .arg(stopwatch.logFormatLastDuration());
2636 }
2637
2638 return (updates_successful);
2639}
2640
2641void
2642HAService::asyncSendHAReset(HttpClient& http_client,
2643 const HAConfig::PeerConfigPtr& config,
2644 PostRequestCallback post_request_action) {
2645 ConstElementPtr command = CommandCreator::createHAReset(config_->getThisServerName(),
2646 server_type_);
2647
2648 // Create HTTP/1.1 request including our command.
2649 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2650 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2651 HostHttpHeader(config->getUrl().getStrippedHostname()));
2652 config->addBasicAuthHttpHeader(request);
2653 request->setBodyAsJson(command);
2654 request->finalize();
2655
2656 // Response object should also be created because the HTTP client needs
2657 // to know the type of the expected response.
2658 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2659
2660 http_client.asyncSendRequest(config->getUrl(), config->getTlsContext(),
2661 request, response,
2662 [this, config, post_request_action]
2663 (const boost::system::error_code& ec,
2664 const HttpResponsePtr& response,
2665 const std::string& error_str) {
2666
2667 int rcode = 0;
2668 std::string error_message;
2669
2670 if (ec || !error_str.empty()) {
2671 error_message = (ec ? ec.message() : error_str);
2672 LOG_WARN(ha_logger, HA_RESET_COMMUNICATIONS_FAILED)
2673 .arg(config_->getThisServerName())
2674 .arg(config->getLogLabel())
2675 .arg(ec ? ec.message() : error_str);
2676
2677 } else {
2678 // Handle third group of errors.
2679 try {
2680 auto args = verifyAsyncResponse(response, rcode);
2681 } catch (const std::exception& ex) {
2682 error_message = ex.what();
2684 .arg(config_->getThisServerName())
2685 .arg(config->getLogLabel())
2686 .arg(ex.what());
2687 }
2688 }
2689
2690 post_request_action(error_message.empty(), error_message, rcode);
2691 });
2692}
2693
2694bool
2695HAService::sendHAReset() {
2696 IOServicePtr io_service(new IOService());
2697 HttpClient client(io_service, false);
2698 auto remote_config = config_->getFailoverPeerConfig();
2699 bool reset_successful = true;
2700
2701 asyncSendHAReset(client, remote_config,
2702 [&](const bool success, const std::string&, const int) {
2703 io_service->stop();
2704 reset_successful = success;
2705 });
2706
2707 // Run the IO service until it is stopped by the callback. This makes it synchronous.
2708 io_service->run();
2709
2710 client.stop();
2711
2712 io_service->stopAndPoll();
2713
2714 return (reset_successful);
2715}
2716
2718HAService::processScopes(const std::vector<std::string>& scopes) {
2719 try {
2720 query_filter_.serveScopes(scopes);
2721 adjustNetworkState();
2722
2723 } catch (const std::exception& ex) {
2724 return (createAnswer(CONTROL_RESULT_ERROR, ex.what()));
2725 }
2726
2727 return (createAnswer(CONTROL_RESULT_SUCCESS, "New HA scopes configured."));
2728}
2729
2731HAService::processContinue() {
2732 if (unpause()) {
2733 return (createAnswer(CONTROL_RESULT_SUCCESS, "HA state machine continues."));
2734 }
2735 return (createAnswer(CONTROL_RESULT_SUCCESS, "HA state machine is not paused."));
2736}
2737
2739HAService::processMaintenanceNotify(const bool cancel) {
2740 if (cancel) {
2741 if (getCurrState() != HA_IN_MAINTENANCE_ST) {
2742 return (createAnswer(CONTROL_RESULT_ERROR, "Unable to cancel the"
2743 " maintenance for the server not in the"
2744 " in-maintenance state."));
2745 }
2746
2747 postNextEvent(HA_MAINTENANCE_CANCEL_EVT);
2748 verboseTransition(getPrevState());
2749 runModel(NOP_EVT);
2750 return (createAnswer(CONTROL_RESULT_SUCCESS, "Server maintenance canceled."));
2751 }
2752
2753 switch (getCurrState()) {
2754 case HA_BACKUP_ST:
2756 case HA_TERMINATED_ST:
2757 // The reason why we don't return an error result here is that we have to
2758 // have a way to distinguish between the errors caused by the communication
2759 // issues and the cases when there is no communication error but the server
2760 // is not allowed to enter the in-maintenance state. In the former case, the
2761 // partner would go to partner-down. In the case signaled by the special
2762 // result code entering the maintenance state is not allowed.
2763 return (createAnswer(HA_CONTROL_RESULT_MAINTENANCE_NOT_ALLOWED,
2764 "Unable to transition the server from the "
2765 + stateToString(getCurrState()) + " to"
2766 " in-maintenance state."));
2767 default:
2768 verboseTransition(HA_IN_MAINTENANCE_ST);
2769 runModel(HA_MAINTENANCE_NOTIFY_EVT);
2770 }
2771 return (createAnswer(CONTROL_RESULT_SUCCESS, "Server is in-maintenance state."));
2772}
2773
2775HAService::processMaintenanceStart() {
2776 switch (getCurrState()) {
2777 case HA_BACKUP_ST:
2780 case HA_TERMINATED_ST:
2781 return (createAnswer(CONTROL_RESULT_ERROR, "Unable to transition the server from"
2782 " the " + stateToString(getCurrState()) + " to"
2783 " partner-in-maintenance state."));
2784 default:
2785 ;
2786 }
2787
2788 HAConfig::PeerConfigPtr remote_config = config_->getFailoverPeerConfig();
2789
2790 // Create HTTP/1.1 request including ha-maintenance-notify command
2791 // with the cancel flag set to false.
2792 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2793 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2794 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
2795 remote_config->addBasicAuthHttpHeader(request);
2796 request->setBodyAsJson(CommandCreator::createMaintenanceNotify(config_->getThisServerName(),
2797 false, server_type_));
2798 request->finalize();
2799
2800 // Response object should also be created because the HTTP client needs
2801 // to know the type of the expected response.
2802 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2803
2804 IOServicePtr io_service(new IOService());
2805 HttpClient client(io_service, false);
2806
2807 boost::system::error_code captured_ec;
2808 std::string captured_error_message;
2809 int captured_rcode = 0;
2810
2811 // Schedule asynchronous HTTP request.
2812 client.asyncSendRequest(remote_config->getUrl(),
2813 remote_config->getTlsContext(),
2814 request, response,
2815 [this, remote_config, &io_service, &captured_ec, &captured_error_message,
2816 &captured_rcode]
2817 (const boost::system::error_code& ec,
2818 const HttpResponsePtr& response,
2819 const std::string& error_str) {
2820
2821 io_service->stop();
2822
2823 // There are three possible groups of errors. One is the IO error
2824 // causing issues in communication with the peer. Another one is
2825 // an HTTP parsing error. The last type of error is when non-success
2826 // error code is returned in the response carried in the HTTP message
2827 // or if the JSON response is otherwise broken.
2828
2829 std::string error_message;
2830
2831 // Handle first two groups of errors.
2832 if (ec || !error_str.empty()) {
2833 error_message = (ec ? ec.message() : error_str);
2834 LOG_ERROR(ha_logger, HA_MAINTENANCE_NOTIFY_COMMUNICATIONS_FAILED)
2835 .arg(config_->getThisServerName())
2836 .arg(remote_config->getLogLabel())
2837 .arg(error_message);
2838
2839 } else {
2840
2841 // Handle third group of errors.
2842 try {
2843 static_cast<void>(verifyAsyncResponse(response, captured_rcode));
2844
2845 } catch (const std::exception& ex) {
2846 error_message = ex.what();
2848 .arg(config_->getThisServerName())
2849 .arg(remote_config->getLogLabel())
2850 .arg(error_message);
2851 }
2852 }
2853
2854 // If there was an error communicating with the partner, mark the
2855 // partner as unavailable.
2856 if (!error_message.empty()) {
2857 communication_state_->setPartnerUnavailable();
2858 }
2859
2860 captured_ec = ec;
2861 captured_error_message = error_message;
2862 },
2864 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
2865 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
2866 std::bind(&HAService::clientCloseHandler, this, ph::_1)
2867 );
2868
2869 // Run the IO service until it is stopped by any of the callbacks. This
2870 // makes it synchronous.
2871 io_service->run();
2872
2873 client.stop();
2874
2875 io_service->stopAndPoll();
2876
2877 // If there was a communication problem with the partner we assume that
2878 // the partner is already down while we receive this command.
2879 if (captured_ec || (captured_rcode == CONTROL_RESULT_ERROR)) {
2880 postNextEvent(HA_MAINTENANCE_START_EVT);
2881 verboseTransition(HA_PARTNER_DOWN_ST);
2882 runModel(NOP_EVT);
2884 "Server is now in the partner-down state as its"
2885 " partner appears to be offline for maintenance."));
2886
2887 } else if (captured_rcode == CONTROL_RESULT_SUCCESS) {
2888 // If the partner responded indicating no error it means that the
2889 // partner has been transitioned to the in-maintenance state. In that
2890 // case we transition to the partner-in-maintenance state.
2891 postNextEvent(HA_MAINTENANCE_START_EVT);
2892 verboseTransition(HA_PARTNER_IN_MAINTENANCE_ST);
2893 runModel(NOP_EVT);
2894
2895 } else {
2896 // Partner server returned a special status code which means that it can't
2897 // transition to the partner-in-maintenance state.
2898 return (createAnswer(CONTROL_RESULT_ERROR, "Unable to transition to the"
2899 " partner-in-maintenance state. The partner server responded"
2900 " with the following message to the ha-maintenance-notify"
2901 " command: " + captured_error_message + "."));
2902
2903 }
2904
2906 "Server is now in the partner-in-maintenance state"
2907 " and its partner is in-maintenance state. The partner"
2908 " can be now safely shut down."));
2909}
2910
2912HAService::processMaintenanceCancel() {
2913 if (getCurrState() != HA_PARTNER_IN_MAINTENANCE_ST) {
2914 return (createAnswer(CONTROL_RESULT_ERROR, "Unable to cancel maintenance"
2915 " request because the server is not in the"
2916 " partner-in-maintenance state."));
2917 }
2918
2919 HAConfig::PeerConfigPtr remote_config = config_->getFailoverPeerConfig();
2920
2921 // Create HTTP/1.1 request including ha-maintenance-notify command
2922 // with the cancel flag set to true.
2923 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
2924 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
2925 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
2926 remote_config->addBasicAuthHttpHeader(request);
2927 request->setBodyAsJson(CommandCreator::createMaintenanceNotify(config_->getThisServerName(),
2928 true, server_type_));
2929 request->finalize();
2930
2931 // Response object should also be created because the HTTP client needs
2932 // to know the type of the expected response.
2933 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
2934
2935 IOServicePtr io_service(new IOService());
2936 HttpClient client(io_service, false);
2937
2938 std::string error_message;
2939
2940 // Schedule asynchronous HTTP request.
2941 client.asyncSendRequest(remote_config->getUrl(),
2942 remote_config->getTlsContext(),
2943 request, response,
2944 [this, remote_config, &io_service, &error_message]
2945 (const boost::system::error_code& ec,
2946 const HttpResponsePtr& response,
2947 const std::string& error_str) {
2948
2949 io_service->stop();
2950
2951 // Handle first two groups of errors.
2952 if (ec || !error_str.empty()) {
2953 error_message = (ec ? ec.message() : error_str);
2954 LOG_ERROR(ha_logger, HA_MAINTENANCE_NOTIFY_CANCEL_COMMUNICATIONS_FAILED)
2955 .arg(config_->getThisServerName())
2956 .arg(remote_config->getLogLabel())
2957 .arg(error_message);
2958
2959 } else {
2960
2961 // Handle third group of errors.
2962 try {
2963 int rcode = 0;
2964 static_cast<void>(verifyAsyncResponse(response, rcode));
2965
2966 } catch (const std::exception& ex) {
2967 error_message = ex.what();
2969 .arg(config_->getThisServerName())
2970 .arg(remote_config->getLogLabel())
2971 .arg(error_message);
2972 }
2973 }
2974
2975 // If there was an error communicating with the partner, mark the
2976 // partner as unavailable.
2977 if (!error_message.empty()) {
2978 communication_state_->setPartnerUnavailable();
2979 }
2980 },
2982 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
2983 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
2984 std::bind(&HAService::clientCloseHandler, this, ph::_1)
2985 );
2986
2987 // Run the IO service until it is stopped by any of the callbacks. This
2988 // makes it synchronous.
2989 io_service->run();
2990
2991 client.stop();
2992
2993 io_service->stopAndPoll();
2994
2995 // There was an error in communication with the partner or the
2996 // partner was unable to revert its state.
2997 if (!error_message.empty()) {
2999 "Unable to cancel maintenance. The partner server responded"
3000 " with the following message to the ha-maintenance-notify"
3001 " command: " + error_message + "."));
3002 }
3003
3004 // Successfully reverted partner's state. Let's also revert our state to the
3005 // previous one.
3006 postNextEvent(HA_MAINTENANCE_CANCEL_EVT);
3007 verboseTransition(getPrevState());
3008 runModel(NOP_EVT);
3009
3011 "Server maintenance successfully canceled."));
3012}
3013
3014void
3015HAService::asyncSyncCompleteNotify(HttpClient& http_client,
3016 const HAConfig::PeerConfigPtr& remote_config,
3017 PostRequestCallback post_request_action) {
3018 // Create HTTP/1.1 request including our command.
3019 PostHttpRequestJsonPtr request = boost::make_shared<PostHttpRequestJson>
3020 (HttpRequest::Method::HTTP_POST, "/", HttpVersion::HTTP_11(),
3021 HostHttpHeader(remote_config->getUrl().getStrippedHostname()));
3022
3023 remote_config->addBasicAuthHttpHeader(request);
3024 request->setBodyAsJson(CommandCreator::createSyncCompleteNotify(getRemoteOrigin(),
3025 config_->getThisServerName(),
3026 server_type_));
3027 request->finalize();
3028
3029 // Response object should also be created because the HTTP client needs
3030 // to know the type of the expected response.
3031 HttpResponseJsonPtr response = boost::make_shared<HttpResponseJson>();
3032
3033 // Schedule asynchronous HTTP request.
3034 http_client.asyncSendRequest(remote_config->getUrl(),
3035 remote_config->getTlsContext(),
3036 request, response,
3037 [this, remote_config, post_request_action]
3038 (const boost::system::error_code& ec,
3039 const HttpResponsePtr& response,
3040 const std::string& error_str) {
3041
3042 // There are three possible groups of errors. One is the IO error
3043 // causing issues in communication with the peer. Another one is an
3044 // HTTP parsing error. The last type of error is when non-success
3045 // error code is returned in the response carried in the HTTP message
3046 // or if the JSON response is otherwise broken.
3047
3048 int rcode = 0;
3049 std::string error_message;
3050
3051 // Handle first two groups of errors.
3052 if (ec || !error_str.empty()) {
3053 error_message = (ec ? ec.message() : error_str);
3054 LOG_ERROR(ha_logger, HA_SYNC_COMPLETE_NOTIFY_COMMUNICATIONS_FAILED)
3055 .arg(config_->getThisServerName())
3056 .arg(remote_config->getLogLabel())
3057 .arg(error_message);
3058
3059 } else {
3060
3061 // Handle third group of errors.
3062 try {
3063 static_cast<void>(verifyAsyncResponse(response, rcode));
3064
3065 } catch (const CommandUnsupportedError& ex) {
3067
3068 } catch (const std::exception& ex) {
3069 error_message = ex.what();
3071 .arg(config_->getThisServerName())
3072 .arg(remote_config->getLogLabel())
3073 .arg(error_message);
3074 }
3075 }
3076
3077 // If there was an error communicating with the partner, mark the
3078 // partner as unavailable.
3079 if (!error_message.empty()) {
3080 communication_state_->setPartnerUnavailable();
3081 }
3082
3083 // Invoke post request action if it was specified.
3084 if (post_request_action) {
3085 post_request_action(error_message.empty(),
3086 error_message,
3087 rcode);
3088 }
3089 },
3091 std::bind(&HAService::clientConnectHandler, this, ph::_1, ph::_2),
3092 std::bind(&HAService::clientHandshakeHandler, this, ph::_1),
3093 std::bind(&HAService::clientCloseHandler, this, ph::_1)
3094 );
3095}
3096
3098HAService::processSyncCompleteNotify(const unsigned int origin_id) {
3099 if (getCurrState() == HA_PARTNER_DOWN_ST) {
3100 sync_complete_notified_ = true;
3101 // We're in the partner-down state and the partner notified us
3102 // that it has synchronized its database. We can't enable the
3103 // service yet, because it may result in some new lease allocations
3104 // that the partner would miss (we don't send lease updates in the
3105 // partner-down state). We must first send the heartbeat and let
3106 // the state machine resolve the situation between the partners.
3107 // It may unblock the network service.
3108 network_state_->disableService(getLocalOrigin());
3109 }
3110 // Release the network state lock for the remote origin because we have
3111 // acquired the local network state lock above (partner-down state), or
3112 // we don't need the lock (other states).
3113 network_state_->enableService(origin_id);
3115 "Server successfully notified about the synchronization completion."));
3116}
3117
3119HAService::verifyAsyncResponse(const HttpResponsePtr& response, int& rcode) {
3120 // Set the return code to error in case of early throw.
3121 rcode = CONTROL_RESULT_ERROR;
3122 // The response must cast to JSON type.
3123 HttpResponseJsonPtr json_response =
3124 boost::dynamic_pointer_cast<HttpResponseJson>(response);
3125 if (!json_response) {
3126 isc_throw(CtrlChannelError, "no valid HTTP response found");
3127 }
3128
3129 // Body holds the response to our command.
3130 ConstElementPtr body = json_response->getBodyAsJson();
3131 if (!body) {
3132 isc_throw(CtrlChannelError, "no body found in the response");
3133 }
3134
3135 // Body should contain a list of responses from multiple servers.
3136 if (body->getType() != Element::list) {
3137 // Some control agent errors are returned as a map.
3138 if (body->getType() == Element::map) {
3140 ElementPtr answer = Element::createMap();
3141 answer->set(CONTROL_RESULT, Element::create(rcode));
3142 ConstElementPtr text = body->get(CONTROL_TEXT);
3143 if (text) {
3144 answer->set(CONTROL_TEXT, text);
3145 }
3146 list->add(answer);
3147 body = list;
3148 } else {
3149 isc_throw(CtrlChannelError, "body of the response must be a list");
3150 }
3151 }
3152
3153 // There must be at least one response.
3154 if (body->empty()) {
3155 isc_throw(CtrlChannelError, "list of responses must not be empty");
3156 }
3157
3158 // Check if the status code of the first response. We don't support multiple
3159 // at this time, because we always send a request to a single location.
3160 ConstElementPtr args = parseAnswer(rcode, body->get(0));
3161 if (rcode == CONTROL_RESULT_SUCCESS) {
3162 return (args);
3163 }
3164
3165 std::ostringstream s;
3166
3167 // The empty status can occur for the lease6-bulk-apply command. In that
3168 // case, the response may contain conflicted or erred leases within the
3169 // arguments, rather than globally. For other error cases let's construct
3170 // the error message from the global values.
3171 if (rcode != CONTROL_RESULT_EMPTY) {
3172 // Include an error text if available.
3173 if (args && args->getType() == Element::string) {
3174 s << args->stringValue() << " (";
3175 }
3176 // Include an error code.
3177 s << "error code " << rcode << ")";
3178 }
3179
3180 switch (rcode) {
3182 isc_throw(CommandUnsupportedError, s.str());
3183
3185 isc_throw(ConflictError, s.str());
3186
3188 // Handle the lease6-bulk-apply error cases.
3189 if (args && (args->getType() == Element::map)) {
3190 auto failed_leases = args->get("failed-leases");
3191 if (!failed_leases || (failed_leases->getType() != Element::list)) {
3192 // If there are no failed leases there is nothing to do.
3193 break;
3194 }
3195 auto conflict = false;
3196 ConstElementPtr conflict_error_message;
3197 for (auto i = 0; i < failed_leases->size(); ++i) {
3198 auto lease = failed_leases->get(i);
3199 if (!lease || lease->getType() != Element::map) {
3200 continue;
3201 }
3202 auto result = lease->get("result");
3203 if (!result || result->getType() != Element::integer) {
3204 continue;
3205 }
3206 auto error_message = lease->get("error-message");
3207 // Error status code takes precedence over the conflict.
3208 if (result->intValue() == CONTROL_RESULT_ERROR) {
3209 if (error_message && error_message->getType()) {
3210 s << error_message->stringValue() << " (";
3211 }
3212 s << "error code " << result->intValue() << ")";
3213 isc_throw(CtrlChannelError, s.str());
3214 }
3215 if (result->intValue() == CONTROL_RESULT_CONFLICT) {
3216 // Let's record the conflict but there may still be some
3217 // leases with an error status code, so do not throw the
3218 // conflict exception yet.
3219 conflict = true;
3220 conflict_error_message = error_message;
3221 }
3222 }
3223 if (conflict) {
3224 // There are no errors. There are only conflicts. Throw
3225 // appropriate exception.
3226 if (conflict_error_message &&
3227 (conflict_error_message->getType() == Element::string)) {
3228 s << conflict_error_message->stringValue() << " (";
3229 }
3230 s << "error code " << CONTROL_RESULT_CONFLICT << ")";
3231 isc_throw(ConflictError, s.str());
3232 }
3233 }
3234 break;
3235 default:
3236 isc_throw(CtrlChannelError, s.str());
3237 }
3238 return (args);
3239}
3240
3241bool
3242HAService::clientConnectHandler(const boost::system::error_code& ec, int tcp_native_fd) {
3243
3244 // If client is running it's own IOService we do NOT want to
3245 // register the socket with IfaceMgr.
3246 if (client_->getThreadIOService()) {
3247 return (true);
3248 }
3249
3250 // If things look OK register the socket with Interface Manager. Note
3251 // we don't register if the FD is < 0 to avoid an exception throw.
3252 // It is unlikely that this will occur but we want to be liberal
3253 // and avoid issues.
3254 if ((!ec || (ec.value() == boost::asio::error::in_progress))
3255 && (tcp_native_fd >= 0)) {
3256 // External socket callback is a NOP. Ready events handlers are
3257 // run by an explicit call IOService ready in kea-dhcp<n> code.
3258 // We are registering the socket only to interrupt main-thread
3259 // select().
3260 IfaceMgr::instance().addExternalSocket(tcp_native_fd,
3261 std::bind(&HAService::socketReadyHandler, this, ph::_1)
3262 );
3263 }
3264
3265 // If ec.value() == boost::asio::error::already_connected, we should already
3266 // be registered, so nothing to do. If it is any other value, then connect
3267 // failed and Connection logic should handle that, not us, so no matter
3268 // what happens we're returning true.
3269 return (true);
3270}
3271
3272void
3273HAService::socketReadyHandler(int tcp_native_fd) {
3274 // If the socket is ready but does not belong to one of our client's
3275 // ongoing transactions, we close it. This will unregister it from
3276 // IfaceMgr and ensure the client starts over with a fresh connection
3277 // if it needs to do so.
3278 client_->closeIfOutOfBand(tcp_native_fd);
3279}
3280
3281void
3282HAService::clientCloseHandler(int tcp_native_fd) {
3283 if (tcp_native_fd >= 0) {
3284 IfaceMgr::instance().deleteExternalSocket(tcp_native_fd);
3285 }
3286};
3287
3288size_t
3289HAService::pendingRequestSize() {
3290 if (MultiThreadingMgr::instance().getMode()) {
3291 std::lock_guard<std::mutex> lock(mutex_);
3292 return (pending_requests_.size());
3293 } else {
3294 return (pending_requests_.size());
3295 }
3296}
3297
3298template<typename QueryPtrType>
3299int
3300HAService::getPendingRequest(const QueryPtrType& query) {
3301 if (MultiThreadingMgr::instance().getMode()) {
3302 std::lock_guard<std::mutex> lock(mutex_);
3303 return (getPendingRequestInternal(query));
3304 } else {
3305 return (getPendingRequestInternal(query));
3306 }
3307}
3308
3309template<typename QueryPtrType>
3310int
3311HAService::getPendingRequestInternal(const QueryPtrType& query) {
3312 if (pending_requests_.count(query) == 0) {
3313 return (0);
3314 } else {
3315 return (pending_requests_[query]);
3316 }
3317}
3318
3319void
3320HAService::checkPermissionsClientAndListener() {
3321 // Since this function is used as CS callback all exceptions must be
3322 // suppressed (except the @ref MultiThreadingInvalidOperation), unlikely
3323 // though they may be.
3324 // The @ref MultiThreadingInvalidOperation is propagated to the scope of the
3325 // @ref MultiThreadingCriticalSection constructor.
3326 try {
3327 if (client_) {
3328 client_->checkPermissions();
3329 }
3330
3331 if (listener_) {
3332 listener_->checkPermissions();
3333 }
3334 } catch (const isc::MultiThreadingInvalidOperation& ex) {
3336 .arg(config_->getThisServerName())
3337 .arg(ex.what());
3338 // The exception needs to be propagated to the caller of the
3339 // @ref MultiThreadingCriticalSection constructor.
3340 throw;
3341 } catch (const std::exception& ex) {
3343 .arg(config_->getThisServerName())
3344 .arg(ex.what());
3345 }
3346}
3347
3348void
3349HAService::startClientAndListener() {
3350 // Add critical section callbacks.
3351 MultiThreadingMgr::instance().addCriticalSectionCallbacks(getCSCallbacksSetName(),
3352 std::bind(&HAService::checkPermissionsClientAndListener, this),
3353 std::bind(&HAService::pauseClientAndListener, this),
3354 std::bind(&HAService::resumeClientAndListener, this));
3355
3356 if (client_) {
3357 client_->start();
3358 }
3359
3360 if (listener_) {
3361 listener_->start();
3362 }
3363}
3364
3365void
3366HAService::pauseClientAndListener() {
3367 // Since this function is used as CS callback all exceptions must be
3368 // suppressed, unlikely though they may be.
3369 try {
3370 if (client_) {
3371 client_->pause();
3372 }
3373
3374 if (listener_) {
3375 listener_->pause();
3376 }
3377 } catch (const std::exception& ex) {
3379 .arg(ex.what());
3380 }
3381}
3382
3383void
3384HAService::resumeClientAndListener() {
3385 // Since this function is used as CS callback all exceptions must be
3386 // suppressed, unlikely though they may be.
3387 try {
3388 if (client_) {
3389 client_->resume();
3390 }
3391
3392 if (listener_) {
3393 listener_->resume();
3394 }
3395 } catch (std::exception& ex) {
3397 .arg(config_->getThisServerName())
3398 .arg(ex.what());
3399 }
3400}
3401
3402void
3403HAService::stopClientAndListener() {
3404 // Remove critical section callbacks.
3405 MultiThreadingMgr::instance().removeCriticalSectionCallbacks(getCSCallbacksSetName());
3406
3407 if (client_) {
3408 client_->stop();
3409 }
3410
3411 if (listener_) {
3412 listener_->stop();
3413 }
3414}
3415
3416// Explicit instantiations.
3417template int HAService::getPendingRequest(const Pkt4Ptr&);
3418template int HAService::getPendingRequest(const Pkt6Ptr&);
3419
3420} // end of namespace isc::ha
3421} // end of namespace isc
Exception thrown when a worker thread is trying to stop or pause the respective thread pool (which wo...
A generic exception that is thrown when an unexpected error condition occurs.
A multi-threaded HTTP listener that can process API commands requests.
static std::unordered_set< std::string > command_accept_list_
The server command accept list.
A standard control channel exception that is thrown if a function is there is a problem with one of t...
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition data.cc:249
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:304
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:299
static IfaceMgr & instance()
IfaceMgr is a singleton class.
Definition iface_mgr.cc:54
static data::ConstElementPtr createLease4Delete(const dhcp::Lease4 &lease4)
Creates lease4-del command.
static std::unordered_set< std::string > ha_commands4_
List of commands used by the High Availability in v4.
static data::ConstElementPtr createLease4Update(const dhcp::Lease4 &lease4)
Creates lease4-update command.
static data::ConstElementPtr createLease6BulkApply(const dhcp::Lease6CollectionPtr &leases, const dhcp::Lease6CollectionPtr &deleted_leases)
Creates lease6-bulk-apply command.
static std::unordered_set< std::string > ha_commands6_
List of commands used by the High Availability in v6.
Holds communication state between DHCPv4 servers.
Holds communication state between DHCPv6 servers.
Role
Server's role in the High Availability setup.
Definition ha_config.h:83
static std::string roleToString(const HAConfig::PeerConfig::Role &role)
Returns role name.
Definition ha_config.cc:82
std::map< std::string, PeerConfigPtr > PeerConfigMap
Map of the servers' configurations.
Definition ha_config.h:245
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition ha_config.cc:233
boost::shared_ptr< PeerConfig > PeerConfigPtr
Pointer to the server's configuration.
Definition ha_config.h:242
static const int HA_MAINTENANCE_START_EVT
ha-maintenance-start command received.
Definition ha_service.h:71
bool inScope(dhcp::Pkt4Ptr &query4)
Checks if the DHCPv4 query should be processed by this server.
void adjustNetworkState()
Enables or disables network state depending on the served scopes.
void stopClientAndListener()
Stop the client and(or) listener instances.
int getNormalState() const
Returns normal operation state for the current configuration.
bool shouldQueueLeaseUpdates(const HAConfig::PeerConfigPtr &peer_config) const
Checks if the lease updates should be queued.
static const int HA_HEARTBEAT_COMPLETE_EVT
Finished heartbeat command.
Definition ha_service.h:56
bool clientConnectHandler(const boost::system::error_code &ec, int tcp_native_fd)
HttpClient connect callback handler.
bool isMaintenanceCanceled() const
Convenience method checking if the current state is a result of canceling the maintenance.
void asyncSendLeaseUpdate(const QueryPtrType &query, const HAConfig::PeerConfigPtr &config, const data::ConstElementPtr &command, const hooks::ParkingLotHandlePtr &parking_lot)
Asynchronously sends lease update to the peer.
void verboseTransition(const unsigned state)
Transitions to a desired state and logs it.
bool sendLeaseUpdatesFromBacklog()
Attempts to send all lease updates from the backlog synchronously.
config::CmdHttpListenerPtr listener_
HTTP listener instance used to receive and respond to HA commands and lease updates.
void clientCloseHandler(int tcp_native_fd)
HttpClient close callback handler.
bool leaseUpdateComplete(QueryPtrType &query, const hooks::ParkingLotHandlePtr &parking_lot)
Handle last pending request for this query.
HAConfigPtr config_
Pointer to the HA hooks library configuration.
unsigned int id_
Unique service id.
bool shouldTerminate() const
Indicates if the server should transition to the terminated state.
dhcp::NetworkStatePtr network_state_
Pointer to the state of the DHCP service (enabled/disabled).
void scheduleHeartbeat()
Schedules asynchronous heartbeat to a peer if it is not scheduled.
QueryFilter query_filter_
Selects queries to be processed/dropped.
static const int HA_MAINTENANCE_NOTIFY_EVT
ha-maintenance-notify command received.
Definition ha_service.h:68
static const int HA_SYNCED_PARTNER_UNAVAILABLE_EVT
The heartbeat command failed after receiving ha-sync-complete-notify command from the partner.
Definition ha_service.h:78
void conditionalLogPausedState() const
Logs if the server is paused in the current state.
bool unpause()
Unpauses the HA state machine with logging.
static const int HA_CONTROL_RESULT_MAINTENANCE_NOT_ALLOWED
Control result returned in response to ha-maintenance-notify.
Definition ha_service.h:81
void serveDefaultScopes()
Instructs the HA service to serve default scopes.
size_t asyncSendLeaseUpdates(const dhcp::Pkt4Ptr &query, const dhcp::Lease4CollectionPtr &leases, const dhcp::Lease4CollectionPtr &deleted_leases, const hooks::ParkingLotHandlePtr &parking_lot)
Schedules asynchronous IPv4 leases updates.
static const int HA_SYNCING_SUCCEEDED_EVT
Lease database synchronization succeeded.
Definition ha_service.h:65
bool sendHAReset()
Sends ha-reset command to partner synchronously.
asiolink::IOServicePtr io_service_
Pointer to the IO service object shared between this hooks library and the DHCP server.
CommunicationStatePtr communication_state_
Holds communication state with a peer.
void logFailedLeaseUpdates(const dhcp::PktPtr &query, const data::ConstElementPtr &args) const
Log failed lease updates.
bool clientHandshakeHandler(const boost::system::error_code &)
HttpClient handshake callback handler.
LeaseUpdateBacklog lease_update_backlog_
Backlog of DHCP lease updates.
virtual ~HAService()
Destructor.
static const int HA_SYNCING_FAILED_EVT
Lease database synchronization failed.
Definition ha_service.h:62
static const int HA_MAINTENANCE_CANCEL_EVT
ha-maintenance-cancel command received.
Definition ha_service.h:74
size_t asyncSendSingleLeaseUpdate(const dhcp::Pkt4Ptr &query, const dhcp::Lease4Ptr &lease, const hooks::ParkingLotHandlePtr &parking_lot)
Schedules an asynchronous IPv4 lease update.
bool isPartnerStateInvalid() const
Indicates if the partner's state is invalid.
data::ConstElementPtr verifyAsyncResponse(const http::HttpResponsePtr &response, int &rcode)
Checks if the response is valid or contains an error.
int synchronize(std::string &status_message, const HAConfig::PeerConfigPtr &remote_config, const unsigned int max_period)
Synchronizes lease database with a partner.
bool shouldSendLeaseUpdates(const HAConfig::PeerConfigPtr &peer_config) const
Checks if the lease updates should be sent as result of leases allocation or release.
void serveFailoverScopes()
Instructs the HA service to serve failover scopes.
static const int HA_LEASE_UPDATES_COMPLETE_EVT
Finished lease updates commands.
Definition ha_service.h:59
HAService(const unsigned int id, const asiolink::IOServicePtr &io_service, const dhcp::NetworkStatePtr &network_state, const HAConfigPtr &config, const HAServerType &server_type=HAServerType::DHCPv4)
Constructor.
Definition ha_service.cc:75
http::HttpClientPtr client_
HTTP client instance used to send HA commands and lease updates.
void updatePendingRequest(QueryPtrType &query)
Update pending request counter for this query.
bool shouldPartnerDown() const
Indicates if the server should transition to the partner down state.
static const int HA_WAITING_TO_TERMINATED_ST_DELAY_MINUTES
A delay in minutes to transition from the waiting to terminated state when the partner remains in ter...
Definition ha_service.h:85
bool push(const OpType op_type, const dhcp::LeasePtr &lease)
Appends lease update to the queue.
OpType
Type of the lease update (operation type).
void clear()
Removes all lease updates from the queue.
bool wasOverflown()
Checks if the queue was overflown.
bool inScope(const dhcp::Pkt4Ptr &query4, std::string &scope_class) const
Checks if this server should process the DHCPv4 query.
void serveFailoverScopes()
Enable scopes required in failover case.
void serveDefaultScopes()
Serve default scopes for the given HA mode.
void serveNoScopes()
Disables all scopes.
Represents HTTP Host header.
Definition http_header.h:68
HTTP client class.
Definition client.h:86
This class parses and generates time values used in HTTP.
Definition date_time.h:41
std::string rfc1123Format() const
Returns time value formatted as specified in RFC 1123.
Definition date_time.cc:30
static MultiThreadingMgr & instance()
Returns a single instance of Multi Threading Manager.
const EventPtr & getEvent(unsigned int value)
Fetches the event referred to by value.
std::string getStateLabel(const int state) const
Fetches the label associated with an state value.
void unpauseModel()
Unpauses state model.
virtual void runModel(unsigned int event)
Processes events through the state model.
bool isModelPaused() const
Returns whether or not the model is paused.
virtual void defineEvents()
Populates the set of events.
void postNextEvent(unsigned int event)
Sets the next event to the given event value.
void defineState(unsigned int value, const std::string &label, StateHandler handler, const StatePausing &state_pausing=STATE_PAUSE_NEVER)
Adds an state value and associated label to the set of states.
bool doOnExit()
Checks if on exit flag is true.
unsigned int getNextEvent() const
Fetches the model's next event.
void defineEvent(unsigned int value, const std::string &label)
Adds an event value and associated label to the set of events.
void transition(unsigned int state, unsigned int event)
Sets up the model to transition into given state with a given event.
virtual void verifyEvents()
Validates the contents of the set of events.
bool doOnEntry()
Checks if on entry flag is true.
static const int NOP_EVT
Signifies that no event has occurred.
void startModel(const int start_state)
Begins execution of the model.
virtual void defineStates()
Populates the set of states.
unsigned int getLastEvent() const
Fetches the model's last event.
unsigned int getCurrState() const
Fetches the model's current state.
Utility class to measure code execution times.
Definition stopwatch.h:35
void stop()
Stops the stopwatch.
Definition stopwatch.cc:34
This file contains several functions and constants that are used for handling commands and responses ...
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
An abstract API for lease database.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition macros.h:32
#define LOG_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
#define LOG_WARN(LOGGER, MESSAGE)
Macro to conveniently test warn output and log it.
Definition macros.h:26
const int CONTROL_RESULT_EMPTY
Status code indicating that the specified command was completed correctly, but failed to produce any ...
const char * CONTROL_TEXT
String used for storing textual description ("text")
constexpr long TIMEOUT_DEFAULT_HTTP_CLIENT_REQUEST
Timeout for the HTTP clients awaiting a response to a request.
Definition timeouts.h:38
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
const int CONTROL_RESULT_CONFLICT
Status code indicating that the command was unsuccessful due to a conflict between the command argume...
const int CONTROL_RESULT_COMMAND_UNSUPPORTED
Status code indicating that the specified command is not supported.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
const char * CONTROL_RESULT
String used for result, i.e. integer status ("result")
const int CONTROL_RESULT_SUCCESS
Status code indicating a successful operation.
boost::shared_ptr< const Element > ConstElementPtr
Definition data.h:29
boost::shared_ptr< Element > ElementPtr
Definition data.h:28
boost::shared_ptr< isc::dhcp::Pkt > PktPtr
A pointer to either Pkt4 or Pkt6 packet.
Definition pkt.h:982
std::string ClientClass
Defines a single class name.
Definition classify.h:42
boost::shared_ptr< Lease4Collection > Lease4CollectionPtr
A shared pointer to the collection of IPv4 leases.
Definition lease.h:503
boost::shared_ptr< Pkt4 > Pkt4Ptr
A pointer to Pkt4 object.
Definition pkt4.h:555
boost::shared_ptr< Lease > LeasePtr
Pointer to the lease object.
Definition lease.h:25
boost::shared_ptr< NetworkState > NetworkStatePtr
Pointer to the NetworkState object.
boost::shared_ptr< Lease6Collection > Lease6CollectionPtr
A shared pointer to the collection of IPv6 leases.
Definition lease.h:676
boost::shared_ptr< Pkt6 > Pkt6Ptr
A pointer to Pkt6 packet.
Definition pkt6.h:31
std::vector< Lease4Ptr > Lease4Collection
A collection of IPv4 leases.
Definition lease.h:500
boost::shared_ptr< Lease4 > Lease4Ptr
Pointer to a Lease4 structure.
Definition lease.h:295
const isc::log::MessageID HA_INVALID_PARTNER_STATE_LOAD_BALANCING
Definition ha_messages.h:52
const isc::log::MessageID HA_RESUME_CLIENT_LISTENER_FAILED
const isc::log::MessageID HA_LOCAL_DHCP_ENABLE
Definition ha_messages.h:83
const isc::log::MessageID HA_LEASES_BACKLOG_NOTHING_TO_SEND
Definition ha_messages.h:62
const isc::log::MessageID HA_LEASES_BACKLOG_FAILED
Definition ha_messages.h:61
const isc::log::MessageID HA_SYNC_FAILED
const isc::log::MessageID HA_TERMINATED_RESTART_PARTNER
const int HA_PASSIVE_BACKUP_ST
In passive-backup state with a single active server and backup servers.
const int HA_HOT_STANDBY_ST
Hot standby state.
const isc::log::MessageID HA_INVALID_PARTNER_STATE_COMMUNICATION_RECOVERY
Definition ha_messages.h:50
const isc::log::MessageID HA_LEASES_BACKLOG_SUCCESS
Definition ha_messages.h:64
const int HA_COMMUNICATION_RECOVERY_ST
Communication recovery state.
const isc::log::MessageID HA_LEASE_UPDATE_COMMUNICATIONS_FAILED
Definition ha_messages.h:74
const isc::log::MessageID HA_STATE_MACHINE_CONTINUED
isc::log::Logger ha_logger("ha-hooks")
Definition ha_log.h:17
const isc::log::MessageID HA_LEASES_SYNC_FAILED
Definition ha_messages.h:67
const isc::log::MessageID HA_SYNC_SUCCESSFUL
const int HA_UNAVAILABLE_ST
Special state indicating that this server is unable to communicate with the partner.
const isc::log::MessageID HA_CONFIG_LEASE_UPDATES_DISABLED_REMINDER
Definition ha_messages.h:34
const isc::log::MessageID HA_SERVICE_STARTED
const int HA_TERMINATED_ST
HA service terminated state.
const int HA_IN_MAINTENANCE_ST
In maintenance state.
const int HA_LOAD_BALANCING_ST
Load balancing state.
const isc::log::MessageID HA_DHCP_ENABLE_FAILED
Definition ha_messages.h:43
const isc::log::MessageID HA_LEASE_UPDATE_DELETE_FAILED_ON_PEER
Definition ha_messages.h:77
const isc::log::MessageID HA_LEASES_BACKLOG_START
Definition ha_messages.h:63
const isc::log::MessageID HA_SYNC_START
const isc::log::MessageID HA_HEARTBEAT_FAILED
Definition ha_messages.h:45
const int HA_PARTNER_DOWN_ST
Partner down state.
const isc::log::MessageID HA_LEASE_UPDATES_ENABLED
Definition ha_messages.h:73
const isc::log::MessageID HA_INVALID_PARTNER_STATE_HOT_STANDBY
Definition ha_messages.h:51
const isc::log::MessageID HA_STATE_MACHINE_PAUSED
const isc::log::MessageID HA_TERMINATED
const isc::log::MessageID HA_DHCP_DISABLE_FAILED
Definition ha_messages.h:41
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition ha_config.h:37
const isc::log::MessageID HA_MAINTENANCE_STARTED_IN_PARTNER_DOWN
Definition ha_messages.h:92
const int HA_PARTNER_IN_MAINTENANCE_ST
Partner in-maintenance state.
const isc::log::MessageID HA_MAINTENANCE_NOTIFY_FAILED
Definition ha_messages.h:88
const int HA_WAITING_ST
Server waiting state, i.e. waiting for another server to be ready.
HAServerType
Lists possible server types for which HA service is created.
const int HA_BACKUP_ST
Backup state.
const isc::log::MessageID HA_PAUSE_CLIENT_LISTENER_ILLEGAL
Definition ha_messages.h:96
const isc::log::MessageID HA_PAUSE_CLIENT_LISTENER_FAILED
Definition ha_messages.h:95
const isc::log::MessageID HA_MAINTENANCE_SHUTDOWN_SAFE
Definition ha_messages.h:90
const isc::log::MessageID HA_MAINTENANCE_NOTIFY_CANCEL_FAILED
Definition ha_messages.h:86
const isc::log::MessageID HA_LEASE_UPDATE_CONFLICT
Definition ha_messages.h:75
const isc::log::MessageID HA_LEASE_UPDATES_DISABLED
Definition ha_messages.h:72
const isc::log::MessageID HA_LOCAL_DHCP_DISABLE
Definition ha_messages.h:82
const int HA_SYNCING_ST
Synchronizing database state.
const isc::log::MessageID HA_RESET_FAILED
Definition ha_messages.h:98
const isc::log::MessageID HA_STATE_TRANSITION
const isc::log::MessageID HA_CONFIG_LEASE_SYNCING_DISABLED_REMINDER
Definition ha_messages.h:31
std::string stateToString(int state)
Returns state name.
const int HA_READY_ST
Server ready state, i.e. synchronized database, can enable DHCP service.
const isc::log::MessageID HA_TERMINATED_PARTNER_DID_NOT_RESTART
const isc::log::MessageID HA_SYNC_COMPLETE_NOTIFY_FAILED
const isc::log::MessageID HA_MAINTENANCE_STARTED
Definition ha_messages.h:91
const isc::log::MessageID HA_LEASE_UPDATE_CREATE_UPDATE_FAILED_ON_PEER
Definition ha_messages.h:76
const isc::log::MessageID HA_LEASE_UPDATE_FAILED
Definition ha_messages.h:78
const isc::log::MessageID HA_STATE_TRANSITION_PASSIVE_BACKUP
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
boost::shared_ptr< PostHttpRequestJson > PostHttpRequestJsonPtr
Pointer to PostHttpRequestJson.
boost::shared_ptr< HttpResponseJson > HttpResponseJsonPtr
Pointer to the HttpResponseJson object.
boost::shared_ptr< HttpResponse > HttpResponsePtr
Pointer to the HttpResponse object.
Definition response.h:81
const char * MessageID
Defines the logger used by the top-level component of kea-lfc.
HTTP request/response timeout value.
Definition client.h:89
static const HttpVersion & HTTP_11()
HTTP version 1.1.
Definition http_types.h:59