bind10_test.py.in 54.8 KB
Newer Older
Shane Kerr's avatar
Shane Kerr committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Copyright (C) 2011  Internet Systems Consortium.
#
# Permission to use, copy, modify, and 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 INTERNET SYSTEMS CONSORTIUM
# DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL
# INTERNET SYSTEMS CONSORTIUM 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.

16
17
18
# Most of the time, we omit the "bind10_src" for brevity. Sometimes,
# we want to be explicit about what we do, like when hijacking a library
# call used by the bind10_src.
19
from bind10_src import ProcessInfo, BoB, parse_args, dump_pid, unlink_pid_file, _BASETIME
20
import bind10_src
Shane Kerr's avatar
Shane Kerr committed
21

22
23
24
# XXX: environment tests are currently disabled, due to the preprocessor
#      setup that we have now complicating the environment

Shane Kerr's avatar
Shane Kerr committed
25
26
27
import unittest
import sys
import os
28
import copy
Shane Kerr's avatar
Shane Kerr committed
29
import signal
Evan Hunt's avatar
Evan Hunt committed
30
import socket
31
from isc.net.addr import IPAddr
32
33
import time
import isc
34
import isc.log
35
import isc.bind10.socket_cache
36
import errno
Shane Kerr's avatar
Shane Kerr committed
37

38
from isc.testutils.parse_args import TestOptParser, OptsError
39
from isc.testutils.ccsession_mock import MockModuleCCSession
Shane Kerr's avatar
Shane Kerr committed
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60

class TestProcessInfo(unittest.TestCase):
    def setUp(self):
        # redirect stdout to a pipe so we can check that our
        # process spawning is doing the right thing with stdout
        self.old_stdout = os.dup(sys.stdout.fileno())
        self.pipes = os.pipe()
        os.dup2(self.pipes[1], sys.stdout.fileno())
        os.close(self.pipes[1])
        # note that we use dup2() to restore the original stdout
        # to the main program ASAP in each test... this prevents
        # hangs reading from the child process (as the pipe is only
        # open in the child), and also insures nice pretty output

    def tearDown(self):
        # clean up our stdout munging
        os.dup2(self.old_stdout, sys.stdout.fileno())
        os.close(self.pipes[0])

    def test_init(self):
        pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
61
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
62
63
64
        os.dup2(self.old_stdout, sys.stdout.fileno())
        self.assertEqual(pi.name, 'Test Process')
        self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
65
66
#        self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
#                                   'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
Shane Kerr's avatar
Shane Kerr committed
67
68
69
70
71
        self.assertEqual(pi.dev_null_stdout, False)
        self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
        self.assertNotEqual(pi.process, None)
        self.assertTrue(type(pi.pid) is int)

72
73
74
75
76
77
#    def test_setting_env(self):
#        pi = ProcessInfo('Test Process', [ '/bin/true' ], env={'FOO': 'BAR'})
#        os.dup2(self.old_stdout, sys.stdout.fileno())
#        self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
#                                   'PYTHON_EXEC': os.environ['PYTHON_EXEC'],
#                                   'FOO': 'BAR' })
Shane Kerr's avatar
Shane Kerr committed
78
79

    def test_setting_null_stdout(self):
80
        pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
Shane Kerr's avatar
Shane Kerr committed
81
                         dev_null_stdout=True)
82
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
83
84
85
86
87
88
        os.dup2(self.old_stdout, sys.stdout.fileno())
        self.assertEqual(pi.dev_null_stdout, True)
        self.assertEqual(os.read(self.pipes[0], 100), b"")

    def test_respawn(self):
        pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ])
89
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
90
91
92
93
94
95
96
97
98
        # wait for old process to work...
        self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
        # respawn it
        old_pid = pi.pid
        pi.respawn()
        os.dup2(self.old_stdout, sys.stdout.fileno())
        # make sure the new one started properly
        self.assertEqual(pi.name, 'Test Process')
        self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
99
100
#        self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
#                                   'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
Shane Kerr's avatar
Shane Kerr committed
101
102
103
104
105
106
        self.assertEqual(pi.dev_null_stdout, False)
        self.assertEqual(os.read(self.pipes[0], 100), b"foo\n")
        self.assertNotEqual(pi.process, None)
        self.assertTrue(type(pi.pid) is int)
        self.assertNotEqual(pi.pid, old_pid)

107
108
109
110
111
112
113
114
115
116
117
118
119
120
class TestCacheCommands(unittest.TestCase):
    """
    Test methods of boss related to the socket cache and socket handling.
    """
    def setUp(self):
        """
        Prepare the boss for some tests.

        Also prepare some variables we need.
        """
        self.__boss = BoB()
        # Fake the cache here so we can pretend it is us and hijack the
        # calls to its methods.
        self.__boss._socket_cache = self
121
        self.__boss._socket_path = '/socket/path'
122
        self.__raise_exception = None
123
124
        self.__socket_args = {
            "port": 53,
125
            "address": "::",
126
127
128
129
            "protocol": "UDP",
            "share_mode": "ANY",
            "share_name": "app"
        }
130
131
        # What was and wasn't called.
        self.__drop_app_called = None
132
133
        self.__get_socket_called = None
        self.__send_fd_called = None
134
135
        self.__get_token_called = None
        self.__drop_socket_called = None
136
137
138
139
140
141
142
        bind10_src.libutil_io_python.send_fd = self.__send_fd

    def __send_fd(self, to, socket):
        """
        A function to hook the send_fd in the bind10_src.
        """
        self.__send_fd_called = (to, socket)
143
144
145
146
147
148

    class FalseSocket:
        """
        A socket where we can fake methods we need instead of having a real
        socket.
        """
149
        def __init__(self):
150
            self.send = b""
151
152
153
154
155
156
        def fileno(self):
            """
            The file number. Used for identifying the remote application.
            """
            return 42

157
158
159
160
161
162
        def sendall(self, data):
            """
            Adds data to the self.send.
            """
            self.send += data

