Kea 3.1.1
class_cmds.cc
Go to the documentation of this file.
1// Copyright (C) 2020-2025 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 <class_cmds.h>
10#include <class_cmds_log.h>
12#include <dhcpsrv/cfgmgr.h>
16#include <string>
17#include <sstream>
18
19using namespace isc::config;
20using namespace isc::dhcp;
21using namespace isc::data;
22using namespace isc::hooks;
23using namespace isc::util;
24using namespace std;
25
26namespace isc {
27namespace class_cmds {
28
33public:
36 family_ = CfgMgr::instance().getFamily();
37 }
38
41
42private:
43
53 ConstElementPtr getMandatoryArguments(CalloutHandle& callout_handle) const {
54 ConstElementPtr command;
55 callout_handle.getArgument("command", command);
56 ConstElementPtr arguments;
57 static_cast<void>(parseCommandWithArgs(arguments, command));
58 return (arguments);
59 }
60
69 ConstElementPtr parseReceivedClass(const std::string& command_name,
70 const ConstElementPtr& arguments) {
71 // Arguments must have one entry.
72 if (arguments->size() != 1) {
74 "invalid number of arguments " << arguments->size()
75 << " for the '" << command_name << "' command. "
76 << "Expecting 'client-classes' list");
77 }
78
79 // The map must contain a 'client-classes' list.
80 ConstElementPtr list = arguments->get("client-classes");
81 if (!list) {
83 "missing 'client-classes' argument for the '"
84 << command_name << "' command");
85 }
86
87 // Make sure it is a list.
88 if (list->getType() != Element::list) {
89 isc_throw(BadValue,
90 "'client-classes' argument specified for the '"
91 << command_name << "' command is not a list");
92 }
93
94 // Currently we allow only one class in the list.
95 if (list->size() != 1) {
96 isc_throw(BadValue,
97 "invalid number of classes specified for the '"
98 << command_name << "' command. Expected one class");
99 }
100
101 return (list);
102 }
103
104public:
105
110 void addClass(CalloutHandle& callout_handle) {
111 ConstElementPtr response;
112
113 try {
114 // Fetch command arguments.
115 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
116
117 // Parse arguments and extract the class list. This list
118 // is expected to contain exactly one class.
119 ConstElementPtr class_list = parseReceivedClass("class-add", arguments);
120
121 // Make sure that the class definition is a map.
122 ConstElementPtr class_def = class_list->get(0);
123 if (class_def->getType() != Element::map) {
125 "invalid class definition specified for the "
126 "'class-add' command. Expected a map");
127 }
128
129 // Make sure that there are no unsupported parameters in the client
130 // class definition.
132 parser.checkParametersSupported(class_def, family_);
133
134 // Finally, let's parse the class definition extended with the
135 // default values.
136 ClientClassDictionaryPtr dictionary =
137 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
138 parser.parse(dictionary, class_def, family_, false);
139
140 // Add text stating that client class has been added.
141
142 // This is an additional sanity check. This should be caught by
143 // the ClientClassDefParser earlier (and throw if name is missing)
144 if (!class_def->contains("name")) {
145 isc_throw(BadValue, "missing 'name' argument for the 'class-get' command");
146 }
147 ostringstream text;
148 string name = class_def->get("name")->stringValue();
149 text << "Class '" << name << "' added";
150
151 // Create the response.
152 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
153
154 LOG_INFO(class_cmds_logger, CLASS_CMDS_CLASS_ADD).arg(class_def->str());
155
156 } catch (const std::exception& ex) {
158 .arg(ex.what());
159
160 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
161 }
162
163 callout_handle.setArgument("response", response);
164 }
165
170 void getClass(CalloutHandle& callout_handle) const {
171 ConstElementPtr response;
172
173 try {
174 // Fetch command arguments.
175 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
176
177 // Arguments must have one entry.
178 if (arguments->size() != 1) {
180 "invalid number of arguments " << arguments->size()
181 << " for the 'class-get' command. "
182 << "Expecting 'name' string");
183 }
184
185 // The map must contain a 'name' string.
186 ConstElementPtr name = arguments->get("name");
187 if (!name) {
189 "missing 'name' argument for the 'class-get' command");
190 }
191
192 // Make sure it is a string.
193 if (name->getType() != Element::string) {
195 "'name' argument specified for the 'class-get' "
196 "command is not a string");
197 }
198
199 string name_str = name->stringValue();
200 ClientClassDictionaryPtr dictionary =
201 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
202 ClientClassDefPtr def = dictionary->findClass(name_str);
203
204 // If class found, wrap its definition in the successful response.
205 if (def) {
207 list->add(def->toElement());
209 map->set("client-classes", list);
210
211 ostringstream text;
212 text << "Class '" << name_str << "' definition returned";
213 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str(), map);
214
216
217 } else {
218 ostringstream text;
219 text << "Class '" << name_str << "' not found";
220
221 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
222
224 }
225
226 } catch (const std::exception& ex) {
228 .arg(ex.what());
229
230 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
231 }
232
233 callout_handle.setArgument("response", response);
234 }
235
240 void getClassList(CalloutHandle& callout_handle) const {
241
242 ConstElementPtr response;
243
244 try {
245 // Create a list where we're going to store classes' names.
247 // Create arguments map and add client classes map.
249 args->set("client-classes", list);
250
251 // Retrieve all classes from the configuration structure.
252 auto const& classes = CfgMgr::instance().getCurrentCfg()->
253 getClientClassDictionary()->getClasses();
254
255 // Iterate over all classes and retrieve names.
256 for (auto const& c : *classes) {
258 entry->set("name", Element::create(c->getName()));
259 list->add(entry);
260 }
261
262 // Generate the status message including the number of classes found.
263 ostringstream text;
264 text << classes->size() << " class";
265 // For 0 classes or more than 1 classes returned, we use plural form 'classes'.
266 if (classes->size() != 1) {
267 text << "es";
268 }
269 text << " found";
270
271 if (classes->size() > 0) {
272 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str(), args);
273
275 } else {
276 response = createAnswer(CONTROL_RESULT_EMPTY, text.str(),args);
277
279 }
280
281 } catch (const std::exception& ex) {
283 .arg(ex.what());
284
285 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
286 }
287
288 callout_handle.setArgument("response", response);
289 }
290
295 void updateClass(CalloutHandle& callout_handle) {
296 ConstElementPtr response;
297
298 try {
299 // Fetch command arguments.
300 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
301
302 // Parse arguments and extract the class list. This list
303 // is expected to contain exactly one class.
304 ConstElementPtr class_list = parseReceivedClass("class-update", arguments);
305
306 // Make sure that the class definition is a map.
307 ConstElementPtr class_def = class_list->get(0);
308 if (class_def->getType() != Element::map) {
310 "invalid class definition specified for the "
311 "'class-update' command. Expected a map");
312 }
313
314 // Check the class is configured. But before we get to that, need to
315 // do some basic sanity check first.
316 if (!class_def->contains("name")) {
317 isc_throw(BadValue, "the first class definition is missing a "
318 "mandatory 'name' parameter");
319 }
320
321 // Make sure that there are no unsupported parameters in the client
322 // class definition.
324 parser.checkParametersSupported(class_def, family_);
325
326 string name = class_def->get("name")->stringValue();
327 ClientClassDictionaryPtr dictionary =
328 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
329 ClientClassDefPtr previous = dictionary->findClass(name);
330 if (previous) {
331 auto const& template_cfg = class_def->get("template-test");
332 if (!template_cfg && dynamic_cast<TemplateClientClassDef*>(previous.get())) {
333 ElementPtr mutable_class_def = boost::const_pointer_cast<Element>(class_def);
334 mutable_class_def->set("template-test", Element::create(previous->getTest()));
335 }
336 } else {
337 ostringstream text;
338 text << "Class '" << name << "' is not found";
339
340 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
341 callout_handle.setArgument("response", response);
342
344 return;
345 }
346
347 // Build the before and after part of the dictionary.
350 ClientClassDefListPtr classes = dictionary->getClasses();
351 bool found = false;
352 for (auto const& it : *classes) {
353 if (it->getName() == name) {
354 found = true;
355 } else if (found) {
356 after->addClass(it);
357 } else {
358 before->addClass(it);
359 }
360 }
361
362 // Finally, let's parse the class definition extended with the
363 // default values.
364 parser.parse(before, class_def, family_, false);
365 ClientClassDefPtr next = before->findClass(name);
366 if (!next) {
368 "Class '" << name << "' updated but does not show");
369 }
370
371 // The dependency on the KNOWN or UNKNOWN built-in class must not
372 // change during the class update. The reason is that such dependency
373 // may be indirect, i.e. some other class may depend on those built-in
374 // classes via this class.
375 if (previous->getDependOnKnown() != next->getDependOnKnown()) {
377 "modification of the class '" << name << "' would "
378 "affect its dependency on the KNOWN and/or UNKNOWN built-in "
379 "classes. Such modification is not allowed because "
380 "there may be other classes depending on those built-ins "
381 "via the updated class");
382 }
383
384 // Merge after part.
385 classes = after->getClasses();
386 for (auto const& it : *classes) {
387 before->addClass(it);
388 }
389
390 // Set the dictionary.
391 CfgMgr::instance().getCurrentCfg()->setClientClassDictionary(before);
392
393 // Add text stating that client class has been updated.
394 ostringstream text;
395 text << "Class '" << name << "' updated";
396
397 // Create the response.
398 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
399
400 LOG_INFO(class_cmds_logger, CLASS_CMDS_CLASS_UPDATE).arg(class_def->str());
401
402 } catch (const std::exception& ex) {
404 .arg(ex.what());
405
406 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
407 }
408
409 callout_handle.setArgument("response", response);
410 }
411
416 void delClass(CalloutHandle& callout_handle) {
417 ConstElementPtr response;
418
419 try {
420 // Fetch command arguments.
421 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
422
423 // Arguments must have one entry.
424 if (arguments->size() != 1) {
426 "invalid number of arguments " << arguments->size()
427 << " for the 'class-del' command. "
428 << "Expecting 'name' string");
429 }
430
431 // The map must contain a 'name' string.
432 ConstElementPtr name = arguments->get("name");
433 if (!name) {
435 "missing 'name' argument for the 'class-del' command");
436 }
437
438 // Make sure it is a string.
439 if (name->getType() != Element::string) {
441 "'name' argument specified for the 'class-del' "
442 "command is not a string");
443 }
444
445 string name_str = name->stringValue();
446 ClientClassDictionaryPtr dictionary =
447 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
448 ClientClassDefPtr def = dictionary->findClass(name_str);
449
450 ostringstream text;
451
452 // Check if the class was found.
453 if (!def) {
454 text << "Class '" << name_str << "' not found";
455
456 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
457 callout_handle.setArgument("response", response);
458
460 return;
461 }
462
463 // Check against dangling dependencies.
464 string depend;
465 if (dictionary->dependOnClass(name_str, depend)) {
467 "Class '" << name_str << "' is used by class '"
468 << depend << "'");
469 }
470
471 // Remove the class from the configuration.
472 dictionary->removeClass(name_str);
473
474 text << "Class '" << name_str << "' deleted";
475
476 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
477
479
480 } catch (const std::exception& ex) {
482 .arg(ex.what());
483
484 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
485 }
486
487 callout_handle.setArgument("response", response);
488 }
489
490private:
491
493 uint16_t family_;
494};
495
497 :impl_(new ClassCmdsImpl()) {
498}
499
500void
501ClassCmds::getClass(CalloutHandle& callout_handle) const {
502 impl_->getClass(callout_handle);
503}
504
505void
507 impl_->getClassList(callout_handle);
508}
509
510// Modifying the class configuration requires a critical section.
511
512void
515 impl_->addClass(callout_handle);
516}
517
518void
521 impl_->updateClass(callout_handle);
522}
523
524void
527 impl_->delClass(callout_handle);
528}
529
530}
531}
static ElementPtr create(const Position &pos=ZERO_POSITION())
Definition data.cc:249
@ map
Definition data.h:147
@ list
Definition data.h:146
@ string
Definition data.h:144
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
int class_list(CalloutHandle &handle)
This is a command callout for 'class-list' command.
const isc::log::MessageID CLASS_CMDS_CLASS_UPDATE_EMPTY
const isc::log::MessageID CLASS_CMDS_CLASS_UPDATE
const isc::log::MessageID CLASS_CMDS_CLASS_DEL_EMPTY
const isc::log::MessageID CLASS_CMDS_CLASS_ADD_FAILED
const isc::log::MessageID CLASS_CMDS_CLASS_LIST_FAILED
const isc::log::MessageID CLASS_CMDS_CLASS_DEL_FAILED
const isc::log::MessageID CLASS_CMDS_CLASS_GET
const isc::log::MessageID CLASS_CMDS_CLASS_LIST
const isc::log::MessageID CLASS_CMDS_CLASS_GET_FAILED
const isc::log::MessageID CLASS_CMDS_CLASS_LIST_EMPTY
const isc::log::MessageID CLASS_CMDS_CLASS_UPDATE_FAILED
const isc::log::MessageID CLASS_CMDS_CLASS_GET_EMPTY
const isc::log::MessageID CLASS_CMDS_CLASS_DEL
const isc::log::MessageID CLASS_CMDS_CLASS_ADD
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.
Implementation of the ClassCmds class.
Definition class_cmds.cc:32
void delClass(CalloutHandle &callout_handle)
Processes and returns a response to 'class-del' command.
void getClassList(CalloutHandle &callout_handle) const
Returns a response to a 'class-list' command.
void addClass(CalloutHandle &callout_handle)
Returns a response to a 'class-add' command.
void getClass(CalloutHandle &callout_handle) const
Returns a response to a 'class-get' command.
void updateClass(CalloutHandle &callout_handle)
Returns a response to a 'class-update' command.
void updateClass(hooks::CalloutHandle &callout_handle)
Returns a response to a 'class-update' command.
void getClass(hooks::CalloutHandle &callout_handle) const
Returns a response to a 'class-get' command.
void getClassList(hooks::CalloutHandle &callout_handle) const
Returns a response to a 'class-list' command.
void delClass(hooks::CalloutHandle &callout_handle)
Processes and returns a response to 'class-del' command.
void addClass(hooks::CalloutHandle &callout_handle)
Returns a response to a 'class-add' command.
Thrown upon an attempt to update a class when dependency on KNOWN or UNKNOWN built-in class is remove...
Definition class_cmds.h:24
Thrown upon an attempt to delete a class which would result in leaving dangling dependencies.
Definition class_cmds.h:33
uint16_t getFamily() const
Returns address family.
Definition cfgmgr.h:246
static CfgMgr & instance()
returns a single instance of Configuration Manager
Definition cfgmgr.cc:29
SrvConfigPtr getCurrentCfg()
Returns a pointer to the current configuration.
Definition cfgmgr.cc:116
Parser for a single client class definition.
void parse(ClientClassDictionaryPtr &class_dictionary, isc::data::ConstElementPtr client_class_def, uint16_t family, bool append_error_position=true, bool check_dependencies=true)
Parses an entry that describes single client class definition.
void checkParametersSupported(const isc::data::ConstElementPtr &class_def_cfg, const uint16_t family)
Iterates over class parameters and checks if they are supported.
Maintains a list of ClientClassDef's.
Per-packet callout handle.
void getArgument(const std::string &name, T &value) const
Get argument.
void setArgument(const std::string &name, T value)
Set argument.
RAII class creating a critical section.
Defines classes for storing client class definitions.
Parsers for client class definitions.
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_INFO(LOGGER, MESSAGE)
Macro to conveniently test info output and log it.
Definition macros.h:20
isc::log::Logger class_cmds_logger("class-cmds-hooks")
const int CONTROL_RESULT_EMPTY
Status code indicating that the specified command was completed correctly, but failed to produce any ...
const int CONTROL_RESULT_ERROR
Status code indicating a general failure.
std::string parseCommandWithArgs(ConstElementPtr &arg, ConstElementPtr command)
Parses the given command into a string containing the command name and an ElementPtr containing the m...
ConstElementPtr createAnswer()
Creates a standard config/command level success answer message (i.e.
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< ClientClassDef > ClientClassDefPtr
a pointer to an ClientClassDef
boost::shared_ptr< ClientClassDictionary > ClientClassDictionaryPtr
Defines a pointer to a ClientClassDictionary.
boost::shared_ptr< ClientClassDefList > ClientClassDefListPtr
Defines a pointer to a ClientClassDefList.
Defines the logger used by the top-level component of kea-lfc.