Beispiel #1
0
    def test_chown_user(self):
        """Test chown argument handling (user only)."""
        # Ensure chown handles a user name:
        with ReloadConf(self.dir, self.file, '/bin/true',
                        chown='nobody') as rc:
            self.assertIsInstance(rc.chown[0], numbers.Number)
            self.assertEqual(-1, rc.chown[1])

        with ReloadConf(self.dir, self.file, '/bin/true',
                        chown=TEST_UID) as rc:
            self.assertEqual((TEST_UID, -1), rc.chown)
Beispiel #2
0
 def test_chmod(self):
     """Test chmod capability."""
     # Ensure chmod must be numeric:
     with self.assertRaises(AssertionError):
         ReloadConf(self.dir, [], None, chmod='foo')
     # Ensure config files are properly chmod()ed:
     with ReloadConf(self.dir, self.file, '/bin/true', chmod=0o700) as rc:
         watch = pathjoin(self.dir, basename(self.file))
         with open(watch, 'wb') as f:
             f.write(b'foo')
         os.chmod(watch, 0o755)
         rc.poll()
         self.assertEqual(stat.S_IMODE(os.stat(self.file).st_mode), 0o700)
Beispiel #3
0
 def test_chown(self):
     """Test chown capability."""
     with ReloadConf(self.dir,
                     self.file,
                     '/bin/true',
                     chown=(TEST_UID, TEST_UID)) as rc:
         with open(pathjoin(self.dir, basename(self.file)), 'wb') as f:
             f.write(b'foo')
         rc.poll()
         self.assertEqual(TEST_UID, os.stat(self.file).st_uid)
         self.assertEqual(TEST_UID, os.stat(self.file).st_gid)
Beispiel #4
0
    def test_nodir(self):
        """Test that watch directory does not need to exist."""
        # Remove the watch directory.
        os.rmdir(self.dir)

        # Ensure reloadconf creates the watch directory.
        with ReloadConf(self.dir,
                        self.file,
                        '/bin/sleep 1',
                        chown=(TEST_UID, TEST_UID),
                        chmod=0o700) as rc:
            rc.poll()
            self.assertTrue(rc.check_command())
            self.assertTrue(isdir(self.dir))
Beispiel #5
0
 def test_inotify(self):
     """Ensure command is reloaded with valid config (inotify)."""
     command = '%s %s' % (self.prog, self.sig)
     with ReloadConf(self.dir, self.file, command, inotify=True) as rc:
         rc.poll()
         # Command should now be running.
         self.assertTrue(rc.check_command())
         # Write out "config" file.
         with open(pathjoin(self.dir, basename(self.file)), 'wb') as f:
             f.write(b'foo')
         # Command should receive HUP.
         rc.poll()
         time.sleep(0.1)
         self.assertTrue(pathexists(self.sig))
Beispiel #6
0
 def test_nohup(self):
     """Ensure that command is not reloaded with invalid config."""
     command = '%s %s' % (self.prog, self.sig)
     with ReloadConf(self.dir, self.file, command, '/bin/true') as rc:
         rc.poll()
         # Command should now be running.
         self.assertTrue(rc.check_command())
         # A bit nasty, but we want the check to fail this time...
         rc.test = '/bin/false'
         # Write out "config" file.
         with open(pathjoin(self.dir, basename(self.file)), 'wb') as f:
             f.write(b'foo')
         # Command should NOT receive HUP.
         rc.poll()
         time.sleep(0.1)
         self.assertFalse(pathexists(self.sig))
Beispiel #7
0
 def test_reload(self):
     """Ensure reload command is run (instead of HUP) when provided."""
     reload = '/bin/touch %s' % self.sig
     with ReloadConf(self.dir, self.file, '/bin/sleep 1',
                     reload=reload) as rc:
         rc.poll()
         # Command should now be running.
         self.assertTrue(rc.check_command())
         self.assertFalse(pathexists(self.sig))
         # Write out "config" file.
         with open(pathjoin(self.dir, basename(self.file)), 'wb') as f:
             f.write(b'foo')
         # Reload command should be executed.
         rc.poll()
         time.sleep(0.1)
         self.assertTrue(pathexists(self.sig))
Beispiel #8
0
def main(argv):
    """
    reloadconf - Monitor config changes and safely restart.

    Usage:
        reloadconf --command=<cmd> --watch=<dir> (--config=<file> ...)
                   [--reload=<cmd> --test=<cmd> --debug --chown=<user,group>]
                   [--chmod=<mode> --inotify --wait-for-path=<file>]
                   [--wait-for-sock=<host:port> --wait-timeout=<secs>]

    Options:
        --command=<cmd>
            The program to run when configuration is valid.
        --watch=<dir>
            The directory to watch for incoming files.
        --config=<file>
            A destination config file path.
        --reload=<cmd>
            The command to reload configuration (defaults to HUP signal).
        --test=<cmd>
            The command to test configuration.
        --chown=<user,group>
            The user and (optionally) group to chown config files to before
            starting service.
        --chmod=<mode>
            Mode to set config files to before starting service.
        --inotify
            Use inotify instead of polling.
        --wait-for-path=<file>
            Delay start until file or directory appears on disk.
        --wait-for-sock=<host:port>
            Delay start until connection succeeds.
        --wait-timeout=<secs>
            Timeout for wait-* commands [default: 5].
        --debug
            Verbose output.

    Assumptions:
     - The command accepts HUP signal to reload it's configuration.
     - Config files don't have the same name (if two config files in different
       directories have the same name, reloadconf will have issues.)

    Upon startup reloadconf will test the configuration and if valid, will run
    command. If command dies for any reason, reloadconf re-runs it. If the
    configuration is invalid (test command fails) then reloadconf waits for new
    files to appear in it's input directory, merges those and re-tests the
    config. If --test is omitted, then the configuration test is skipped, but
    reloadconf still monitors for new config files and reloads command. Command
    is reloaded by sending a HUP signal.

    Config files are matched by name. For example, if the input directory is
    /tmp, and a given config file is /etc/foo.conf, then reloadconf will watch
    for /tmp/foo.conf to appear, and will overwrite /etc/foo.conf with it and
    then test the config. Reloadconf can handle multiple config files, but
    since it uses the file name to determine a file's destination, names must
    be unique.

    Reloadconf will wait for 1 second after seeing any configuration file
    appear to give the configuration generator time to complete all files for
    a configuration file set. If it takes more than 1 second to generate a full
    configuration, then the generator program should write them to temporary
    space before moving them into the input directory.
    """
    opt = docopt(textwrap.dedent(main.__doc__), argv)

    try:
        opt = Schema({
            '--command':
            Use(str),
            '--watch':
            Use(str),
            '--chown':
            Or(None, Use(user_and_group)),
            '--chmod':
            Or(None, Use(int)),
            '--wait-for-sock':
            Or(None, Use(host_and_port), error='Invalid socket'),
            '--wait-timeout':
            Or(None, Use(float), error='Invalid timeout'),
            object:
            object,
        }).validate(opt)

    except SchemaError as e:
        raise DocoptExit(e.args[0])

    logger = logging.getLogger()
    # Set up logging so we can see output.
    logger.addHandler(logging.StreamHandler(sys.stdout))

    logger.setLevel(logging.INFO)
    if opt.pop('--debug', None):
        logger.setLevel(logging.DEBUG)

    # Convert from CLI arguments to kwargs.
    kwargs = {}
    for k in opt.keys():
        kwargs[k.lstrip('-').replace('-', '_')] = opt[k]

    try:
        rc = ReloadConf(**kwargs)

    except AssertionError as e:
        raise DocoptExit(e.args[0])

    with rc:
        LOGGER.info('Reloadconf monitoring %s for %s', kwargs['watch'],
                    kwargs['command'])

        while True:
            try:
                rc.poll()

            except Exception:
                LOGGER.exception('Error polling', exc_info=True)

            # Check up to 20 times a minute.
            time.sleep(3.0)
Beispiel #9
0
 def test_chown_fail(self):
     """Test chown validation."""
     # Ensure chown must have len() == 2:
     with self.assertRaises(AssertionError):
         ReloadConf(self.dir, [], None, chown=(1, 2, 3))
Beispiel #10
0
 def test_no_test(self):
     """Ensure command is run when test is omitted."""
     rc = ReloadConf(self.dir, self.file, '/bin/sleep 1')
     rc.poll()
     # Command should have run.
     self.assertTrue(rc.check_command())
Beispiel #11
0
 def test_success(self):
     """Ensure command is run when test succeeds."""
     rc = ReloadConf(self.dir, self.file, '/bin/sleep 1', test='/bin/true')
     rc.poll()
     # Command should have run.
     self.assertTrue(rc.check_command())
Beispiel #12
0
 def test_fail(self):
     """Ensure command is NOT run when test fails."""
     rc = ReloadConf(self.dir, self.file, '/bin/sleep 1', test='/bin/false')
     rc.poll()
     # Command should NOT have run.
     self.assertFalse(rc.check_command())