163
164
165
166
    def drop_application(self, application):
        """
        Part of pretending to be the cache. Logs the parameter to
        self.__drop_app_called.
167
168
169

        In the case self.__raise_exception is set, the exception there
        is raised instead.
170
        """
171
172
        if self.__raise_exception is not None:
            raise self.__raise_exception
173
174
175
176
177
178
179
180
181
        self.__drop_app_called = application

    def test_consumer_dead(self):
        """
        Test that it calls the drop_application method of the cache.
        """
        self.__boss.socket_consumer_dead(self.FalseSocket())
        self.assertEqual(42, self.__drop_app_called)

182
183
184
185
186
187
188
189
190
191
    def test_consumer_dead_invalid(self):
        """
        Test that it doesn't crash in case the application is not known to
        the cache, the boss doesn't crash, as this actually can happen in
        practice.
        """
        self.__raise_exception = ValueError("This application is unknown")
        # This doesn't crash
        self.__boss.socket_consumer_dead(self.FalseSocket())

192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
    def get_socket(self, token, application):
        """
        Part of pretending to be the cache. If there's anything in
        __raise_exception, it is raised. Otherwise, the call is logged
        into __get_socket_called and a number is returned.
        """
        if self.__raise_exception is not None:
            raise self.__raise_exception
        self.__get_socket_called = (token, application)
        return 13

    def test_request_handler(self):
        """
        Test that a request for socket is forwarded and the socket is sent
        back, if it returns a socket.
        """
        socket = self.FalseSocket()
        # An exception from the cache
        self.__raise_exception = ValueError("Test value error")
211
        self.__boss.socket_request_handler(b"token", socket)
212
213
        # It was called, but it threw, so it is not noted here
        self.assertIsNone(self.__get_socket_called)
214
        self.assertEqual(b"0\n", socket.send)
215
216
217
218
        # It should not have sent any socket.
        self.assertIsNone(self.__send_fd_called)
        # Now prepare a valid scenario
        self.__raise_exception = None
219
220
221
        socket.send = b""
        self.__boss.socket_request_handler(b"token", socket)
        self.assertEqual(b"1\n", socket.send)
222
223
224
        self.assertEqual((42, 13), self.__send_fd_called)
        self.assertEqual(("token", 42), self.__get_socket_called)

225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
    def get_token(self, protocol, address, port, share_mode, share_name):
        """
        Part of pretending to be the cache. If there's anything in
        __raise_exception, it is raised. Otherwise, the parameters are
        logged into __get_token_called and a token is returned.
        """
        if self.__raise_exception is not None:
            raise self.__raise_exception
        self.__get_token_called = (protocol, address, port, share_mode,
                                   share_name)
        return "token"

    def test_get_socket_ok(self):
        """
        Test the successful scenario of getting a socket.
        """
241
        result = self.__boss._get_socket(self.__socket_args)
242
243
244
245
246
247
248
        [code, answer] = result['result']
        self.assertEqual(0, code)
        self.assertEqual({
            'token': 'token',
            'path': '/socket/path'
        }, answer)
        addr = self.__get_token_called[1]
249
        self.assertTrue(isinstance(addr, IPAddr))
250
        self.assertEqual("::", str(addr))
251
252
253
        self.assertEqual(("UDP", addr, 53, "ANY", "app"),
                         self.__get_token_called)

254
255
256
257
258
259
    def test_get_socket_error(self):
        """
        Test that bad inputs are handled correctly, etc.
        """
        def check_code(code, args):
            """
260
            Pass the args there and check if it returns success or not.
261
262
263
264
265
266

            The rest is not tested, as it is already checked in the
            test_get_socket_ok.
            """
            [rcode, ranswer] = self.__boss._get_socket(args)['result']
            self.assertEqual(code, rcode)
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
267
            if code != 0:
268
                # This should be an error message. The exact formatting
269
                # is unknown, but we check it is string at least
270
                self.assertTrue(isinstance(ranswer, str))
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
271

272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
        def mod_args(name, value):
            """
            Override a parameter in the args.
            """
            result = dict(self.__socket_args)
            result[name] = value
            return result

        # Port too large
        check_code(1, mod_args('port', 65536))
        # Not numeric address
        check_code(1, mod_args('address', 'example.org.'))
        # Some bad values of enum-like params
        check_code(1, mod_args('protocol', 'BAD PROTO'))
        check_code(1, mod_args('share_mode', 'BAD SHARE'))
        # Check missing parameters
        for param in self.__socket_args.keys():
            args = dict(self.__socket_args)
            del args[param]
            check_code(1, args)
        # These are OK values for the enum-like parameters
        # The ones from test_get_socket_ok are not tested here
        check_code(0, mod_args('protocol', 'TCP'))
        check_code(0, mod_args('share_mode', 'SAMEAPP'))
        check_code(0, mod_args('share_mode', 'NO'))
        # If an exception is raised from within the cache, it is converted
        # to an error, not propagated
        self.__raise_exception = Exception("Test exception")
        check_code(1, self.__socket_args)
301
302
303
304
305
306
307
        # The special "expected" exceptions
        self.__raise_exception = \
            isc.bind10.socket_cache.ShareError("Not shared")
        check_code(3, self.__socket_args)
        self.__raise_exception = \
            isc.bind10.socket_cache.SocketError("Not shared", 13)
        check_code(2, self.__socket_args)
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
333
334
335
336
337
338
339
340
    def drop_socket(self, token):
        """
        Part of pretending to be the cache. If there's anything in
        __raise_exception, it is raised. Otherwise, the parameter is stored
        in __drop_socket_called.
        """
        if self.__raise_exception is not None:
            raise self.__raise_exception
        self.__drop_socket_called = token

    def test_drop_socket(self):
        """
        Check the drop_socket command. It should directly call the method
        on the cache. Exceptions should be translated to error messages.
        """
        # This should be OK and just propagated to the call.
        self.assertEqual({"result": [0]},
                         self.__boss.command_handler("drop_socket",
                                                     {"token": "token"}))
        self.assertEqual("token", self.__drop_socket_called)
        self.__drop_socket_called = None
        # Missing parameter
        self.assertEqual({"result": [1, "Missing token parameter"]},
                         self.__boss.command_handler("drop_socket", {}))
        self.assertIsNone(self.__drop_socket_called)
        # An exception is raised from within the cache
        self.__raise_exception = ValueError("Test error")
        self.assertEqual({"result": [1, "Test error"]},
                         self.__boss.command_handler("drop_socket",
                         {"token": "token"}))


