summaryrefslogtreecommitdiffstats
path: root/src/lib/config/ccsession.h
blob: 0f8e34a49b2c41d1530f0073b1ffd2a7c9816f96 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
// Copyright (C) 2009  Internet Systems Consortium, Inc. ("ISC")
//
// Permission to use, copy, modify, and/or distribute this software for any
// purpose with or without fee is hereby granted, provided that the above
// copyright notice and this permission notice appear in all copies.
//
// THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH
// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
// AND FITNESS.  IN NO EVENT SHALL ISC BE LIABLE FOR ANY SPECIAL, DIRECT,
// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.

#ifndef __CCSESSION_H
#define __CCSESSION_H 1

#include <string>

#include <config/config_data.h>
#include <config/module_spec.h>
#include <cc/session.h>
#include <cc/data.h>

namespace isc {
namespace config {

///
/// \brief Creates a standard config/command level success answer message
///        (i.e. of the form { "result": [ 0 ] }
/// \return Standard command/config success answer message
isc::data::ConstElementPtr createAnswer();

///
/// \brief Creates a standard config/command level answer message
///        (i.e. of the form { "result": [ rcode, arg ] }
/// If rcode != 0, arg must be a StringElement
///
/// \param rcode The return code (0 for success)
/// \param arg For rcode == 0, this is an optional argument of any
///            Element type. For rcode == 1, this argument is mandatory,
///            and must be a StringElement containing an error description
/// \return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int rcode,
                                        isc::data::ConstElementPtr arg);

///
/// \brief Creates a standard config/command level answer message
/// (i.e. of the form { "result": [ rcode, arg ] }
///
/// \param rcode The return code (0 for success)
/// \param arg A string to put into the StringElement argument
/// \return Standard command/config answer message
isc::data::ConstElementPtr createAnswer(const int rcode,
                                        const std::string& arg);

///
/// Parses a standard config/command level answer message
/// 
/// \param rcode This value will be set to the return code contained in
///              the message
/// \param msg The message to parse
/// \return The optional argument in the message, or an empty ElementPtr
///         if there was no argument. If rcode != 0, this contains a
///         StringElement with the error description.
isc::data::ConstElementPtr parseAnswer(int &rcode,
                                       isc::data::ConstElementPtr msg);

///
/// \brief Creates a standard config/command command message with no
/// argument (of the form { "command": [ "my_command" ] }
/// 
/// \param command The command string
/// \return The created message
isc::data::ConstElementPtr createCommand(const std::string& command);

///
/// \brief Creates a standard config/command command message with the
/// given argument (of the form { "command": [ "my_command", arg ] }
/// 
/// \param command The command string
/// \param arg The optional argument for the command. This can be of 
///        any Element type, but it should conform to the .spec file.
/// \return The created message
isc::data::ConstElementPtr createCommand(const std::string& command,
                                         isc::data::ConstElementPtr arg);

///
/// \brief Parses the given command into a string containing the actual
///        command and an ElementPtr containing the optional argument.
///
/// Raises a CCSessionError if this is not a well-formed command
///
/// Example code: (command_message is a ConstElementPtr that is
/// passed here)
/// \code
/// ElementPtr command_message = Element::fromJSON("{ \"command\": [\"foo\", { \"bar\": 123 } ] }");
/// try {
///     ConstElementPtr args;
///     std::string command_str = parseCommand(args, command_message);
///     if (command_str == "foo") {
///         std::cout << "The command 'foo' was given" << std::endl;
///         if (args->contains("bar")) {
///             std::cout << "It had argument name 'bar' set, which has"
///                       << "value " 
///                       << args->get("bar")->intValue();
///         }
///     } else {
///         std::cout << "Unknown command '" << command_str << std::endl;
///     }
/// } catch (CCSessionError cse) {
///     std::cerr << "Bad command in CC Session: "
///     << cse.what() << std::endl;
/// }
/// \endcode
/// 
/// \param arg This value will be set to the ElementPtr pointing to
///        the argument, or to an empty Map (ElementPtr) if there was none.
/// \param command The command message containing the command (as made
///        by createCommand()
/// \return The command name
std::string parseCommand(isc::data::ConstElementPtr& arg,
                         isc::data::ConstElementPtr command);


///
/// \brief A standard cc session exception that is thrown if a function
/// is there is a problem with one of the messages
///
// todo: include types and called function in the exception
class CCSessionError : public isc::Exception {
public:
    CCSessionError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) {}
};

///
/// \brief This exception is thrown if the constructor fails
///
class CCSessionInitError : public isc::Exception {
public:
    CCSessionInitError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) {}
};

