command_unittest.cc 20.3 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
#include <config.h>

17
18
#include "datasrc_util.h"

19
20
21
#include <auth/auth_srv.h>
#include <auth/auth_config.h>
#include <auth/command.h>
22
23
24
25

#include <dns/name.h>
#include <dns/rrclass.h>
#include <dns/rrtype.h>
26
#include <dns/rrttl.h>
27
28
29
30
31
32
33
34
35
36
37

#include <cc/data.h>

#include <config/ccsession.h>

#include <datasrc/memory_datasrc.h>

#include <asiolink/asiolink.h>

#include <testutils/mockups.h>

38
39
40
41
42
43
44
45
46
47
48
49
#include <cassert>
#include <cstdlib>
#include <string>
#include <stdexcept>

#include <boost/bind.hpp>

#include <gtest/gtest.h>

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

50
51
52
53
54
using namespace std;
using namespace isc::dns;
using namespace isc::data;
using namespace isc::datasrc;
using namespace isc::config;
55
using namespace isc::testutils;
56
using namespace isc::auth::unittest;
57
58

namespace {
59

60
class AuthCommandTest : public ::testing::Test {
61
protected:
62
    AuthCommandTest() :
63
64
        server_(false, xfrout_),
        rcode_(-1),
65
        expect_rcode_(0),
66
        itimer_(server_.getIOService())
67
    {
68
        server_.setStatisticsSession(&statistics_session_);
69
70
    }
    void checkAnswer(const int expected_code) {
71
        parseAnswer(rcode_, result_);
72
        EXPECT_EQ(expected_code, rcode_) << result_->str();
73
    }
74
75
76
77
    MockSession statistics_session_;
    MockXfroutClient xfrout_;
    AuthSrv server_;
    ConstElementPtr result_;
78
    // The shutdown command parameter
79
    ConstElementPtr param_;
80
    int rcode_, expect_rcode_;
81
    isc::asiolink::IntervalTimer itimer_;
82
83
public:
    void stopServer();          // need to be public for boost::bind
84
    void dontStopServer();          // need to be public for boost::bind
85
86
};

87
TEST_F(AuthCommandTest, unknownCommand) {
88
89
90
91
    result_ = execAuthServerCommand(server_, "no_such_command",
                                    ConstElementPtr());
    parseAnswer(rcode_, result_);
    EXPECT_EQ(1, rcode_);
92
93
}

94
TEST_F(AuthCommandTest, DISABLED_unexpectedException) {
95
96
97
    // execAuthServerCommand() won't catch standard exceptions.
    // Skip this test for now: ModuleCCSession doesn't seem to validate
    // commands.
98
    EXPECT_THROW(execAuthServerCommand(server_, "_throw_exception",
99
100
101
102
                                       ConstElementPtr()),
                 runtime_error);
}

103
TEST_F(AuthCommandTest, sendStatistics) {
104
    result_ = execAuthServerCommand(server_, "sendstats", ConstElementPtr());
105
106
    // Just check some message has been sent.  Detailed tests specific to
    // statistics are done in its own tests.
107
    EXPECT_EQ("Stats", statistics_session_.getMessageDest());
108
109
110
111
    checkAnswer(0);
}

void
112
AuthCommandTest::stopServer() {
113
114
115
    result_ = execAuthServerCommand(server_, "shutdown", param_);
    parseAnswer(rcode_, result_);
    assert(rcode_ == 0); // make sure the test stops when something is wrong
116
117
}

118
TEST_F(AuthCommandTest, shutdown) {
119
120
    // Param defaults to empty/null pointer on creation
    itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
121
122
    server_.getIOService().run();
    EXPECT_EQ(0, rcode_);
123
124
125
126
}

TEST_F(AuthCommandTest, shutdownCorrectPID) {
    // Put the pid parameter there
JINMEI Tatuya's avatar
JINMEI Tatuya committed
127
    const pid_t pid(getpid());
128
129
    ElementPtr param(new isc::data::MapElement());
    param->set("pid", ConstElementPtr(new isc::data::IntElement(pid)));
130
131
132
133
    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);
134
135
    server_.getIOService().run();
    EXPECT_EQ(0, rcode_);
136
137
}

138
139
140
141
// This is like stopServer, but the server should not stop after the
// command, it should be running
void
AuthCommandTest::dontStopServer() {
142
143
    result_ = execAuthServerCommand(server_, "shutdown", param_);
    parseAnswer(rcode_, result_);
144
    EXPECT_EQ(expect_rcode_, rcode_);
145
    rcode_ = -1;
146
147
148
    // 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.
149
    param_ = ConstElementPtr();
150
151
152
153
    itimer_.cancel();
    itimer_.setup(boost::bind(&AuthCommandTest::stopServer, this), 1);
}

