ccsession.cc 13.5 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
// 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.

// $Id$

17 18 19 20 21 22
// 
// todo: generalize this and make it into a specific API for all modules
//       to use (i.e. connect to cc, send config and commands, get config,
//               react on config change announcements)
//

23
#include <config.h>
24

JINMEI Tatuya's avatar
JINMEI Tatuya committed
25
#include <stdexcept>
26
#include <stdlib.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
27
#include <string.h>
28
#include <sys/time.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
29

30
#include <iostream>
31
#include <fstream>
32
#include <sstream>
33
#include <cerrno>
34

35
#include <boost/bind.hpp>
36
#include <boost/foreach.hpp>
37

38
#include <cc/data.h>
39
#include <module_spec.h>
40
#include <cc/session.h>
Jelte Jansen's avatar
Jelte Jansen committed
41
#include <exceptions/exceptions.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
42

43
#include <config/ccsession.h>
JINMEI Tatuya's avatar
JINMEI Tatuya committed
44

45
using namespace std;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
46

Jelte Jansen's avatar
Jelte Jansen committed
47 48
using isc::data::Element;
using isc::data::ElementPtr;
Jelte Jansen's avatar
Jelte Jansen committed
49
using isc::data::JSONError;
50 51 52

namespace isc {
namespace config {
53

Jelte Jansen's avatar
Jelte Jansen committed
54
/// Creates a standard config/command protocol answer message
55
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
56
createAnswer() {
57
    ElementPtr answer = Element::fromJSON("{\"result\": [] }");
58
    ElementPtr answer_content = Element::createList();
59
    answer_content->add(Element::create(0));
60 61
    answer->set("result", answer_content);

JINMEI Tatuya's avatar
JINMEI Tatuya committed
62
    return (answer);
63 64
}

65 66
ConstElementPtr
createAnswer(const int rcode, ConstElementPtr arg) {
67 68 69
    if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
        isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
    }
70
    ElementPtr answer = Element::fromJSON("{\"result\": [] }");
71
    ElementPtr answer_content = Element::createList();
72 73
    answer_content->add(Element::create(rcode));
    answer_content->add(arg);
74 75
    answer->set("result", answer_content);

JINMEI Tatuya's avatar
JINMEI Tatuya committed
76
    return (answer);
77 78
}

79
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
80
createAnswer(const int rcode, const std::string& arg) {
81
    ElementPtr answer = Element::fromJSON("{\"result\": [] }");
82
    ElementPtr answer_content = Element::createList();
83 84
    answer_content->add(Element::create(rcode));
    answer_content->add(Element::create(arg));
85 86
    answer->set("result", answer_content);

JINMEI Tatuya's avatar
JINMEI Tatuya committed
87
    return (answer);
88 89
}

90 91
ConstElementPtr
parseAnswer(int &rcode, ConstElementPtr msg) {
92 93 94
    if (msg &&
        msg->getType() == Element::map &&
        msg->contains("result")) {
95
        ConstElementPtr result = msg->get("result");
96 97 98
        if (result->getType() != Element::list) {
            isc_throw(CCSessionError, "Result element in answer message is not a list");
        } else if (result->get(0)->getType() != Element::integer) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
99
            isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
Jelte Jansen's avatar
Jelte Jansen committed
100 101 102
        }
        rcode = result->get(0)->intValue();
        if (result->size() > 1) {
103
            if (rcode == 0 || result->get(1)->getType() == Element::string) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
104
                return (result->get(1));
105 106 107
            } else {
                isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
            }
Jelte Jansen's avatar
Jelte Jansen committed
108
        } else {
109
            if (rcode == 0) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
110
                return (ElementPtr());
111 112 113
            } else {
                isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
            }
Jelte Jansen's avatar
Jelte Jansen committed
114
        }
115 116
    } else {
        isc_throw(CCSessionError, "No result part in answer message");
Jelte Jansen's avatar
Jelte Jansen committed
117 118
    }
}
119

