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