|
| 1 | +# Copyright (C) 2025 SUSE LLC |
| 2 | +# |
| 3 | +# This program is free software; you can redistribute it and/or modify |
| 4 | +# it under the terms of the GNU General Public License as published by |
| 5 | +# the Free Software Foundation; either version 2 of the License, or |
| 6 | +# (at your option) any later version. |
| 7 | +# |
| 8 | +# This program is distributed in the hope that it will be useful, |
| 9 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 10 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 11 | +# GNU General Public License for more details. |
| 12 | +# |
| 13 | +# You should have received a copy of the GNU General Public License along |
| 14 | +# with this program; if not, see <http://www.gnu.org/licenses/>. |
| 15 | + |
| 16 | +package MirrorCache::Task::Exec; |
| 17 | +use diagnostics; |
| 18 | +use IPC::Open3; |
| 19 | +use IO::Select; |
| 20 | +use Symbol 'gensym'; |
| 21 | +use POSIX ":sys_wait_h"; |
| 22 | + |
| 23 | +use Mojo::Base 'Mojolicious::Plugin'; |
| 24 | +use MirrorCache::Utils 'datetime_now'; |
| 25 | + |
| 26 | +# Exec command will execute bash commands provided by argv |
| 27 | +# |
| 28 | +# Examples to print string "1 2" |
| 29 | +# perl: |
| 30 | +# my $res = $minion->enqueue(exec => ("echo 1 2")); |
| 31 | +# my $res = $minion->enqueue(exec => ("echo", 1, 2)); |
| 32 | +# my %args = (CMD => 'echo 1', DESC => 'Command that prints "1 2"', TIMEOUT => 1200, LOCK => 'mylock', LOCK_TIMEOUT => 60); |
| 33 | +# my $res = $minion->enqueue(exec => (\%args, 1, 2)); |
| 34 | +# |
| 35 | +# shell: |
| 36 | +# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '["echo 1 2"]' |
| 37 | +# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '["echo", 1, 2]' |
| 38 | +# /usr/share/mirrorcache/script/mirrorcache minion job -e exec -q myqueue -a '[{"CMD":"echo","DESC":"Command that prints","LOCK":"mylock"}, 1, 2]' |
| 39 | + |
| 40 | +sub register { |
| 41 | + my ($self, $app) = @_; |
| 42 | + $app->minion->add_task(exec => sub { _run($app, @_) }); |
| 43 | +} |
| 44 | + |
| 45 | +sub _run { |
| 46 | + my ($app, $job, $arg0, @argv) = @_; |
| 47 | + my $minion = $app->minion; |
| 48 | + |
| 49 | + return $job->finish('No command provided') unless $arg0; |
| 50 | + |
| 51 | + my ($cmd, $cmdline, $desc, $timeout, $lockname, $locktimeout, @args); |
| 52 | + |
| 53 | + if (ref $arg0 eq "HASH") { |
| 54 | + $cmdline = $arg0->{CMD}; |
| 55 | + $desc = $arg0->{DESC}; |
| 56 | + $timeout = $arg0->{TIMEOUT}; |
| 57 | + $lockname = $arg0->{LOCK}; |
| 58 | + $locktimeout = $arg0->{LOCK_TIMEOUT}; |
| 59 | + } else { |
| 60 | + $cmdline = $arg0; |
| 61 | + } |
| 62 | + |
| 63 | + my @cmdline = split(/\s/, $cmdline, 2); |
| 64 | + if (scalar(@cmdline) > 1) { |
| 65 | + $cmd = $cmdline[0]; |
| 66 | + } else { |
| 67 | + $cmd = $cmdline; |
| 68 | + } |
| 69 | + $desc = $desc // "Command $cmd"; |
| 70 | + $timeout = $timeout // 1200; |
| 71 | + $lockname = $lockname // "EXEC_LOCK_$cmd"; |
| 72 | + $locktimeout = $locktimeout // $timeout; |
| 73 | + |
| 74 | + return $job->finish("Cannot lock $lockname") |
| 75 | + unless my $guard = $minion->guard($lockname, $locktimeout); |
| 76 | + |
| 77 | + my $pid; |
| 78 | + my ($infh,$outfh,$errfh); |
| 79 | + $errfh = gensym(); |
| 80 | + |
| 81 | + my $success = 0; |
| 82 | + my $error; |
| 83 | + eval { |
| 84 | + $pid = open3($infh, $outfh, $errfh, $cmdline); |
| 85 | + $success = 1; |
| 86 | + }; |
| 87 | + return $job->fail("open3: $@") unless $success; |
| 88 | + close($infh); |
| 89 | + |
| 90 | + my $sel = new IO::Select; |
| 91 | + $sel->add($outfh, $errfh); |
| 92 | + |
| 93 | + my $start_time = time; |
| 94 | + $job->note(pid => $pid, start_time => $start_time, cmdline => $cmdline); |
| 95 | + |
| 96 | + while(1) { |
| 97 | + $! = 0; |
| 98 | + my @ready = $sel->can_read(5); |
| 99 | + my $last = 0; |
| 100 | + if ($!) { # error |
| 101 | + $job->note(error_code => $!); |
| 102 | + print STDERR "ERRR: $!\n"; |
| 103 | + waitpid(-1, WNOHANG); |
| 104 | + $last = 1; |
| 105 | + } |
| 106 | + my $curr_time = time; |
| 107 | + my (@lines, @elines); |
| 108 | + foreach my $fh (@ready) { |
| 109 | + my $line = <$fh>; |
| 110 | + if(not defined $line){ |
| 111 | + $sel->remove($fh); |
| 112 | + next; |
| 113 | + } |
| 114 | + chomp($line); |
| 115 | + if($fh == $outfh) { |
| 116 | + push @lines, $line; |
| 117 | + } elsif($fh == $errfh) {# do the same for errfh |
| 118 | + push @elines, $line; |
| 119 | + } |
| 120 | + } |
| 121 | + $job->note("$curr_time O" => @lines) if @lines; |
| 122 | + $job->note("$curr_time E" => @elines) if @elines; |
| 123 | + |
| 124 | + my $x = waitpid($pid, WNOHANG); |
| 125 | + if ($x < 0) { |
| 126 | + $job->note(finished => $pid); |
| 127 | + $last = 1; |
| 128 | + } |
| 129 | + |
| 130 | + last if $last; |
| 131 | + if (($curr_time - $start_time) >= $timeout) { |
| 132 | + waitpid(-1, WNOHANG); |
| 133 | + return $job->fail("timeout expired!"); |
| 134 | + } |
| 135 | + } |
| 136 | + |
| 137 | + waitpid(-1, WNOHANG); |
| 138 | + return $job->finish('finish'); |
| 139 | +} |
| 140 | + |
| 141 | +1; |
0 commit comments