Kea 2.6.0
ha_impl.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 <ha_config_parser.h>
10#include <ha_impl.h>
11#include <ha_log.h>
12#include <asiolink/io_service.h>
13#include <cc/data.h>
15#include <dhcp/pkt4.h>
16#include <dhcp/pkt6.h>
17#include <dhcpsrv/lease.h>
19#include <dhcpsrv/subnet.h>
20#include <stats/stats_mgr.h>
21
22using namespace isc::asiolink;
23using namespace isc::config;
24using namespace isc::data;
25using namespace isc::dhcp;
26using namespace isc::hooks;
27using namespace isc::log;
28using namespace isc::stats;
29
30namespace isc {
31namespace ha {
32
34 : io_service_(new IOService()), config_(), services_(new HAServiceMapper()) {
35}
36
37void
38HAImpl::configure(const ConstElementPtr& input_config) {
39 config_ = HAConfigParser::parse(input_config);
40}
41
42void
44 const HAServerType& server_type) {
45 auto configs = config_->getAll();
46 for (auto id = 0; id < configs.size(); ++id) {
47 // Create the HA service and crank up the state machine.
48 auto service = boost::make_shared<HAService>(id, io_service_, network_state,
49 configs[id], server_type);
50 for (auto const& peer_config : configs[id]->getAllServersConfig()) {
51 services_->map(peer_config.first, service);
52 }
53 }
54 // Schedule a start of the services. This ensures we begin after
55 // the dust has settled and Kea MT mode has been firmly established.
56 io_service_->post([&]() {
57 for (auto const& service : services_->getAll()) {
58 service->startClientAndListener();
59 }
60 });
61}
62
64 for (auto const& service : services_->getAll()) {
65 // Shut down the services explicitly, we need finer control
66 // than relying on destruction order.
67 service->stopClientAndListener();
68 }
69}
70
71void
73 // If there are multiple relationships, the HA-specific processing is
74 // in the subnet4_select hook point.
75 if (services_->hasMultiple()) {
76 return;
77 }
78
79 Pkt4Ptr query4;
80 callout_handle.getArgument("query4", query4);
81
84 try {
85 // We have to unpack the query to get access into HW address which is
86 // used to load balance the packet.
87 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
88 query4->unpack();
89 }
90
91 } catch (const SkipRemainingOptionsError& ex) {
92 // An option failed to unpack but we are to attempt to process it
93 // anyway. Log it and let's hope for the best.
96 .arg(ex.what());
97
98 } catch (const std::exception& ex) {
99 // Packet parsing failed. Drop the packet.
101 .arg(query4->getRemoteAddr().toText())
102 .arg(query4->getLocalAddr().toText())
103 .arg(query4->getIface())
104 .arg(ex.what());
105
106 // Increase the statistics of parse failures and dropped packets.
107 StatsMgr::instance().addValue("pkt4-parse-failed", static_cast<int64_t>(1));
108 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
109
110
112 return;
113 }
114
115 // Check if we should process this query. If not, drop it.
116 if (!services_->get()->inScope(query4)) {
118 .arg(query4->getLabel());
120
121 } else {
122 // We have successfully parsed the query so we have to signal
123 // to the server that it must not parse it.
125 }
126}
127
128void
130 // This callout only applies in the case of multiple relationships.
131 // When there is only one relationship it has no effect because
132 // the decision if we should process the packet has been made
133 // in the buffer4_receive callout.
134 if (!services_->hasMultiple()) {
135 // Return silently. It is not an error.
136 return;
137 }
138
139 Pkt4Ptr query4;
140 callout_handle.getArgument("query4", query4);
141
142 Subnet4Ptr subnet4;
143 callout_handle.getArgument("subnet4", subnet4);
144
145 // If the server failed to select the subnet this pointer is null.
146 // There is nothing we can do with this packet because we don't know
147 // which relationship it belongs to. We're even unable to check if the
148 // server is responsible for this packet.
149 if (!subnet4) {
150 // Log at debug level because that's the level at which the server
151 // logs the subnet selection failure.
153 .arg(query4->getLabel());
155 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
156 return;
157 }
158
159 // The subnet configuration should contain a user context
160 // and this context should contain a mapping of the subnet to a
161 // relationship. If the context doesn't exist there is no way
162 // to determine which relationship the packet belongs to.
163 std::string server_name;
164 try {
165 server_name = HAConfig::getSubnetServerName(subnet4);
166 if (server_name.empty()) {
168 .arg(query4->getLabel())
169 .arg(subnet4->toText());
171 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
172 return;
173 }
174
175 } catch (...) {
177 .arg(query4->getLabel())
178 .arg(subnet4->toText());
180 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
181 return;
182 }
183
184 // Try to find a relationship matching this server name.
185 auto service = services_->get(server_name);
186 if (!service) {
188 .arg(query4->getLabel())
189 .arg(server_name);
191 StatsMgr::instance().addValue("pkt4-receive-drop", static_cast<int64_t>(1));
192 return;
193 }
194
195 // We have found appropriate relationship. Let's see if we should
196 // process the packet. We'll drop the packet if our partner is
197 // operational and is responsible for this packet.
198 if (!service->inScope(query4)) {
200 .arg(query4->getLabel())
201 .arg(server_name);
203 return;
204 }
205
206 // Remember the server name we retrieved from the subnet. We will
207 // need it in a leases4_committed callout that doesn't have access
208 // to the subnet object.
209 callout_handle.setContext("ha-server-name", server_name);
210}
211
212void
214 Pkt4Ptr query4;
215 Lease4CollectionPtr leases4;
216 Lease4CollectionPtr deleted_leases4;
217
218 // Get all arguments available for the leases4_committed hook point.
219 // If any of these arguments is not available this is a programmatic
220 // error. An exception will be thrown which will be caught by the
221 // caller and logged.
222 callout_handle.getArgument("query4", query4);
223
224 callout_handle.getArgument("leases4", leases4);
225 callout_handle.getArgument("deleted_leases4", deleted_leases4);
226
227 // In some cases we may have no leases, e.g. DHCPNAK.
228 if (leases4->empty() && deleted_leases4->empty()) {
230 .arg(query4->getLabel());
231 return;
232 }
233
234 // Get default config and service instances.
235 HAConfigPtr config = config_->get();
236 HAServicePtr service = services_->get();
237
238 // If we have multiple relationships we need to find the one that
239 // matches our subnet.
240 if (services_->hasMultiple()) {
241 try {
242 // Retrieve the server name from the context and the respective
243 // config and service instances.
244 std::string server_name;
245 callout_handle.getContext("ha-server-name", server_name);
246 config = config_->get(server_name);
247 service = services_->get(server_name);
248
249 // This is rather impossible but let's be safe.
250 if (!config || !service) {
251 isc_throw(Unexpected, "relationship not configured for server '" << server_name << "'");
252 }
253
254 } catch (const std::exception& ex) {
256 .arg(query4->getLabel())
257 .arg(ex.what());
259 return;
260 }
261 }
262
263 // If the hook library is configured to not send lease updates to the
264 // partner, there is nothing to do because this whole callout is
265 // currently about sending lease updates.
266 if (!config->amSendingLeaseUpdates()) {
267 // No need to log it, because it was already logged when configuration
268 // was applied.
269 return;
270 }
271
272 // Get the parking lot for this hook point. We're going to remember this
273 // pointer until we unpark the packet.
274 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
275
276 // Create a reference to the parked packet. This signals that we have a
277 // stake in unparking it.
278 parking_lot->reference(query4);
279
280 // Asynchronously send lease updates. In some cases no updates will be sent,
281 // e.g. when this server is in the partner-down state and there are no backup
282 // servers. In those cases we simply return without parking the DHCP query.
283 // The response will be sent to the client immediately.
284 try {
285 if (service->asyncSendLeaseUpdates(query4, leases4, deleted_leases4, parking_lot) == 0) {
286 // Dereference the parked packet. This releases our stake in it.
287 parking_lot->dereference(query4);
288 return;
289 }
290 } catch (...) {
291 // Make sure we dereference.
292 parking_lot->dereference(query4);
293 throw;
294 }
295
296 // The callout returns this status code to indicate to the server that it
297 // should leave the packet parked. It will be parked until each hook
298 // library with a reference, unparks the packet.
300}
301
302void
304 // Always return CONTINUE.
306 size_t peers_to_update = 0;
307
308 // If the hook library is configured to not send lease updates to the
309 // partner, there is nothing to do because this whole callout is
310 // currently about sending lease updates.
311 if (!config_->get()->amSendingLeaseUpdates()) {
312 // No need to log it, because it was already logged when configuration
313 // was applied.
314 callout_handle.setArgument("peers_to_update", peers_to_update);
315 return;
316 }
317
318 // Get all arguments available for the lease4_server_decline hook point.
319 // If any of these arguments is not available this is a programmatic
320 // error. An exception will be thrown which will be caught by the
321 // caller and logged.
322 Pkt4Ptr query4;
323 callout_handle.getArgument("query4", query4);
324
325 Lease4Ptr lease4;
326 callout_handle.getArgument("lease4", lease4);
327
328 // Asynchronously send the lease update. In some cases no updates will be sent,
329 // e.g. when this server is in the partner-down state and there are no backup
330 // servers.
331 peers_to_update = services_->get()->asyncSendSingleLeaseUpdate(query4, lease4, 0);
332 callout_handle.setArgument("peers_to_update", peers_to_update);
333}
334
335void
337 // If there are multiple relationships, the HA-specific processing is
338 // in the subnet6_select hook point.
339 if (services_->hasMultiple()) {
340 return;
341 }
342
343 Pkt6Ptr query6;
344 callout_handle.getArgument("query6", query6);
345
348 try {
349 // We have to unpack the query to get access into DUID which is
350 // used to load balance the packet.
351 if (callout_handle.getStatus() != CalloutHandle::NEXT_STEP_SKIP) {
352 query6->unpack();
353 }
354
355 } catch (const SkipRemainingOptionsError& ex) {
356 // An option failed to unpack but we are to attempt to process it
357 // anyway. Log it and let's hope for the best.
360 .arg(ex.what());
361
362 } catch (const std::exception& ex) {
363 // Packet parsing failed. Drop the packet.
365 .arg(query6->getRemoteAddr().toText())
366 .arg(query6->getLocalAddr().toText())
367 .arg(query6->getIface())
368 .arg(ex.what());
369
370 // Increase the statistics of parse failures and dropped packets.
371 StatsMgr::instance().addValue("pkt6-parse-failed", static_cast<int64_t>(1));
372 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
373
374
376 return;
377 }
378
379 // Check if we should process this query. If not, drop it.
380 if (!services_->get()->inScope(query6)) {
382 .arg(query6->getLabel());
384
385 } else {
386 // We have successfully parsed the query so we have to signal
387 // to the server that it must not parse it.
389 }
390}
391
392void
394 // This callout only applies in the case of multiple relationships.
395 // When there is only one relationship it has no effect because
396 // the decision if we should process the packet has been made
397 // in the buffer6_receive callout.
398 if (!services_->hasMultiple()) {
399 // Return silently. It is not an error.
400 return;
401 }
402
403 Pkt6Ptr query6;
404 callout_handle.getArgument("query6", query6);
405
406 Subnet6Ptr subnet6;
407 callout_handle.getArgument("subnet6", subnet6);
408
409 // If the server failed to select the subnet this pointer is null.
410 // There is nothing we can do with this packet because we don't know
411 // which relationship it belongs to. We're even unable to check if the
412 // server is responsible for this packet.
413 if (!subnet6) {
414 // Log at debug level because that's the level at which the server
415 // logs the subnet selection failure.
417 .arg(query6->getLabel());
419 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
420 return;
421 }
422
423 // The subnet configuration should contain a user context
424 // and this context should contain a mapping of the subnet to a
425 // relationship. If the context doesn't exist there is no way
426 // to determine which relationship the packet belongs to.
427 std::string server_name;
428 try {
429 server_name = HAConfig::getSubnetServerName(subnet6);
430 if (server_name.empty()) {
432 .arg(query6->getLabel())
433 .arg(subnet6->toText());
435 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
436 return;
437 }
438
439 } catch (...) {
441 .arg(query6->getLabel())
442 .arg(subnet6->toText());
444 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
445 return;
446 }
447
448 // Try to find a relationship matching this server name.
449 auto service = services_->get(server_name);
450 if (!service) {
452 .arg(query6->getLabel())
453 .arg(server_name);
455 StatsMgr::instance().addValue("pkt6-receive-drop", static_cast<int64_t>(1));
456 return;
457 }
458
459 // We have found appropriate relationship. Let's see if we should
460 // process the packet. We'll drop the packet if our partner is
461 // operational and is responsible for this packet.
462 if (!service->inScope(query6)) {
464 .arg(query6->getLabel())
465 .arg(server_name);
467 return;
468 }
469
470 // Remember the server name we retrieved from the subnet. We will
471 // need it in a leases4_committed callout that doesn't have access
472 // to the subnet object.
473 callout_handle.setContext("ha-server-name", server_name);
474}
475
476void
478 Pkt6Ptr query6;
479 Lease6CollectionPtr leases6;
480 Lease6CollectionPtr deleted_leases6;
481
482 // Get all arguments available for the leases6_committed hook point.
483 // If any of these arguments is not available this is a programmatic
484 // error. An exception will be thrown which will be caught by the
485 // caller and logged.
486 callout_handle.getArgument("query6", query6);
487
488 callout_handle.getArgument("leases6", leases6);
489 callout_handle.getArgument("deleted_leases6", deleted_leases6);
490
491 // In some cases we may have no leases.
492 if (leases6->empty() && deleted_leases6->empty()) {
494 .arg(query6->getLabel());
495 return;
496 }
497
498 HAConfigPtr config = config_->get();
499 HAServicePtr service = services_->get();
500 if (services_->hasMultiple()) {
501 try {
502 std::string server_name;
503 callout_handle.getContext("ha-server-name", server_name);
504 config = config_->get(server_name);
505 service = services_->get(server_name);
506
507 if (!config || !service) {
508 isc_throw(Unexpected, "relationship not found for the ha-server-name='" << server_name << "'");
509 }
510
511 } catch (const std::exception& ex) {
513 .arg(query6->getLabel())
514 .arg(ex.what());
516 return;
517 }
518 }
519
520 // If the hook library is configured to not send lease updates to the
521 // partner, there is nothing to do because this whole callout is
522 // currently about sending lease updates.
523 if (!config->amSendingLeaseUpdates()) {
524 // No need to log it, because it was already logged when configuration
525 // was applied.
526 return;
527 }
528
529 // Get the parking lot for this hook point. We're going to remember this
530 // pointer until we unpark the packet.
531 ParkingLotHandlePtr parking_lot = callout_handle.getParkingLotHandlePtr();
532
533 // Create a reference to the parked packet. This signals that we have a
534 // stake in unparking it.
535 parking_lot->reference(query6);
536
537 // Asynchronously send lease updates. In some cases no updates will be sent,
538 // e.g. when this server is in the partner-down state and there are no backup
539 // servers. In those cases we simply return without parking the DHCP query.
540 // The response will be sent to the client immediately.
541 try {
542 if (service->asyncSendLeaseUpdates(query6, leases6, deleted_leases6, parking_lot) == 0) {
543 // Dereference the parked packet. This releases our stake in it.
544 parking_lot->dereference(query6);
545 return;
546 }
547 } catch (...) {
548 // Make sure we dereference.
549 parking_lot->dereference(query6);
550 throw;
551 }
552
553 // The callout returns this status code to indicate to the server that it
554 // should leave the packet parked. It will be unparked until each hook
555 // library with a reference, unparks the packet.
557}
558
559void
561 std::string command_name;
562 callout_handle.getArgument("name", command_name);
563 if (command_name == "status-get") {
564 // Get the response.
565 ConstElementPtr response;
566 callout_handle.getArgument("response", response);
567 if (!response || (response->getType() != Element::map)) {
568 return;
569 }
570 // Get the arguments item from the response.
571 ConstElementPtr resp_args = response->get("arguments");
572 if (!resp_args || (resp_args->getType() != Element::map)) {
573 return;
574 }
575 // Add the ha servers info to arguments.
576 ElementPtr mutable_resp_args =
577 boost::const_pointer_cast<Element>(resp_args);
578
579 // Process the status get command for each HA service.
580 auto ha_relationships = Element::createList();
581 for (auto const& service : services_->getAll()) {
582 auto ha_relationship = Element::createMap();
583 ConstElementPtr ha_servers = service->processStatusGet();
584 ha_relationship->set("ha-servers", ha_servers);
585 ha_relationship->set("ha-mode", Element::create(HAConfig::HAModeToString(config_->get()->getHAMode())));
586 ha_relationships->add(ha_relationship);
587 mutable_resp_args->set("high-availability", ha_relationships);
588 }
589 }
590}
591
592void
594 // Command must always be provided.
595 ConstElementPtr command;
596 callout_handle.getArgument("command", command);
597
598 // Retrieve arguments.
599 ConstElementPtr args;
600 static_cast<void>(parseCommand(args, command));
601
602 HAServicePtr service;
603 try {
604 service = getHAServiceByServerName("ha-heartbeat", args);
605
606 } catch (const std::exception& ex) {
607 // There was an error while parsing command arguments. Return an error status
608 // code to notify the user.
610 callout_handle.setArgument("response", response);
611 return;
612 }
613
614 // Command parsing was successful, so let's process the command.
615 ConstElementPtr response = service->processHeartbeat();
616 callout_handle.setArgument("response", response);
617}
618
619void
621 // Command must always be provided.
622 ConstElementPtr command;
623 callout_handle.getArgument("command", command);
624
625 // Retrieve arguments.
626 ConstElementPtr args;
627 static_cast<void>(parseCommand(args, command));
628
629 ConstElementPtr server_name;
630 unsigned int max_period_value = 0;
631
632 HAServicePtr service;
633 try {
634 // Arguments are required for the ha-sync command.
635 if (!args) {
636 isc_throw(BadValue, "arguments not found in the 'ha-sync' command");
637 }
638
639 // Arguments must be a map.
640 if (args->getType() != Element::map) {
641 isc_throw(BadValue, "arguments in the 'ha-sync' command are not a map");
642 }
643
644 // server-name is mandatory. Otherwise how can we know the server to
645 // communicate with.
646 server_name = args->get("server-name");
647 if (!server_name) {
648 isc_throw(BadValue, "'server-name' is mandatory for the 'ha-sync' command");
649 }
650
651 // server-name must obviously be a string.
652 if (server_name->getType() != Element::string) {
653 isc_throw(BadValue, "'server-name' must be a string in the 'ha-sync' command");
654 }
655
656 // max-period is optional. In fact it is optional for dhcp-disable command too.
657 ConstElementPtr max_period = args->get("max-period");
658 if (max_period) {
659 // If it is specified, it must be a positive integer.
660 if ((max_period->getType() != Element::integer) ||
661 (max_period->intValue() <= 0)) {
662 isc_throw(BadValue, "'max-period' must be a positive integer in the 'ha-sync' command");
663 }
664
665 max_period_value = static_cast<unsigned int>(max_period->intValue());
666 }
667
668 service = getHAServiceByServerName("ha-sync", args);
669
670 } catch (const std::exception& ex) {
671 // There was an error while parsing command arguments. Return an error status
672 // code to notify the user.
674 callout_handle.setArgument("response", response);
675 return;
676 }
677
678 // Command parsing was successful, so let's process the command.
679 ConstElementPtr response = service->processSynchronize(server_name->stringValue(),
680 max_period_value);
681 callout_handle.setArgument("response", response);
682}
683
684void
686 // Command must always be provided.
687 ConstElementPtr command;
688 callout_handle.getArgument("command", command);
689
690 // Retrieve arguments.
691 ConstElementPtr args;
692 static_cast<void>(parseCommand(args, command));
693
694 HAServicePtr service;
695 std::vector<std::string> scopes_vector;
696 try {
697 // Arguments must be present.
698 if (!args) {
699 isc_throw(BadValue, "arguments not found in the 'ha-scopes' command");
700 }
701
702 // Arguments must be a map.
703 if (args->getType() != Element::map) {
704 isc_throw(BadValue, "arguments in the 'ha-scopes' command are not a map");
705 }
706
707 // scopes argument is mandatory.
708 ConstElementPtr scopes = args->get("scopes");
709 if (!scopes) {
710 isc_throw(BadValue, "'scopes' is mandatory for the 'ha-scopes' command");
711 }
712
713 // It contains a list of scope names.
714 if (scopes->getType() != Element::list) {
715 isc_throw(BadValue, "'scopes' must be a list in the 'ha-scopes' command");
716 }
717
718 // Retrieve scope names from this list. The list may be empty to clear the
719 // scopes.
720 for (size_t i = 0; i < scopes->size(); ++i) {
721 ConstElementPtr scope = scopes->get(i);
722 if (!scope || scope->getType() != Element::string) {
723 isc_throw(BadValue, "scope name must be a string in the 'scopes' argument");
724 }
725 scopes_vector.push_back(scope->stringValue());
726 }
727
728 service = getHAServiceByServerName("ha-scopes", args);
729
730 } catch (const std::exception& ex) {
731 // There was an error while parsing command arguments. Return an error status
732 // code to notify the user.
734 callout_handle.setArgument("response", response);
735 return;
736 }
737
738 // Command parsing was successful, so let's process the command.
739 ConstElementPtr response = service->processScopes(scopes_vector);
740 callout_handle.setArgument("response", response);
741}
742
743void
745 // Command must always be provided.
746 ConstElementPtr command;
747 callout_handle.getArgument("command", command);
748
749 // Retrieve arguments.
750 ConstElementPtr args;
751 static_cast<void>(parseCommand(args, command));
752
753 HAServicePtr service;
754 try {
755 service = getHAServiceByServerName("ha-continue", args);
756
757 } catch (const std::exception& ex) {
758 // There was an error while parsing command arguments. Return an error status
759 // code to notify the user.
761 callout_handle.setArgument("response", response);
762 return;
763 }
764 ConstElementPtr response = service->processContinue();
765 callout_handle.setArgument("response", response);
766}
767
768void
770 // Command must always be provided.
771 ConstElementPtr command;
772 callout_handle.getArgument("command", command);
773
774 HAServicePtr service;
775 try {
776 // Retrieve arguments.
777 ConstElementPtr args;
778 static_cast<void>(parseCommandWithArgs(args, command));
779
780 ConstElementPtr cancel_op = args->get("cancel");
781 if (!cancel_op) {
782 isc_throw(BadValue, "'cancel' is mandatory for the 'ha-maintenance-notify' command");
783 }
784
785 if (cancel_op->getType() != Element::boolean) {
786 isc_throw(BadValue, "'cancel' must be a boolean in the 'ha-maintenance-notify' command");
787 }
788
789 service = getHAServiceByServerName("ha-maintenance-notify", args);
790
791 ConstElementPtr response = service->processMaintenanceNotify(cancel_op->boolValue());
792 callout_handle.setArgument("response", response);
793
794 } catch (const std::exception& ex) {
795 // There was an error while parsing command arguments. Return an error status
796 // code to notify the user.
798 callout_handle.setArgument("response", response);
799 }
800}
801
802void
804 ConstElementPtr response;
805 for (auto const& service : services_->getAll()) {
806 response = service->processMaintenanceStart();
807 int rcode = CONTROL_RESULT_SUCCESS;
808 static_cast<void>(parseAnswer(rcode, response));
809 if (rcode != CONTROL_RESULT_SUCCESS) {
810 break;
811 }
812 }
813 callout_handle.setArgument("response", response);
814}
815
816void
818 ConstElementPtr response;
819 for (auto const& service : services_->getAll()) {
820 response = service->processMaintenanceCancel();
821 }
822 callout_handle.setArgument("response", response);
823}
824
825void
827 // Command must always be provided.
828 ConstElementPtr command;
829 callout_handle.getArgument("command", command);
830
831 // Retrieve arguments.
832 ConstElementPtr args;
833 static_cast<void>(parseCommand(args, command));
834
835 HAServicePtr service;
836 try {
837 service = getHAServiceByServerName("ha-reset", args);
838
839 } catch (const std::exception& ex) {
840 // There was an error while parsing command arguments. Return an error status
841 // code to notify the user.
843 callout_handle.setArgument("response", response);
844 return;
845 }
846
847 ConstElementPtr response = service->processHAReset();
848 callout_handle.setArgument("response", response);
849}
850
851void
853 // Command must always be provided.
854 ConstElementPtr command;
855 callout_handle.getArgument("command", command);
856
857 // Retrieve arguments.
858 ConstElementPtr args;
859 static_cast<void>(parseCommand(args, command));
860
861 HAServicePtr service;
862 auto origin_id_value = NetworkState::HA_REMOTE_COMMAND+1;
863 try {
864 if (args) {
865 auto origin_id = args->get("origin-id");
866 auto origin = args->get("origin");
867 // The origin-id is a new parameter replacing the origin. However, some versions
868 // of Kea may still send the origin parameter instead.
869 if (origin_id) {
870 if (origin_id->getType() != Element::integer) {
871 isc_throw(BadValue, "'origin-id' must be an integer in the 'ha-sync-complete-notify' command");
872 }
873 origin_id_value = origin_id->intValue();
874
875 } else if (origin) {
876 if (origin->getType() != Element::integer) {
877 isc_throw(BadValue, "'origin' must be an integer in the 'ha-sync-complete-notify' command");
878 }
879 origin_id_value = origin->intValue();
880 }
881 }
882
883 service = getHAServiceByServerName("ha-sync-complete-notify", args);
884
885 } catch (const std::exception& ex) {
886 // There was an error while parsing command arguments. Return an error status
887 // code to notify the user.
889 callout_handle.setArgument("response", response);
890 return;
891 }
892
893 ConstElementPtr response = service->processSyncCompleteNotify(origin_id_value);
894 callout_handle.setArgument("response", response);
895}
896
898HAImpl::getHAServiceByServerName(const std::string& command_name, ConstElementPtr args) const {
899 HAServicePtr service;
900 if (args) {
901 // Arguments must be a map.
902 if (args->getType() != Element::map) {
903 isc_throw(BadValue, "arguments in the '" << command_name << "' command are not a map");
904 }
905
906 auto server_name = args->get("server-name");
907
908 if (server_name) {
909 if (server_name->getType() != Element::string) {
910 isc_throw(BadValue, "'server-name' must be a string in the '" << command_name << "' command");
911 }
912 service = services_->get(server_name->stringValue());
913 if (!service) {
914 isc_throw(BadValue, server_name->stringValue() << " matches no configured"
915 << " 'server-name'");
916 }
917 }
918 }
919
920 if (!service) {
921 service = services_->get();
922 }
923
924 return (service);
925}
926
927} // end of namespace isc::ha
928} // end of namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
virtual const char * what() const
Returns a C-style character string of the cause of the exception.
A generic exception that is thrown when an unexpected error condition occurs.
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
static const unsigned int HA_REMOTE_COMMAND
The network state is being altered by a "dhcp-disable" or "dhcp-enable" command sent by a HA partner.
Exception thrown during option unpacking This exception is thrown when an error has occurred,...
Definition: option.h:52
static HAConfigMapperPtr parse(const data::ConstElementPtr &config)
Parses HA configuration.
static std::string getSubnetServerName(const dhcp::SubnetPtr &subnet)
Convenience function extracting a value of the ha-server-name parameter from a subnet context.
Definition: ha_config.cc:524
static std::string HAModeToString(const HAMode &ha_mode)
Returns HA mode name.
Definition: ha_config.cc:233
void scopesHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-scopes command.
Definition: ha_impl.cc:685
void continueHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-continue command.
Definition: ha_impl.cc:744
HAConfigMapperPtr config_
Holds parsed configuration.
Definition: ha_impl.h:244
void syncCompleteNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync-complete-notify command.
Definition: ha_impl.cc:852
HAServicePtr getHAServiceByServerName(const std::string &command_name, data::ConstElementPtr args) const
Attempts to get an HAService by server name.
Definition: ha_impl.cc:898
HAServiceMapperPtr services_
Pointer to the high availability services (state machines).
Definition: ha_impl.h:247
void startServices(const dhcp::NetworkStatePtr &network_state, const HAServerType &server_type)
Creates high availability services using current configuration.
Definition: ha_impl.cc:43
void subnet4Select(hooks::CalloutHandle &callout_handle)
Implementation of the "subnet4_select" callout.
Definition: ha_impl.cc:129
void configure(const data::ConstElementPtr &input_config)
Parses configuration.
Definition: ha_impl.cc:38
~HAImpl()
Destructor.
Definition: ha_impl.cc:63
void maintenanceCancelHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-cancel command.
Definition: ha_impl.cc:817
void haResetHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-reset command.
Definition: ha_impl.cc:826
void maintenanceNotifyHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-notify command.
Definition: ha_impl.cc:769
void synchronizeHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-sync command.
Definition: ha_impl.cc:620
void maintenanceStartHandler(hooks::CalloutHandle &callout_handle)
Implements handler for the ha-maintenance-start command.
Definition: ha_impl.cc:803
isc::asiolink::IOServicePtr io_service_
The hook I/O service.
Definition: ha_impl.h:241
void leases4Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases4_committed" callout.
Definition: ha_impl.cc:213
void buffer4Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer4_receive" callout.
Definition: ha_impl.cc:72
void buffer6Receive(hooks::CalloutHandle &callout_handle)
Implementation of the "buffer6_receive" callout.
Definition: ha_impl.cc:336
void commandProcessed(hooks::CalloutHandle &callout_handle)
Implementation of the "command_processed" callout.
Definition: ha_impl.cc:560
HAImpl()
Constructor.
Definition: ha_impl.cc:33
void leases6Committed(hooks::CalloutHandle &callout_handle)
Implementation of the "leases6_committed" callout.
Definition: ha_impl.cc:477
void subnet6Select(hooks::CalloutHandle &callout_handle)
Implementation of the "subnet6_select" callout.
Definition: ha_impl.cc:393
void lease4ServerDecline(hooks::CalloutHandle &callout_handle)
Implementation of the "lease4_server_decline" callout.
Definition: ha_impl.cc:303
void heartbeatHandler(hooks::CalloutHandle &callout_handle)
Implements handle for the heartbeat command.
Definition: ha_impl.cc:593
Holds associations between objects and HA relationships.
Per-packet callout handle.
void getContext(const std::string &name, T &value) const
Get context.
@ NEXT_STEP_PARK
park the packet
@ NEXT_STEP_CONTINUE
continue normally
@ NEXT_STEP_DROP
drop the packet
@ NEXT_STEP_SKIP
skip the next processing step
ParkingLotHandlePtr getParkingLotHandlePtr() const
Returns pointer to the parking lot handle for this hook point.
void setContext(const std::string &name, T value)
Set context.
CalloutNextStep getStatus() const
Returns the next processing step.
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
static StatsMgr & instance()
Statistics Manager accessor method.
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.
void addValue(const std::string &name, const int64_t value)
Records incremental integer observation.
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommand(ConstElementPtr &arg, ConstElementPtr command)
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
ConstElementPtr parseAnswer(int &rcode, const ConstElementPtr &msg)
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< Subnet4 > Subnet4Ptr
A pointer to a Subnet4 object.
Definition: subnet.h:458
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< Subnet6 > Subnet6Ptr
A pointer to a Subnet6 object.
Definition: subnet.h:623
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
boost::shared_ptr< Lease4 > Lease4Ptr
Pointer to a Lease4 structure.
Definition: lease.h:292
const isc::log::MessageID HA_BUFFER4_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:14
const isc::log::MessageID HA_SUBNET4_SELECT_NO_SUBNET_SELECTED
Definition: ha_messages.h:112
const isc::log::MessageID HA_LEASES6_COMMITTED_NOTHING_TO_UPDATE
Definition: ha_messages.h:58
const isc::log::MessageID HA_LEASES6_COMMITTED_NO_RELATIONSHIP
Definition: ha_messages.h:59
const isc::log::MessageID HA_BUFFER4_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition: ha_messages.h:13
const isc::log::MessageID HA_BUFFER6_RECEIVE_UNPACK_FAILED
Definition: ha_messages.h:18
const isc::log::MessageID HA_SUBNET6_SELECT_NO_SUBNET_SELECTED
Definition: ha_messages.h:118
const isc::log::MessageID HA_SUBNET6_SELECT_INVALID_HA_SERVER_NAME
Definition: ha_messages.h:114
const isc::log::MessageID HA_LEASES4_COMMITTED_NO_RELATIONSHIP
Definition: ha_messages.h:56
isc::log::Logger ha_logger("ha-hooks")
Definition: ha_log.h:17
const isc::log::MessageID HA_SUBNET6_SELECT_NOT_FOR_US
Definition: ha_messages.h:115
const isc::log::MessageID HA_BUFFER6_RECEIVE_PACKET_OPTIONS_SKIPPED
Definition: ha_messages.h:17
boost::shared_ptr< HAConfig > HAConfigPtr
Pointer to the High Availability configuration structure.
Definition: ha_config.h:37
const isc::log::MessageID HA_SUBNET4_SELECT_INVALID_HA_SERVER_NAME
Definition: ha_messages.h:108
HAServerType
Lists possible server types for which HA service is created.
const isc::log::MessageID HA_SUBNET4_SELECT_NO_RELATIONSHIP_FOR_SUBNET
Definition: ha_messages.h:110
const isc::log::MessageID HA_SUBNET4_SELECT_NOT_FOR_US
Definition: ha_messages.h:109
const isc::log::MessageID HA_SUBNET6_SELECT_NO_RELATIONSHIP_FOR_SUBNET
Definition: ha_messages.h:116
const isc::log::MessageID HA_LEASES4_COMMITTED_NOTHING_TO_UPDATE
Definition: ha_messages.h:55
const isc::log::MessageID HA_SUBNET4_SELECT_NO_RELATIONSHIP_SELECTOR_FOR_SUBNET
Definition: ha_messages.h:111
const isc::log::MessageID HA_SUBNET6_SELECT_NO_RELATIONSHIP_SELECTOR_FOR_SUBNET
Definition: ha_messages.h:117
const isc::log::MessageID HA_BUFFER4_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:12
const isc::log::MessageID HA_BUFFER6_RECEIVE_NOT_FOR_US
Definition: ha_messages.h:16
boost::shared_ptr< HAService > HAServicePtr
Pointer to the HAService class.
Definition: ha_service.h:1384
boost::shared_ptr< ParkingLotHandle > ParkingLotHandlePtr
Pointer to the parking lot handle.
Definition: parking_lots.h:381
const int DBGLVL_TRACE_BASIC
Trace basic operations.
Definition: log_dbglevels.h:69
Defines the logger used by the top-level component of kea-lfc.