Runner.pm 6.52 KB
Newer Older
1 2 3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8 9 10 11 12 13

# XXX In order to support Windows, we have to make gd_redirect_output
# use Log4Perl or something instead of calling "logger". We probably
# also need to use Win32::Daemon or something like that to daemonize.

package Bugzilla::JobQueue::Runner;

14
use 5.10.1;
15
use strict;
16
use warnings;
17

18
use Cwd qw(abs_path);
19
use File::Basename;
20
use File::Copy;
21 22 23 24 25
use Pod::Usage;

use Bugzilla::Constants;
use Bugzilla::JobQueue;
use Bugzilla::Util qw(get_text);
26
BEGIN { eval "use parent qw(Daemon::Generic)"; }
27 28 29

our $VERSION = BUGZILLA_VERSION;

30
# Info we need to install/uninstall the daemon.
31 32 33 34
our $chkconfig = "/sbin/chkconfig";
our $initd = "/etc/init.d";
our $initscript = "bugzilla-queue";

35 36 37 38
# The Daemon::Generic docs say that it uses all sorts of
# things from gd_preconfig, but in fact it does not. The
# only thing it uses from gd_preconfig is the "pidfile"
# config parameter.
39
sub gd_preconfig {
40 41
    my $self = shift;

42
    $self->{_run_command} = 'subprocess_worker';
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
    my $pidfile = $self->{gd_args}{pidfile};
    if (!$pidfile) {
        $pidfile = bz_locations()->{datadir} . '/' . $self->{gd_progname} 
                   . ".pid";
    }
    return (pidfile => $pidfile);
}

# All config other than the pidfile has to be done in gd_getopt
# in order for it to be set up early enough.
sub gd_getopt {
    my $self = shift;

    $self->SUPER::gd_getopt();

    if ($self->{gd_args}{progname}) {
        $self->{gd_progname} = $self->{gd_args}{progname};
    }
    else {
        $self->{gd_progname} = basename($0);
    }

    # There are places that Daemon Generic's new() uses $0 instead of
    # gd_progname, which it really shouldn't, but this hack fixes it.
    $self->{_original_zero} = $0;
    $0 = $self->{gd_progname};
}

sub gd_postconfig {
    my $self = shift;
    # See the hack above in gd_getopt. This just reverses it
    # in case anything else needs the accurate $0.
    $0 = delete $self->{_original_zero};
}

sub gd_more_opt {
    my $self = shift;
    return (
        'pidfile=s' => \$self->{gd_args}{pidfile},
        'n=s'       => \$self->{gd_args}{progname},
    );
84 85 86 87 88 89 90
}

sub gd_usage {
    pod2usage({ -verbose => 0, -exitval => 'NOEXIT' });
    return 0
}

91 92 93
sub gd_can_install {
    my $self = shift;

94 95 96 97 98 99
    my $source_file;
    if ( -e "/etc/SuSE-release" ) {
        $source_file = "contrib/$initscript.suse";
    } else {
        $source_file = "contrib/$initscript.rhel";
    }
100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140
    my $dest_file = "$initd/$initscript";
    my $sysconfig = '/etc/sysconfig';
    my $config_file = "$sysconfig/$initscript";

    if (!-x $chkconfig  or !-d $initd) {
        return $self->SUPER::gd_can_install(@_);
    }

    return sub {
        if (!-w $initd) {
            print "You must run the 'install' command as root.\n";
            return;
        }
        if (-e $dest_file) {
            print "$initscript already in $initd.\n";
        }
        else {
            copy($source_file, $dest_file)
                or die "Could not copy $source_file to $dest_file: $!";
            chmod(0755, $dest_file)
                or die "Could not change permissions on $dest_file: $!";
        }

        system($chkconfig, '--add', $initscript);
        print "$initscript installed.",
              " To start the daemon, do \"$dest_file start\" as root.\n";

        if (-d $sysconfig and -w $sysconfig) {
            if (-e $config_file) {
                print "$config_file already exists.\n";
                return;
            }

            open(my $config_fh, ">", $config_file)
                or die "Could not write to $config_file: $!";
            my $directory = abs_path(dirname($self->{_original_zero}));
            my $owner_id = (stat $self->{_original_zero})[4];
            my $owner = getpwuid($owner_id);
            print $config_fh <<END;
#!/bin/sh
BUGZILLA="$directory"
141
# This user must have write access to Bugzilla's data/ directory.
142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
USER=$owner
END
            close($config_fh);
        }
        else {
            print "Please edit $dest_file to configure the daemon.\n";
        }
    }
}

sub gd_can_uninstall {
    my $self = shift;

    if (-x $chkconfig and -d $initd) {
        return sub {
            if (!-e "$initd/$initscript") {
                print "$initscript not installed.\n";
                return;
            }
            system($chkconfig, '--del', $initscript);
            print "$initscript disabled.",
                  " To stop it, run: $initd/$initscript stop\n";
        }
    }

    return $self->SUPER::gd_can_install(@_);
}

170 171 172 173 174 175 176 177 178 179 180 181 182
sub gd_check {
    my $self = shift;

    # Get a count of all the jobs currently in the queue.
    my $jq = Bugzilla->job_queue();
    my @dbs = $jq->bz_databases();
    my $count = 0;
    foreach my $driver (@dbs) {
        $count += $driver->select_one('SELECT COUNT(*) FROM ts_job', []);
    }
    print get_text('job_queue_depth', { count => $count }) . "\n";
}

183 184 185 186 187 188
sub gd_setup_signals {
    my $self = shift;
    $self->SUPER::gd_setup_signals();
    $SIG{TERM} = sub { $self->gd_quit_event(); }
}

189 190 191 192
sub gd_quit_event {
    Bugzilla->job_queue->kill_worker();
    exit(1);
}
193

194 195 196 197 198 199 200 201
sub gd_other_cmd {
    my ($self, $do, $locked) = @_;
    if ($do eq "once") {
        $self->{_run_command} = 'work_once';
    } elsif ($do eq "onepass") {
        $self->{_run_command} = 'work_until_done';
    } else {
        $self->SUPER::gd_other_cmd($do, $locked);
202 203 204
    }
}

205 206
sub gd_run {
    my $self = shift;
207
    $self->_do_work($self->{_run_command});
208 209 210 211 212
}

sub _do_work {
    my ($self, $fn) = @_;

213 214
    my $jq = Bugzilla->job_queue();
    $jq->set_verbose($self->{debug});
215
    $jq->set_pidfile($self->{gd_pidfile});
216
    foreach my $module (values %{ Bugzilla::JobQueue->job_map() }) {
217 218 219
        eval "use $module";
        $jq->can_do($module);
    }
220
    $jq->$fn;
221 222 223 224 225 226 227 228 229 230 231
}

1;

__END__

=head1 NAME

Bugzilla::JobQueue::Runner - A class representing the daemon that runs the
job queue.

232 233
=head1 SYNOPSIS

234 235 236 237 238 239 240
 use Bugzilla::JobQueue::Runner;
 Bugzilla::JobQueue::Runner->new();

=head1 DESCRIPTION

This is a subclass of L<Daemon::Generic> that is used by L<jobqueue>
to run the Bugzilla job queue.
241 242 243 244 245 246 247 248 249 250 251

=head1 B<Methods in need of POD>

=over

=item gd_check

=item gd_run

=item gd_can_install

252 253
=item gd_quit_event

254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
=item gd_other_cmd

=item gd_more_opt

=item gd_postconfig

=item gd_usage

=item gd_getopt

=item gd_preconfig

=item gd_can_uninstall

=item gd_setup_signals

=back