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