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