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