Shane Kerr's avatar
Shane Kerr committed
341
342
343
344
class TestBoB(unittest.TestCase):
    def test_init(self):
        bob = BoB()
        self.assertEqual(bob.verbose, False)
Jelte Jansen's avatar
Jelte Jansen committed
345
        self.assertEqual(bob.msgq_socket_file, None)
346
347
        self.assertEqual(bob.cc_session, None)
        self.assertEqual(bob.ccs, None)
348
        self.assertEqual(bob.components, {})
Shane Kerr's avatar
Shane Kerr committed
349
        self.assertEqual(bob.runnable, False)
350
351
352
        self.assertEqual(bob.uid, None)
        self.assertEqual(bob.username, None)
        self.assertEqual(bob.nocache, False)
353
354
        self.assertIsNone(bob._socket_cache)

355
    def test_set_creator(self):
356
        """
357
        Test the call to set_creator. First time, the cache is created
358
359
360
361
362
363
        with the passed creator. The next time, it throws an exception.
        """
        bob = BoB()
        # The cache doesn't use it at start, so just create an empty class
        class Creator: pass
        creator = Creator()
364
        bob.set_creator(creator)
365
366
        self.assertTrue(isinstance(bob._socket_cache,
                        isc.bind10.socket_cache.Cache))
367
        self.assertEqual(creator, bob._socket_cache._creator)
368
        self.assertRaises(ValueError, bob.set_creator, creator)
369

Jelte Jansen's avatar
Jelte Jansen committed
370
371
    def test_init_alternate_socket(self):
        bob = BoB("alt_socket_file")
Shane Kerr's avatar
Shane Kerr committed
372
        self.assertEqual(bob.verbose, False)
Jelte Jansen's avatar
Jelte Jansen committed
373
        self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
374
375
        self.assertEqual(bob.cc_session, None)
        self.assertEqual(bob.ccs, None)
376
        self.assertEqual(bob.components, {})
Evan Hunt's avatar
Evan Hunt committed
377
        self.assertEqual(bob.runnable, False)
378
379
380
381
        self.assertEqual(bob.uid, None)
        self.assertEqual(bob.username, None)
        self.assertEqual(bob.nocache, False)

382
383
384
385
386
    def test_command_handler(self):
        class DummySession():
            def group_sendmsg(self, msg, group):
                (self.msg, self.group) = (msg, group)
            def group_recvmsg(self, nonblock, seq): pass
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
        class DummyModuleCCSession():
            module_spec = isc.config.module_spec.ModuleSpec({
                    "module_name": "Boss",
                    "statistics": [
                        {
                            "item_name": "boot_time",
                            "item_type": "string",
                            "item_optional": False,
                            "item_default": "1970-01-01T00:00:00Z",
                            "item_title": "Boot time",
                            "item_description": "A date time when bind10 process starts initially",
                            "item_format": "date-time"
                            }
                        ]
                    })
            def get_module_spec(self):
                return self.module_spec
404
        bob = BoB()
Naoki Kambe's avatar
Naoki Kambe committed
405
        bob.verbose = True
406
        bob.cc_session = DummySession()
407
        bob.ccs = DummyModuleCCSession()
Naoki Kambe's avatar
Naoki Kambe committed
408
        # a bad command
409
410
        self.assertEqual(bob.command_handler(-1, None),
                         isc.config.ccsession.create_answer(1, "bad command"))
Naoki Kambe's avatar
Naoki Kambe committed
411
        # "shutdown" command
412
413
414
        self.assertEqual(bob.command_handler("shutdown", None),
                         isc.config.ccsession.create_answer(0))
        self.assertFalse(bob.runnable)
415
416
417
        # "getstats" command
        self.assertEqual(bob.command_handler("getstats", None),
                         isc.config.ccsession.create_answer(0,
418
419
420
                            { "owner": "Boss",
                              "data": {
                                'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME)
421
                            }}))
Naoki Kambe's avatar
Naoki Kambe committed
422
        # "sendstats" command
423
424
425
426
427
        self.assertEqual(bob.command_handler("sendstats", None),
                         isc.config.ccsession.create_answer(0))
        self.assertEqual(bob.cc_session.group, "Stats")
        self.assertEqual(bob.cc_session.msg,
                         isc.config.ccsession.create_command(
428
429
430
                "set", { "owner": "Boss",
                         "data": {
                        "boot_time": time.strftime("%Y-%m-%dT%H:%M:%SZ", _BASETIME)
431
                        }}))
Naoki Kambe's avatar
Naoki Kambe committed
432
433
434
435
436
437
438
439
        # "ping" command
        self.assertEqual(bob.command_handler("ping", None),
                         isc.config.ccsession.create_answer(0, "pong"))
        # "show_processes" command
        self.assertEqual(bob.command_handler("show_processes", None),
                         isc.config.ccsession.create_answer(0,
                                                            bob.get_processes()))
        # an unknown command
440
441
442
        self.assertEqual(bob.command_handler("__UNKNOWN__", None),
                         isc.config.ccsession.create_answer(1, "Unknown command"))

443
444
445
446
447
448
        # Fake the get_token of cache and test the command works
        bob._socket_path = '/socket/path'
        class cache:
            def get_token(self, protocol, addr, port, share_mode, share_name):
                return str(addr) + ':' + str(port)
        bob._socket_cache = cache()
449
450
451
452
453
454
455
456
        args = {
            "port": 53,
            "address": "0.0.0.0",
            "protocol": "UDP",
            "share_mode": "ANY",
            "share_name": "app"
        }
        # at all and this is the easiest way to check.
457
458
        self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
                                         'path': '/socket/path'}]},