///
/// \brief This module keeps a connection to the command channel,
/// holds configuration information, and handles messages from
/// the command channel
///
class ModuleCCSession : public ConfigData {
public:
    /**
     * Initialize a config/command session
     *
     * @param spec_file_name The name of the file containing the
     *                        module specification.
     * @param session A Session object over which configuration and command
     * data are exchanged.
     * @param config_handler A callback function pointer to be called when
     * configuration of the local module needs to be updated.
     * This must refer to a valid object of a concrete derived class of
     * AbstractSession without establishing the session.
     *
     * Note: the design decision on who is responsible for establishing the
     * session is in flux, and may change in near future.
     *
     * \exception CCSessionInitError when the initialization fails,
     *            either because the file cannot be read or there is
     *            a communication problem with the config manager.
     *
     * @param command_handler A callback function pointer to be called when
     * a control command from a remote agent needs to be performed on the
     * local module.
     * @param start_immediately If true (default), start listening to new commands
     * and configuration changes asynchronously at the end of the constructor;
     * if false, it will be delayed until the start() method is explicitly
     * called. (This is a short term workaround for an initialization trouble.
     * We'll need to develop a cleaner solution, and then remove this knob)
     * @param handle_logging If true, the ModuleCCSession will automatically
     * take care of logging configuration through the virtual Logging config
     * module. Defaults to true.
     */
    ModuleCCSession(const std::string& spec_file_name,
                    isc::cc::AbstractSession& session,
                    isc::data::ConstElementPtr(*config_handler)(
                        isc::data::ConstElementPtr new_config) = NULL,
                    isc::data::ConstElementPtr(*command_handler)(
                        const std::string& command,
                        isc::data::ConstElementPtr args) = NULL,
                    bool start_immediately = true,
                    bool handle_logging = true
                    );

    /// Start receiving new commands and configuration changes asynchronously.
    ///
    /// This method must be called only once, and only when the ModuleCCSession
    /// was constructed with start_immediately being false.  Otherwise
    /// CCSessionError will be thrown.
    ///
    /// As noted in the constructor, this method should be considered a short
    /// term workaround and will be removed in future.
    void start();

    /**
     * Optional optimization for checkCommand loop; returns true
     * if there are unhandled queued messages in the cc session.
     * (if either this is true or there is data on the socket found
     * by the select() call on getSocket(), run checkCommand())
     * 
     * @return true if there are unhandled queued messages
     */
    bool hasQueuedMsgs() const;

    /**
     * Check if there is a command or config change on the command
     * session. If so, the appropriate handler is called if set.
     * If not set, a default answer is returned.
     * This is a non-blocking read; if there is nothing this function
     * will return 0.
     */
    int checkCommand();

    /**
     * The config handler function should expect an ElementPtr containing
     * the full configuration where non-default values have been set.
     * Later we might want to think about more granular control
     * (i.e. this does not scale to for instance lists containing
     * 100000 zones, where the whole list is passed every time a single
     * thing changes)
     */
    void setConfigHandler(isc::data::ConstElementPtr(*config_handler)(
                              isc::data::ConstElementPtr new_config))
    {
        config_handler_ = config_handler;
    }

    /**
     * Set a command handler; the function that is passed takes an
     * ElementPtr, pointing to a list element, containing
     * [ module_name, command_name, arg1, arg2, ... ]
     * The returned ElementPtr should look like
     * { "result": [ return_value, result_value ] }
     * result value here is optional and depends on the command
     *
     * This protocol is very likely to change.
     */
    void setCommandHandler(isc::data::ConstElementPtr(*command_handler)(
                               const std::string& command,
                               isc::data::ConstElementPtr args))
    {
        command_handler_ = command_handler;
    }

    /**
     * Gives access to the configuration values of a different module
     * Once this function has been called with the name of the specification
     * file or the module you want the configuration of, you can use
     * \c getRemoteConfigValue() to get a specific setting.
     * Changes are automatically updated, and you can specify handlers
     * for those changes. This function will subscribe to the relevant module
     * channel.
     *
     * This method must be called before calling the \c start() method on the
     * ModuleCCSession (it also implies the ModuleCCSession must have been
     * constructed with start_immediately being false).
     *
     * \param spec_name This specifies the module to add. It is either a
     *                  filename of the spec file to use or a name of module
     *                  (in case it's a module name, the spec data is
     *                  downloaded from the configuration manager, therefore
     *                  the configuration manager must know it). If
     *                  spec_is_filename is true (the default), then a
     *                  filename is assumed, otherwise a module name.
     * \param handler The handler function called whenever there's a change.
     *                Called once initally from this function. May be NULL
     *                if you don't want any handler to be called and you're
     *                fine with requesting the data through
     *                getRemoteConfigValue() each time.
     *
     *                The handler should not throw, or it'll fall trough and
     *                the exception will get into strange places, probably
     *                aborting the application.
     * \param spec_is_filename Says if spec_name is filename or module name.
     * \return The name of the module specified in the given specification
     *         file
     */
    std::string addRemoteConfig(const std::string& spec_name,
                                void (*handler)(const std::string& module_name,
                                                isc::data::ConstElementPtr
                                                update,
                                                const ConfigData& config_data) = NULL,
                                bool spec_is_filename = true);

    /**
     * Removes the module with the given name from the remote config
     * settings. If the module was not added with \c addRemoteConfig(),
     * nothing happens. If there was a handler for this config, it is
     * removed as well.
     */
    void removeRemoteConfig(const std::string& module_name);

