From f624d1371ab5bb9fdcfb77f49f2abf896611cdd5 Mon Sep 17 00:00:00 2001 From: Andrei Pavel Date: Fri, 20 Jun 2025 14:57:19 +0300 Subject: [PATCH] [#3907] Progressed some more with YANG modules --- doc/examples/kea4/all-keys-netconf.json | 13 ++++-- doc/examples/kea6/all-keys-netconf.json | 13 ++++-- .../translator_control_socket_unittests.cc | 2 +- .../tests/translator_database_unittests.cc | 10 ++--- src/lib/yang/translator_config.cc | 2 +- src/lib/yang/translator_control_socket.cc | 44 ++++++++++--------- src/lib/yang/translator_control_socket.h | 3 +- src/lib/yang/translator_database.cc | 21 ++++++--- src/lib/yang/translator_database.h | 10 ++++- .../modules/hashes/kea-types@2025-06-25.hash | 2 +- .../yang/modules/kea-types@2025-06-25.yang | 19 ++++---- 11 files changed, 87 insertions(+), 52 deletions(-) diff --git a/doc/examples/kea4/all-keys-netconf.json b/doc/examples/kea4/all-keys-netconf.json index e350af1bd2..92b09359ca 100644 --- a/doc/examples/kea4/all-keys-netconf.json +++ b/doc/examples/kea4/all-keys-netconf.json @@ -236,7 +236,9 @@ // If password is not specified an empty // password is used. - "password": "1234" + "password": "1234", + "password-file": "", + "user-file": "" }, // This specifies a hidden client. @@ -244,10 +246,12 @@ // The user id is the content of the // file /usr/local/share/kea/kea-creds/hiddenu. "user-file": "hiddenu", + "user": "", // The password is the content of the // file /usr/local/share/kea/kea-creds/hiddenp. - "password-file": "hiddenp" + "password-file": "hiddenp", + "password": "" }, // This specifies a hidden client using a @@ -256,7 +260,10 @@ // The secret is the content of the file // /usr/local/share/kea/kea-creds/hiddens which must be in // the : format. - "password-file": "hiddens" + "password-file": "hiddens", + "user-file": "", + "user": "", + "password": "" } ] } diff --git a/doc/examples/kea6/all-keys-netconf.json b/doc/examples/kea6/all-keys-netconf.json index e070989d04..ccb8ccde0a 100644 --- a/doc/examples/kea6/all-keys-netconf.json +++ b/doc/examples/kea6/all-keys-netconf.json @@ -187,7 +187,9 @@ // If password is not specified an empty // password is used. - "password": "1234" + "password": "1234", + "password-file": "", + "user-file": "" }, // This specifies a hidden client. @@ -195,10 +197,12 @@ // The user id is the content of the // file /usr/local/share/kea/kea-creds/hiddenu. "user-file": "hiddenu", + "user": "", // The password is the content of the // file /usr/local/share/kea/kea-creds/hiddenp. - "password-file": "hiddenp" + "password-file": "hiddenp", + "password": "" }, // This specifies a hidden client using a @@ -207,7 +211,10 @@ // The secret is the content of the file // /usr/local/share/kea/kea-creds/hiddens which must be in // the : format. - "password-file": "hiddens" + "password-file": "hiddens", + "user-file": "", + "user": "", + "password": "" } ] } diff --git a/src/lib/yang/tests/translator_control_socket_unittests.cc b/src/lib/yang/tests/translator_control_socket_unittests.cc index 526bfbf896..2777c023f9 100644 --- a/src/lib/yang/tests/translator_control_socket_unittests.cc +++ b/src/lib/yang/tests/translator_control_socket_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 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 diff --git a/src/lib/yang/tests/translator_database_unittests.cc b/src/lib/yang/tests/translator_database_unittests.cc index 4203bcb56d..35b9170cc1 100644 --- a/src/lib/yang/tests/translator_database_unittests.cc +++ b/src/lib/yang/tests/translator_database_unittests.cc @@ -1,4 +1,4 @@ -// Copyright (C) 2018-2022 Internet Systems Consortium, Inc. ("ISC") +// Copyright (C) 2018-2025 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 @@ -92,7 +92,7 @@ TEST_F(TranslatorDatabaseTestv4, set) { ElementPtr database = Element::createMap(); database->set("type", Element::create("memfile")); database->set("lfc-interval", Element::create(3600)); - ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, database)); + ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, database, /* has_mandatory_key = */ false)); // Get it back. ConstElementPtr got; @@ -125,7 +125,7 @@ TEST_F(TranslatorDatabaseTestv4, setEmpty) { sess_->applyChanges(); // Reset to empty. - ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, ConstElementPtr())); + ASSERT_NO_THROW_LOG(translator_->setDatabase(xpath, ConstElementPtr(), /* has_mandatory_key = */ false)); // Get it back. ConstElementPtr database; @@ -280,7 +280,7 @@ TEST_F(TranslatorDatabasesTestv4, setEmpty) { sess_->applyChanges(); // Reset to empty. - EXPECT_NO_THROW_LOG(translator_->setDatabase(xdatabase, ConstElementPtr())); + EXPECT_NO_THROW_LOG(translator_->setDatabase(xdatabase, ConstElementPtr(), /* has_mandatory_key = */ true)); // Get empty. ConstElementPtr databases; @@ -313,7 +313,7 @@ TEST_F(TranslatorDatabasesTestv4, setEmpties) { sess_->applyChanges(); // Reset to empty. - EXPECT_NO_THROW_LOG(translator_->setDatabases(xdatabase, ConstElementPtr())); + EXPECT_NO_THROW_LOG(translator_->setDatabase(xdatabase, ConstElementPtr(), /* has_mandatory_key = */ true)); // Get empty. ConstElementPtr databases; diff --git a/src/lib/yang/translator_config.cc b/src/lib/yang/translator_config.cc index d695a740d7..bdd098c5b9 100644 --- a/src/lib/yang/translator_config.cc +++ b/src/lib/yang/translator_config.cc @@ -632,7 +632,7 @@ TranslatorConfig::setServerKeaDhcpCommon(string const& xpath, ConstElementPtr database = elem->get("lease-database"); if (database && !database->empty()) { - setDatabase(xpath + "/lease-database", database); + setDatabase(xpath + "/lease-database", database, /* has_mandatory_key = */ false); } ConstElementPtr loggers = elem->get("loggers"); diff --git a/src/lib/yang/translator_control_socket.cc b/src/lib/yang/translator_control_socket.cc index cf4c44f3af..b997ae7f47 100644 --- a/src/lib/yang/translator_control_socket.cc +++ b/src/lib/yang/translator_control_socket.cc @@ -66,8 +66,8 @@ TranslatorControlSocket::getControlSocketsKea(DataNode const& data_node) { ElementPtr TranslatorControlSocket::getControlSocketKea(DataNode const& data_node) { ElementPtr result(Element::createMap()); - getMandatoryLeaf(result, data_node, "socket-type"); checkAndGetLeaf(result, data_node, "socket-name"); + checkAndGetLeaf(result, data_node, "socket-type"); if (model_ != KEA_CTRL_AGENT) { checkAndGetLeaf(result, data_node, "socket-address"); checkAndGetLeaf(result, data_node, "socket-port"); @@ -86,7 +86,7 @@ TranslatorControlSocket::getControlSocketKea(DataNode const& data_node) { authentication = Element::createMap(); } - getMandatoryDivergingLeaf(authentication, node, "type", "auth-type"); + checkAndGetDivergingLeaf(authentication, node, "type", "auth-type"); checkAndGetLeaf(authentication, node, "realm"); checkAndGetLeaf(authentication, node, "directory"); ConstElementPtr clients = getControlSocketAuthenticationClients(node); @@ -153,7 +153,7 @@ TranslatorControlSocket::setControlSockets(string const& xpath, (model_ == KEA_DHCP_DDNS)) { setControlSocketsKea(xpath, elem); } else if (model_ == KEA_CTRL_AGENT) { - setControlSocketKea(xpath, elem); + setControlSocketKea(xpath, elem, /* has_mandatory_key = */ true); } else { isc_throw(NotImplemented, "setControlSocket not implemented for the model: " @@ -174,7 +174,7 @@ TranslatorControlSocket::setControlSocket(string const& xpath, (model_ == KEA_DHCP6_SERVER) || (model_ == KEA_DHCP_DDNS) || (model_ == KEA_CTRL_AGENT)) { - setControlSocketKea(xpath, elem); + setControlSocketKea(xpath, elem, /* has_mandatory_key = */ false); } else { isc_throw(NotImplemented, "setControlSocket not implemented for the model: " @@ -202,28 +202,37 @@ TranslatorControlSocket::setControlSocketsKea(string const& xpath, string type = control_socket->get("socket-type")->stringValue(); ostringstream key; key << xpath << "[socket-type='" << type << "']"; - setControlSocketKea(key.str(), control_socket); + setControlSocketKea(key.str(), control_socket, /* has_mandatory_key = */ true); } } void -TranslatorControlSocket::setControlSocketKea(string const& xpath, ConstElementPtr elem) { +TranslatorControlSocket::setControlSocketKea(string const& xpath, + ConstElementPtr elem, + bool has_mandatory_key) { if (!elem) { deleteItem(xpath); return; } + if (has_mandatory_key) { + // Set the list element. This is important in case we have no other elements except the key. + setItem(xpath, ElementPtr(), LeafBaseType::Unknown); + } else { + checkAndSetLeaf(elem, xpath, "socket-type", LeafBaseType::String); + } + checkAndSetLeaf(elem, xpath, "socket-name", LeafBaseType::String); if (model_ != KEA_CTRL_AGENT) { checkAndSetLeaf(elem, xpath, "socket-address", LeafBaseType::String); - checkAndSetLeaf(elem, xpath, "socket-port", LeafBaseType::Uint32); + checkAndSetLeaf(elem, xpath, "socket-port", LeafBaseType::Uint16); checkAndSetLeaf(elem, xpath, "trust-anchor", LeafBaseType::String); checkAndSetLeaf(elem, xpath, "cert-file", LeafBaseType::String); checkAndSetLeaf(elem, xpath, "key-file", LeafBaseType::String); checkAndSetLeaf(elem, xpath, "cert-required", LeafBaseType::Bool); ConstElementPtr authentication = elem->get("authentication"); if (authentication && !authentication->empty()) { - setMandatoryDivergingLeaf(authentication, xpath , "type", "auth-type", LeafBaseType::String); + setMandatoryDivergingLeaf(authentication, xpath +"/authentication" , "type", "auth-type", LeafBaseType::String); checkAndSetLeaf(authentication, xpath + "/authentication", "realm", LeafBaseType::String); checkAndSetLeaf(authentication, xpath + "/authentication", "directory", LeafBaseType::String); ConstElementPtr clients = authentication->get("clients"); @@ -231,13 +240,9 @@ TranslatorControlSocket::setControlSocketKea(string const& xpath, ConstElementPt } ConstElementPtr http_headers = elem->get("http-headers"); if (http_headers && !http_headers->empty()) { - for (size_t i = 0; i < http_headers->size(); ++i) { - ElementPtr header = elem->getNonConst(i); - // setHeader - } + setControlSocketHttpHeaders(xpath + "/http-headers", http_headers); } } - setMandatoryLeaf(elem, xpath, "socket-type", LeafBaseType::Enum); checkAndSetUserContext(elem, xpath); } @@ -271,7 +276,7 @@ TranslatorControlSocket::setControlSocketAuthenticationClients(string const& xpa if (password_file) { password_file_str = password_file->stringValue(); } - key << xpath << "[user='" << user_str << "'][password=']" << password_str + key << xpath << "[user='" << user_str << "'][password='" << password_str << "'][user-file='" << user_file_str << "'][password-file='" << password_file_str << "']"; setControlSocketAuthenticationClient(key.str(), client); @@ -291,13 +296,13 @@ TranslatorControlSocket::setControlSocketHttpHeaders(const std::string& xpath, deleteItem(xpath); return; } - for (size_t i = 0; i < elem->size(); ++i) { - ElementPtr header = elem->getNonConst(i); - ostringstream key; - if (!header->contains("name")) { + for (ConstElementPtr header : elem->listValue()) { + ConstElementPtr name(header->get("name")); + if (!name) { isc_throw(BadValue, "http header without name: " << header->str()); } - key << xpath << "[name='" << header->stringValue() << "']"; + ostringstream key; + key << xpath << "[name='" << name->stringValue() << "']"; setControlSocketHttpHeader(key.str(), header); } } @@ -309,7 +314,6 @@ TranslatorControlSocket::setControlSocketHttpHeader(const std::string& xpath, checkAndSetLeaf(elem, xpath, "value", LeafBaseType::String); checkAndSetUserContext(elem, xpath); - setMandatoryLeaf(elem, xpath, "name", LeafBaseType::Enum); } } // namespace yang diff --git a/src/lib/yang/translator_control_socket.h b/src/lib/yang/translator_control_socket.h index 4ee8b17ec4..d4d77b8577 100644 --- a/src/lib/yang/translator_control_socket.h +++ b/src/lib/yang/translator_control_socket.h @@ -187,8 +187,9 @@ protected: /// /// @param xpath The xpath of the control socket. /// @param elem The JSON element. + /// @param has_mandatory_key Whether this specific database instance has a mandatory key. /// @throw BadValue on control socket without socket type or name. - void setControlSocketKea(const std::string& xpath, isc::data::ConstElementPtr elem); + void setControlSocketKea(const std::string& xpath, isc::data::ConstElementPtr elem, bool has_mandatory_key); /// @brief setControlSocketAuthenticationClients for kea models. /// diff --git a/src/lib/yang/translator_database.cc b/src/lib/yang/translator_database.cc index cb5e67ba51..37427dae7e 100644 --- a/src/lib/yang/translator_database.cc +++ b/src/lib/yang/translator_database.cc @@ -51,6 +51,7 @@ ElementPtr TranslatorDatabase::getDatabaseKea(DataNode const& data_node) { ElementPtr result = Element::createMap(); + checkAndGetDivergingLeaf(result, data_node, "type", "database-type"); checkAndGetLeaf(result, data_node, "cert-file"); checkAndGetLeaf(result, data_node, "cipher-list"); checkAndGetLeaf(result, data_node, "connect-timeout"); @@ -79,11 +80,11 @@ TranslatorDatabase::getDatabaseKea(DataNode const& data_node) { } void -TranslatorDatabase::setDatabase(string const& xpath, ConstElementPtr elem) { +TranslatorDatabase::setDatabase(string const& xpath, ConstElementPtr elem, bool has_mandatory_key) { try { if ((model_ == KEA_DHCP4_SERVER) || (model_ == KEA_DHCP6_SERVER)) { - setDatabaseKea(xpath, elem); + setDatabaseKea(xpath, elem, has_mandatory_key); } else { isc_throw(NotImplemented, "setDatabase not implemented for the model: " << model_); @@ -96,8 +97,18 @@ TranslatorDatabase::setDatabase(string const& xpath, ConstElementPtr elem) { } void -TranslatorDatabase::setDatabaseKea(string const& xpath, ConstElementPtr elem) { - setItem(xpath, ElementPtr(), LeafBaseType::Unknown); +TranslatorDatabase::setDatabaseKea(string const& xpath, ConstElementPtr elem, bool has_mandatory_key) { + if (!elem) { + deleteItem(xpath); + return; + } + + if (has_mandatory_key) { + // Set the list element. This is important in case we have no other elements except the key. + setItem(xpath, ElementPtr(), LeafBaseType::Unknown); + } else { + checkAndSetDivergingLeaf(elem, xpath, "type", "database-type", LeafBaseType::String); + } checkAndSetLeaf(elem, xpath, "connect-timeout", LeafBaseType::Uint32); checkAndSetLeaf(elem, xpath, "cert-file", LeafBaseType::String); @@ -194,7 +205,7 @@ TranslatorDatabases::setDatabasesKea(string const& xpath, string type = database->get("type")->stringValue(); ostringstream key; key << xpath << "[database-type='" << type << "']"; - setDatabase(key.str(), database); + setDatabase(key.str(), database, /* has_mandatory_key = */ true); } } diff --git a/src/lib/yang/translator_database.h b/src/lib/yang/translator_database.h index 7866427428..909f7fec8e 100644 --- a/src/lib/yang/translator_database.h +++ b/src/lib/yang/translator_database.h @@ -141,7 +141,10 @@ public: /// /// @param xpath The xpath of the database access. /// @param elem The JSON element. - void setDatabase(const std::string& xpath, isc::data::ConstElementPtr elem); + /// @param has_mandatory_key Whether this specific database instance has a mandatory key. + void setDatabase(const std::string& xpath, + isc::data::ConstElementPtr elem, + bool has_mandatory_key); protected: /// @brief getDatabase JSON for kea-dhcp[46]-server models. @@ -157,8 +160,11 @@ protected: /// /// @param xpath The xpath of the database access. /// @param elem The JSON element. + /// @param has_mandatory_key Whether this specific database instance has a mandatory key. /// @throw BadValue on database without type, - void setDatabaseKea(const std::string& xpath, isc::data::ConstElementPtr elem); + void setDatabaseKea(const std::string& xpath, + isc::data::ConstElementPtr elem, + bool has_mandatory_key); }; // TranslatorDatabase /// @brief A translator class for converting a database access list between diff --git a/src/share/yang/modules/hashes/kea-types@2025-06-25.hash b/src/share/yang/modules/hashes/kea-types@2025-06-25.hash index d9aa537f50..d5437bc1be 100644 --- a/src/share/yang/modules/hashes/kea-types@2025-06-25.hash +++ b/src/share/yang/modules/hashes/kea-types@2025-06-25.hash @@ -1 +1 @@ -68739faa231a48c837adb3b306858b5656a1439b39b675f601d21515278d8747 +160eb58d10c1c29ab40763f69b9d905644ee767badfdceee6bdd730e9a6071f6 diff --git a/src/share/yang/modules/kea-types@2025-06-25.yang b/src/share/yang/modules/kea-types@2025-06-25.yang index ccfff985e9..0442731b21 100644 --- a/src/share/yang/modules/kea-types@2025-06-25.yang +++ b/src/share/yang/modules/kea-types@2025-06-25.yang @@ -103,7 +103,7 @@ module kea-types { description "HTTP/HTTPS socket address."; } leaf socket-port { - type string; + type uint16; description "HTTP/HTTPS socket port."; } uses control-socket-tls; @@ -162,16 +162,15 @@ module kea-types { grouping authentication { description "HTTP authentication."; container authentication { - grouping auth-type { - leaf auth-type { - type enumeration { - enum "basic" { - description "Basic HTTP authentication"; - } + presence ""; + leaf auth-type { + type enumeration { + enum "basic" { + description "Basic HTTP authentication"; } - description "HTTP authentication type."; - mandatory true; } + description "HTTP authentication type."; + mandatory true; } leaf realm { type string; @@ -181,8 +180,8 @@ module kea-types { type string; description "HTTP authentication directory."; } + uses clients; } - uses clients; } grouping hooks-libraries { -- 2.47.2