123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480 |
- // 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.
- #include <config.h>
- #include <stdexcept>
- #include <stdlib.h>
- #include <string.h>
- #include <sys/time.h>
- #include <iostream>
- #include <fstream>
- #include <sstream>
- #include <cerrno>
- #include <boost/bind.hpp>
- #include <boost/foreach.hpp>
- #include <cc/data.h>
- #include <module_spec.h>
- #include <cc/session.h>
- #include <exceptions/exceptions.h>
- #include <config/config_log.h>
- #include <config/ccsession.h>
- using namespace std;
- using isc::data::Element;
- using isc::data::ConstElementPtr;
- using isc::data::ElementPtr;
- using isc::data::JSONError;
- namespace isc {
- namespace config {
- /// Creates a standard config/command protocol answer message
- ConstElementPtr
- createAnswer() {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
- ElementPtr answer_content = Element::createList();
- answer_content->add(Element::create(0));
- answer->set("result", answer_content);
- return (answer);
- }
- ConstElementPtr
- createAnswer(const int rcode, ConstElementPtr arg) {
- if (rcode != 0 && (!arg || arg->getType() != Element::string)) {
- isc_throw(CCSessionError, "Bad or no argument for rcode != 0");
- }
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
- ElementPtr answer_content = Element::createList();
- answer_content->add(Element::create(rcode));
- answer_content->add(arg);
- answer->set("result", answer_content);
- return (answer);
- }
- ConstElementPtr
- createAnswer(const int rcode, const std::string& arg) {
- ElementPtr answer = Element::fromJSON("{\"result\": [] }");
- ElementPtr answer_content = Element::createList();
- answer_content->add(Element::create(rcode));
- answer_content->add(Element::create(arg));
- answer->set("result", answer_content);
- return (answer);
- }
- ConstElementPtr
- parseAnswer(int &rcode, ConstElementPtr msg) {
- if (msg &&
- msg->getType() == Element::map &&
- msg->contains("result")) {
- ConstElementPtr result = msg->get("result");
- 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) {
- isc_throw(CCSessionError, "First element of result is not an rcode in answer message");
- }
- rcode = result->get(0)->intValue();
- if (result->size() > 1) {
- if (rcode == 0 || result->get(1)->getType() == Element::string) {
- return (result->get(1));
- } else {
- isc_throw(CCSessionError, "Error description in result with rcode != 0 is not a string");
- }
- } else {
- if (rcode == 0) {
- return (ElementPtr());
- } else {
- isc_throw(CCSessionError, "Result with rcode != 0 does not have an error description");
- }
- }
- } else {
- isc_throw(CCSessionError, "No result part in answer message");
- }
- }
- ConstElementPtr
- createCommand(const std::string& command) {
- return (createCommand(command, ElementPtr()));
- }
- ConstElementPtr
- createCommand(const std::string& command, ConstElementPtr arg) {
- ElementPtr cmd = Element::createMap();
- ElementPtr cmd_parts = Element::createList();
- cmd_parts->add(Element::create(command));
- if (arg) {
- cmd_parts->add(arg);
- }
- cmd->set("command", cmd_parts);
- return (cmd);
- }
- std::string
- parseCommand(ConstElementPtr& arg, ConstElementPtr command) {
- if (command &&
- command->getType() == Element::map &&
- command->contains("command")) {
- ConstElementPtr cmd = command->get("command");
- if (cmd->getType() == Element::list &&
- cmd->size() > 0 &&
- cmd->get(0)->getType() == Element::string) {
- if (cmd->size() > 1) {
- arg = cmd->get(1);
- } else {
- arg = Element::createMap();
- }
- return (cmd->get(0)->stringValue());
- } else {
- isc_throw(CCSessionError, "Command part in command message missing, empty, or not a list");
- }
- } else {
- isc_throw(CCSessionError, "Command Element empty or not a map with \"command\"");
- }
- }
- ModuleSpec
- ModuleCCSession::readModuleSpecification(const std::string& filename) {
- std::ifstream file;
- ModuleSpec module_spec;
-
- // this file should be declared in a @something@ directive
- file.open(filename.c_str());
- if (!file) {
- LOG_ERROR(config_logger, CONFIG_FOPEN_ERR).arg(filename).arg(strerror(errno));
- isc_throw(CCSessionInitError, strerror(errno));
- }
- try {
- module_spec = moduleSpecFromFile(file, true);
- } catch (const JSONError& pe) {
- LOG_ERROR(config_logger, CONFIG_JSON_PARSE).arg(filename).arg(pe.what());
- isc_throw(CCSessionInitError, pe.what());
- } catch (const ModuleSpecError& dde) {
- LOG_ERROR(config_logger, CONFIG_MODULE_SPEC).arg(filename).arg(dde.what());
- isc_throw(CCSessionInitError, dde.what());
- }
- file.close();
- return (module_spec);
- }
- void
- ModuleCCSession::startCheck() {
- // data available on the command channel. process it in the synchronous
- // mode.
- checkCommand();
- // start asynchronous read again.
- session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
- }
- ModuleCCSession::ModuleCCSession(
- const std::string& spec_file_name,
- isc::cc::AbstractSession& session,
- isc::data::ConstElementPtr(*config_handler)(
- isc::data::ConstElementPtr new_config),
- isc::data::ConstElementPtr(*command_handler)(
- const std::string& command, isc::data::ConstElementPtr args),
- bool start_immediately, const char* socket_path
- ) :
- started_(false),
- session_(session)
- {
- module_specification_ = readModuleSpecification(spec_file_name);
- setModuleSpec(module_specification_);
- module_name_ = module_specification_.getFullSpec()->get("module_name")->stringValue();
- config_handler_ = config_handler;
- command_handler_ = command_handler;
- session_.establish(socket_path);
- session_.subscribe(module_name_, "*");
- //session_.subscribe("Boss", "*");
- //session_.subscribe("statistics", "*");
- // send the data specification
- ConstElementPtr spec_msg = createCommand("module_spec",
- module_specification_.getFullSpec());
- unsigned int seq = session_.group_sendmsg(spec_msg, "ConfigManager");
- ConstElementPtr answer, env;
- session_.group_recvmsg(env, answer, false, seq);
- int rcode;
- ConstElementPtr err = parseAnswer(rcode, answer);
- if (rcode != 0) {
- LOG_ERROR(config_logger, CONFIG_MANAGER_MOD_SPEC).arg(answer->str());
- isc_throw(CCSessionInitError, answer->str());
- }
-
- setLocalConfig(Element::fromJSON("{}"));
- // get any stored configuration from the manager
- if (config_handler_) {
- ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name_ + "\"} ] }");
- seq = session_.group_sendmsg(cmd, "ConfigManager");
- session_.group_recvmsg(env, answer, false, seq);
- ConstElementPtr new_config = parseAnswer(rcode, answer);
- if (rcode == 0) {
- handleConfigUpdate(new_config);
- } else {
- LOG_ERROR(config_logger, CONFIG_MANAGER_CONFIG).arg(new_config->str());
- isc_throw(CCSessionInitError, answer->str());
- }
- }
- if (start_immediately) {
- start();
- }
- }
- void
- ModuleCCSession::start() {
- if (started_) {
- isc_throw(CCSessionError, "Module CC session already started");
- }
- // register callback for asynchronous read
- session_.startRead(boost::bind(&ModuleCCSession::startCheck, this));
- }
- /// Validates the new config values, if they are correct,
- /// call the config handler with the values that have changed
- /// If that results in success, store the new config
- ConstElementPtr
- ModuleCCSession::handleConfigUpdate(ConstElementPtr new_config) {
- ConstElementPtr answer;
- ElementPtr errors = Element::createList();
- if (!config_handler_) {
- answer = createAnswer(1, module_name_ + " does not have a config handler");
- } else if (!module_specification_.validateConfig(new_config, false,
- errors)) {
- std::stringstream ss;
- ss << "Error in config validation: ";
- BOOST_FOREACH(ConstElementPtr error, errors->listValue()) {
- ss << error->stringValue();
- }
- answer = createAnswer(2, ss.str());
- } else {
- // remove the values that have not changed
- ConstElementPtr diff = removeIdentical(new_config, getLocalConfig());
- // handle config update
- answer = config_handler_(diff);
- int rcode;
- parseAnswer(rcode, answer);
- if (rcode == 0) {
- ElementPtr local_config = getLocalConfig();
- isc::data::merge(local_config, diff);
- setLocalConfig(local_config);
- }
- }
- return (answer);
- }
- bool
- ModuleCCSession::hasQueuedMsgs() const {
- return (session_.hasQueuedMsgs());
- }
- ConstElementPtr
- ModuleCCSession::checkConfigUpdateCommand(const std::string& target_module,
- ConstElementPtr arg)
- {
- if (target_module == module_name_) {
- return (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
- return (ElementPtr());
- }
- }
- ConstElementPtr
- ModuleCCSession::checkModuleCommand(const std::string& cmd_str,
- const std::string& target_module,
- ConstElementPtr arg) const
- {
- if (target_module == module_name_) {
- if (command_handler_) {
- ElementPtr errors = Element::createList();
- if (module_specification_.validateCommand(cmd_str,
- arg,
- errors)) {
- return (command_handler_(cmd_str, arg));
- } else {
- std::stringstream ss;
- ss << "Error in command validation: ";
- BOOST_FOREACH(ConstElementPtr error,
- errors->listValue()) {
- ss << error->stringValue();
- }
- return (createAnswer(3, ss.str()));
- }
- } else {
- return (createAnswer(1,
- "Command given but no "
- "command handler for module"));
- }
- }
- return (ElementPtr());
- }
- int
- ModuleCCSession::checkCommand() {
- ConstElementPtr cmd, routing, data;
- if (session_.group_recvmsg(routing, data, true)) {
-
- /* ignore result messages (in case we're out of sync, to prevent
- * pingpongs */
- if (data->getType() != Element::map || data->contains("result")) {
- return (0);
- }
- ConstElementPtr arg;
- ConstElementPtr answer;
- try {
- std::string cmd_str = parseCommand(arg, data);
- std::string target_module = routing->get("group")->stringValue();
- if (cmd_str == "config_update") {
- answer = checkConfigUpdateCommand(target_module, arg);
- } else {
- answer = checkModuleCommand(cmd_str, target_module, arg);
- }
- } catch (const CCSessionError& re) {
- LOG_ERROR(config_logger, CONFIG_CCSESSION_MSG).arg(re.what());
- }
- if (!isNull(answer)) {
- session_.reply(routing, answer);
- }
- }
- return (0);
- }
- std::string
- ModuleCCSession::addRemoteConfig(const std::string& spec_name,
- void (*handler)(const std::string& module,
- ConstElementPtr),
- bool spec_is_filename)
- {
- std::string module_name;
- ModuleSpec rmod_spec;
- if (spec_is_filename) {
- // It's a file name, so load it
- rmod_spec = readModuleSpecification(spec_name);
- module_name =
- rmod_spec.getFullSpec()->get("module_name")->stringValue();
- } else {
- // It's module name, request it from config manager
- ConstElementPtr cmd = Element::fromJSON("{ \"command\": ["
- "\"get_module_spec\","
- "{\"module_name\": \"" +
- spec_name + "\"} ] }");
- unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
- ConstElementPtr env, answer;
- session_.group_recvmsg(env, answer, false, seq);
- int rcode;
- ConstElementPtr spec_data = parseAnswer(rcode, answer);
- if (rcode == 0 && spec_data) {
- rmod_spec = ModuleSpec(spec_data);
- module_name = spec_name;
- if (module_name != rmod_spec.getModuleName()) {
- isc_throw(CCSessionError, "Module name mismatch");
- }
- } else {
- isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
- }
- }
- ConfigData rmod_config = ConfigData(rmod_spec);
- // Get the current configuration values for that module
- ConstElementPtr cmd = Element::fromJSON("{ \"command\": [\"get_config\", {\"module_name\":\"" + module_name + "\"} ] }");
- unsigned int seq = session_.group_sendmsg(cmd, "ConfigManager");
- ConstElementPtr env, answer;
- session_.group_recvmsg(env, answer, false, seq);
- int rcode;
- ConstElementPtr new_config = parseAnswer(rcode, answer);
- ElementPtr local_config;
- if (rcode == 0 && new_config) {
- local_config = rmod_config.getLocalConfig();
- isc::data::merge(local_config, new_config);
- rmod_config.setLocalConfig(local_config);
- } else {
- isc_throw(CCSessionError, "Error getting config for " + module_name + ": " + answer->str());
- }
- // all ok, add it
- remote_module_configs_[module_name] = rmod_config;
- if (handler) {
- remote_module_handlers_[module_name] = handler;
- handler(module_name, local_config);
- }
- session_.subscribe(module_name);
- return (module_name);
- }
- void
- ModuleCCSession::removeRemoteConfig(const std::string& module_name) {
- 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);
- remote_module_handlers_.erase(module_name);
- session_.unsubscribe(module_name);
- }
- }
- ConstElementPtr
- ModuleCCSession::getRemoteConfigValue(const std::string& module_name,
- const std::string& identifier) const
- {
- std::map<std::string, ConfigData>::const_iterator it =
- remote_module_configs_.find(module_name);
- if (it != remote_module_configs_.end()) {
- return ((*it).second.getValue(identifier));
- } else {
- isc_throw(CCSessionError,
- "Remote module " + module_name + " not found.");
- }
- }
- void
- ModuleCCSession::updateRemoteConfig(const std::string& module_name,
- ConstElementPtr new_config)
- {
- 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);
- std::map<std::string, RemoteHandler>::iterator hit =
- remote_module_handlers_.find(module_name);
- if (hit != remote_module_handlers_.end()) {
- hit->second(module_name, new_config);
- }
- }
- }
- }
- }
|