154
155
156
157
158
159
160
161
162
163
164
165
166
167
// 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_);
}

168
169
170
TEST_F(AuthCommandTest, shutdownIncorrectPID) {
    // The PID = 0 should be taken by init, so we are not init and the
    // PID should be different
171
    param_ = Element::fromJSON("{\"pid\": 0}");
172
    itimer_.setup(boost::bind(&AuthCommandTest::dontStopServer, this), 1);
173
174
    server_.getIOService().run();
    EXPECT_EQ(0, rcode_);
175
176
177
178
179
180
181
}

// A helper function commonly used for the "loadzone" command tests.
// It configures the server with a memory data source containing two
// zones, and checks the zones are correctly loaded.
void
zoneChecks(AuthSrv& server) {
182
183
    EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
184
              findZone(Name("ns.test1.example")).zone_finder->
185
              find(Name("ns.test1.example"), RRType::A())->code);
186
    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
187
              findZone(Name("ns.test1.example")).zone_finder->
188
              find(Name("ns.test1.example"), RRType::AAAA())->code);
189
    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
190
              findZone(Name("ns.test2.example")).zone_finder->
191
              find(Name("ns.test2.example"), RRType::A())->code);
192
    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
193
              findZone(Name("ns.test2.example")).zone_finder->
194
              find(Name("ns.test2.example"), RRType::AAAA())->code);
195
196
197
198
}

void
configureZones(AuthSrv& server) {
199
    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test1.zone.in "
200
                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
201
    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR "/test2.zone.in "
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
                        TEST_DATA_BUILDDIR "/test2.zone.copied"));
    configureAuthServer(server, Element::fromJSON(
                            "{\"datasources\": "
                            " [{\"type\": \"memory\","
                            "   \"zones\": "
                            "[{\"origin\": \"test1.example\","
                            "  \"file\": \""
                               TEST_DATA_BUILDDIR "/test1.zone.copied\"},"
                            " {\"origin\": \"test2.example\","
                            "  \"file\": \""
                               TEST_DATA_BUILDDIR "/test2.zone.copied\"}"
                            "]}]}"));
    zoneChecks(server);
}

void
newZoneChecks(AuthSrv& server) {
219
220
    EXPECT_TRUE(server.getInMemoryClient(RRClass::IN()));
    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
221
              findZone(Name("ns.test1.example")).zone_finder->
222
              find(Name("ns.test1.example"), RRType::A())->code);
223
    // now test1.example should have ns/AAAA
224
    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
225
              findZone(Name("ns.test1.example")).zone_finder->
226
              find(Name("ns.test1.example"), RRType::AAAA())->code);
227
228

    // test2.example shouldn't change
229
    EXPECT_EQ(ZoneFinder::SUCCESS, server.getInMemoryClient(RRClass::IN())->
230
              findZone(Name("ns.test2.example")).zone_finder->
231
              find(Name("ns.test2.example"), RRType::A())->code);
232
    EXPECT_EQ(ZoneFinder::NXRRSET, server.getInMemoryClient(RRClass::IN())->
233
              findZone(Name("ns.test2.example")).zone_finder->
234
              find(Name("ns.test2.example"), RRType::AAAA())->code);
235
236
}

237
238
239
240
241
242
243
244
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
       DISABLED_loadZone
#else
       loadZone
#endif
    )
{
245
    configureZones(server_);
246

247
    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
248
249
                        "/test1-new.zone.in "
                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
250
    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
251
252
253
                        "/test2-new.zone.in "
                        TEST_DATA_BUILDDIR "/test2.zone.copied"));

254
255
256
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\"}"));
257
    checkAnswer(0);
Jelte Jansen's avatar
Jelte Jansen committed
258
    newZoneChecks(server_);
259
260
}

261
262
263
264
265
266
267
268
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
       DISABLED_loadZoneSQLite3
#else
       loadZoneSQLite3