120
ConstElementPtr
JINMEI Tatuya's avatar
JINMEI Tatuya committed
121
createCommand(const std::string& command) {
122
    return (createCommand(command, ElementPtr()));
123 124
}

125 126
ConstElementPtr
createCommand(const std::string& command, ConstElementPtr arg) {
127 128
    ElementPtr cmd = Element::createMap();
    ElementPtr cmd_parts = Element::createList();
129 130 131 132 133
    cmd_parts->add(Element::create(command));
    if (arg) {
        cmd_parts->add(arg);
    }
    cmd->set("command", cmd_parts);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
134
    return (cmd);
135 136 137 138
}

/// Returns "" and empty ElementPtr() if this does not
/// look like a command
139 140
std::string
parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
141 142
    if (command &&
        command->getType() == Element::map &&
143
        command->contains("command")) {
144
        ConstElementPtr cmd = command->get("command");
145 146 147 148 149 150 151 152
        if (cmd->getType() == Element::list &&
            cmd->size() > 0 &&
            cmd->get(0)->getType() == Element::string) {
            if (cmd->size() > 1) {
                arg = cmd->get(1);
            } else {
                arg = ElementPtr();
            }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
153
            return (cmd->get(0)->stringValue());
154 155
        } else {
            isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
156
        }
157 158
    } else {
        isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
159 160 161
    }
}

162
ModuleSpec
163
ModuleCCSession::readModuleSpecification(const std::string& filename) {
164
    std::ifstream file;
165 166
    ModuleSpec module_spec;
    
167
    // this file should be declared in a @something@ directive
168
    file.open(filename.c_str());
169
    if (!file) {
170
        cout << "error opening " << filename << ": " << strerror(errno) << endl;
171 172
        exit(1);
    }
173

174
    try {
175
        module_spec = moduleSpecFromFile(file, true);
Jelte Jansen's avatar
Jelte Jansen committed
176
    } catch (JSONError pe) {
177
        cout << "Error parsing module specification file: " << pe.what() << endl;
178
        exit(1);
179
    } catch (ModuleSpecError dde) {
180
        cout << "Error reading module specification file: " << dde.what() << endl;
181 182 183
        exit(1);
    }
    file.close();
JINMEI Tatuya's avatar
JINMEI Tatuya committed
184
    return (module_spec);
185
}
186

187 188 189 190
void
ModuleCCSession::startCheck() {
    // data available on the command channel.  process it in the synchronous
    // mode.
191
    checkCommand();
192 193 194 195 196 197

    // start asynchronous read again.
    session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
}

ModuleCCSession::ModuleCCSession(
198
    const std::string& spec_file_name,
JINMEI Tatuya's avatar
JINMEI Tatuya committed
199
    isc::cc::AbstractSession& session,
200 201 202 203
    isc::data::ConstElementPtr(*config_handler)(
        isc::data::ConstElementPtr new_config),
    isc::data::ConstElementPtr(*command_handler)(
        const std::string& command, isc::data::ConstElementPtr args)
204
    ) :
JINMEI Tatuya's avatar
JINMEI Tatuya committed
205
    session_(session)
