command.cc 13.5 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14
// Copyright (C) 2010  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.

15 16 17
#include <auth/command.h>
#include <auth/auth_log.h>
#include <auth/auth_srv.h>
18 19 20

#include <cc/data.h>
#include <datasrc/memory_datasrc.h>
21
#include <datasrc/factory.h>
22
#include <config/ccsession.h>
23 24
#include <exceptions/exceptions.h>
#include <dns/rrclass.h>
25

26 27 28 29 30 31 32
#include <string>

#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>

#include <sys/types.h>
#include <unistd.h>
33 34

using boost::scoped_ptr;
35 36
using namespace isc::auth;
using namespace isc::config;
37 38
using namespace isc::data;
using namespace isc::datasrc;
39 40
using namespace isc::dns;
using namespace std;
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

namespace {
/// An exception that is thrown if an error occurs while handling a command
/// on an \c AuthSrv object.
///
/// Currently it's only used internally, since \c execAuthServerCommand()
/// (which is the only interface to this module) catches all \c isc::
/// exceptions and converts them.
class AuthCommandError : public isc::Exception {
public:
    AuthCommandError(const char* file, size_t line, const char* what) :
        isc::Exception(file, line, what) {}
};

/// An abstract base class that represents a single command identifier
/// for an \c AuthSrv object.
///
/// Each of derived classes of \c AuthCommand, which are hidden inside the
/// implementation, corresponds to a command executed on \c AuthSrv, such as
/// "shutdown".  The derived class is responsible to execute the corresponding
/// command with the given command arguments (if any) in its \c exec()
/// method.
///
/// In the initial implementation the existence of the command classes is
/// hidden inside the implementation since the only public interface is
/// \c execAuthServerCommand(), which does not expose this class.
/// In future, we may want to make this framework more dynamic, i.e.,
/// registering specific derived classes run time outside of this
/// implementation.  If and when that happens the definition of the abstract
/// class will be published.
class AuthCommand {
    ///
    /// \name Constructors and Destructor
    ///
    /// Note: The copy constructor and the assignment operator are
    /// intentionally defined as private to make it explicit that this is a
    /// pure base class.
    //@{
private:
    AuthCommand(const AuthCommand& source);
    AuthCommand& operator=(const AuthCommand& source);
protected:
    /// \brief The default constructor.
    ///
    /// This is intentionally defined as \c protected as this base class should
    /// never be instantiated (except as part of a derived class).
    AuthCommand() {}
public:
    /// The destructor.
    virtual ~AuthCommand() {}
    //@}

    /// Execute a single control command.
    ///
    /// Specific derived methods can throw exceptions.  When called via
    /// \c execAuthServerCommand(), all BIND 10 exceptions are caught
    /// and converted into an error code.
    /// The derived method may also throw an exception of class
    /// \c AuthCommandError when it encounters an internal error, such as
    /// semantics error on the command arguments.
    ///
    /// \param server The \c AuthSrv object on which the command is executed.
    /// \param args Command specific argument.
    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
};

107 108
// Handle the "shutdown" command. An optional parameter "pid" is used to
// see if it is really for our instance.
109 110
class ShutdownCommand : public AuthCommand {
public:
111 112
    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
        // Is the pid argument provided?
113
        if (args && args->contains("pid")) {
114
            // If it is, we check it is the same as our PID
115 116 117 118 119 120 121 122 123 124 125 126 127

            // This might throw in case the type is not an int, but that's
            // OK, as it'll get converted to an error on higher level.
            const int pid(args->get("pid")->intValue());
            const pid_t my_pid(getpid());
            if (my_pid != pid) {
                // It is not for us
                //
                // Note that this is completely expected situation, if
                // there are multiple instances of the server running and
                // another instance is being shut down, we get the message
                // too, due to the multicast nature of our message bus.
                return;
128 129
            }
        }
130
        LOG_DEBUG(auth_logger, DBG_AUTH_SHUT, AUTH_SHUTDOWN);
131 132 133 134 135 136 137 138
        server.stop();
    }
};

