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 
9 #include <hooks/callout_handle.h>
10 #include <hooks/callout_manager.h>
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 
22 using namespace std;
23 
24 namespace isc {
25 namespace hooks {
26 
27 // Constructor
28 CalloutManager::CalloutManager(int num_libraries)
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 
46 void
47 CalloutManager::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 
60 void
61 CalloutManager::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 
98 bool
99 CalloutManager::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 
110 bool
111 CalloutManager::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 
133 void
134 CalloutManager::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 
218 void
219 CalloutManager::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 
234 bool
235 CalloutManager::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 
287 bool
288 CalloutManager::deregisterAllCallouts(const std::string& name) {
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 
324 void
325 CalloutManager::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 
344 void
345 CalloutManager::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
void setStatus(const CalloutNextStep next)
Sets the next processing step.
const int HOOKS_DBG_EXTENDED_CALLS
Definition: hooks_log.h:29
int(* CalloutPtr)(CalloutHandle &)
Typedef for a callout pointer. (Callouts must have "C" linkage.)
void registerCommandHook(const std::string &command_name)
Registers a hook point for the specified command name.
int findIndex(const std::string &name) const
Find hook index.
void callCallouts(int hook_index, CalloutHandle &callout_handle)
Calls the callouts for a given hook.
void registerCallout(const std::string &name, CalloutPtr callout)
Register a callout on a hook for the current library.
static ServerHooks & getServerHooks()
Return ServerHooks object.
bool commandHandlersPresent(const std::string &command_name) const
Checks if control command handlers are present for the specified command.
int getCount() const
Return number of hooks.
Definition: server_hooks.h:133
#define LOG_ERROR(LOGGER, MESSAGE)
Macro to conveniently test error output and log it.
Definition: macros.h:32
std::string getName(int index) const
Get hook name.
void * dlsymPtr() const
Return pointer returned by dlsym call.
Local class for conversion of void pointers to function pointers.
#define isc_throw(type, stream)
A shortcut macro to insert known values into exception arguments.
A generic exception that is thrown if a parameter given to a method is considered invalid in that con...
void stop()
Stops the stopwatch.
Definition: stopwatch.cc:35
isc::log::Logger callouts_logger("callouts")
Callouts logger.
Definition: hooks_log.h:44
bool deregisterCallout(const std::string &name, CalloutPtr callout)
De-Register a callout on a hook for the current library.
Per-packet callout handle.
bool calloutsPresent(int hook_index) const
Checks if callouts are present on a hook.
Utility class to measure code execution times.
Definition: stopwatch.h:35
std::string logFormatLastDuration() const
Returns the last measured duration in the format directly usable in log messages.
Definition: stopwatch.cc:75
bool deregisterAllCallouts(const std::string &name)
Removes all callouts on a hook for the current library.
const int HOOKS_DBG_CALLS
Definition: hooks_log.h:25
Defines the logger used by the top-level component of kea-dhcp-ddns.
static std::string commandToHookName(const std::string &command_name)
Generates hook point name for the given control command name.
#define LOG_DEBUG(LOGGER, LEVEL, MESSAGE)
Macro to conveniently test debug output and log it.
Definition: macros.h:14
int getIndex(const std::string &name) const
Get hook index.
Server hook collection.
Definition: server_hooks.h:62
std::string logFormatTotalDuration() const
Returns the total measured duration in the format directly usable in the log messages.
Definition: stopwatch.cc:80
void callCommandHandlers(const std::string &command_name, CalloutHandle &callout_handle)
Calls the callouts/command handlers for a given command name.
int registerHook(const std::string &name)
Register a hook.
Definition: server_hooks.cc:44
void start()
Starts the stopwatch.
Definition: stopwatch.cc:30