ccsession.h 16.9 KB
Newer Older
JINMEI Tatuya's avatar
JINMEI Tatuya committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// 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>

20
#include <config/config_data.h>
21
#include <config/module_spec.h>
22
23
#include <cc/session.h>
#include <cc/data.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
24

25
26
27
namespace isc {
namespace config {

28
29
30
31
///
/// \brief Creates a standard config/command level success answer message
///        (i.e. of the form { "result": [ 0 ] }
/// \return Standard command/config success answer message
32
isc::data::ConstElementPtr createAnswer();
33
34
35
36
37
38
39
40
41
42
43

///
/// \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
44
45
isc::data::ConstElementPtr createAnswer(const int rcode,
                                        isc::data::ConstElementPtr arg);
46
47
48
49
50
51
52
53

///
/// \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
54
55
isc::data::ConstElementPtr createAnswer(const int rcode,
                                        const std::string& arg);
56
57
58
59
60
61
62
63
64
65

///
/// 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.
66
67
isc::data::ConstElementPtr parseAnswer(int &rcode,
                                       isc::data::ConstElementPtr msg);
68
69
70
71
72
73
74

///
/// \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
75
isc::data::ConstElementPtr createCommand(const std::string& command);
76
77
78
79
80
81
82
83
84

///
/// \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
85
86
isc::data::ConstElementPtr createCommand(const std::string& command,
                                         isc::data::ConstElementPtr arg);
87
88
89
90
91

///
/// \brief Parses the given command into a string containing the actual
///        command and an ElementPtr containing the optional argument.
///
92
93
/// Raises a CCSessionError if this is not a well-formed command
///
94
95
96
/// Example code: (command_message is a ConstElementPtr that is
/// passed here)
/// \code
Jelte Jansen's avatar
Jelte Jansen committed
97
/// ElementPtr command_message = Element::fromJSON("{ \"command\": [\"foo\", { \"bar\": 123 } ] }");
98
/// try {
Jelte Jansen's avatar
Jelte Jansen committed
99
100
///     ConstElementPtr args;
///     std::string command_str = parseCommand(args, command_message);
101
102
///     if (command_str == "foo") {
///         std::cout << "The command 'foo' was given" << std::endl;
Jelte Jansen's avatar
Jelte Jansen committed
103
104
///         if (args->contains("bar")) {
///             std::cout << "It had argument name 'bar' set, which has"
105
///                       << "value " 
Jelte Jansen's avatar
Jelte Jansen committed
106
///                       << args->get("bar")->intValue();
107
108
109
110
111
112
113
114
115
116
///         }
///     } else {
///         std::cout << "Unknown command '" << command_str << std::endl;
///     }
/// } catch (CCSessionError cse) {
///     std::cerr << "Bad command in CC Session: "
///     << cse.what() << std::endl;
/// }
/// \endcode
/// 
117
/// \param arg This value will be set to the ElementPtr pointing to
118
///        the argument, or to an empty Map (ElementPtr) if there was none.
119
120
/// \param command The command message containing the command (as made
///        by createCommand()
121
/// \return The command name
122
123
std::string parseCommand(isc::data::ConstElementPtr& arg,
                         isc::data::ConstElementPtr command);
124
125


Jelte Jansen's avatar
Jelte Jansen committed
126
127
128
129
130
131
132
133
134
135
136
///
/// \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) {}
};

137
138
139
140
141
142
143
144
145
///
/// \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) {}
};

146
///
147
/// \brief This module keeps a connection to the command channel,
148
149
150
/// holds configuration information, and handles messages from
/// the command channel
///
151
class ModuleCCSession : public ConfigData {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
152
public:
153
154
    /**
     * Initialize a config/command session
155
156
     *
     * @param spec_file_name The name of the file containing the
157
     *                        module specification.
158
159
160
161
     * @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.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
162
163
     * This must refer to a valid object of a concrete derived class of
     * AbstractSession without establishing the session.
Jelte Jansen's avatar
Jelte Jansen committed
164
     *
165
166
     * Note: the design decision on who is responsible for establishing the
     * session is in flux, and may change in near future.
167
     *
Jelte Jansen's avatar
Jelte Jansen committed
168
169
170
     * \exception CCSessionInitError when the initialization fails,
     *            either because the file cannot be read or there is
     *            a communication problem with the config manager.
171
     *
172
173
174
     * @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.
175
     * @param start_immediately If true (default), start listening to new commands
176
177
178
179
     * 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)
180
181
182
     * @param handle_logging If true, the ModuleCCSession will automatically
     * take care of logging configuration through the virtual Logging config
     * module.
183
     */
184
    ModuleCCSession(const std::string& spec_file_name,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
185
                    isc::cc::AbstractSession& session,
186
187
188
                    isc::data::ConstElementPtr(*config_handler)(
                        isc::data::ConstElementPtr new_config) = NULL,
                    isc::data::ConstElementPtr(*command_handler)(
JINMEI Tatuya's avatar
JINMEI Tatuya committed
189
                        const std::string& command,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
190
                        isc::data::ConstElementPtr args) = NULL,
Jelte Jansen's avatar
Jelte Jansen committed
191
192
                    bool start_immediately = true,
                    bool handle_logging = false
193
                    );
194

JINMEI Tatuya's avatar
JINMEI Tatuya committed
195
    /// Start receiving new commands and configuration changes asynchronously.
JINMEI Tatuya's avatar
JINMEI Tatuya committed
196
197
198
199
200
201
202
203
204
    ///
    /// 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();

Jelte Jansen's avatar
Jelte Jansen committed
205
206
207
208
209
210
211
212
    /**
     * 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
     */
213
    bool hasQueuedMsgs() const;
Jelte Jansen's avatar
Jelte Jansen committed
214

215
216
217
218
219
220
221
    /**
     * 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.
     */
222
    int checkCommand();
223
224
225
226
227
228
229
230
231