459
                         bob.command_handler("get_socket", args))
460
461
        # The drop_socket is not tested here, but in TestCacheCommands.
        # It needs the cache mocks to be in place and they are there.
462

463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
    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)

479
480
481
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
482
#
483
# Testing that external processes start is outside the scope
484
485
486
# of the unit test, by overriding the process start methods we can check
# that the right processes are started depending on the configuration
# options.
487
class MockBob(BoB):
488
489
490
    def __init__(self):
        BoB.__init__(self)

491
        # Set flags as to which of the overridden methods has been run.
492
493
494
495
        self.msgq = False
        self.cfgmgr = False
        self.ccsession = False
        self.auth = False
496
        self.resolver = False
497
498
499
500
        self.xfrout = False
        self.xfrin = False
        self.zonemgr = False
        self.stats = False
Naoki Kambe's avatar
Naoki Kambe committed
501
        self.stats_httpd = False
502
        self.cmdctl = False
503
504
        self.dhcp6 = False
        self.dhcp4 = False
505
        self.c_channel_env = {}
506
        self.components = { }
507
508
        self.creator = False

509
510
511
512
513
514
515
516
517
518
519
        class MockSockCreator(isc.bind10.component.Component):
            def __init__(self, process, boss, kind, address=None, params=None):
                isc.bind10.component.Component.__init__(self, process, boss,
                                                        kind, 'SockCreator')
                self._start_func = boss.start_creator

        specials = isc.bind10.special_component.get_specials()
        specials['sockcreator'] = MockSockCreator
        self._component_configurator = \
            isc.bind10.component.Configurator(self, specials)

520
521
    def start_creator(self):
        self.creator = True
522
523
524
        procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
        procinfo.pid = 1
        return procinfo
525

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
526
    def _read_bind10_config(self):
527
528
529
        # Configuration options are set directly
        pass

530
    def start_msgq(self):
531
        self.msgq = True
532
533
534
        procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
        procinfo.pid = 2
        return procinfo
535
536

    def start_ccsession(self, c_channel_env):
537
        # this is not a process, don't have to do anything with procinfo
538
539
        self.ccsession = True

540
541
542
543
544
545
546
    def start_cfgmgr(self):
        self.cfgmgr = True
        procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
        procinfo.pid = 3
        return procinfo

    def start_auth(self):
547
        self.auth = True
548
549
550
        procinfo = ProcessInfo('b10-auth', ['/bin/false'])
        procinfo.pid = 5
        return procinfo
551

552
    def start_resolver(self):
553
        self.resolver = True
554
555
556
557
558
        procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
        procinfo.pid = 6
        return procinfo

    def start_simple(self, name):
559
        procmap = { 'b10-zonemgr': self.start_zonemgr,
560
561
562
563
                    'b10-stats': self.start_stats,
                    'b10-stats-httpd': self.start_stats_httpd,
                    'b10-cmdctl': self.start_cmdctl,
                    'b10-dhcp6': self.start_dhcp6,
564
565
566
                    'b10-dhcp4': self.start_dhcp4,
                    'b10-xfrin': self.start_xfrin,
                    'b10-xfrout': self.start_xfrout }
567
568
569
        return procmap[name]()

    def start_xfrout(self):
570
        self.xfrout = True
571
572
573
        procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
        procinfo.pid = 7
        return procinfo
574

575
    def start_xfrin(self):
576
        self.xfrin = True
577
578
579
        procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
        procinfo.pid = 8
        return procinfo
580

581
    def start_zonemgr(self):
582
        self.zonemgr = True
583
584
585
        procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
        procinfo.pid = 9
        return procinfo
586

587
    def start_stats(self):
588
        self.stats = True
589
590
591
        procinfo = ProcessInfo('b10-stats', ['/bin/false'])
        procinfo.pid = 10
        return procinfo
592

593
    def start_stats_httpd(self):
Naoki Kambe's avatar
Naoki Kambe committed
594
        self.stats_httpd = True
595
596
597
        procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
        procinfo.pid = 11
        return procinfo
Naoki Kambe's avatar
Naoki Kambe committed
598

599
    def start_cmdctl(self):
600
        self.cmdctl = True
601
602
603
        procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
        procinfo.pid = 12
        return procinfo
604

605
    def start_dhcp6(self):
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
606
        self.dhcp6 = True
607
608
609
        procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
        procinfo.pid = 13
        return procinfo
610

611
    def start_dhcp4(self):
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
612
        self.dhcp4 = True
613
614
615
616
        procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
        procinfo.pid = 14
        return procinfo

617
    def stop_process(self, process, recipient, pid):
618
619
620
621
622
623
624
625
626
        procmap = { 'b10-auth': self.stop_auth,
                    'b10-resolver': self.stop_resolver,
                    'b10-xfrout': self.stop_xfrout,
                    'b10-xfrin': self.stop_xfrin,
                    'b10-zonemgr': self.stop_zonemgr,
                    'b10-stats': self.stop_stats,
                    'b10-stats-httpd': self.stop_stats_httpd,
                    'b10-cmdctl': self.stop_cmdctl }
        procmap[process]()
627

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
628
    # Some functions to pretend we stop processes, use by stop_process
629
    def stop_msgq(self):
630
        if self.msgq:
631
            del self.components[2]
632
633
        self.msgq = False

634
    def stop_cfgmgr(self):
635
        if self.cfgmgr:
636
            del self.components[3]
637
638
        self.cfgmgr = False

639
    def stop_auth(self):
640
        if self.auth:
641
            del self.components[5]
642
643
        self.auth = False

644
    def stop_resolver(self):
645
        if self.resolver:
646
            del self.components[6]
647
648
        self.resolver = False

649
    def stop_xfrout(self):
650
        if self.xfrout:
651
            del self.components[7]
652
653
        self.xfrout = False

654
    def stop_xfrin(self):
655
        if self.xfrin:
656
            del self.components[8]
657
658
        self.xfrin = False

659
    def stop_zonemgr(self):