    /**
     * Returns the current configuration value for the given module
     * name at the given identifier. See \c ConfigData::getValue() for
     * more details.
     * Raises a ModuleCCSessionError if the module name is unknown
     * Raises a DataNotFoundError if the identifier does not exist
     * in the specification.
     *
     * \param module_name The name of the module to get a config value for
     * \param identifier The identifier of the config value
     * \return The configuration setting at the given identifier
     */
    isc::data::ConstElementPtr getRemoteConfigValue(
        const std::string& module_name,
        const std::string& identifier) const;

    /**
     * Send a message to the underlying CC session.
     * This has the same interface as isc::cc::Session::group_sendmsg()
     *
     * \param msg see isc::cc::Session::group_sendmsg()
     * \param group see isc::cc::Session::group_sendmsg()
     * \param instance see isc::cc::Session::group_sendmsg()
     * \param to see isc::cc::Session::group_sendmsg()
     * \return see isc::cc::Session::group_sendmsg()
     */
    int groupSendMsg(isc::data::ConstElementPtr msg,
                     std::string group,
                     std::string instance = "*",
                     std::string to = "*") {
        return session_.group_sendmsg(msg, group, instance, to);
    };

    /**
     * Receive a message from the underlying CC session.
     * This has the same interface as isc::cc::Session::group_recvmsg()
     *
     * \param envelope see isc::cc::Session::group_recvmsg()
     * \param msg see isc::cc::Session::group_recvmsg()
     * \param nonblock see isc::cc::Session::group_recvmsg()
     * \param seq see isc::cc::Session::group_recvmsg()
     * \return see isc::cc::Session::group_recvmsg()
     */
    bool groupRecvMsg(isc::data::ConstElementPtr& envelope,
                      isc::data::ConstElementPtr& msg,
                      bool nonblock = true,
                      int seq = -1) {
        return session_.group_recvmsg(envelope, msg, nonblock, seq);
    };

private:
    ModuleSpec readModuleSpecification(const std::string& filename);
    void startCheck();

    bool started_;
    std::string module_name_;
    isc::cc::AbstractSession& session_;
    ModuleSpec module_specification_;
    isc::data::ConstElementPtr handleConfigUpdate(
        isc::data::ConstElementPtr new_config);

    isc::data::ConstElementPtr checkConfigUpdateCommand(
        const std::string& target_module,
        isc::data::ConstElementPtr arg);

    isc::data::ConstElementPtr checkModuleCommand(
        const std::string& cmd_str,
        const std::string& target_module,
        isc::data::ConstElementPtr arg) const;

    isc::data::ConstElementPtr(*config_handler_)(
        isc::data::ConstElementPtr new_config);
    isc::data::ConstElementPtr(*command_handler_)(
        const std::string& command,
        isc::data::ConstElementPtr args);

    typedef void (*RemoteHandler)(const std::string&,
                                  isc::data::ConstElementPtr,
                                  const ConfigData&);
    std::map<std::string, ConfigData> remote_module_configs_;
    std::map<std::string, RemoteHandler> remote_module_handlers_;

    void updateRemoteConfig(const std::string& module_name,
                            isc::data::ConstElementPtr new_config);

    ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
};

/// \brief Default handler for logging config updates
///
/// When CCSession is initialized with handle_logging set to true,
/// this callback will be used to update the logger when a configuration
/// change comes in.
///
/// This function updates the (global) loggers by initializing a
/// LoggerManager and passing the settings as specified in the given
/// configuration update.
///
/// \param module_name The name of the module
/// \param new_config The modified configuration values
/// \param config_data The full config data for the (remote) logging
///                    module.
void
default_logconfig_handler(const std::string& module_name,
                          isc::data::ConstElementPtr new_config,
                          const ConfigData& config_data);


/// \brief Returns the loggers related to this module
///
/// This function does two things;
/// - it drops the configuration parts for loggers for other modules.
/// - it replaces the '*' in the name of the loggers by the name of
///   this module, but *only* if the expanded name is not configured
///   explicitly.
///
/// Examples: if this is the module b10-resolver,
/// For the config names ['*', 'b10-auth']
/// The '*' is replaced with 'b10-resolver', and this logger is used.
/// 'b10-auth' is ignored (of course, it will not be in the b10-auth
/// module).
///
/// For ['*', 'b10-resolver']
/// The '*' is ignored, and only 'b10-resolver' is used.
///
/// For ['*.reslib', 'b10-resolver']
/// Or ['b10-resolver.reslib', '*']
/// Both are used, where the * will be expanded to b10-resolver
///
/// \note This is a public function at this time, but mostly for
/// the purposes of testing. Once we can directly test what loggers
/// are running, this function may be moved to the unnamed namespace
///
/// \param loggers the original 'loggers' config list
/// \return ListElement containing only loggers relevant for this
///         module, where * is replaced by the root logger name
isc::data::ConstElementPtr
getRelatedLoggers(isc::data::ConstElementPtr loggers);

} // namespace config

} // namespace isc
#endif // __CCSESSION_H

// Local Variables:
// mode: c++
// End: