Kea 3.3.3
class_cmds.cc
Go to the documentation of this file.
1// Copyright (C) 2020-2026 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
11#include <dhcpsrv/cfgmgr.h>
15
16#include <sstream>
17#include <string>
18
19#include <class_cmds_log.h>
20
21using namespace isc::config;
22using namespace isc::dhcp;
23using namespace isc::data;
24using namespace isc::hooks;
25using namespace isc::util;
26using namespace std;
27
28namespace isc {
29namespace class_cmds {
30
35public:
38 family_ = CfgMgr::instance().getFamily();
39 }
40
43
44private:
45
55 ConstElementPtr getMandatoryArguments(CalloutHandle& callout_handle) const {
56 ConstElementPtr command;
57 callout_handle.getArgument("command", command);
58 ConstElementPtr arguments;
59 static_cast<void>(parseCommandWithArgs(arguments, command));
60 return (arguments);
61 }
62
71 ConstElementPtr parseReceivedClass(const std::string& command_name,
72 const ConstElementPtr& arguments) {
73 // Arguments must have one entry.
74 if (arguments->size() != 1) {
76 "invalid number of arguments " << arguments->size()
77 << " for the '" << command_name << "' command. "
78 << "Expecting 'client-classes' list");
79 }
80
81 // The map must contain a 'client-classes' list.
82 ConstElementPtr list = arguments->get("client-classes");
83 if (!list) {
85 "missing 'client-classes' argument for the '"
86 << command_name << "' command");
87 }
88
89 // Make sure it is a list.
90 if (list->getType() != Element::list) {
91 isc_throw(BadValue,
92 "'client-classes' argument specified for the '"
93 << command_name << "' command is not a list");
94 }
95
96 // Currently we allow only one class in the list.
97 if (list->size() != 1) {
98 isc_throw(BadValue,
99 "invalid number of classes specified for the '"
100 << command_name << "' command. Expected one class");
101 }
102
103 return (list);
104 }
105
106public:
107
112 void addClass(CalloutHandle& callout_handle) {
113 ConstElementPtr response;
114
115 try {
116 // Fetch command arguments.
117 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
118
119 // Parse arguments and extract the class list. This list
120 // is expected to contain exactly one class.
121 ConstElementPtr class_list = parseReceivedClass("class-add", arguments);
122
123 // Make sure that the class definition is a map.
124 ConstElementPtr class_def = class_list->get(0);
125 if (class_def->getType() != Element::map) {
127 "invalid class definition specified for the "
128 "'class-add' command. Expected a map");
129 }
130
131 // Make sure that there are no unsupported parameters in the client
132 // class definition.
134 parser.checkParametersSupported(class_def, family_);
135
136 // Finally, let's parse the class definition extended with the
137 // default values.
138 ClientClassDictionaryPtr dictionary =
139 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
140 parser.parse(dictionary, class_def, family_, false);
141
142 // Add text stating that client class has been added.
143
144 // This is an additional sanity check. This should be caught by
145 // the ClientClassDefParser earlier (and throw if name is missing)
146 if (!class_def->contains("name")) {
147 isc_throw(BadValue, "missing 'name' argument for the 'class-get' command");
148 }
149 ostringstream text;
150 string name = class_def->get("name")->stringValue();
151 text << "Class '" << name << "' added";
152
153 // Create the response.
154 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
155
156 LOG_INFO(class_cmds_logger, CLASS_CMDS_CLASS_ADD).arg(class_def->str());
157
158 } catch (const std::exception& ex) {
160 .arg(ex.what());
161
162 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
163 }
164
165 callout_handle.setArgument("response", response);
166 }
167
172 void getClass(CalloutHandle& callout_handle) const {
173 ConstElementPtr response;
174
175 try {
176 // Fetch command arguments.
177 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
178
179 // Arguments must have one entry.
180 if (arguments->size() != 1) {
182 "invalid number of arguments " << arguments->size()
183 << " for the 'class-get' command. "
184 << "Expecting 'name' string");
185 }
186
187 // The map must contain a 'name' string.
188 ConstElementPtr name = arguments->get("name");
189 if (!name) {
191 "missing 'name' argument for the 'class-get' command");
192 }
193
194 // Make sure it is a string.
195 if (name->getType() != Element::string) {
197 "'name' argument specified for the 'class-get' "
198 "command is not a string");
199 }
200
201 string name_str = name->stringValue();
202 ClientClassDictionaryPtr dictionary =
203 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
204 ClientClassDefPtr def = dictionary->findClass(name_str);
205
206 // If class found, wrap its definition in the successful response.
207 if (def) {
209 list->add(def->toElement());
211 map->set("client-classes", list);
212
213 ostringstream text;
214 text << "Class '" << name_str << "' definition returned";
215 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str(), map);
216
218
219 } else {
220 ostringstream text;
221 text << "Class '" << name_str << "' not found";
222
223 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
224
226 }
227
228 } catch (const std::exception& ex) {
230 .arg(ex.what());
231
232 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
233 }
234
235 callout_handle.setArgument("response", response);
236 }
237
242 void getClassList(CalloutHandle& callout_handle) const {
243
244 ConstElementPtr response;
245
246 try {
247 // Create a list where we're going to store classes' names.
249 // Create arguments map and add client classes map.
251 args->set("client-classes", list);
252
253 // Retrieve all classes from the configuration structure.
254 auto const& classes = CfgMgr::instance().getCurrentCfg()->
255 getClientClassDictionary()->getClasses();
256
257 // Iterate over all classes and retrieve names.
258 for (auto const& c : *classes) {
260 entry->set("name", Element::create(c->getName()));
261 list->add(entry);
262 }
263
264 // Generate the status message including the number of classes found.
265 ostringstream text;
266 text << classes->size() << " class";
267 // For 0 classes or more than 1 classes returned, we use plural form 'classes'.
268 if (classes->size() != 1) {
269 text << "es";
270 }
271 text << " found";
272
273 if (classes->size() > 0) {
274 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str(), args);
275
277 } else {
278 response = createAnswer(CONTROL_RESULT_EMPTY, text.str(),args);
279
281 }
282
283 } catch (const std::exception& ex) {
285 .arg(ex.what());
286
287 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
288 }
289
290 callout_handle.setArgument("response", response);
291 }
292
297 void updateClass(CalloutHandle& callout_handle) {
298 ConstElementPtr response;
299
300 try {
301 // Fetch command arguments.
302 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
303
304 // Parse arguments and extract the class list. This list
305 // is expected to contain exactly one class.
306 ConstElementPtr class_list = parseReceivedClass("class-update", arguments);
307
308 // Make sure that the class definition is a map.
309 ConstElementPtr class_def = class_list->get(0);
310 if (class_def->getType() != Element::map) {
312 "invalid class definition specified for the "
313 "'class-update' command. Expected a map");
314 }
315
316 // Check the class is configured. But before we get to that, need to
317 // do some basic sanity check first.
318 if (!class_def->contains("name")) {
319 isc_throw(BadValue, "the first class definition is missing a "
320 "mandatory 'name' parameter");
321 }
322
323 // Make sure that there are no unsupported parameters in the client
324 // class definition.
326 parser.checkParametersSupported(class_def, family_);
327
328 string name = class_def->get("name")->stringValue();
329 ClientClassDictionaryPtr dictionary =
330 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
331 ClientClassDefPtr previous = dictionary->findClass(name);
332 if (previous) {
333 auto const& template_cfg = class_def->get("template-test");
334 if (!template_cfg && dynamic_cast<TemplateClientClassDef*>(previous.get())) {
335 ElementPtr mutable_class_def = boost::const_pointer_cast<Element>(class_def);
336 mutable_class_def->set("template-test", Element::create(previous->getTest()));
337 }
338 } else {
339 ostringstream text;
340 text << "Class '" << name << "' is not found";
341
342 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
343 callout_handle.setArgument("response", response);
344
346 return;
347 }
348
349 // Build the before and after part of the dictionary.
352 ClientClassDefListPtr classes = dictionary->getClasses();
353 bool found = false;
354 for (auto const& it : *classes) {
355 if (it->getName() == name) {
356 found = true;
357 } else if (found) {
358 after->addClass(it);
359 } else {
360 before->addClass(it);
361 }
362 }
363
364 // Finally, let's parse the class definition extended with the
365 // default values.
366 parser.parse(before, class_def, family_, false);
367 ClientClassDefPtr next = before->findClass(name);
368 if (!next) {
370 "Class '" << name << "' updated but does not show");
371 }
372
373 // The dependency on the KNOWN or UNKNOWN built-in class must not
374 // change during the class update. The reason is that such dependency
375 // may be indirect, i.e. some other class may depend on those built-in
376 // classes via this class.
377 if (previous->getDependOnKnown() != next->getDependOnKnown()) {
379 "modification of the class '" << name << "' would "
380 "affect its dependency on the KNOWN and/or UNKNOWN built-in "
381 "classes. Such modification is not allowed because "
382 "there may be other classes depending on those built-ins "
383 "via the updated class");
384 }
385
386 // Merge after part.
387 classes = after->getClasses();
388 for (auto const& it : *classes) {
389 before->addClass(it);
390 }
391
392 // Set the dictionary.
393 CfgMgr::instance().getCurrentCfg()->setClientClassDictionary(before);
394
395 // Add text stating that client class has been updated.
396 ostringstream text;
397 text << "Class '" << name << "' updated";
398
399 // Create the response.
400 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
401
402 LOG_INFO(class_cmds_logger, CLASS_CMDS_CLASS_UPDATE).arg(class_def->str());
403
404 } catch (const std::exception& ex) {
406 .arg(ex.what());
407
408 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
409 }
410
411 callout_handle.setArgument("response", response);
412 }
413
418 void delClass(CalloutHandle& callout_handle) {
419 ConstElementPtr response;
420
421 try {
422 // Fetch command arguments.
423 ConstElementPtr arguments = getMandatoryArguments(callout_handle);
424
425 // Arguments must have one entry.
426 if (arguments->size() != 1) {
428 "invalid number of arguments " << arguments->size()
429 << " for the 'class-del' command. "
430 << "Expecting 'name' string");
431 }
432
433 // The map must contain a 'name' string.
434 ConstElementPtr name = arguments->get("name");
435 if (!name) {
437 "missing 'name' argument for the 'class-del' command");
438 }
439
440 // Make sure it is a string.
441 if (name->getType() != Element::string) {
443 "'name' argument specified for the 'class-del' "
444 "command is not a string");
445 }
446
447 string name_str = name->stringValue();
448 ClientClassDictionaryPtr dictionary =
449 CfgMgr::instance().getCurrentCfg()->getClientClassDictionary();
450 ClientClassDefPtr def = dictionary->findClass(name_str);
451
452 ostringstream text;
453
454 // Check if the class was found.
455 if (!def) {
456 text << "Class '" << name_str << "' not found";
457
458 response = createAnswer(CONTROL_RESULT_EMPTY, text.str());
459 callout_handle.setArgument("response", response);
460
462 return;
463 }
464
465 // Check against dangling dependencies.
466 string depend;
467 if (dictionary->dependOnClass(name_str, depend)) {
469 "Class '" << name_str << "' is used by class '"
470 << depend << "'");
471 }
472
473 // Remove the class from the configuration.
474 dictionary->removeClass(name_str);
475
476 text << "Class '" << name_str << "' deleted";
477
478 response = createAnswer(CONTROL_RESULT_SUCCESS, text.str());
479
481
482 } catch (const std::exception& ex) {
484 .arg(ex.what());
485
486 response = createAnswer(CONTROL_RESULT_ERROR, ex.what());
487 }
488
489 callout_handle.setArgument("response", response);
490 }
491
492private:
493
495 uint16_t family_;
496};
497
499 :impl_(new ClassCmdsImpl()) {
500}
501
502void
503ClassCmds::getClass(CalloutHandle& callout_handle) const {
504 impl_->getClass(callout_handle);
505}
506
507void
509 impl_->getClassList(callout_handle);
510}
511
512// Modifying the class configuration requires a critical section.
513
514void
517 impl_->addClass(callout_handle);
518}
519
520void
523 impl_->updateClass(callout_handle);
524}
525
526void
529 impl_->delClass(callout_handle);
530}
531
532}
533}
static ElementPtr create(const Position &pos=ZERO_POSITION())
Create a NullElement.
Definition data.cc:300
@ map
Definition data.h:160
@ list
Definition data.h:159
@ string
Definition data.h:157
static ElementPtr createMap(const Position &pos=ZERO_POSITION())
Creates an empty MapElement type ElementPtr.
Definition data.cc:355
static ElementPtr createList(const Position &pos=ZERO_POSITION())
Creates an empty ListElement type ElementPtr.
Definition data.cc:350
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:34
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:30
boost::shared_ptr< Element > ElementPtr
Definition data.h:29
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.