660
        if self.zonemgr:
661
            del self.components[9]
662
663
        self.zonemgr = False

664
    def stop_stats(self):
665
        if self.stats:
666
            del self.components[10]
667
668
        self.stats = False

Naoki Kambe's avatar
Naoki Kambe committed
669
    def stop_stats_httpd(self):
670
        if self.stats_httpd:
671
            del self.components[11]
Naoki Kambe's avatar
Naoki Kambe committed
672
673
        self.stats_httpd = False

674
    def stop_cmdctl(self):
675
        if self.cmdctl:
676
            del self.components[12]
677
678
679
680
        self.cmdctl = False

class TestStartStopProcessesBob(unittest.TestCase):
    """
681
682
    Check that the start_all_components method starts the right combination
    of components and that the right components are started and stopped
683
684
    according to changes in configuration.
    """
Jelte Jansen's avatar
Jelte Jansen committed
685
686
687
688
    def check_environment_unchanged(self):
        # Check whether the environment has not been changed
        self.assertEqual(original_os_environ, os.environ)

689
690
    def check_started(self, bob, core, auth, resolver):
        """
691
692
        Check that the right sets of services are started. The ones that
        should be running are specified by the core, auth and resolver parameters
693
694
695
696
697
698
        (they are groups of processes, eg. auth means b10-auth, -xfrout, -xfrin
        and -zonemgr).
        """
        self.assertEqual(bob.msgq, core)
        self.assertEqual(bob.cfgmgr, core)
        self.assertEqual(bob.ccsession, core)
699
        self.assertEqual(bob.creator, core)
700
701
702
703
704
705
        self.assertEqual(bob.auth, auth)
        self.assertEqual(bob.resolver, resolver)
        self.assertEqual(bob.xfrout, auth)
        self.assertEqual(bob.xfrin, auth)
        self.assertEqual(bob.zonemgr, auth)
        self.assertEqual(bob.stats, core)
Naoki Kambe's avatar
Naoki Kambe committed
706
        self.assertEqual(bob.stats_httpd, core)
707
        self.assertEqual(bob.cmdctl, core)
Jelte Jansen's avatar
Jelte Jansen committed
708
        self.check_environment_unchanged()
709

710
    def check_preconditions(self, bob):
711
        self.check_started(bob, False, False, False)
712

713
714
715
    def check_started_none(self, bob):
        """
        Check that the situation is according to configuration where no servers
716
        should be started. Some components still need to be running.
717
        """
718
        self.check_started(bob, True, False, False)
Jelte Jansen's avatar
Jelte Jansen committed
719
        self.check_environment_unchanged()
720
721
722
723
724
725

    def check_started_both(self, bob):
        """
        Check the situation is according to configuration where both servers
        (auth and resolver) are enabled.
        """
726
        self.check_started(bob, True, True, True)
Jelte Jansen's avatar
Jelte Jansen committed
727
        self.check_environment_unchanged()
728
729
730

    def check_started_auth(self, bob):
        """
731
        Check the set of components needed to run auth only is started.
732
        """
733
        self.check_started(bob, True, True, False)
Jelte Jansen's avatar
Jelte Jansen committed
734
        self.check_environment_unchanged()
735
736
737

    def check_started_resolver(self, bob):
        """
738
        Check the set of components needed to run resolver only is started.
739
        """
740
        self.check_started(bob, True, False, True)
Jelte Jansen's avatar
Jelte Jansen committed
741
        self.check_environment_unchanged()
742

743
744
745
746
    def check_started_dhcp(self, bob, v4, v6):
        """
        Check if proper combinations of DHCPv4 and DHCpv6 can be started
        """
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
747
748
        self.assertEqual(v4, bob.dhcp4)
        self.assertEqual(v6, bob.dhcp6)
Jelte Jansen's avatar
Jelte Jansen committed
749
        self.check_environment_unchanged()
750

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
751
752
753
754
755
756
757
758
759
760
    def construct_config(self, start_auth, start_resolver):
        # The things that are common, not turned on an off
        config = {}
        config['b10-stats'] = { 'kind': 'dispensable', 'address': 'Stats' }
        config['b10-stats-httpd'] = { 'kind': 'dispensable',
                                      'address': 'StatsHttpd' }
        config['b10-cmdctl'] = { 'kind': 'needed', 'special': 'cmdctl' }
        if start_auth:
            config['b10-auth'] = { 'kind': 'needed', 'special': 'auth' }
            config['b10-xfrout'] = { 'kind': 'dispensable',
761
762
763
                                     'address': 'Xfrout' }
            config['b10-xfrin'] = { 'kind': 'dispensable',
                                    'address': 'Xfrin' }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
764
765
766
767
768
769
            config['b10-zonemgr'] = { 'kind': 'dispensable',
                                      'address': 'Zonemgr' }
        if start_resolver:
            config['b10-resolver'] = { 'kind': 'needed',
                                       'special': 'resolver' }
        return {'components': config}
770

771
772
773
774
    def config_start_init(self, start_auth, start_resolver):
        """
        Test the configuration is loaded at the startup.
        """
775
        bob = MockBob()
776
777
778
779
780
781
782
783
784
785
786
787
        config = self.construct_config(start_auth, start_resolver)
        class CC:
            def get_full_config(self):
                return config
        # Provide the fake CC with data
        bob.ccs = CC()
        # And make sure it's not overwritten
        def start_ccsession():
            bob.ccsession = True
        bob.start_ccsession = lambda _: start_ccsession()
        # We need to return the original _read_bind10_config
        bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
788
        bob.start_all_components()
789
790
        self.check_started(bob, True, start_auth, start_resolver)
        self.check_environment_unchanged()
791

792
793
    def test_start_none(self):
        self.config_start_init(False, False)
794

795
    def test_start_resolver(self):
796
        self.config_start_init(False, True)
797

798
799
    def test_start_auth(self):
        self.config_start_init(True, False)
800
801

    def test_start_both(self):
802
        self.config_start_init(True, True)
803
804
805

    def test_config_start(self):
        """
806
        Test that the configuration starts and stops components according
807
808
809
        to configuration changes.
        """

810
        # Create BoB and ensure correct initialization
811
        bob = MockBob()
812
813
        self.check_preconditions(bob)

814
        bob.start_all_components()
815
        bob.runnable = True
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
816
        bob.config_handler(self.construct_config(False, False))
817
818
819
        self.check_started_none(bob)

        # Enable both at once
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
820
        bob.config_handler(self.construct_config(True, True))
821
822
823
824
825
826
827
        self.check_started_both(bob)

        # Not touched by empty change
        bob.config_handler({})
        self.check_started_both(bob)

        # Not touched by change to the same configuration
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
828
        bob.config_handler(self.construct_config(True, True))
829
830
831
        self.check_started_both(bob)

        # Turn them both off again
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
832
        bob.config_handler(self.construct_config(False, False))
833
834
835
836
837
838
839
        self.check_started_none(bob)

        # Not touched by empty change
        bob.config_handler({})
        self.check_started_none(bob)

        # Not touched by change to the same configuration
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
840
        bob.config_handler(self.construct_config(False, False))
841
842
843
        self.check_started_none(bob)

        # Start and stop auth separately
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
844
        bob.config_handler(self.construct_config(True, False))
845
846
        self.check_started_auth(bob)

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
847
        bob.config_handler(self.construct_config(False, False))
848
849
850
        self.check_started_none(bob)

        # Start and stop resolver separately
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
851
        bob.config_handler(self.construct_config(False, True))
852
853
        self.check_started_resolver(bob)

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
854
        bob.config_handler(self.construct_config(False, False))
855
856
857
        self.check_started_none(bob)

        # Alternate
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
858
        bob.config_handler(self.construct_config(True, False))
859
860
        self.check_started_auth(bob)

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
861
        bob.config_handler(self.construct_config(False, True))
862
        self.check_started_resolver(bob)
863

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
864
        bob.config_handler(self.construct_config(True, False))
865
        self.check_started_auth(bob)
Shane Kerr's avatar
Shane Kerr committed
866

867
868
    def test_config_start_once(self):
        """
869
        Tests that a component is started only once.
870
        """
871
        # Create BoB and ensure correct initialization
872
        bob = MockBob()
873
874
        self.check_preconditions(bob)

875
        bob.start_all_components()
876

877
        bob.runnable = True
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
878
        bob.config_handler(self.construct_config(True, True))
879
880
881
882
883
884
885
886
887
        self.check_started_both(bob)

        bob.start_auth = lambda: self.fail("Started auth again")
        bob.start_xfrout = lambda: self.fail("Started xfrout again")
        bob.start_xfrin = lambda: self.fail("Started xfrin again")
        bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
        bob.start_resolver = lambda: self.fail("Started resolver again")

        # Send again we want to start them. Should not do it, as they are.
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
888
        bob.config_handler(self.construct_config(True, True))
889

890
891
    def test_config_not_started_early(self):
        """
892
        Test that components are not started by the config handler before
893
894
        startup.
        """
895
        bob = MockBob()
896
897
898
899
900
901
902
903
904
905
        self.check_preconditions(bob)

        bob.start_auth = lambda: self.fail("Started auth again")
        bob.start_xfrout = lambda: self.fail("Started xfrout again")
        bob.start_xfrin = lambda: self.fail("Started xfrin again")
        bob.start_zonemgr = lambda: self.fail("Started zonemgr again")
        bob.start_resolver = lambda: self.fail("Started resolver again")

        bob.config_handler({'start_auth': True, 'start_resolver': True})

906
    # Checks that DHCP (v4 and v6) components are started when expected
907
908
909
910
911
912
    def test_start_dhcp(self):

        # Create BoB and ensure correct initialization
        bob = MockBob()
        self.check_preconditions(bob)

913
        bob.start_all_components()
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
914
        bob.config_handler(self.construct_config(False, False))
915
916
        self.check_started_dhcp(bob, False, False)

917
918
919
920
    def test_start_dhcp_v6only(self):
        # Create BoB and ensure correct initialization
        bob = MockBob()
        self.check_preconditions(bob)
921
        # v6 only enabled
922
        bob.start_all_components()
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
923
924
925
926
927
928
        bob.runnable = True
        bob._BoB_started = True
        config = self.construct_config(False, False)
        config['components']['b10-dhcp6'] = { 'kind': 'needed',
                                              'address': 'Dhcp6' }
        bob.config_handler(config)
929
930
931
932
933
934
935
936
937
938
939
940
941
        self.check_started_dhcp(bob, False, True)

        # uncomment when dhcpv4 becomes implemented
        # v4 only enabled
        #bob.cfg_start_dhcp6 = False
        #bob.cfg_start_dhcp4 = True
        #self.check_started_dhcp(bob, True, False)

        # both v4 and v6 enabled
        #bob.cfg_start_dhcp6 = True
        #bob.cfg_start_dhcp4 = True
        #self.check_started_dhcp(bob, True, True)

942
943
944
945
946
947
class MockComponent:
    def __init__(self, name, pid):
        self.name = lambda: name
        self.pid = lambda: pid


948
949
950
951
952
953
954
955
956
class TestBossCmd(unittest.TestCase):
    def test_ping(self):
        """
        Confirm simple ping command works.
        """
        bob = MockBob()
        answer = bob.command_handler("ping", None)
        self.assertEqual(answer, {'result': [0, 'pong']})

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
957
    def test_show_processes_empty(self):
958
959
960
961
962
963
964
        """
        Confirm getting a list of processes works.
        """
        bob = MockBob()
        answer = bob.command_handler("show_processes", None)
        self.assertEqual(answer, {'result': [0, []]})

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
965
    def test_show_processes(self):
966
967
968
969
        """
        Confirm getting a list of processes works.
        """
        bob = MockBob()
970
971
        bob.register_process(1, MockComponent('first', 1))
        bob.register_process(2, MockComponent('second', 2))
972
        answer = bob.command_handler("show_processes", None)
973
974
        processes = [[1, 'first'],
                     [2, 'second']]
