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