From 9ab9a5534206b5bf86985e8d88ba34e82b63d65b Mon Sep 17 00:00:00 2001 From: Francis Dupont Date: Wed, 26 May 2021 20:09:25 +0000 Subject: [PATCH] [(no branch, rebasing 1897-add-d2-server-hook-syntax)] [(no branch, rebasing 1897-add-d2-server-hook-syntax)] [#1897] Checkpoint before regen --- doc/devel/mainpage.dox | 1 + src/bin/d2/Makefile.am | 2 +- src/bin/d2/d2_cfg_mgr.cc | 5 +++ src/bin/d2/d2_cfg_mgr.h | 20 ++++++++- src/bin/d2/d2_hooks.dox | 43 ++++++++++++++++++++ src/bin/d2/d2_lexer.ll | 31 +++++++++++++- src/bin/d2/d2_parser.yy | 74 ++++++++++++++++++++++++++++++++++ src/bin/d2/d2_simple_parser.cc | 30 +++++++++++--- src/bin/d2/parser_context.cc | 4 +- src/bin/d2/parser_context.h | 12 ++++-- 10 files changed, 209 insertions(+), 13 deletions(-) create mode 100644 src/bin/d2/d2_hooks.dox diff --git a/doc/devel/mainpage.dox b/doc/devel/mainpage.dox index c58376eb8f..34dc5f759c 100644 --- a/doc/devel/mainpage.dox +++ b/doc/devel/mainpage.dox @@ -52,6 +52,7 @@ * - @subpage dhcpv4Hooks * - @subpage dhcpv6Hooks * - @subpage agentHooks + * - @subpage d2Hooks * - @subpage hooksComponentDeveloperGuide * - @subpage hooksmgMaintenanceGuide * - @subpage libdhcp_ha diff --git a/src/bin/d2/Makefile.am b/src/bin/d2/Makefile.am index b7186e1d05..28acbaf2e8 100644 --- a/src/bin/d2/Makefile.am +++ b/src/bin/d2/Makefile.am @@ -12,7 +12,7 @@ endif CLEANFILES = *.gcno *.gcda -EXTRA_DIST = d2.dox +EXTRA_DIST = d2.dox d2_hooks.dox EXTRA_DIST += d2_parser.yy EXTRA_DIST += images/abstract_app_classes.svg images/add_state_model.svg diff --git a/src/bin/d2/d2_cfg_mgr.cc b/src/bin/d2/d2_cfg_mgr.cc index da1ae21ca5..7d23ecefbf 100644 --- a/src/bin/d2/d2_cfg_mgr.cc +++ b/src/bin/d2/d2_cfg_mgr.cc @@ -53,6 +53,8 @@ D2CfgContext::D2CfgContext(const D2CfgContext& rhs) : ConfigBase(rhs) { keys_ = rhs.keys_; control_socket_ = rhs.control_socket_; + + hooks_config_ = rhs.hooks_config_; } D2CfgContext::~D2CfgContext() { @@ -101,6 +103,8 @@ D2CfgContext::toElement() const { if (!isNull(control_socket_)) { d2->set("control-socket", UserContext::toElement(control_socket_)); } + // Set hooks-librairies + d2->set("hooks-libraries", hooks_config_.toElement()); // Set DhcpDdns ElementPtr result = Element::createMap(); result->set("DhcpDdns", d2); @@ -313,6 +317,7 @@ std::list> D2CfgMgr::jsonPathsToRedact() const { static std::list> const list({ {"tsig-keys", "[]"}, + {"hooks-libraries", "[]", "parameters", "*"}, }); return list; } diff --git a/src/bin/d2/d2_cfg_mgr.h b/src/bin/d2/d2_cfg_mgr.h index 11b6a0c538..00f5827f32 100644 --- a/src/bin/d2/d2_cfg_mgr.h +++ b/src/bin/d2/d2_cfg_mgr.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include @@ -102,6 +103,20 @@ public: control_socket_ = control_socket; } + /// @brief Returns non-const reference to configured hooks libraries. + /// + /// @return non-const reference to configured hooks libraries. + isc::hooks::HooksConfig& getHooksConfig() { + return (hooks_config_); + } + + /// @brief Returns const reference to configured hooks libraries. + /// + /// @return const reference to configured hooks libraries. + const isc::hooks::HooksConfig& getHooksConfig() const { + return (hooks_config_); + } + /// @brief Unparse a configuration object /// /// @return a pointer to a configuration @@ -129,13 +144,14 @@ private: /// @brief Pointer to the control-socket information. isc::data::ConstElementPtr control_socket_; + + /// @brief Configured hooks libraries. + isc::hooks::HooksConfig hooks_config_; }; /// @brief Defines a pointer for DdnsDomain instances. typedef boost::shared_ptr DdnsDomainListMgrPtr; - - /// @brief DHCP-DDNS Configuration Manager /// /// Provides the mechanisms for managing the DHCP-DDNS application's diff --git a/src/bin/d2/d2_hooks.dox b/src/bin/d2/d2_hooks.dox new file mode 100644 index 0000000000..3099504498 --- /dev/null +++ b/src/bin/d2/d2_hooks.dox @@ -0,0 +1,43 @@ +// Copyright (C) 2021 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +/** +@page d2Hooks The Hooks API for the DDNS Server + +@section d2HooksIntroduction Introduction +The Kea DDNS Server features "Hooks" API that allows the user-written +code to be integrated with the DDNS Server and handle some +of the control commands. The hooks library can be either used to provide +support for the new commands (not supported natively by the DDNS Server) +or "override" implementation of the existing handlers. The hooks library +signals to the DDNS Server that it has processed the given command by +setting "next step status" value to SKIP. + +The hooks library can also be used to perform some additional tasks +related to reception of the control command instead of handling it, e.g. +logging or notifying some external service about reception of the +command. + +@section d2HooksHookPoints Hooks in the DDNS Server + + @subsection d2HooksD2SrvConfigured d2_srv_configured + - @b Arguments: + - name: @b io_context, type: isc::asiolink::IOServicePtr, direction: in + - name: @b json_config, type: isc::data::ConstElementPtr, direction: in + - name: @b server_config, type: isc::d2::D2CfgContextPtr, direction: in + + - @b Description: this callout is executed when the server has completed + its (re)configuration. The server provides received and parsed configuration + structures to the hook library. It also provides a pointer to the IOService + object which is used by the server to run asynchronous operations. The hooks + libraries can use this IOService object to schedule asynchronous tasks which + are triggered by the D2 server's main loop. The hook library should hold the + provided pointer until the library is unloaded. + + - Next step status: Status codes returned by the callouts installed on + this hook point are ignored. + +*/ \ No newline at end of file diff --git a/src/bin/d2/d2_lexer.ll b/src/bin/d2/d2_lexer.ll index e484aeebf7..d3d1c4caee 100644 --- a/src/bin/d2/d2_lexer.ll +++ b/src/bin/d2/d2_lexer.ll @@ -1,4 +1,4 @@ -/* Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +/* Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of the MPL was not distributed with this @@ -126,6 +126,8 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu] return isc::d2::D2Parser::make_SUB_DDNS_DOMAINS(driver.loc_); case D2ParserContext::PARSER_DNS_SERVER: return isc::d2::D2Parser::make_SUB_DNS_SERVER(driver.loc_); + case D2ParserContext::PARSER_HOOKS_LIBRARY: + return isc::d2::D2Parser::make_SUB_HOOKS_LIBRARY(driver.loc_); } } %} @@ -420,6 +422,33 @@ ControlCharacterFill [^"\\]|\\["\\/bfnrtu] } } +\"hooks-libraries\" { + switch(driver.ctx_) { + case isc::d2::D2ParserContext::DHCPDDNS: + return isc::d2::D2Parser::make_HOOKS_LIBRARIES(driver.loc_); + default: + return isc::d2::D2Parser::make_STRING("hooks-libraries", driver.loc_); + } +} + +\"parameters\" { + switch(driver.ctx_) { + case isc::d2::D2ParserContext::HOOKS_LIBRARIES: + return isc::d2::D2Parser::make_PARAMETERS(driver.loc_); + default: + return isc::d2::D2Parser::make_STRING("parameters", driver.loc_); + } +} + +\"library\" { + switch(driver.ctx_) { + case isc::d2::D2ParserContext::HOOKS_LIBRARIES: + return isc::d2::D2Parser::make_LIBRARY(driver.loc_); + default: + return isc::d2::D2Parser::make_STRING("library", driver.loc_); + } +} + \"loggers\" { switch(driver.ctx_) { case isc::d2::D2ParserContext::DHCPDDNS: diff --git a/src/bin/d2/d2_parser.yy b/src/bin/d2/d2_parser.yy index 5ddb00170a..8d7213e253 100644 --- a/src/bin/d2/d2_parser.yy +++ b/src/bin/d2/d2_parser.yy @@ -75,6 +75,10 @@ using namespace std; SOCKET_TYPE "socket-type" SOCKET_NAME "socket-name" + HOOKS_LIBRARIES "hooks-libraries" + LIBRARY "library" + PARAMETERS "parameters" + LOGGERS "loggers" NAME "name" OUTPUT_OPTIONS "output_options" @@ -97,6 +101,7 @@ using namespace std; SUB_DDNS_DOMAINS SUB_DNS_SERVER SUB_DNS_SERVERS + SUB_HOOKS_LIBRARY ; %token STRING "constant string" @@ -126,6 +131,7 @@ start: TOPLEVEL_JSON { ctx.ctx_ = ctx.NO_KEYWORD; } sub_json | SUB_DDNS_DOMAINS { ctx.ctx_ = ctx.DDNS_DOMAINS; } sub_ddns_domains | SUB_DNS_SERVER { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_server | SUB_DNS_SERVERS { ctx.ctx_ = ctx.DNS_SERVERS; } sub_dns_servers + | SUB_HOOKS_LIBRARY { ctx.ctx_ = ctx.HOOKS_LIBRARIES; } sub_hooks_library ; // ---- generic JSON parser --------------------------------- @@ -259,6 +265,7 @@ dhcpddns_param: ip_address | reverse_ddns | tsig_keys | control_socket + | hooks_libraries | loggers | user_context | comment @@ -716,6 +723,73 @@ control_socket_name: SOCKET_NAME { ctx.leave(); }; +// --- hooks libraries ----------------------------------------- + +hooks_libraries: HOOKS_LIBRARIES { + ctx.unique("hooks-libraries", ctx.loc2pos(@1)); + ElementPtr l(new ListElement(ctx.loc2pos(@1))); + ctx.stack_.back()->set("hooks-libraries", l); + ctx.stack_.push_back(l); + ctx.enter(ctx.HOOKS_LIBRARIES); +} COLON LSQUARE_BRACKET hooks_libraries_list RSQUARE_BRACKET { + ctx.stack_.pop_back(); + ctx.leave(); +}; + +hooks_libraries_list: %empty + | not_empty_hooks_libraries_list + ; + +not_empty_hooks_libraries_list: hooks_library + | not_empty_hooks_libraries_list COMMA hooks_library + ; + +hooks_library: LCURLY_BRACKET { + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.back()->add(m); + ctx.stack_.push_back(m); +} hooks_params RCURLY_BRACKET { + // The library hooks parameter is required + ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4)); + ctx.stack_.pop_back(); +}; + +sub_hooks_library: LCURLY_BRACKET { + // Parse the hooks-libraries list entry map + ElementPtr m(new MapElement(ctx.loc2pos(@1))); + ctx.stack_.push_back(m); +} hooks_params RCURLY_BRACKET { + // The library hooks parameter is required + ctx.require("library", ctx.loc2pos(@1), ctx.loc2pos(@4)); + // parsing completed +}; + +hooks_params: hooks_param + | hooks_params COMMA hooks_param + | unknown_map_entry + ; + +hooks_param: library + | parameters + ; + +library: LIBRARY { + ctx.unique("library", ctx.loc2pos(@1)); + ctx.enter(ctx.NO_KEYWORD); +} COLON STRING { + ElementPtr lib(new StringElement($4, ctx.loc2pos(@4))); + ctx.stack_.back()->set("library", lib); + ctx.leave(); +}; + +parameters: PARAMETERS { + ctx.unique("parameters", ctx.loc2pos(@1)); + ctx.enter(ctx.NO_KEYWORD); +} COLON map_value { + ctx.stack_.back()->set("parameters", $4); + ctx.leave(); +}; + // --- loggers entry ----------------------------------------- loggers: LOGGERS { diff --git a/src/bin/d2/d2_simple_parser.cc b/src/bin/d2/d2_simple_parser.cc index 06607e707f..5f0b5a42a2 100644 --- a/src/bin/d2/d2_simple_parser.cc +++ b/src/bin/d2/d2_simple_parser.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -9,6 +9,8 @@ #include #include #include +#include +#include #include using namespace isc::data; @@ -196,7 +198,7 @@ D2SimpleParser::setManagerDefaults(ElementPtr global, void D2SimpleParser::parse(const D2CfgContextPtr& ctx, const isc::data::ConstElementPtr& config, - bool /*check_only*/) { + bool check_only) { // TSIG keys need to parse before the Domains, so we can catch Domains // that specify undefined keys. Create the necessary parsing order now. // addToParseOrder("tsig-keys"); @@ -276,14 +278,32 @@ void D2SimpleParser::parse(const D2CfgContextPtr& ctx, ctx->setControlSocketInfo(socket); } + // Finally, let's get the hook libs! + using namespace isc::hooks; + HooksConfig& libraries = ctx->getHooksConfig(); + ConstElementPtr hooks = config->get("hooks-libraries"); + if (hooks) { + HooksLibrariesParser hooks_parser; + hooks_parser.parse(libraries, hooks); + libraries.verifyLibraries(hooks->getPosition()); + } + // Attempt to create the new client config. This ought to fly as // we already validated everything. D2ParamsPtr params(new D2Params(ip_address, port, dns_server_timeout, ncr_protocol, ncr_format)); ctx->getD2Params() = params; + + if (!check_only) { + // This occurs last as if it succeeds, there is no easy way + // revert it. As a result, the failure to commit a subsequent + // change causes problems when trying to roll back. + HooksManager::prepareUnloadLibraries(); + static_cast(HooksManager::unloadLibraries()); + libraries.loadLibraries(); + } } - -}; -}; +} +} diff --git a/src/bin/d2/parser_context.cc b/src/bin/d2/parser_context.cc index fce0c9f79b..0466ae632d 100644 --- a/src/bin/d2/parser_context.cc +++ b/src/bin/d2/parser_context.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -175,6 +175,8 @@ D2ParserContext::contextName() return ("ncr-protocol"); case NCR_FORMAT: return ("ncr-format"); + case HOOKS_LIBRARIES: + return ("hooks-libraries"); default: return ("__unknown__"); } diff --git a/src/bin/d2/parser_context.h b/src/bin/d2/parser_context.h index 0d596cf56f..3e8982f6bb 100644 --- a/src/bin/d2/parser_context.h +++ b/src/bin/d2/parser_context.h @@ -1,4 +1,4 @@ -// Copyright (C) 2017-2020 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2017-2021 Internet Systems Consortium, Inc. ("ISC") // // This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this @@ -69,7 +69,10 @@ public: PARSER_DNS_SERVER, ///< Used for parsing a list of DNS servers. - PARSER_DNS_SERVERS + PARSER_DNS_SERVERS, + + ///< Used for parsing content of hooks libraries. + PARSER_HOOKS_LIBRARY } ParserType; /// @brief Default constructor. @@ -233,7 +236,10 @@ public: NCR_PROTOCOL, /// Used while parsing DhcpDdns/ncr-format - NCR_FORMAT + NCR_FORMAT, + + /// Used while parsing DhcpDdns/hooks-libraries. + HOOKS_LIBRARIES } ParserContext;