975
976
        self.assertEqual(answer, {'result': [0, processes]})

977
978
979
980
981
982
983
984
985
class TestParseArgs(unittest.TestCase):
    """
    This tests parsing of arguments of the bind10 master process.
    """
    #TODO: Write tests for the original parsing, bad options, etc.
    def test_no_opts(self):
        """
        Test correct default values when no options are passed.
        """
986
        options = parse_args([], TestOptParser)
987
988
        self.assertEqual(None, options.data_path)
        self.assertEqual(None, options.config_file)
989
        self.assertEqual(None, options.cmdctl_port)
990
991
992
993
994

    def test_data_path(self):
        """
        Test it can parse the data path.
        """
995
996
997
        self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
        self.assertRaises(OptsError, parse_args, ['--data-path'],
                          TestOptParser)
998
        options = parse_args(['-p', '/data/path'], TestOptParser)
999
        self.assertEqual('/data/path', options.data_path)
1000
        options = parse_args(['--data-path=/data/path'], TestOptParser)
1001
        self.assertEqual('/data/path', options.data_path)
1002

1003
1004
1005
1006
    def test_config_filename(self):
        """
        Test it can parse the config switch.
        """
1007
1008
1009
        self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
        self.assertRaises(OptsError, parse_args, ['--config-file'],
                          TestOptParser)
1010
        options = parse_args(['-c', 'config-file'], TestOptParser)
1011
        self.assertEqual('config-file', options.config_file)
1012
        options = parse_args(['--config-file=config-file'], TestOptParser)
1013
        self.assertEqual('config-file', options.config_file)
1014

1015
1016
1017
1018
1019
1020
    def test_clear_config(self):
        options = parse_args([], TestOptParser)
        self.assertEqual(False, options.clear_config)
        options = parse_args(['--clear-config'], TestOptParser)
        self.assertEqual(True, options.clear_config)

1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
    def test_nokill(self):
        options = parse_args([], TestOptParser)
        self.assertEqual(False, options.nokill)
        options = parse_args(['--no-kill'], TestOptParser)
        self.assertEqual(True, options.nokill)
        options = parse_args([], TestOptParser)
        self.assertEqual(False, options.nokill)
        options = parse_args(['-i'], TestOptParser)
        self.assertEqual(True, options.nokill)

1031
1032
1033
1034
1035
1036
1037
1038
    def test_cmdctl_port(self):
        """
        Test it can parse the command control port.
        """
        self.assertRaises(OptsError, parse_args, ['--cmdctl-port=abc'],
                                                TestOptParser)
        self.assertRaises(OptsError, parse_args, ['--cmdctl-port=100000000'],
                                                TestOptParser)
1039
1040
        self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
                          TestOptParser)
1041
1042
        options = parse_args(['--cmdctl-port=1234'], TestOptParser)
        self.assertEqual(1234, options.cmdctl_port)
1043

1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
class TestPIDFile(unittest.TestCase):
    def setUp(self):
        self.pid_file = '@builddir@' + os.sep + 'bind10.pid'
        if os.path.exists(self.pid_file):
            os.unlink(self.pid_file)

    def tearDown(self):
        if os.path.exists(self.pid_file):
            os.unlink(self.pid_file)

    def check_pid_file(self):
        # dump PID to the file, and confirm the content is correct
        dump_pid(self.pid_file)
        my_pid = os.getpid()
        self.assertEqual(my_pid, int(open(self.pid_file, "r").read()))

    def test_dump_pid(self):
        self.check_pid_file()

        # make sure any existing content will be removed
        open(self.pid_file, "w").write('dummy data\n')
        self.check_pid_file()

1067
    def test_unlink_pid_file_notexist(self):
1068
1069
        dummy_data = 'dummy_data\n'
        open(self.pid_file, "w").write(dummy_data)
1070
1071
1072
        unlink_pid_file("no_such_pid_file")
        # the file specified for unlink_pid_file doesn't exist,
        # and the original content of the file should be intact.
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
        self.assertEqual(dummy_data, open(self.pid_file, "r").read())

    def test_dump_pid_with_none(self):
        # Check the behavior of dump_pid() and unlink_pid_file() with None.
        # This should be no-op.
        dump_pid(None)
        self.assertFalse(os.path.exists(self.pid_file))

        dummy_data = 'dummy_data\n'
        open(self.pid_file, "w").write(dummy_data)
        unlink_pid_file(None)
        self.assertEqual(dummy_data, open(self.pid_file, "r").read())

    def test_dump_pid_failure(self):
        # the attempt to open file will fail, which should result in exception.
        self.assertRaises(IOError, dump_pid,
                          'nonexistent_dir' + os.sep + 'bind10.pid')

1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
class TestBossComponents(unittest.TestCase):
    """
    Test the boss propagates component configuration properly to the
    component configurator and acts sane.
    """
    def setUp(self):
        self.__param = None
        self.__called = False
        self.__compconfig = {
            'comp': {
                'kind': 'needed',
                'process': 'cat'
            }
        }

    def __unary_hook(self, param):
        """
        A hook function that stores the parameter for later examination.
        """
        self.__param = param

    def __nullary_hook(self):
        """
        A hook function that notes down it was called.
        """
        self.__called = True

    def __check_core(self, config):
        """
        A function checking that the config contains parts for the valid
        core component configuration.
        """
        self.assertIsNotNone(config)
        for component in ['sockcreator', 'msgq', 'cfgmgr']:
            self.assertTrue(component in config)
            self.assertEqual(component, config[component]['special'])
            self.assertEqual('core', config[component]['kind'])

    def __check_extended(self, config):
        """
        This checks that the config contains the core and one more component.
        """
        self.__check_core(config)
        self.assertTrue('comp' in config)
        self.assertEqual('cat', config['comp']['process'])
        self.assertEqual('needed', config['comp']['kind'])
        self.assertEqual(4, len(config))

    def test_correct_run(self):
        """
        Test the situation when we run in usual scenario, nothing fails,
        we just start, reconfigure and then stop peacefully.
        """
        bob = MockBob()
        # Start it
        orig = bob._component_configurator.startup
        bob._component_configurator.startup = self.__unary_hook