JINMEI Tatuya's avatar
JINMEI Tatuya committed
206
{
207
    module_specification_ = readModuleSpecification(spec_file_name);
208
    setModuleSpec(module_specification_);
209

210
    module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
211 212 213
    config_handler_ = config_handler;
    command_handler_ = command_handler;

JINMEI Tatuya's avatar
JINMEI Tatuya committed
214
    session_.establish(NULL);
215
    session_.subscribe(module_name_, "*");
216 217
    //session_.subscribe("Boss", "*");
    //session_.subscribe("statistics", "*");
Jelte Jansen's avatar
Jelte Jansen committed
218
    // send the data specification
219 220 221

    ConstElementPtr spec_msg = createCommand("module_spec",
                                             module_specification_.getFullSpec());
Jelte Jansen's avatar
Jelte Jansen committed
222
    unsigned int seq = session_.group_sendmsg(spec_msg, "ConfigManager");
223 224

    ConstElementPtr answer, env;
Jelte Jansen's avatar
Jelte Jansen committed
225
    session_.group_recvmsg(env, answer, false, seq);
226
    int rcode;
227
    ConstElementPtr err = parseAnswer(rcode, answer);
228 229 230 231
    if (rcode != 0) {
        std::cerr << "[" << module_name_ << "] Error in specification: " << answer << std::endl;
    }
    
232
    setLocalConfig(Element::fromJSON("{}"));
Jelte Jansen's avatar
Jelte Jansen committed
233 234
    // get any stored configuration from the manager
    if (config_handler_) {
235
        ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
Jelte Jansen's avatar
Jelte Jansen committed
236 237
        seq = session_.group_sendmsg(cmd, "ConfigManager");
        session_.group_recvmsg(env, answer, false, seq);
238
        ConstElementPtr new_config = parseAnswer(rcode, answer);
239 240 241 242 243
        if (rcode == 0) {
            handleConfigUpdate(new_config);
        } else {
            std::cerr << "[" << module_name_ << "] Error getting config: " << new_config << std::endl;
        }
Jelte Jansen's avatar
Jelte Jansen committed
244
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
245 246 247

    // register callback for asynchronous read
    session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
Jelte Jansen's avatar
Jelte Jansen committed
248 249 250
}

/// Validates the new config values, if they are correct,
251
/// call the config handler with the values that have changed
Jelte Jansen's avatar
Jelte Jansen committed
252
/// If that results in success, store the new config
253 254 255
ConstElementPtr
ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
    ConstElementPtr answer;
256
    ElementPtr errors = Element::createList();
Jelte Jansen's avatar
Jelte Jansen committed
257 258
    if (!config_handler_) {
        answer = createAnswer(1, module_name_ + " does not have a config handler");
259 260
    } else if (!module_specification_.validate_config(new_config, false,
                                                      errors)) {
261 262
        std::stringstream ss;
        ss << "Error in config validation: ";
263
        BOOST_FOREACH(ConstElementPtr error, errors->listValue()) {
264 265 266
            ss << error->stringValue();
        }
        answer = createAnswer(2, ss.str());
Jelte Jansen's avatar
Jelte Jansen committed
267
    } else {
268
        // remove the values that have not changed
269
        ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
Jelte Jansen's avatar
Jelte Jansen committed
270
        // handle config update
271
        answer = config_handler_(diff);
Jelte Jansen's avatar
Jelte Jansen committed
272 273 274
        int rcode;
        parseAnswer(rcode, answer);
        if (rcode == 0) {
275
            ElementPtr local_config = getLocalConfig();
276
            isc::data::merge(local_config, diff);
277
            setLocalConfig(local_config);
278
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
279
    }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
280
    return (answer);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
281 282
}

Jelte Jansen's avatar
Jelte Jansen committed
283
bool
284
ModuleCCSession::hasQueuedMsgs() const {
Jelte Jansen's avatar
Jelte Jansen committed
285 286 287
    return (session_.hasQueuedMsgs());
}

288
int
289 290
ModuleCCSession::checkCommand() {
    ConstElementPtr cmd, routing, data;
291
    if (session_.group_recvmsg(routing, data, true)) {
292
        
293 294
        /* ignore result messages (in case we're out of sync, to prevent
         * pingpongs */
295
        if (data->getType() != Element::map || data->contains("result")) {
JINMEI Tatuya's avatar
JINMEI Tatuya committed
296
            return (0);
297
        }
298 299
        ConstElementPtr arg;
        ConstElementPtr answer;
300 301
        try {
            std::string cmd_str = parseCommand(arg, data);
302
            std::string target_module = routing->get("group")->stringValue();
303 304 305 306 307 308 309 310
            if (cmd_str == "config_update") {
                if (target_module == module_name_) {
                    answer = handleConfigUpdate(arg);
                } else {
                    // ok this update is not for us, if we have this module
                    // in our remote config list, update that
                    updateRemoteConfig(target_module, arg);
                    // we're not supposed to answer to this, so return
JINMEI Tatuya's avatar
JINMEI Tatuya committed
311
                    return (0);
312
                }
313
            } else {
314 315 316 317 318 319
                if (target_module == module_name_) {
                    if (command_handler_) {
                        answer = command_handler_(cmd_str, arg);
                    } else {
                        answer = createAnswer(1, "Command given but no command handler for module");
                    }
320
                }
321
            }
322
        } catch (const CCSessionError& re) {
323 324
            // TODO: Once we have logging and timeouts, we should not
            // answer here (potential interference)
325
            answer = createAnswer(1, re.what());
326
        }
327 328 329
        if (!isNull(answer)) {
            session_.reply(routing, answer);
        }
JINMEI Tatuya's avatar
JINMEI Tatuya committed
330
    }
331
    
JINMEI Tatuya's avatar
JINMEI Tatuya committed
332
    return (0);
JINMEI Tatuya's avatar
JINMEI Tatuya committed
333
}
334

335
std::string
336
ModuleCCSession::addRemoteConfig(const std::string& spec_file_name) {
337
    ModuleSpec rmod_spec = readModuleSpecification(spec_file_name);
338 339 340 341 342
    std::string module_name = rmod_spec.getFullSpec()->get("module_name")->stringValue();
    ConfigData rmod_config = ConfigData(rmod_spec);
    session_.subscribe(module_name);

    // Get the current configuration values for that module
343
    ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
Jelte Jansen's avatar
Jelte Jansen committed
344
    unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
345 346

    ConstElementPtr env, answer;
Jelte Jansen's avatar
Jelte Jansen committed
347
    session_.group_recvmsg(env, answer, false, seq);
348 349 350 351 352 353
    int rcode;
    ConstElementPtr new_config = parseAnswer(rcode, answer);
    if (rcode == 0 && new_config) {
        ElementPtr local_config = rmod_config.getLocalConfig();
        isc::data::merge(local_config, new_config);
        rmod_config.setLocalConfig(local_config);
354 355 356 357 358 359
    } else {
        isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
    }

    // all ok, add it
    remote_module_configs_[module_name] = rmod_config;
JINMEI Tatuya's avatar
JINMEI Tatuya committed
360
    return (module_name);
361 362 363
}

void
364
ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
365 366 367 368 369 370 371 372 373
    std::map<std::string, ConfigData>::iterator it;

    it = remote_module_configs_.find(module_name);
    if (it != remote_module_configs_.end()) {
        remote_module_configs_.erase(it);
        session_.unsubscribe(module_name);
    }
}

374 375 376
ConstElementPtr
ModuleCCSession::getRemoteConfigValue(const std::string& module_name,
                                      const std::string& identifier) const
377
{
378 379
    std::map<std::string, ConfigData>::const_iterator it =
        remote_module_configs_.find(module_name);
380 381

    if (it != remote_module_configs_.end()) {
382
        return ((*it).second.getValue(identifier));
383
    } else {
384 385
        isc_throw(CCSessionError,
                  "Remote module " + module_name + " not found.");
386 387 388 389
    }
}

void
390 391
ModuleCCSession::updateRemoteConfig(const std::string& module_name,
                                    ConstElementPtr new_config)
392 393 394 395 396 397 398 399 400 401
{
    std::map<std::string, ConfigData>::iterator it;

    it = remote_module_configs_.find(module_name);
    if (it != remote_module_configs_.end()) {
        ElementPtr rconf = (*it).second.getLocalConfig();
        isc::data::merge(rconf, new_config);
    }
}

402 403
}
}