summaryrefslogtreecommitdiffstats
path: root/Bugzilla/JobQueue/Runner.pm
blob: 113193cd8514c37671f63eb892ba68f96914a29b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
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
141
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
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
# 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/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

# 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;

use 5.14.0;
use strict;
use warnings;

use Cwd qw(abs_path);
use File::Basename;
use File::Copy;
use Pod::Usage;

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

our $VERSION = BUGZILLA_VERSION;

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

# 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.
sub gd_preconfig {
    my $self = shift;

    $self->{_run_command} = 'subprocess_worker';
    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},
        'j=s@'      => \$self->{gd_args}{job_name},
    );
}

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

sub gd_can_install {
    my $self = shift;

    my $source_file;
    if ( -e "/etc/SuSE-release" ) {
        $source_file = "contrib/$initscript.suse";
    } else {
        $source_file = "contrib/$initscript.rhel";
    }
    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"
# This user must have write access to Bugzilla's data/ directory.
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(@_);
}

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";
}

sub gd_setup_signals {
    my $self = shift;
    $self->SUPER::gd_setup_signals();
    $SIG{TERM} = sub { $self->gd_quit_event(); }
}

sub gd_quit_event {
    Bugzilla->job_queue->kill_worker();
    exit(1);
}

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);
    }
}

sub gd_run {
    my $self = shift;
    $self->_do_work($self->{_run_command});
}

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

    my @job_name = @{ $self->{gd_args}{job_name} // [] };
    my $jq = Bugzilla->job_queue();
    $jq->set_verbose($self->{debug});
    $jq->set_pidfile($self->{gd_pidfile});
    while (my ($key, $module) = each %{ Bugzilla::JobQueue->job_map() }) {
        next if @job_name and ! grep { $_ eq $key } @job_name;
        eval "use $module";
        $jq->can_do($module);
    }
    $jq->$fn;
}

1;

__END__

=head1 NAME

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

=head1 SYNOPSIS

 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.

=head1 B<Methods in need of POD>

=over

=item gd_check

=item gd_run

=item gd_can_install

=item gd_quit_event

=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