From 56fc475a3dbd22ba825a49cadf4b91743a9d065f Mon Sep 17 00:00:00 2001 From: Dylan William Hardison Date: Tue, 27 Nov 2018 12:52:21 -0500 Subject: [PATCH] Bug 1508201 - Add framework for running background tasks --- Bugzilla/Job/RunTask.pm | 72 +++++++++++++++++++++++++++++++++++++++++ Bugzilla/JobQueue.pm | 12 +++++++ Bugzilla/Task.pm | 57 ++++++++++++++++++++++++++++++++ Bugzilla/Types.pm | 3 +- 4 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 Bugzilla/Job/RunTask.pm create mode 100644 Bugzilla/Task.pm diff --git a/Bugzilla/Job/RunTask.pm b/Bugzilla/Job/RunTask.pm new file mode 100644 index 000000000..54cc74c14 --- /dev/null +++ b/Bugzilla/Job/RunTask.pm @@ -0,0 +1,72 @@ +# 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. + +package Bugzilla::Job::RunTask; + +use 5.10.1; +use strict; +use warnings; + +use parent 'TheSchwartz::Worker'; + +use constant grab_for => 300; +use constant max_retries => 0; + +use Bugzilla::Logging; +use Module::Runtime qw(require_module); +use Scalar::Util qw(blessed); +use Email::MIME; +use Bugzilla::Mailer qw(MessageToMTA); + +sub work { + my ($class, $job) = @_; + my $task = $job->arg; + my $task_class = blessed($task) // ''; + die "Invalid task class: $task_class" + unless $task_class =~ /^Bugzilla::Task::/; + require_module($task_class); + + my $template = Bugzilla->template; + my $vars = $task->run(); + my $name = $task->name; + my $html = ""; + my $ok = $template->process("email/task/$name.html.tmpl", $vars, \$html); + + unless ($ok) { + FATAL($template->error); + $html = "Something went run running task '$name'"; + } + + my @parts = ( + Email::MIME->create( + attributes => { + content_type => 'text/html', + charset => 'UTF-8', + encoding => 'quoted-printable', + }, + body_str => $html, + ), + ); + + my $email = Email::MIME->create( + header_str => [ + From => Bugzilla->params->{'mailfrom'}, + To => $task->user->email, + Subject => $task->subject, + 'X-Bugzilla-Type' => 'task', + ], + parts => [@parts], + ); + + # We're already in the jobqueue, so send right away. + MessageToMTA($email, 1); + + $job->completed; +} + + +1; diff --git a/Bugzilla/JobQueue.pm b/Bugzilla/JobQueue.pm index a78a4d0ae..9214d87e4 100644 --- a/Bugzilla/JobQueue.pm +++ b/Bugzilla/JobQueue.pm @@ -20,12 +20,15 @@ use IO::Async::Timer::Periodic; use IO::Async::Loop; use Future; use base qw(TheSchwartz); +use Bugzilla::Types qw(Task); +use Type::Params qw( compile Invocant ); # This maps job names for Bugzilla::JobQueue to the appropriate modules. # If you add new types of jobs, you should add a mapping here. use constant JOB_MAP => { send_mail => 'Bugzilla::Job::Mailer', bug_mail => 'Bugzilla::Job::BugMail', + run_task => 'Bugzilla::Job::RunTask', }; # Without a driver cache TheSchwartz opens a new database connection @@ -77,6 +80,15 @@ sub bz_databases { return map { $self->driver_for($_) } @hashes; } +sub run_task { + state $check = compile(Invocant, Task); + my ($self, $task) = $check->(@_); + + $task->prepare; + + return $self->insert(run_task => $task); +} + # inserts a job into the queue to be processed and returns immediately sub insert { my $self = shift; diff --git a/Bugzilla/Task.pm b/Bugzilla/Task.pm new file mode 100644 index 000000000..a5e9c9c1c --- /dev/null +++ b/Bugzilla/Task.pm @@ -0,0 +1,57 @@ +# 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. + +package Bugzilla::Task; +use 5.10.1; +use Moo::Role; + +use Bugzilla::Constants; +use Bugzilla::Logging; +use Bugzilla::Types qw(User); +use Types::Standard qw(Str); +use Type::Utils; +use Scalar::Util qw(blessed); +use Mojo::Util qw(decamelize); +use Try::Tiny; +use Capture::Tiny qw(capture_stdout); + +requires 'prepare', 'run', 'subject', '_build_estimated_duration'; + +my $Duration = class_type { class => 'DateTime::Duration' }; + +has 'user' => (is => 'ro', isa => User, required => 1); +has 'name' => (is => 'lazy', isa => Str, init_arg => undef); +has 'estimated_duration' => (is => 'lazy', isa => $Duration, init_arg => undef); + +around 'run' => sub { + my ($original_method, $self, @args) = @_; + my $scope = Bugzilla->set_user($self->user, scope_guard => 1); + Bugzilla->error_mode(ERROR_MODE_MOJO); + my $result; + try { + my $stdout = capture_stdout { + $result = $self->$original_method(@args); + }; + if ($stdout) { + FATAL("$self sent output to STDOUT: $stdout"); + } + } + catch { + $result = {error => $_}; + }; + return $result; +}; + +sub _build_name { + my ($self) = @_; + my $class = blessed($self); + my $pkg = __PACKAGE__; + $class =~ s/^\Q$pkg\E:://; + return decamelize($class); +} + +1; diff --git a/Bugzilla/Types.pm b/Bugzilla/Types.pm index 93d699f49..f85335bcb 100644 --- a/Bugzilla/Types.pm +++ b/Bugzilla/Types.pm @@ -13,7 +13,7 @@ use warnings; use Type::Library -base, - -declare => qw( Bug User Group Attachment Comment JSONBool ); + -declare => qw( Bug User Group Attachment Comment JSONBool Task ); use Type::Utils -all; use Types::Standard -types; @@ -23,5 +23,6 @@ class_type Group, { class => 'Bugzilla::Group' }; class_type Attachment, { class => 'Bugzilla::Attachment' }; class_type Comment, { class => 'Bugzilla::Comment' }; class_type JSONBool, { class => 'JSON::PP::Boolean' }; +role_type Task, { role => 'Bugzilla::Task' }; 1; -- 2.47.3