#endif
    )
{
269
270
    const char* const SPEC_FILE = AUTH_OBJ_DIR "/auth.spec";

271
272
    // Prepare the database first
    const string test_db = TEST_DATA_BUILDDIR "/auth_test.sqlite3.copied";
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
273
    const string bad_db = TEST_DATA_BUILDDIR "/does-not-exist.sqlite3";
274
275
276
277
278
279
280
281
282
    stringstream ss("example.org. 3600 IN SOA . . 0 0 0 0 0\n");
    createSQLite3DB(RRClass::IN(), Name("example.org"), test_db.c_str(), ss);

    // Then store a config of the zone to the auth server
    // This omits many config options of the auth server, but these are
    // not read now.
    isc::testutils::MockSession session;
    // The session should not take care of anything or start anything, we
    // need it only to hold the config we're going to put into it.
283
    ModuleCCSession module_session(SPEC_FILE, session, NULL, NULL, false,
284
285
                                  false);
    // This describes the data source in the configuration
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
286
287
288
289
290
291
292
293
294
295
296
297
    const ElementPtr
        map(Element::fromJSON("{\"datasources\": ["
                              "  {"
                              "    \"type\": \"memory\","
                              "    \"zones\": ["
                              "      {"
                              "        \"origin\": \"example.org\","
                              "        \"file\": \"" + test_db + "\","
                              "        \"filetype\": \"sqlite3\""
                              "      }"
                              "    ]"
                              "  }"
298
299
300
                              "],"
                              " \"database_file\": \"" + test_db + "\""
                              "}"));
301
302
    module_session.setLocalConfig(map);
    server_.setConfigSession(&module_session);
303

304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
    server_.updateConfig(map);

    // Check that the A record at www.example.org does not exist
    ASSERT_TRUE(server_.hasInMemoryClient());
    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
              findZone(Name("example.org")).zone_finder->
              find(Name("www.example.org"), RRType::A())->code);

    // Add the record to the underlying sqlite database, by loading
    // it as a separate datasource, and updating it
    ConstElementPtr sql_cfg = Element::fromJSON("{ \"type\": \"sqlite3\","
                                                "\"database_file\": \""
                                                + test_db + "\"}");
    DataSourceClientContainer sql_ds("sqlite3", sql_cfg);
    ZoneUpdaterPtr sql_updater =
        sql_ds.getInstance().getUpdater(Name("example.org"), false);
    RRsetPtr rrset(new RRset(Name("www.example.org."), RRClass::IN(),
                             RRType::A(), RRTTL(60)));
    rrset->addRdata(rdata::createRdata(rrset->getType(),
                                       rrset->getClass(),
                                       "192.0.2.1"));
    sql_updater->addRRset(*rrset);
    sql_updater->commit();

    // This new record is in the database now, but should not be in the
    // memory-datasource yet, so check again
    EXPECT_EQ(ZoneFinder::NXDOMAIN, server_.getInMemoryClient(RRClass::IN())->
              findZone(Name("example.org")).zone_finder->
              find(Name("www.example.org"), RRType::A())->code);
333
334
335

    // Now send the command to reload it
    result_ = execAuthServerCommand(server_, "loadzone",
336
337
                                    Element::fromJSON(
                                        "{\"origin\": \"example.org\"}"));
338
339
    checkAnswer(0);

340
    // And now it should be present too.
341
    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
342
              findZone(Name("example.org")).zone_finder->
343
              find(Name("www.example.org"), RRType::A())->code);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
344

345
    // Some error cases. First, the zone has no configuration. (note .com here)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
346
347
348
    result_ = execAuthServerCommand(server_, "loadzone",
        Element::fromJSON("{\"origin\": \"example.com\"}"));
    checkAnswer(1);
349
    // The previous zone is not hurt in any way
350
    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
351
352
353
              findZone(Name("example.org")).zone_finder->
              find(Name("example.org"), RRType::SOA())->code);

354
    module_session.setLocalConfig(Element::fromJSON("{\"datasources\": []}"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
355
    result_ = execAuthServerCommand(server_, "loadzone",
356
357
                                    Element::fromJSON(
                                        "{\"origin\": \"example.org\"}"));
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
358
359
360
    checkAnswer(1);

    // The previous zone is not hurt in any way
361
    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
              findZone(Name("example.org")).zone_finder->
              find(Name("example.org"), RRType::SOA())->code);
    // Configure an unreadable zone. Should fail, but leave the original zone
    // data there
    const ElementPtr
        mapBad(Element::fromJSON("{\"datasources\": ["
                                 "  {"
                                 "    \"type\": \"memory\","
                                 "    \"zones\": ["
                                 "      {"
                                 "        \"origin\": \"example.org\","
                                 "        \"file\": \"" + bad_db + "\","
                                 "        \"filetype\": \"sqlite3\""
                                 "      }"
                                 "    ]"
                                 "  }"
                                 "]}"));
379
    module_session.setLocalConfig(mapBad);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
380
381
382
383
    result_ = execAuthServerCommand(server_, "loadzone",
        Element::fromJSON("{\"origin\": \"example.com\"}"));
    checkAnswer(1);
    // The previous zone is not hurt in any way
384
    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
385
386
387
388
389
390
391
392
393
394
395
              findZone(Name("example.org")).zone_finder->
              find(Name("example.org"), RRType::SOA())->code);

    // Broken configuration (not valid against the spec)
    const ElementPtr
        broken(Element::fromJSON("{\"datasources\": ["
                                 "  {"
                                 "    \"type\": \"memory\","
                                 "    \"zones\": [[]]"
                                 "  }"
                                 "]}"));
