bind10_test.py.in 83 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 os.path
29
import copy
Shane Kerr's avatar
Shane Kerr committed
30
import signal
Evan Hunt's avatar
Evan Hunt committed
31
import socket
32
from isc.net.addr import IPAddr
33
34
import time
import isc
35
import isc.log
36
import isc.bind10.socket_cache
37
import errno
38
import random
Shane Kerr's avatar
Shane Kerr committed
39

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

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' ])
63
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
64
65
66
        os.dup2(self.old_stdout, sys.stdout.fileno())
        self.assertEqual(pi.name, 'Test Process')
        self.assertEqual(pi.args, [ '/bin/echo', 'foo' ])
67
68
#        self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
#                                   'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
Shane Kerr's avatar
Shane Kerr committed
69
70
71
72
73
        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)

74
75
76
77
78
79
#    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
80
81

    def test_setting_null_stdout(self):
82
        pi = ProcessInfo('Test Process', [ '/bin/echo', 'foo' ],
Shane Kerr's avatar
Shane Kerr committed
83
                         dev_null_stdout=True)
84
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
85
86
87
88
89
90
        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' ])
91
        pi.spawn()
Shane Kerr's avatar
Shane Kerr committed
92
93
94
95
96
97
98
99
100
        # 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' ])
101
102
#        self.assertEqual(pi.env, { 'PATH': os.environ['PATH'],
#                                   'PYTHON_EXEC': os.environ['PYTHON_EXEC'] })
Shane Kerr's avatar
Shane Kerr committed
103
104
105
106
107
108
        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)

109
110
111
112
113
114
115
116
117
118
119
120
121
122
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
123
        self.__boss._socket_path = '/socket/path'
124
        self.__raise_exception = None
125
126
        self.__socket_args = {
            "port": 53,
127
            "address": "::",
128
129
130
131
            "protocol": "UDP",
            "share_mode": "ANY",
            "share_name": "app"
        }
132
133
        # What was and wasn't called.
        self.__drop_app_called = None
134
135
        self.__get_socket_called = None
        self.__send_fd_called = None
136
137
        self.__get_token_called = None
        self.__drop_socket_called = None
138
139
140
141
142
143
144
        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)
145
146
147
148
149
150

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

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

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

        In the case self.__raise_exception is set, the exception there
        is raised instead.
172
        """
173
174
        if self.__raise_exception is not None:
            raise self.__raise_exception
175
176
177
178
179
180
181
182
183
        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)

184
185
186
187
188
189
190
191
192
193
    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())

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
    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")
213
        self.__boss.socket_request_handler(b"token", socket)
214
215
        # It was called, but it threw, so it is not noted here
        self.assertIsNone(self.__get_socket_called)
216
        self.assertEqual(b"0\n", socket.send)
217
218
219
220
        # It should not have sent any socket.
        self.assertIsNone(self.__send_fd_called)
        # Now prepare a valid scenario
        self.__raise_exception = None
221
222
223
        socket.send = b""
        self.__boss.socket_request_handler(b"token", socket)
        self.assertEqual(b"1\n", socket.send)
224
225
226
        self.assertEqual((42, 13), self.__send_fd_called)
        self.assertEqual(("token", 42), self.__get_socket_called)

227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
    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.
        """
243
        result = self.__boss._get_socket(self.__socket_args)
244
245
246
247
248
249
250
        [code, answer] = result['result']
        self.assertEqual(0, code)
        self.assertEqual({
            'token': 'token',
            'path': '/socket/path'
        }, answer)
        addr = self.__get_token_called[1]
251
        self.assertTrue(isinstance(addr, IPAddr))
252
        self.assertEqual("::", str(addr))
253
254
255
        self.assertEqual(("UDP", addr, 53, "ANY", "app"),
                         self.__get_token_called)

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

            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
269
            if code != 0:
270
                # This should be an error message. The exact formatting
271
                # is unknown, but we check it is string at least
272
                self.assertTrue(isinstance(ranswer, str))
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
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
301
302
        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)
303
304
305
306
307
308
309
        # 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)
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
341
342
    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
343
344
345
346
class TestBoB(unittest.TestCase):
    def test_init(self):
        bob = BoB()
        self.assertEqual(bob.verbose, False)
Jelte Jansen's avatar
Jelte Jansen committed
347
        self.assertEqual(bob.msgq_socket_file, None)
348
349
        self.assertEqual(bob.cc_session, None)
        self.assertEqual(bob.ccs, None)
350
        self.assertEqual(bob.components, {})
Shane Kerr's avatar
Shane Kerr committed
351
        self.assertEqual(bob.runnable, False)
352
353
        self.assertEqual(bob.uid, None)
        self.assertEqual(bob.username, None)
354
355
        self.assertIsNone(bob._socket_cache)

356
    def test_set_creator(self):
357
        """
358
        Test the call to set_creator. First time, the cache is created
359
360
361
362
363
364
        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()
365
        bob.set_creator(creator)
366
367
        self.assertTrue(isinstance(bob._socket_cache,
                        isc.bind10.socket_cache.Cache))
368
        self.assertEqual(creator, bob._socket_cache._creator)
369
        self.assertRaises(ValueError, bob.set_creator, creator)
370

371
372
373
374
375
376
377
378
379
380
381
    def test_socket_srv(self):
        """Tests init_socket_srv() and remove_socket_srv() work as expected."""
        bob = BoB()

        self.assertIsNone(bob._srv_socket)
        self.assertIsNone(bob._tmpdir)
        self.assertIsNone(bob._socket_path)

        bob.init_socket_srv()

        self.assertIsNotNone(bob._srv_socket)
382
        self.assertNotEqual(-1, bob._srv_socket.fileno())
383
384
385
        self.assertEqual(os.path.join(bob._tmpdir, 'sockcreator'),
                         bob._srv_socket.getsockname())

386
387
388
389
390
        self.assertIsNotNone(bob._tmpdir)
        self.assertTrue(os.path.isdir(bob._tmpdir))
        self.assertIsNotNone(bob._socket_path)
        self.assertTrue(os.path.exists(bob._socket_path))

391
392
393
394
395
396
397
398
399
400
401
402
403
        # Check that it's possible to connect to the socket file (this
        # only works if the socket file exists and the server listens on
        # it).
        s = socket.socket(socket.AF_UNIX)
        try:
            s.connect(bob._socket_path)
            can_connect = True
            s.close()
        except socket.error as e:
            can_connect = False

        self.assertTrue(can_connect)

404
405
        bob.remove_socket_srv()

406
407
408
        self.assertEqual(-1, bob._srv_socket.fileno())
        self.assertFalse(os.path.exists(bob._socket_path))
        self.assertFalse(os.path.isdir(bob._tmpdir))
409

410
411
412
413
414
415
416
417
        # These should not fail either:

        # second call
        bob.remove_socket_srv()

        bob._srv_socket = None
        bob.remove_socket_srv()

Jelte Jansen's avatar
Jelte Jansen committed
418
419
    def test_init_alternate_socket(self):
        bob = BoB("alt_socket_file")
Shane Kerr's avatar
Shane Kerr committed
420
        self.assertEqual(bob.verbose, False)
Jelte Jansen's avatar
Jelte Jansen committed
421
        self.assertEqual(bob.msgq_socket_file, "alt_socket_file")
422
423
        self.assertEqual(bob.cc_session, None)
        self.assertEqual(bob.ccs, None)
424
        self.assertEqual(bob.components, {})
Evan Hunt's avatar
Evan Hunt committed
425
        self.assertEqual(bob.runnable, False)
426
427
428
        self.assertEqual(bob.uid, None)
        self.assertEqual(bob.username, None)

429
430
431
432
433
    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
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
        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
451
        bob = BoB()
Naoki Kambe's avatar
Naoki Kambe committed
452
        bob.verbose = True
453
        bob.cc_session = DummySession()
454
        bob.ccs = DummyModuleCCSession()
Naoki Kambe's avatar
Naoki Kambe committed
455
        # a bad command
456
457
        self.assertEqual(bob.command_handler(-1, None),
                         isc.config.ccsession.create_answer(1, "bad command"))
Naoki Kambe's avatar
Naoki Kambe committed
458
        # "shutdown" command
459
460
461
        self.assertEqual(bob.command_handler("shutdown", None),
                         isc.config.ccsession.create_answer(0))
        self.assertFalse(bob.runnable)
462
463
464
        # "getstats" command
        self.assertEqual(bob.command_handler("getstats", None),
                         isc.config.ccsession.create_answer(0,
465
                            { 'boot_time': time.strftime('%Y-%m-%dT%H:%M:%SZ', _BASETIME) }))
Naoki Kambe's avatar
Naoki Kambe committed
466
467
468
469
470
471
472
473
        # "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
474
475
476
        self.assertEqual(bob.command_handler("__UNKNOWN__", None),
                         isc.config.ccsession.create_answer(1, "Unknown command"))

477
478
479
480
481
482
        # 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()
483
484
485
486
487
488
489
490
        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.
491
492
        self.assertEqual({'result': [0, {'token': '0.0.0.0:53',
                                         'path': '/socket/path'}]},
493
                         bob.command_handler("get_socket", args))
494
495
        # The drop_socket is not tested here, but in TestCacheCommands.
        # It needs the cache mocks to be in place and they are there.
496

497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
    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)

513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
# Mock class for testing BoB's usage of ProcessInfo
class MockProcessInfo:
    def __init__(self, name, args, env={}, dev_null_stdout=False,
                 dev_null_stderr=False):
        self.name = name
        self.args = args
        self.env = env
        self.dev_null_stdout = dev_null_stdout
        self.dev_null_stderr = dev_null_stderr
        self.process = None
        self.pid = None

    def spawn(self):
        # set some pid (only used for testing that it is not None anymore)
        self.pid = 42147

529
530
531
# Class for testing the BoB without actually starting processes.
# This is used for testing the start/stop components routines and
# the BoB commands.
532
#
533
# Testing that external processes start is outside the scope
534
535
536
# of the unit test, by overriding the process start methods we can check
# that the right processes are started depending on the configuration
# options.
537
class MockBob(BoB):
538
539
540
    def __init__(self):
        BoB.__init__(self)

541
        # Set flags as to which of the overridden methods has been run.
542
543
544
545
        self.msgq = False
        self.cfgmgr = False
        self.ccsession = False
        self.auth = False
546
        self.resolver = False
547
548
549
550
        self.xfrout = False
        self.xfrin = False
        self.zonemgr = False
        self.stats = False
Naoki Kambe's avatar
Naoki Kambe committed
551
        self.stats_httpd = False
552
        self.cmdctl = False
553
554
        self.dhcp6 = False
        self.dhcp4 = False
555
        self.c_channel_env = {}
556
        self.components = { }
557
        self.creator = False
558
        self.get_process_exit_status_called = False
559

560
561
562
563
564
565
566
567
568
569
570
        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)

571
572
    def start_creator(self):
        self.creator = True
573
574
575
        procinfo = ProcessInfo('b10-sockcreator', ['/bin/false'])
        procinfo.pid = 1
        return procinfo
576

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
577
    def _read_bind10_config(self):
578
579
580
        # Configuration options are set directly
        pass

581
    def start_msgq(self):
582
        self.msgq = True
583
584
585
        procinfo = ProcessInfo('b10-msgq', ['/bin/false'])
        procinfo.pid = 2
        return procinfo
586
587

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

591
592
593
594
595
596
597
    def start_cfgmgr(self):
        self.cfgmgr = True
        procinfo = ProcessInfo('b10-cfgmgr', ['/bin/false'])
        procinfo.pid = 3
        return procinfo

    def start_auth(self):
598
        self.auth = True
599
600
601
        procinfo = ProcessInfo('b10-auth', ['/bin/false'])
        procinfo.pid = 5
        return procinfo
602

603
    def start_resolver(self):
604
        self.resolver = True
605
606
607
608
609
        procinfo = ProcessInfo('b10-resolver', ['/bin/false'])
        procinfo.pid = 6
        return procinfo

    def start_simple(self, name):
610
        procmap = { 'b10-zonemgr': self.start_zonemgr,
611
612
613
614
                    'b10-stats': self.start_stats,
                    'b10-stats-httpd': self.start_stats_httpd,
                    'b10-cmdctl': self.start_cmdctl,
                    'b10-dhcp6': self.start_dhcp6,
615
616
617
                    'b10-dhcp4': self.start_dhcp4,
                    'b10-xfrin': self.start_xfrin,
                    'b10-xfrout': self.start_xfrout }
618
619
620
        return procmap[name]()

    def start_xfrout(self):
621
        self.xfrout = True
622
623
624
        procinfo = ProcessInfo('b10-xfrout', ['/bin/false'])
        procinfo.pid = 7
        return procinfo
625

626
    def start_xfrin(self):
627
        self.xfrin = True
628
629
630
        procinfo = ProcessInfo('b10-xfrin', ['/bin/false'])
        procinfo.pid = 8
        return procinfo
631

632
    def start_zonemgr(self):
633
        self.zonemgr = True
634
635
636
        procinfo = ProcessInfo('b10-zonemgr', ['/bin/false'])
        procinfo.pid = 9
        return procinfo
637

638
    def start_stats(self):
639
        self.stats = True
640
641
642
        procinfo = ProcessInfo('b10-stats', ['/bin/false'])
        procinfo.pid = 10
        return procinfo
643

644
    def start_stats_httpd(self):
Naoki Kambe's avatar
Naoki Kambe committed
645
        self.stats_httpd = True
646
647
648
        procinfo = ProcessInfo('b10-stats-httpd', ['/bin/false'])
        procinfo.pid = 11
        return procinfo
Naoki Kambe's avatar
Naoki Kambe committed
649

650
    def start_cmdctl(self):
651
        self.cmdctl = True
652
653
654
        procinfo = ProcessInfo('b10-cmdctl', ['/bin/false'])
        procinfo.pid = 12
        return procinfo
655

656
    def start_dhcp6(self):
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
657
        self.dhcp6 = True
658
659
660
        procinfo = ProcessInfo('b10-dhcp6', ['/bin/false'])
        procinfo.pid = 13
        return procinfo
661

662
    def start_dhcp4(self):
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
663
        self.dhcp4 = True
664
665
666
667
        procinfo = ProcessInfo('b10-dhcp4', ['/bin/false'])
        procinfo.pid = 14
        return procinfo

668
    def stop_process(self, process, recipient, pid):
669
670
671
672
673
674
675
676
677
        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]()
678

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
679
    # Some functions to pretend we stop processes, use by stop_process
680
    def stop_msgq(self):
681
        if self.msgq:
682
            del self.components[2]
683
684
        self.msgq = False

685
    def stop_cfgmgr(self):
686
        if self.cfgmgr:
687
            del self.components[3]
688
689
        self.cfgmgr = False

690
    def stop_auth(self):
691
        if self.auth:
692
            del self.components[5]
693
694
        self.auth = False

695
    def stop_resolver(self):
696
        if self.resolver:
697
            del self.components[6]
698
699
        self.resolver = False

700
    def stop_xfrout(self):
701
        if self.xfrout:
702
            del self.components[7]
703
704
        self.xfrout = False

705
    def stop_xfrin(self):
706
        if self.xfrin:
707
            del self.components[8]
708
709
        self.xfrin = False

710
    def stop_zonemgr(self):
711
        if self.zonemgr:
712
            del self.components[9]
713
714
        self.zonemgr = False

715
    def stop_stats(self):
716
        if self.stats:
717
            del self.components[10]
718
719
        self.stats = False

Naoki Kambe's avatar
Naoki Kambe committed
720
    def stop_stats_httpd(self):
721
        if self.stats_httpd:
722
            del self.components[11]
Naoki Kambe's avatar
Naoki Kambe committed
723
724
        self.stats_httpd = False

725
    def stop_cmdctl(self):
726
        if self.cmdctl:
727
            del self.components[12]
728
729
        self.cmdctl = False

730
731
732
733
734
735
    def _get_process_exit_status(self):
        if self.get_process_exit_status_called:
            return (0, 0)
        self.get_process_exit_status_called = True
        return (53, 0)

736
737
738
739
740
741
    def _get_process_exit_status_unknown_pid(self):
        if self.get_process_exit_status_called:
            return (0, 0)
        self.get_process_exit_status_called = True
        return (42, 0)

742
743
744
745
746
747
748
749
750
    def _get_process_exit_status_raises_oserror_echild(self):
        raise OSError(errno.ECHILD, 'Mock error')

    def _get_process_exit_status_raises_oserror_other(self):
        raise OSError(0, 'Mock error')

    def _get_process_exit_status_raises_other(self):
        raise Exception('Mock error')

751
752
753
754
    def _make_mock_process_info(self, name, args, c_channel_env,
                                dev_null_stdout=False, dev_null_stderr=False):
        return MockProcessInfo(name, args, c_channel_env,
                               dev_null_stdout, dev_null_stderr)
755

756
class MockBobSimple(BoB):
757
758
759
760
761
    def __init__(self):
        BoB.__init__(self)
        # Set which process has been started
        self.started_process_name = None
        self.started_process_args = None
762
        self.started_process_env = None
763

764
765
766
767
768
    def _make_mock_process_info(self, name, args, c_channel_env,
                                dev_null_stdout=False, dev_null_stderr=False):
        return MockProcessInfo(name, args, c_channel_env,
                               dev_null_stdout, dev_null_stderr)

769
770
    def start_process(self, name, args, c_channel_env, port=None,
                      address=None):
771
772
        self.started_process_name = name
        self.started_process_args = args
773
        self.started_process_env = c_channel_env
774
775
        return None

776
777
class TestStartStopProcessesBob(unittest.TestCase):
    """
778
779
    Check that the start_all_components method starts the right combination
    of components and that the right components are started and stopped
780
781
    according to changes in configuration.
    """
Jelte Jansen's avatar
Jelte Jansen committed
782
783
784
785
    def check_environment_unchanged(self):
        # Check whether the environment has not been changed
        self.assertEqual(original_os_environ, os.environ)

786
787
    def check_started(self, bob, core, auth, resolver):
        """
788
789
        Check that the right sets of services are started. The ones that
        should be running are specified by the core, auth and resolver parameters
790
791
792
793
794
795
        (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)
796
        self.assertEqual(bob.creator, core)
797
798
799
800
801
802
        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
803
        self.assertEqual(bob.stats_httpd, core)
804
        self.assertEqual(bob.cmdctl, core)
Jelte Jansen's avatar
Jelte Jansen committed
805
        self.check_environment_unchanged()
806

807
    def check_preconditions(self, bob):
808
        self.check_started(bob, False, False, False)
809

810
811
812
    def check_started_none(self, bob):
        """
        Check that the situation is according to configuration where no servers
813
        should be started. Some components still need to be running.
814
        """
815
        self.check_started(bob, True, False, False)
Jelte Jansen's avatar
Jelte Jansen committed
816
        self.check_environment_unchanged()
817
818
819
820
821
822

    def check_started_both(self, bob):
        """
        Check the situation is according to configuration where both servers
        (auth and resolver) are enabled.
        """
823
        self.check_started(bob, True, True, True)
Jelte Jansen's avatar
Jelte Jansen committed
824
        self.check_environment_unchanged()
825
826
827

    def check_started_auth(self, bob):
        """
828
        Check the set of components needed to run auth only is started.
829
        """
830
        self.check_started(bob, True, True, False)
Jelte Jansen's avatar
Jelte Jansen committed
831
        self.check_environment_unchanged()
832
833
834

    def check_started_resolver(self, bob):
        """
835
        Check the set of components needed to run resolver only is started.
836
        """
837
        self.check_started(bob, True, False, True)
Jelte Jansen's avatar
Jelte Jansen committed
838
        self.check_environment_unchanged()
839

840
841
842
843
    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
844
845
        self.assertEqual(v4, bob.dhcp4)
        self.assertEqual(v6, bob.dhcp6)
Jelte Jansen's avatar
Jelte Jansen committed
846
        self.check_environment_unchanged()
847

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
848
849
850
851
852
853
854
855
856
857
    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',
858
859
860
                                     'address': 'Xfrout' }
            config['b10-xfrin'] = { 'kind': 'dispensable',
                                    'address': 'Xfrin' }
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
861
862
863
864
865
866
            config['b10-zonemgr'] = { 'kind': 'dispensable',
                                      'address': 'Zonemgr' }
        if start_resolver:
            config['b10-resolver'] = { 'kind': 'needed',
                                       'special': 'resolver' }
        return {'components': config}
867

868
869
870
871
    def config_start_init(self, start_auth, start_resolver):
        """
        Test the configuration is loaded at the startup.
        """
872
        bob = MockBob()
873
874
875
876
877
878
879
880
881
882
883
884
        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)
885
        bob.start_all_components()
886
887
        self.check_started(bob, True, start_auth, start_resolver)
        self.check_environment_unchanged()
888

889
890
    def test_start_none(self):
        self.config_start_init(False, False)
891

892
    def test_start_resolver(self):
893
        self.config_start_init(False, True)
894

895
896
    def test_start_auth(self):
        self.config_start_init(True, False)
897
898

    def test_start_both(self):
899
        self.config_start_init(True, True)
900
901
902

    def test_config_start(self):
        """
903
        Test that the configuration starts and stops components according
904
905
906
        to configuration changes.
        """

907
        # Create BoB and ensure correct initialization
908
        bob = MockBob()
909
910
        self.check_preconditions(bob)

911
        bob.start_all_components()
912
        bob.runnable = True
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
913
        bob.config_handler(self.construct_config(False, False))
914
915
916
        self.check_started_none(bob)

        # Enable both at once
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
917
        bob.config_handler(self.construct_config(True, True))
918
919
920
921
922
923
924
        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
925
        bob.config_handler(self.construct_config(True, True))