1148
        bob.start_all_components()
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
        bob._component_configurator.startup = orig
        self.__check_core(self.__param)
        self.assertEqual(3, len(self.__param))

        # Reconfigure it
        self.__param = None
        orig = bob._component_configurator.reconfigure
        bob._component_configurator.reconfigure = self.__unary_hook
        # Otherwise it does not work
        bob.runnable = True
        bob.config_handler({'components': self.__compconfig})
        self.__check_extended(self.__param)
        currconfig = self.__param
        # If we reconfigure it, but it does not contain the components part,
        # nothing is called
        bob.config_handler({})
        self.assertEqual(self.__param, currconfig)
        self.__param = None
        bob._component_configurator.reconfigure = orig
        # Check a configuration that messes up the core components is rejected.
        compconf = dict(self.__compconfig)
        compconf['msgq'] = { 'process': 'echo' }
        result = bob.config_handler({'components': compconf})
        # Check it rejected it
        self.assertEqual(1, result['result'][0])

        # We can't call shutdown, that one relies on the stuff in main
        # We check somewhere else that the shutdown is actually called
        # from there (the test_kills).

1179
    def __real_test_kill(self, nokill = False):
1180
        """
1181
        Helper function that does the actual kill functionality testing.
1182
1183
        """
        bob = MockBob()
1184
1185
        bob.nokill = nokill

1186
1187
1188
1189
1190
1191
1192
        killed = []
        class ImmortalComponent:
            """
            An immortal component. It does not stop when it is told so
            (anyway it is not told so). It does not die if it is killed
            the first time. It dies only when killed forcefully.
            """
1193
1194
1195
            def kill(self, forceful=False):
                killed.append(forceful)
                if forceful:
1196
                    bob.components = {}
1197
1198
1199
1200
            def pid(self):
                return 1
            def name(self):
                return "Immortal"
1201
        bob.components = {}
1202
1203
1204
1205
1206
1207
1208
        bob.register_process(1, ImmortalComponent())

        # While at it, we check the configurator shutdown is actually called
        orig = bob._component_configurator.shutdown
        bob._component_configurator.shutdown = self.__nullary_hook
        self.__called = False

1209
        bob.ccs = MockModuleCCSession()
Jelte Jansen's avatar
Jelte Jansen committed
1210
1211
        self.assertFalse(bob.ccs.stopped)

1212
1213
        bob.shutdown()

Jelte Jansen's avatar
Jelte Jansen committed
1214
        self.assertTrue(bob.ccs.stopped)
1215
1216
1217
1218

        # Here, killed is an array where False is added if SIGTERM
        # should be sent, or True if SIGKILL should be sent, in order in
        # which they're sent.
1219
1220
1221
1222
        if nokill:
            self.assertEqual([], killed)
        else:
            self.assertEqual([False, True], killed)
1223

1224
1225
1226
        self.assertTrue(self.__called)

        bob._component_configurator.shutdown = orig
1227

1228
1229
1230
1231
1232
1233
    def test_kills(self):
        """
        Test that the boss kills components which don't want to stop.
        """
        self.__real_test_kill()

1234
1235
1236
1237
1238
1239
    def test_nokill(self):
        """
        Test that the boss *doesn't* kill components which don't want to
        stop, when asked not to (by passing the --no-kill option which
        sets bob.nokill to True).
        """
1240
        self.__real_test_kill(True)
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261

    def test_component_shutdown(self):
        """
        Test the component_shutdown sets all variables accordingly.
        """
        bob = MockBob()
        self.assertRaises(Exception, bob.component_shutdown, 1)
        self.assertEqual(1, bob.exitcode)
        bob._BoB__started = True
        bob.component_shutdown(2)
        self.assertEqual(2, bob.exitcode)
        self.assertFalse(bob.runnable)

    def test_init_config(self):
        """
        Test initial configuration is loaded.
        """
        bob = MockBob()
        # Start it
        bob._component_configurator.reconfigure = self.__unary_hook
        # We need to return the original read_bind10_config
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
1262
        bob._read_bind10_config = lambda: BoB._read_bind10_config(bob)
1263
1264
1265
1266
1267
        # And provide a session to read the data from
        class CC:
            pass
        bob.ccs = CC()
        bob.ccs.get_full_config = lambda: {'components': self.__compconfig}
1268
        bob.start_all_components()
1269
1270
        self.__check_extended(self.__param)

1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
class SocketSrvTest(unittest.TestCase):
    """
    This tests some methods of boss related to the unix domain sockets used
    to transfer other sockets to applications.
    """
    def setUp(self):
        """
        Create the boss to test, testdata and backup some functions.
        """
        self.__boss = BoB()
        self.__select_backup = bind10_src.select.select
        self.__select_called = None
1283
        self.__socket_data_called = None
1284
        self.__consumer_dead_called = None
1285
        self.__socket_request_handler_called = None
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296

    def tearDown(self):
        """
        Restore functions.
        """
        bind10_src.select.select = self.__select_backup

    class __FalseSocket:
        """
        A mock socket for the select and accept and stuff like that.
        """
1297
1298
1299
        def __init__(self, owner, fileno=42):
            self.__owner = owner
            self.__fileno = fileno
1300
1301
            self.data = None
            self.closed = False
1302

1303
        def fileno(self):
1304
1305
1306
            return self.__fileno

        def accept(self):
1307
            return (self.__class__(self.__owner, 13), "/path/to/socket")
1308

1309
1310
1311
        def recv(self, bufsize, flags=0):
            self.__owner.assertEqual(1, bufsize)
            self.__owner.assertEqual(socket.MSG_DONTWAIT, flags)
1312
1313
1314
            if isinstance(self.data, socket.error):
                raise self.data
            elif self.data is not None:
1315
1316
1317
1318
1319
1320
                if len(self.data):
                    result = self.data[0:1]
                    self.data = self.data[1:]
                    return result