396
    module_session.setLocalConfig(broken);
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
397
398
    checkAnswer(1);
    // The previous zone is not hurt in any way
Jelte Jansen's avatar
Jelte Jansen committed
399
    EXPECT_EQ(ZoneFinder::SUCCESS, server_.getInMemoryClient(RRClass::IN())->
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
400
401
              findZone(Name("example.org")).zone_finder->
              find(Name("example.org"), RRType::SOA())->code);
402
403
}

404
405
406
407
408
409
410
411
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
       DISABLED_loadBrokenZone
#else
       loadBrokenZone
#endif
    )
{
412
    configureZones(server_);
413

414
    ASSERT_EQ(0, system(INSTALL_PROG " " TEST_DATA_DIR
415
416
                        "/test1-broken.zone.in "
                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
417
418
419
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\"}"));
420
    checkAnswer(1);
421
    zoneChecks(server_);     // zone shouldn't be replaced
422
423
}

424
425
426
427
428
429
430
431
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
       DISABLED_loadUnreadableZone
#else
       loadUnreadableZone
#endif
    )
{
432
    configureZones(server_);
433
434

    // install the zone file as unreadable
435
    ASSERT_EQ(0, system(INSTALL_PROG " -m 000 " TEST_DATA_DIR
436
437
                        "/test1.zone.in "
                        TEST_DATA_BUILDDIR "/test1.zone.copied"));
438
439
440
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\"}"));
441
    checkAnswer(1);
442
    zoneChecks(server_);     // zone shouldn't be replaced
443
444
}

445
TEST_F(AuthCommandTest, loadZoneWithoutDataSrc) {
446
447
    // try to execute load command without configuring the zone beforehand.
    // it should fail.
448
449
450
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\"}"));
451
452
453
    checkAnswer(1);
}

454
TEST_F(AuthCommandTest, loadSqlite3DataSrc) {
455
456
    // For sqlite3 data source we don't have to do anything (the data source
    // (re)loads itself automatically)
457
458
459
460
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"datasrc\": \"sqlite3\"}"));
461
462
463
    checkAnswer(0);
}

464
465
466
467
468
469
470
471
TEST_F(AuthCommandTest,
#ifdef USE_STATIC_LINK
       DISABLED_loadZoneInvalidParams
#else
       loadZoneInvalidParams
#endif
    )
{
472
    configureZones(server_);
473
474

    // null arg
475
    result_ = execAuthServerCommand(server_, "loadzone", ElementPtr());
476
477
478
    checkAnswer(1);

    // zone class is bogus
479
480
481
482
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"class\": \"no_such_class\"}"));
483
484
    checkAnswer(1);

485
486
487
488
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"class\": 1}"));
489
490
491
    checkAnswer(1);

    // unsupported zone class
492
493
494
495
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"class\": \"CH\"}"));
496
497
498
    checkAnswer(1);

    // unsupported data source class
499
500
501
502
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"datasrc\": \"not supported\"}"));
503
504
505
    checkAnswer(1);

    // data source is bogus
506
507
508
509
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"test1.example\","
                                        " \"datasrc\": 0}"));
510
511
512
    checkAnswer(1);

    // origin is missing
513
514
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON("{}"));
515
516
517
    checkAnswer(1);

    // zone doesn't exist in the data source
518
519
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON("{\"origin\": \"xx\"}"));
520
521
522
    checkAnswer(1);

    // origin is bogus
523
524
525
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON(
                                        "{\"origin\": \"...\"}"));
526
527
    checkAnswer(1);

528
529
    result_ = execAuthServerCommand(server_, "loadzone",
                                    Element::fromJSON("{\"origin\": 10}"));
530
531
532
    checkAnswer(1);
}
}