    /**
     * 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)
     */
232
233
234
235
236
    void setConfigHandler(isc::data::ConstElementPtr(*config_handler)(
                              isc::data::ConstElementPtr new_config))
    {
        config_handler_ = config_handler;
    }
237
238
239
240
241
242
243
244
245
246
247

    /**
     * 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.
     */
248
249
250
251
252
253
    void setCommandHandler(isc::data::ConstElementPtr(*command_handler)(
                               const std::string& command,
                               isc::data::ConstElementPtr args))
    {
        command_handler_ = command_handler;
    }
Jelte Jansen's avatar
Jelte Jansen committed
254

255
256
257
    /**
     * Gives access to the configuration values of a different module
     * Once this function has been called with the name of the specification
258
     * file or the module you want the configuration of, you can use
259
     * \c getRemoteConfigValue() to get a specific setting.
260
261
262
     * Changes are automatically updated, and you can specify handlers
     * for those changes. This function will subscribe to the relevant module
     * channel.
263
     *
264
265
266
     * 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).
267
     *
268
269
270
271
     * \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
272
     *                  the configuration manager must know it). If
273
     *                  spec_is_filename is true (the default), then a
274
     *                  filename is assumed, otherwise a module name.
275
276
277
278
279
     * \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.
280
281
282
283
     *
     *                The handler should not throw, or it'll fall trough and
     *                the exception will get into strange places, probably
     *                aborting the application.
284
     * \param spec_is_filename Says if spec_name is filename or module name.
285
286
287
     * \return The name of the module specified in the given specification
     *         file
     */
288
289
290
    std::string addRemoteConfig(const std::string& spec_name,
                                void (*handler)(const std::string& module_name,
                                                isc::data::ConstElementPtr
291
292
                                                update,
                                                const ConfigData& config_data) = NULL,
293
                                bool spec_is_filename = true);
294
295
296
297

    /**
     * Removes the module with the given name from the remote config
     * settings. If the module was not added with \c addRemoteConfig(),
298
299
     * nothing happens. If there was a handler for this config, it is
     * removed as well.
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
     */
    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
     */
315
316
317
    isc::data::ConstElementPtr getRemoteConfigValue(
        const std::string& module_name,
        const std::string& identifier) const;
318
    
JINMEI Tatuya's avatar
JINMEI Tatuya committed
319
private:
320
    ModuleSpec readModuleSpecification(const std::string& filename);
321
    void startCheck();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
322
323

    bool started_;
324
    std::string module_name_;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
325
    isc::cc::AbstractSession& session_;
326
    ModuleSpec module_specification_;
327
328
    isc::data::ConstElementPtr handleConfigUpdate(
        isc::data::ConstElementPtr new_config);
329

Jelte Jansen's avatar
Jelte Jansen committed
330
331
332
    isc::data::ConstElementPtr checkConfigUpdateCommand(
        const std::string& target_module,
        isc::data::ConstElementPtr arg);
333

Jelte Jansen's avatar
Jelte Jansen committed
334
335
336
    isc::data::ConstElementPtr checkModuleCommand(
        const std::string& cmd_str,
        const std::string& target_module,
337
        isc::data::ConstElementPtr arg) const;
Jelte Jansen's avatar
Jelte Jansen committed
338

339
340
341
342
343
    isc::data::ConstElementPtr(*config_handler_)(
        isc::data::ConstElementPtr new_config);
    isc::data::ConstElementPtr(*command_handler_)(
        const std::string& command,
        isc::data::ConstElementPtr args);
344

345
    typedef void (*RemoteHandler)(const std::string&,
346
347
                                  isc::data::ConstElementPtr,
                                  const ConfigData&);
348
    std::map<std::string, ConfigData> remote_module_configs_;
349
350
    std::map<std::string, RemoteHandler> remote_module_handlers_;

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

354
    ModuleSpec fetchRemoteSpec(const std::string& module, bool is_filename);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
355
356
};

357
358
359
360
361
362
363
364
365
366
/// \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.
///
367
/// \param module_name The name of the module
368
369
370
371
/// \param new_config The modified configuration values
/// \param config_data The full config data for the (remote) logging
///                    module.
void
372
default_logconfig_handler(const std::string& module_name,
373
374
375
                          isc::data::ConstElementPtr new_config,
                          const ConfigData& config_data);

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

/// \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
///   explicitely
///
/// 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
Jelte Jansen's avatar
Jelte Jansen committed
403
404
/// \return ListElement containing only loggers relevant for this
///         module, where * is replaced by the root logger name
405
406
407
408
409
410
isc::data::ConstElementPtr
getRelatedLoggers(isc::data::ConstElementPtr loggers);

} // namespace config

} // namespace isc
JINMEI Tatuya's avatar
JINMEI Tatuya committed
411
412
413
414
415
#endif // __CCSESSION_H

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