Commit 4c8164b1 authored by Michal 'vorner' Vaner's avatar Michal 'vorner' Vaner
Browse files

Merge branch #1596

Conflicts:
	src/bin/resolver/main.cc
parents 4d266d80 f15c13fb
This diff is collapsed.
......@@ -913,7 +913,24 @@ address, but the usual ones don't." mean? -->
In short, you should think twice before disabling something here.
</para>
</note>
<para>
It is possible to start some components multiple times (currently
<command>b10-auth</command> and <command>b10-resolzer</command>).
You might want to do that to gain more performance (each one uses only
single core). Just put multiple entries under different names, like
this, with the same config:
<screen>&gt; <userinput>config add Boss/components b10-resolver-2</userinput>
&gt; <userinput>config set Boss/components/b10-resolver-2/special resolver</userinput>
&gt; <userinput>config set Boss/components/b10-resolver-2/kind needed</userinput>
&gt; <userinput>config commit</userinput></screen>
</para>
<para>
However, this is work in progress and the support is not yet complete.
For example, each resolver will have its own cache, each authoritative
server will keep its own copy of in-memory data and there could be
problems with locking the sqlite database, if used. The configuration
might be changed to something more convenient in future.
</para>
</section>
</chapter>
......
......@@ -97,7 +97,13 @@
{
"command_name": "shutdown",
"command_description": "Shut down authoritative DNS server",
"command_args": []
"command_args": [
{
"item_name": "pid",
"item_type": "integer",
"item_optional": true
}
]
},
{
"command_name": "sendstats",
......
......@@ -29,6 +29,8 @@ namespace auth {
// Debug messages indicating normal startup are logged at this debug level.
const int DBG_AUTH_START = DBGLVL_START_SHUT;
// Debug messages upon shutdown
const int DBG_AUTH_SHUT = DBGLVL_START_SHUT;
// Debug level used to log setting information (such as configuration changes).
const int DBG_AUTH_OPS = DBGLVL_COMMAND;
......
......@@ -192,6 +192,10 @@ reason for the failure is included in the message.
Initialization of the authoritative server has completed successfully
and it is entering the main loop, waiting for queries to arrive.
% AUTH_SHUTDOWN asked to stop, doing so
This is a debug message indicating the server was asked to shut down and it is
complying to the request.
% AUTH_SQLITE3 nothing to do for loading sqlite3
This is a debug message indicating that the authoritative server has
found that the data source it is loading is an SQLite3 data source,
......
......@@ -344,6 +344,7 @@ public:
/// \param type Type of a counter to get the value of
///
/// \return the value of the counter.
uint64_t getCounter(const AuthCounters::ServerCounterType type) const;
/// \brief Get the value of per Opcode counter in the Auth Counters.
......
......@@ -12,24 +12,23 @@
// OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
// PERFORMANCE OF THIS SOFTWARE.
#include <string>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <auth/command.h>
#include <auth/auth_log.h>
#include <auth/auth_srv.h>
#include <cc/data.h>
#include <datasrc/memory_datasrc.h>
#include <config/ccsession.h>
#include <exceptions/exceptions.h>
#include <dns/rrclass.h>
#include <cc/data.h>
#include <datasrc/memory_datasrc.h>
#include <string>
#include <config/ccsession.h>
#include <boost/scoped_ptr.hpp>
#include <boost/shared_ptr.hpp>
#include <auth/auth_log.h>
#include <auth/auth_srv.h>
#include <auth/command.h>
#include <sys/types.h>
#include <unistd.h>
using boost::scoped_ptr;
using namespace isc::auth;
......@@ -104,10 +103,30 @@ public:
virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) = 0;
};
// Handle the "shutdown" command. No argument is assumed.
// Handle the "shutdown" command. An optional parameter "pid" is used to
// see if it is really for our instance.
class ShutdownCommand : public AuthCommand {
public:
virtual void exec(AuthSrv& server, isc::data::ConstElementPtr) {
virtual void exec(AuthSrv& server, isc::data::ConstElementPtr args) {
// Is the pid argument provided?
if (args && args->contains("pid")) {
// If it is, we check it is the same as our PID
// 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;
}
}
LOG_DEBUG(auth_logger, DBG_AUTH_SHUT, AUTH_SHUTDOWN);
server.stop();
}
};
......
......@@ -14,14 +14,9 @@
#include <config.h>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/command.h>
#include <dns/name.h>
#include <dns/rrclass.h>
......@@ -33,14 +28,22 @@
#include <datasrc/memory_datasrc.h>
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/command.h>
#include <asiolink/asiolink.h>
#include <testutils/mockups.h>
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>
#include <boost/bind.hpp>
#include <gtest/gtest.h>
#include <sys/types.h>
#include <unistd.h>
using namespace std;
using namespace isc::dns;
using namespace isc::data;
......@@ -50,58 +53,119 @@ using namespace isc::config;
namespace {
class AuthCommandTest : public ::testing::Test {
protected:
AuthCommandTest() : server(false, xfrout), rcode(-1) {
server.setStatisticsSession(&statistics_session);
AuthCommandTest() :
server_(false, xfrout_),
rcode_(-1),
expect_rcode_(0),
itimer_(server_.getIOService())
{
server_.setStatisticsSession(&statistics_session_);
}
void checkAnswer(const int expected_code) {
parseAnswer(rcode, result);
EXPECT_EQ(expected_code, rcode);
parseAnswer(rcode_, result_);
EXPECT_EQ(expected_code, rcode_);
}
MockSession statistics_session;
MockXfroutClient xfrout;
AuthSrv server;
ConstElementPtr result;
int rcode;
MockSession statistics_session_;
MockXfroutClient xfrout_;
AuthSrv server_;
ConstElementPtr result_;
// The shutdown command parameter
ConstElementPtr param_;
int rcode_, expect_rcode_;
isc::asiolink::IntervalTimer itimer_;
public:
void stopServer(); // need to be public for boost::bind
void dontStopServer(); // need to be public for boost::bind
};
TEST_F(AuthCommandTest, unknownCommand) {
result = execAuthServerCommand(server, "no_such_command",
ConstElementPtr());
parseAnswer(rcode, result);
EXPECT_EQ(1, rcode);
result_ = execAuthServerCommand(server_, "no_such_command",
ConstElementPtr());
parseAnswer(rcode_, result_);
EXPECT_EQ(1, rcode_);
}
TEST_F(AuthCommandTest, DISABLED_unexpectedException) {
// execAuthServerCommand() won't catch standard exceptions.
// Skip this test for now: ModuleCCSession doesn't seem to validate
// commands.
EXPECT_THROW(execAuthServerCommand(server, "_throw_exception",
EXPECT_THROW(execAuthServerCommand(server_, "_throw_exception",
ConstElementPtr()),
runtime_error);
}
TEST_F(AuthCommandTest, sendStatistics) {
result = execAuthServerCommand(server, "sendstats", ConstElementPtr());
result_ = execAuthServerCommand(server_, "sendstats", ConstElementPtr());
// Just check some message has been sent. Detailed tests specific to
// statistics are done in its own tests.
EXPECT_EQ("Stats", statistics_session.getMessageDest());
EXPECT_EQ("Stats", statistics_session_.getMessageDest());
checkAnswer(0);
}
void
AuthCommandTest::stopServer() {
result = execAuthServerCommand(server, "shutdown", ConstElementPtr());
parseAnswer(rcode, result);
assert(rcode == 0); // make sure the test stops when something is wrong
result_ = execAuthServerCommand(server_, "shutdown", param_);
parseAnswer(rcode_, result_);
assert(rcode_ == 0); // make sure the test stops when something is wrong
}
TEST_F(AuthCommandTest, shutdown) {
isc::asiolink::IntervalTimer itimer(server.getIOService());
itimer.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
server.getIOService().run();
EXPECT_EQ(0, rcode);
// Param defaults to empty/null pointer on creation
itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
server_.getIOService().run();
EXPECT_EQ(0, rcode_);
}
TEST_F(AuthCommandTest, shutdownCorrectPID) {
// Put the pid parameter there
const pid_t pid(getpid());
ElementPtr param(new isc::data::MapElement());
param->set("pid", ConstElementPtr(new isc::data::IntElement(pid)));
param_ = param;
// With the correct PID, it should act exactly the same as in case
// of no parameter
itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
server_.getIOService().run();
EXPECT_EQ(0, rcode_);
}
// This is like stopServer, but the server should not stop after the
// command, it should be running
void
AuthCommandTest::dontStopServer() {
result_ = execAuthServerCommand(server_, "shutdown", param_);
parseAnswer(rcode_, result_);
EXPECT_EQ(expect_rcode_, rcode_);
rcode_ = -1;
// We run the stopServer now, to really stop the server.
// If it had stopped already, it won't be run and the rcode -1 will
// be left here.
param_ = ConstElementPtr();
itimer_.cancel();
itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
}
// If we provide something not an int, the PID is not really specified, so
// act as if nothing came.
TEST_F(AuthCommandTest, shutdownNotInt) {
// Put the pid parameter there
ElementPtr param(new isc::data::MapElement());
param->set("pid", ConstElementPtr(new isc::data::StringElement("pid")));
param_ = param;
expect_rcode_ = 1;
// It should reject to stop if the PID is not an int.
itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
server_.getIOService().run();
EXPECT_EQ(0, rcode_);
}
TEST_F(AuthCommandTest, shutdownIncorrectPID) {
// The PID = 0 should be taken by init, so we are not init and the
// PID should be different
param_ = Element::fromJSON("{\"pid\": 0}");
itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
server_.getIOService().run();
EXPECT_EQ(0, rcode_);
}
// A helper function commonly used for the "loadzone" command tests.
......@@ -165,7 +229,7 @@ newZoneChecks(AuthSrv& server) {
}
TEST_F(AuthCommandTest, loadZone) {
configureZones(server);
configureZones(server_);
ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
"/test1-new.zone.in "
......@@ -174,118 +238,118 @@ TEST_F(AuthCommandTest, loadZone) {
"/test2-new.zone.in "
TEST_DATA_BUILDDIR "/test2.zone.copied"));
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(0);
newZoneChecks(server);
newZoneChecks(server_);
}
TEST_F(AuthCommandTest, loadBrokenZone) {
configureZones(server);
configureZones(server_);
ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
"/test1-broken.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(1);
zoneChecks(server); // zone shouldn't be replaced
zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest, loadUnreadableZone) {
configureZones(server);
configureZones(server_);
// install the zone file as unreadable
ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
"/test1.zone.in "
TEST_DATA_BUILDDIR "/test1.zone.copied"));
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(1);
zoneChecks(server); // zone shouldn't be replaced
zoneChecks(server_); // zone shouldn't be replaced
}
TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
// try to execute load command without configuring the zone beforehand.
// it should fail.
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\"}"));
checkAnswer(1);
}
TEST_F(AuthCommandTest, loadSqlite3DataSrc) {
// For sqlite3 data source we don't have to do anything (the data source
// (re)loads itself automatically)
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": \"sqlite3\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": \"sqlite3\"}"));
checkAnswer(0);
}
TEST_F(AuthCommandTest, loadZoneInvalidParams) {
configureZones(server);
configureZones(server_);
// null arg
result = execAuthServerCommand(server, "loadzone", ElementPtr());
result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
checkAnswer(1);
// zone class is bogus
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": \"no_such_class\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": \"no_such_class\"}"));
checkAnswer(1);
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": 1}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": 1}"));
checkAnswer(1);
// unsupported zone class
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": \"CH\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"class\": \"CH\"}"));
checkAnswer(1);
// unsupported data source class
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": \"not supported\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": \"not supported\"}"));
checkAnswer(1);
// data source is bogus
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": 0}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"test1.example\","
" \"datasrc\": 0}"));
checkAnswer(1);
// origin is missing
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON("{}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{}"));
checkAnswer(1);
// zone doesn't exist in the data source
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON("{\"origin\": \"xx\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": \"xx\"}"));
checkAnswer(1);
// origin is bogus
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON(
"{\"origin\": \"...\"}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON(
"{\"origin\": \"...\"}"));
checkAnswer(1);
result = execAuthServerCommand(server, "loadzone",
Element::fromJSON("{\"origin\": 10}"));
result_ = execAuthServerCommand(server_, "loadzone",
Element::fromJSON("{\"origin\": 10}"));
checkAnswer(1);
}
}
......@@ -193,9 +193,13 @@ class BoB:
self.nocache = nocache
self.component_config = {}
# Some time in future, it may happen that a single component has
# multple processes. If so happens, name "components" may be
# inapropriate. But as the code isn't probably completely ready
# for it, we leave it at components for now.
# multple processes (like a pipeline-like component). If so happens,
# name "components" may be inapropriate. But as the code isn't probably
# completely ready for it, we leave it at components for now. We also
# want to support multiple instances of a single component. If it turns
# out that we'll have a single component with multiple same processes
# or if we start multiple components with the same configuration (we do
# this now, but it might change) is an open question.
self.components = {}
# Simply list of components that died and need to wait for a
# restart. Components manage their own restart schedule now
......@@ -649,14 +653,17 @@ class BoB:
self.__started = True
return None
def stop_process(self, process, recipient):
def stop_process(self, process, recipient, pid):
"""
Stop the given process, friendly-like. The process is the name it has
(in logs, etc), the recipient is the address on msgq.
(in logs, etc), the recipient is the address on msgq. The pid is the
pid of the process (if we have multiple processes of the same name,
it might want to choose if it is for this one).
"""
logger.info(BIND10_STOP_PROCESS, process)
self.cc_session.group_sendmsg({'command': ['shutdown']}, recipient,
recipient)
self.cc_session.group_sendmsg(isc.config.ccsession.
create_command('shutdown', {'pid': pid}),
recipient, recipient)
def component_shutdown(self, exitcode=0):
"""
......
......@@ -460,6 +460,22 @@ class TestBoB(unittest.TestCase):
# The drop_socket is not tested here, but in TestCacheCommands.
# It needs the cache mocks to be in place and they are there.
def test_stop_process(self):
"""
Test checking the stop_process method sends the right message over
the message bus.
"""
class DummySession():
def group_sendmsg(self, msg, group, instance="*"):
(self.msg, self.group, self.instance) = (msg, group, instance)
bob = BoB()
bob.cc_session = DummySession()
bob.stop_process('process', 'address', 42)
self.assertEqual('address', bob.cc_session.group)
self.assertEqual('address', bob.cc_session.instance)
self.assertEqual({'command': ['shutdown', {'pid': 42}]},
bob.cc_session.msg)
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
......@@ -598,7 +614,7 @@ class MockBob(BoB):
procinfo.pid = 14
return procinfo
def stop_process(self, process, recipient):
def stop_process(self, process, recipient, pid):
procmap = { 'b10-auth': self.stop_auth,
'b10-resolver': self.stop_resolver,
'b10-xfrout': self.stop_xfrout,
......
......@@ -14,18 +14,10 @@
#include <config.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/select.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <errno.h>
#include <string>
#include <iostream>
#include <boost/foreach.hpp>
#include <resolver/spec_config.h>
#include <resolver/resolver.h>
#include "resolver_log.h"
#include "common.h"
#include <asiodns/asiodns.h>
#include <asiolink/asiolink.h>
......@@ -47,16 +39,26 @@
#include <auth/common.h>
#include <resolver/spec_config.h>
#include <resolver/resolver.h>
#include <cache/resolver_cache.h>
#include <nsas/nameserver_address_store.h>