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