Kea 1.5.0
callout_manager.cc
Go to the documentation of this file.
1// Copyright (C) 2013-2018 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 <hooks/hooks_log.h>
13#include <util/stopwatch.h>
14
15#include <boost/static_assert.hpp>
16
17#include <algorithm>
18#include <climits>
19#include <functional>
20#include <utility>
21
22using namespace std;
23
24namespace isc {
25namespace hooks {
26
27// Constructor
29 : server_hooks_(ServerHooks::getServerHooks()),
30 current_hook_(-1), current_library_(-1),
31 hook_vector_(ServerHooks::getServerHooks().getCount()),
32 library_handle_(this), pre_library_handle_(this, 0),
33 post_library_handle_(this, INT_MAX), num_libraries_(num_libraries)
34{
35 if (num_libraries < 0) {
36 isc_throw(isc::BadValue, "number of libraries passed to the "
37 "CalloutManager must be >= 0");
38 }
39}
40
41// Check that the index of a library is valid. It can range from 1 - n
42// (n is the number of libraries), 0 (pre-user library callouts), or INT_MAX
43// (post-user library callouts). It can also be -1 to indicate an invalid
44// value.
45
46void
47CalloutManager::checkLibraryIndex(int library_index) const {
48 if (((library_index >= -1) && (library_index <= num_libraries_)) ||
49 (library_index == INT_MAX)) {
50 return;
51 }
52
53 isc_throw(NoSuchLibrary, "library index " << library_index <<
54 " is not valid for the number of loaded libraries (" <<
55 num_libraries_ << ")");
56}
57
58// Register a callout for the current library.
59
60void
61CalloutManager::registerCallout(const std::string& name, CalloutPtr callout) {
62 // Note the registration.
63 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUT_REGISTRATION)
64 .arg(current_library_).arg(name);
65
66 // Sanity check that the current library index is set to a valid value.
67 checkLibraryIndex(current_library_);
68
69 // New hooks could have been registered since the manager was constructed.
70 ensureHookLibsVectorSize();
71
72 // Get the index associated with this hook (validating the name in the
73 // process).
74 int hook_index = server_hooks_.getIndex(name);
75
76 // Iterate through the callout vector for the hook from start to end,
77 // looking for the first entry where the library index is greater than
78 // the present index.
79 for (CalloutVector::iterator i = hook_vector_[hook_index].begin();
80 i != hook_vector_[hook_index].end(); ++i) {
81 if (i->first > current_library_) {
82 // Found an element whose library index number is greater than the
83 // current index, so insert the new element ahead of this one.
84 hook_vector_[hook_index].insert(i, make_pair(current_library_,
85 callout));
86 return;
87 }
88 }
89
90 // Reached the end of the vector, so there is no element in the (possibly
91 // empty) set of callouts with a library index greater than the current
92 // library index. Inset the callout at the end of the list.
93 hook_vector_[hook_index].push_back(make_pair(current_library_, callout));
94}
95
96// Check if callouts are present for a given hook index.
97
98bool
99CalloutManager::calloutsPresent(int hook_index) const {
100 // Validate the hook index.
101 if ((hook_index < 0) || (hook_index >= hook_vector_.size())) {
102 isc_throw(NoSuchHook, "hook index " << hook_index <<
103 " is not valid for the list of registered hooks");
104 }
105
106 // Valid, so are there any callouts associated with that hook?
107 return (!hook_vector_[hook_index].empty());
108}
109
110bool
111CalloutManager::commandHandlersPresent(const std::string& command_name) const {
112 // Check if the hook point for the specified command exists.
114 ServerHooks::commandToHookName(command_name));
115 if (index >= 0) {
116 // The hook point exits but it is possible that there are no
117 // callouts/command handlers. This is possible if there was a
118 // hook library supporting this command attached, but it was
119 // later unloaded. The hook points are not deregistered in
120 // this case. Only callouts are deregistered.
121 // Let's check if callouts are present for this hook point.
122 return (calloutsPresent(index));
123 }
124
125 // Hook point not created, so we don't support this command in
126 // any of the hooks libraries.
127 return (false);
128}
129
130
131// Call all the callouts for a given hook.
132
133void
134CalloutManager::callCallouts(int hook_index, CalloutHandle& callout_handle) {
135
136 // Clear the "skip" flag so we don't carry state from a previous call.
137 // This is done regardless of whether callouts are present to avoid passing
138 // any state from the previous call of callCallouts().
140
141 // Only initialize and iterate if there are callouts present. This check
142 // also catches the case of an invalid index.
143 if (calloutsPresent(hook_index)) {
144
145 // Set the current hook index. This is used should a callout wish to
146 // determine to what hook it is attached.
147 current_hook_ = hook_index;
148
149 // Duplicate the callout vector for this hook and work through that.
150 // This step is needed because we allow dynamic registration and
151 // deregistration of callouts. If a callout attached to a hook modified
152 // the list of callouts on that hook, the underlying CalloutVector would
153 // change and potentially affect the iteration through that vector.
154 CalloutVector callouts(hook_vector_[hook_index]);
155
156 // This object will be used to measure execution time of each callout
157 // and the total time spent in callouts for this hook point.
158 util::Stopwatch stopwatch;
159
160 // Mark that the callouts begin for the hook.
161 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_BEGIN)
162 .arg(server_hooks_.getName(current_hook_));
163
164 // Call all the callouts.
165 for (CalloutVector::const_iterator i = callouts.begin();
166 i != callouts.end(); ++i) {
167 // In case the callout tries to register or deregister a callout,
168 // set the current library index to the index associated with the
169 // library that registered the callout being called.
170 current_library_ = i->first;
171
172 // Call the callout
173 try {
174 stopwatch.start();
175 int status = (*i->second)(callout_handle);
176 stopwatch.stop();
177 if (status == 0) {
179 HOOKS_CALLOUT_CALLED).arg(current_library_)
180 .arg(server_hooks_.getName(current_hook_))
181 .arg(PointerConverter(i->second).dlsymPtr())
182 .arg(stopwatch.logFormatLastDuration());
183 } else {
184 LOG_ERROR(callouts_logger, HOOKS_CALLOUT_ERROR)
185 .arg(current_library_)
186 .arg(server_hooks_.getName(current_hook_))
187 .arg(PointerConverter(i->second).dlsymPtr())
188 .arg(stopwatch.logFormatLastDuration());
189 }
190 } catch (const std::exception& e) {
191 // If an exception occurred, the stopwatch.stop() hasn't been
192 // called, so we have to call it here.
193 stopwatch.stop();
194 // Any exception, not just ones based on isc::Exception
195 LOG_ERROR(callouts_logger, HOOKS_CALLOUT_EXCEPTION)
196 .arg(current_library_)
197 .arg(server_hooks_.getName(current_hook_))
198 .arg(PointerConverter(i->second).dlsymPtr())
199 .arg(e.what())
200 .arg(stopwatch.logFormatLastDuration());
201 }
202
203 }
204
205 // Mark end of callout execution. Include the total execution
206 // time for callouts.
207 LOG_DEBUG(callouts_logger, HOOKS_DBG_CALLS, HOOKS_CALLOUTS_COMPLETE)
208 .arg(server_hooks_.getName(current_hook_))
209 .arg(stopwatch.logFormatTotalDuration());
210
211 // Reset the current hook and library indexes to an invalid value to
212 // catch any programming errors.
213 current_hook_ = -1;
214 current_library_ = -1;
215 }
216}
217
218void
219CalloutManager::callCommandHandlers(const std::string& command_name,
220 CalloutHandle& callout_handle) {
221 // Get the index of the hook point for the specified command.
222 // This will throw an exception if the hook point doesn't exist.
223 // The caller should check if the hook point exists by calling
224 // commandHandlersPresent.
226 ServerHooks::commandToHookName(command_name));
227 // Call the handlers for this command.
228 callCallouts(index, callout_handle);
229}
230
231
232// Deregister a callout registered by the current library on a particular hook.
233
234bool
235CalloutManager::deregisterCallout(const std::string& name, CalloutPtr callout) {
236 // Sanity check that the current library index is set to a valid value.
237 checkLibraryIndex(current_library_);
238
239 // New hooks could have been registered since the manager was constructed.
240 ensureHookLibsVectorSize();
241
242 // Get the index associated with this hook (validating the name in the
243 // process).
244 int hook_index = server_hooks_.getIndex(name);
245
246 // New hooks can have been registered since the manager was constructed.
247 if (hook_index >= hook_vector_.size()) {
248 return (false);
249 }
250
253 CalloutEntry target(current_library_, callout);
254
258 size_t initial_size = hook_vector_[hook_index].size();
259
260 // The next bit is standard STL (see "Item 33" in "Effective STL" by
261 // Scott Meyers).
262 //
263 // remove_if reorders the hook vector so that all items not matching
264 // the predicate are at the start of the vector and returns a pointer
265 // to the next element. (In this case, the predicate is that the item
266 // is equal to the value of the passed callout.) The erase() call
267 // removes everything from that element to the end of the vector, i.e.
268 // all the matching elements.
269 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
270 hook_vector_[hook_index].end(),
271 bind1st(equal_to<CalloutEntry>(),
272 target)),
273 hook_vector_[hook_index].end());
274
275 // Return an indication of whether anything was removed.
276 bool removed = initial_size != hook_vector_[hook_index].size();
277 if (removed) {
279 HOOKS_CALLOUT_DEREGISTERED).arg(current_library_).arg(name);
280 }
281
282 return (removed);
283}
284
285// Deregister all callouts on a given hook.
286
287bool
289
290 // New hooks could have been registered since the manager was constructed.
291 ensureHookLibsVectorSize();
292
293 // Get the index associated with this hook (validating the name in the
294 // process).
295 int hook_index = server_hooks_.getIndex(name);
296
299 CalloutEntry target(current_library_, static_cast<CalloutPtr>(0));
300
304 size_t initial_size = hook_vector_[hook_index].size();
305
306 // Remove all callouts matching this library.
307 hook_vector_[hook_index].erase(remove_if(hook_vector_[hook_index].begin(),
308 hook_vector_[hook_index].end(),
309 bind1st(CalloutLibraryEqual(),
310 target)),
311 hook_vector_[hook_index].end());
312
313 // Return an indication of whether anything was removed.
314 bool removed = initial_size != hook_vector_[hook_index].size();
315 if (removed) {
317 HOOKS_ALL_CALLOUTS_DEREGISTERED).arg(current_library_)
318 .arg(name);
319 }
320
321 return (removed);
322}
323
324void
325CalloutManager::registerCommandHook(const std::string& command_name) {
326
327 // New hooks could have been registered since the manager was constructed.
328 ensureHookLibsVectorSize();
329
331 int hook_index = hooks.findIndex(ServerHooks::commandToHookName(command_name));
332 if (hook_index < 0) {
333 // Hook for this command doesn't exist. Let's create one.
334 hooks.registerHook(ServerHooks::commandToHookName(command_name));
335 // Callout Manager's vector of hooks have to be resized to hold the
336 // information about callouts for this new hook point. This should
337 // add new element at the end of the hook_vector_. The index of this
338 // element will match the index of the hook point in the ServerHooks
339 // because ServerHooks allocates indexes incrementally.
340 hook_vector_.resize(server_hooks_.getCount());
341 }
342}
343
344void
345CalloutManager::ensureHookLibsVectorSize() {
347 if (hooks.getCount() > hook_vector_.size()) {
348 // Uh oh, there are more hook points that our vector allows.
349 hook_vector_.resize(hooks.getCount());
350 }
351}
352
353} // namespace util
354} // namespace isc
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
Per-packet callout handle.
@ NEXT_STEP_CONTINUE
continue normally
void setStatus(const CalloutNextStep next)
Sets the next processing step.
void registerCallout(const std::string &name, CalloutPtr callout)
Register a callout on a hook for the current library.
void callCallouts(int hook_index, CalloutHandle &callout_handle)
Calls the callouts for a given hook.
bool commandHandlersPresent(const std::string &command_name) const
Checks if control command handlers are present for the specified command.
void callCommandHandlers(const std::string &command_name, CalloutHandle &callout_handle)
Calls the callouts/command handlers for a given command name.
CalloutManager(int num_libraries=0)
Constructor.
bool deregisterCallout(const std::string &name, CalloutPtr callout)
De-Register a callout on a hook for the current library.
bool calloutsPresent(int hook_index) const
Checks if callouts are present on a hook.
bool deregisterAllCallouts(const std::string &name)
Removes all callouts on a hook for the current library.
void registerCommandHook(const std::string &command_name)
Registers a hook point for the specified command name.
Local class for conversion of void pointers to function pointers.
void * dlsymPtr() const
Return pointer returned by dlsym call.
Server hook collection.
Definition: server_hooks.h:62
int getIndex(const std::string &name) const
Get hook index.
static ServerHooks & getServerHooks()
Return ServerHooks object.
int findIndex(const std::string &name) const
Find hook index.
int getCount() const
Return number of hooks.
Definition: server_hooks.h:133
static std::string commandToHookName(const std::string &command_name)
Generates hook point name for the given control command name.
int registerHook(const std::string &name)
Register a hook.
Definition: server_hooks.cc:44
std::string getName(int index) const
Get hook name.
Utility class to measure code execution times.
Definition: stopwatch.h:35
void stop()
Stops the stopwatch.
Definition: stopwatch.cc:35
void start()
Starts the stopwatch.
Definition: stopwatch.cc:30
std::string logFormatTotalDuration() const
Returns the total measured duration in the format directly usable in the log messages.
Definition: stopwatch.cc:80
std::string logFormatLastDuration() const
Returns the last measured duration in the format directly usable in log messages.
Definition: stopwatch.cc:75
#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
int(* CalloutPtr)(CalloutHandle &)
Typedef for a callout pointer. (Callouts must have "C" linkage.)
const int HOOKS_DBG_EXTENDED_CALLS
Definition: hooks_log.h:29
const int HOOKS_DBG_CALLS
Definition: hooks_log.h:25
isc::log::Logger callouts_logger("callouts")
Callouts logger.
Definition: hooks_log.h:44
Defines the logger used by the top-level component of kea-dhcp-ddns.