926
927
928
        self.check_started_both(bob)

        # Turn them both off again
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
929
        bob.config_handler(self.construct_config(False, False))
930
931
932
933
934
935
936
        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
937
        bob.config_handler(self.construct_config(False, False))
938
939
940
        self.check_started_none(bob)

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

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
944
        bob.config_handler(self.construct_config(False, False))
945
946
947
        self.check_started_none(bob)

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

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
951
        bob.config_handler(self.construct_config(False, False))
952
953
954
        self.check_started_none(bob)

        # Alternate
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
955
        bob.config_handler(self.construct_config(True, False))
956
957
        self.check_started_auth(bob)

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
958
        bob.config_handler(self.construct_config(False, True))
959
        self.check_started_resolver(bob)
960

Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
961
        bob.config_handler(self.construct_config(True, False))
962
        self.check_started_auth(bob)
Shane Kerr's avatar
Shane Kerr committed
963

964
965
    def test_config_start_once(self):
        """
966
        Tests that a component is started only once.
967
        """
968
        # Create BoB and ensure correct initialization
969
        bob = MockBob()
970
971
        self.check_preconditions(bob)

972
        bob.start_all_components()
973

974
        bob.runnable = True
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
975
        bob.config_handler(self.construct_config(True, True))
976
977
978
979
980
981
982
983
984
        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
985
        bob.config_handler(self.construct_config(True, True))
986

987
988
    def test_config_not_started_early(self):
        """
989
        Test that components are not started by the config handler before
990
991
        startup.
        """
992
        bob = MockBob()
993
994
995
996
997
998
999
1000
1001
1002
        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})

1003
    # Checks that DHCP (v4 and v6) components are started when expected
1004
1005
1006
1007
1008
1009
    def test_start_dhcp(self):

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

1010
        bob.start_all_components()
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
1011
        bob.config_handler(self.construct_config(False, False))
1012
1013
        self.check_started_dhcp(bob, False, False)

1014
1015
1016
1017
    def test_start_dhcp_v6only(self):
        # Create BoB and ensure correct initialization
        bob = MockBob()
        self.check_preconditions(bob)
1018
        # v6 only enabled
1019
        bob.start_all_components()
Michal 'vorner' Vaner's avatar
Michal 'vorner' Vaner committed
1020
1021
1022
1023
1024
1025
        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)
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
        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)