// Handle the "sendstats" command.  No argument is assumed.
class SendStatsCommand : public AuthCommand {
public:
    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
139
        LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_RECEIVED_SENDSTATS);
140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
        server.submitStatistics();
    }
};

// Handle the "loadzone" command.
class LoadZoneCommand : public AuthCommand {
public:
    virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
        // parse and validate the args.
        if (!validate(server, args)) {
            return;
        }

        // Load a new zone and replace the current zone with the new one.
        // TODO: eventually this should be incremental or done in some way
        // that doesn't block other server operations.
        // TODO: we may (should?) want to check the "last load time" and
        // the timestamp of the file and skip loading if the file isn't newer.
158
        const ConstElementPtr type(zone_config_->get("filetype"));
159
        boost::shared_ptr<InMemoryZoneFinder> zone_finder(
160 161
            new InMemoryZoneFinder(old_zone_finder->getClass(),
                                   old_zone_finder->getOrigin()));
162 163 164 165 166 167 168 169 170 171 172
        if (type && type->stringValue() == "sqlite3") {
            scoped_ptr<DataSourceClientContainer>
                container(new DataSourceClientContainer("sqlite3",
                                                        Element::fromJSON(
                    "{\"database_file\": \"" +
                    zone_config_->get("file")->stringValue() + "\"}")));
            zone_finder->load(*container->getInstance().getIterator(
                old_zone_finder->getOrigin()));
        } else {
            zone_finder->load(old_zone_finder->getFileName());
        }
173
        old_zone_finder->swap(*zone_finder);
174
        LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_LOAD_ZONE)
175
                  .arg(zone_finder->getOrigin()).arg(zone_finder->getClass());
176 177 178
    }

private:
179
    // zone finder to be updated with the new file.
180
    boost::shared_ptr<InMemoryZoneFinder> old_zone_finder;
181 182
    // The configuration corresponding to the zone.
    ConstElementPtr zone_config_;
183 184

    // A helper private method to parse and validate command parameters.
185
    // On success, it sets 'old_zone_finder' to the zone to be updated.
186 187 188 189 190 191 192 193 194 195 196 197
    // It returns true if everything is okay; and false if the command is
    // valid but there's no need for further process.
    bool validate(AuthSrv& server, isc::data::ConstElementPtr args) {
        if (args == NULL) {
            isc_throw(AuthCommandError, "Null argument");
        }

        // In this initial implementation, we assume memory data source
        // for class IN by default.
        ConstElementPtr datasrc_elem = args->get("datasrc");
        if (datasrc_elem) {
            if (datasrc_elem->stringValue() == "sqlite3") {
198
                LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_SQLITE3);
199 200 201 202 203 204 205 206 207 208 209 210 211 212
                return (false);
            } else if (datasrc_elem->stringValue() != "memory") {
                // (note: at this point it's guaranteed that datasrc_elem
                // is of string type)
                isc_throw(AuthCommandError,
                          "Data source type " << datasrc_elem->stringValue()
                          << " is not supported");
            }
        }

        ConstElementPtr class_elem = args->get("class");
        const RRClass zone_class = class_elem ?
            RRClass(class_elem->stringValue()) : RRClass::IN();

213 214
        AuthSrv::InMemoryClientPtr datasrc(server.
                                           getInMemoryClient(zone_class));
215 216 217 218 219 220 221 222 223 224 225
        if (datasrc == NULL) {
            isc_throw(AuthCommandError, "Memory data source is disabled");
        }

        ConstElementPtr origin_elem = args->get("origin");
        if (!origin_elem) {
            isc_throw(AuthCommandError, "Zone origin is missing");
        }
        const Name origin(origin_elem->stringValue());

        // Get the current zone
226
        const InMemoryClient::FindResult result = datasrc->findZone(origin);