1039
class MockComponent:
1040
    def __init__(self, name, pid, address=None):
1041
1042
        self.name = lambda: name
        self.pid = lambda: pid
1043
        self.address = lambda: address
1044
        self.restarted = False
1045
        self.forceful = False
1046
1047
        self.running = True
        self.has_failed = False
1048

1049
1050
1051
1052
1053
1054
    def get_restart_time(self):
        return 0                # arbitrary dummy value

    def restart(self, now):
        self.restarted = True
        return True
1055

1056
    def is_running(self):
1057
        return self.running
1058
1059

    def failed(self, status):
1060
        return self.has_failed
1061

1062
1063
    def kill(self, forceful):
        self.forceful = forceful
1064

1065
1066
1067
1068
1069
1070
1071
1072
1073
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
1074
    def test_show_processes_empty(self):
1075
1076
1077
1078
1079
1080
1081
        """
        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
1082
    def test_show_processes(self):
1083
1084
1085
1086
        """
        Confirm getting a list of processes works.
        """
        bob = MockBob()
1087
        bob.register_process(1, MockComponent('first', 1))
1088
        bob.register_process(2, MockComponent('second', 2, 'Second'))
1089
        answer = bob.command_handler("show_processes", None)
1090
1091
        processes = [[1, 'first', None],
                     [2, 'second', 'Second']]
1092
1093
        self.assertEqual(answer, {'result': [0, processes]})

1094
1095
1096
1097
1098
1099
1100
1101
1102
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.
        """
1103
        options = parse_args([], TestOptParser)
1104
1105
        self.assertEqual(None, options.data_path)
        self.assertEqual(None, options.config_file)
1106
        self.assertEqual(None, options.cmdctl_port)
1107
1108
1109
1110
1111

    def test_data_path(self):
        """
        Test it can parse the data path.
        """
1112
1113
1114
        self.assertRaises(OptsError, parse_args, ['-p'], TestOptParser)
        self.assertRaises(OptsError, parse_args, ['--data-path'],
                          TestOptParser)
1115
        options = parse_args(['-p', '/data/path'], TestOptParser)
1116
        self.assertEqual('/data/path', options.data_path)
1117
        options = parse_args(['--data-path=/data/path'], TestOptParser)
1118
        self.assertEqual('/data/path', options.data_path)
1119

1120
1121
1122
1123
    def test_config_filename(self):
        """
        Test it can parse the config switch.
        """
1124
1125
1126
        self.assertRaises(OptsError, parse_args, ['-c'], TestOptParser)
        self.assertRaises(OptsError, parse_args, ['--config-file'],
                          TestOptParser)
1127
        options = parse_args(['-c', 'config-file'], TestOptParser)
1128
        self.assertEqual('config-file', options.config_file)
1129
        options = parse_args(['--config-file=config-file'], TestOptParser)
1130
        self.assertEqual('config-file', options.config_file)
1131

1132
1133
1134
1135
1136
1137
    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)

1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
    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)

1148
1149
1150
1151
1152
1153
1154
1155
    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)
1156
1157
        self.assertRaises(OptsError, parse_args, ['--cmdctl-port'],
                          TestOptParser)
1158
1159
        options = parse_args(['--cmdctl-port=1234'], TestOptParser)
        self.assertEqual(1234, options.cmdctl_port)
1160

1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
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()
1175
1176
        with open(self.pid_file, "r") as f:
            self.assertEqual(my_pid, int(f.read()))
1177
1178
1179
1180
1181

    def test_dump_pid(self):
        self.check_pid_file()

        # make sure any existing content will be removed
1182
1183
        with open(self.pid_file, "w") as f:
            f.write('dummy data\n')
1184
1185
        self.check_pid_file()

1186
    def test_unlink_pid_file_notexist(self):
1187
        dummy_data = 'dummy_data\n'
1188

1189
1190
        with open(self.pid_file, "w") as f:
            f.write(dummy_data)
1191

1192
        unlink_pid_file("no_such_pid_file")
1193

1194
1195
        # the file specified for unlink_pid_file doesn't exist,
        # and the original content of the file should be intact.
Mukund Sivaraman's avatar