227 228 229 230 231
        if (result.code != result::SUCCESS) {
            isc_throw(AuthCommandError, "Zone " << origin <<
                      " is not found in data source");
        }

232
        old_zone_finder = boost::dynamic_pointer_cast<InMemoryZoneFinder>(
233
            result.zone_finder);
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
        if (!server.getConfigSession()) {
            // FIXME: This is a hack to make older tests pass. We should
            // update these tests as well sometime and remove this hack.
            // (note that under normal situation, the
            // server.getConfigSession() does not return NULL)

            // We provide an empty map, which means no configuration --
            // defaults.
            zone_config_ = ConstElementPtr(new MapElement());
            return (true);
        }

        // Find the config corresponding to the zone.
        // We expect the configuration to be valid, as we have it and we
        // accepted it before, therefore it must be validated.
        const ConstElementPtr config(server.getConfigSession()->
                                     getValue("datasources"));
        ConstElementPtr zone_list;
        // Unfortunately, we need to walk the list to find the correct data
        // source.
        // TODO: Make it named sets. These lists are uncomfortable.
        for (size_t i(0); i < config->size(); ++ i) {
            // We use the getValue to get defaults as well
            const ConstElementPtr dsrc_config(config->get(i));
            const ConstElementPtr class_config(dsrc_config->get("class"));
            const string class_type(class_config ?
                                    class_config->stringValue() : "IN");
            // It is in-memory and our class matches.
            // FIXME: Is it allowed to have two datasources for the same
            // type and class at once? It probably would not work now
            // anyway and we may want to change the configuration of
            // datasources somehow.
            if (dsrc_config->get("type")->stringValue() == "memory" &&
                RRClass(class_type) == zone_class) {
                zone_list = dsrc_config->get("zones");
                break;
            }
        }

        if (!zone_list) {
            isc_throw(AuthCommandError,
                      "Corresponding data source configuration was not found");
        }

        // Now we need to walk the zones and find the correct one.
        for (size_t i(0); i < zone_list->size(); ++ i) {
            const ConstElementPtr zone_config(zone_list->get(i));
            if (Name(zone_config->get("origin")->stringValue()) == origin) {
                // The origins are the same, so we consider this config to be
                // for the zone.
                zone_config_ = zone_config;
                break;
            }
        }

        if (!zone_config_) {
            isc_throw(AuthCommandError,
                      "Corresponding zone configuration was not found");
        }

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
        return (true);
    }
};

// The factory of command objects.
AuthCommand*
createAuthCommand(const string& command_id) {
    // For the initial implementation we use a naive if-else blocks
    // (see also createAuthConfigParser())
    if (command_id == "shutdown") {
        return (new ShutdownCommand());
    } else if (command_id == "sendstats") {
        return (new SendStatsCommand());
    } else if (command_id == "loadzone") {
        return (new LoadZoneCommand());
    } else if (false && command_id == "_throw_exception") {
        // This is for testing purpose only and should not appear in the
        // actual configuration syntax.
        // XXX: ModuleCCSession doesn't seem to validate commands (unlike
        // config), so we should disable this case for now.
        throw runtime_error("throwing for test");
    }

    isc_throw(AuthCommandError, "Unknown command identifier: " << command_id);
}
} // end of unnamed namespace

ConstElementPtr
execAuthServerCommand(AuthSrv& server, const string& command_id,
                      ConstElementPtr args)
{
326
    LOG_DEBUG(auth_logger, DBG_AUTH_OPS, AUTH_RECEIVED_COMMAND).arg(command_id);
327 328 329 330
    try {
        scoped_ptr<AuthCommand>(createAuthCommand(command_id))->exec(server,
                                                                     args);
    } catch (const isc::Exception& ex) {
331 332
        LOG_ERROR(auth_logger, AUTH_COMMAND_FAILED).arg(command_id)
                                                   .arg(ex.what());
333 334 335 336 337
        return (createAnswer(1, ex.what()));
    }

    return (createAnswer());
}