File Coverage

blib/lib/cPanel/TaskQueue/Task.pm
Criterion Covered Total %
statement 122 122 100.0
branch 68 68 100.0
condition 15 15 100.0
subroutine 26 26 100.0
pod 20 20 100.0
total 251 251 100.0


line stmt bran cond sub pod time code
1             package cPanel::TaskQueue::Task;
2             {
3             $cPanel::TaskQueue::Task::VERSION = '0.606';
4             }
5              
6             # cpanel - cPanel/TaskQueue/Task.pm Copyright(c) 2014 cPanel, Inc.
7             # All rights Reserved.
8             # copyright@cpanel.net http://cpanel.net
9             #
10             # Redistribution and use in source and binary forms, with or without
11             # modification, are permitted provided that the following conditions are met:
12             # * Redistributions of source code must retain the above copyright
13             # notice, this list of conditions and the following disclaimer.
14             # * Redistributions in binary form must reproduce the above copyright
15             # notice, this list of conditions and the following disclaimer in the
16             # documentation and/or other materials provided with the distribution.
17             # * Neither the name of the owner nor the names of its contributors may
18             # be used to endorse or promote products derived from this software
19             # without specific prior written permission.
20             #
21             # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
22             # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23             # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
24             # DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY
25             # DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
26             # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27             # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
28             # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29             # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
30             # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31              
32 43     43   270550 use strict;
  43         172  
  43         93438  
33              
34             #use warnings;
35              
36             # Namespace for the ids created by this class.
37             my $task_uuid = 'TaskQueue-Task';
38              
39             my @fields = qw/_command _argstring _args _timestamp _uuid _child_timeout _started _pid _retries _userdata/;
40              
41             # These methods are intended to help document the importance of the message and to supply 'seam' that
42             # could be used to modify the logging behavior of the TaskQueue.
43             sub _throw {
44 44     44   72 my $class = shift;
45 44         414 die @_;
46             }
47              
48             # Not using _warn or _info, so don't define them.
49              
50             sub new {
51 151     151 1 16772 my ( $class, $args ) = @_;
52              
53 151 100       688 $class->_throw('Missing arguments') unless defined $args;
54 150 100       457 $class->_throw('Args parameter must be a hash ref.') unless 'HASH' eq ref $args;
55 149 100 100     1201 $class->_throw('Missing command string.') unless exists $args->{cmd} and $args->{cmd} =~ /\S/;
56 147 100 100     655 $class->_throw('Invalid Namespace UUID.') if exists $args->{nsid} and !_is_valid_ns( $args->{nsid} );
57 144 100       444 $class->_throw('Invalid id.') unless _is_pos_int( $args->{id} );
58              
59 141 100       728 my $uuid = _make_name_based_uuid(
60             exists $args->{nsid} ? $args->{nsid} : $task_uuid,
61             $args->{id}
62             );
63              
64 141         222 my $timeout = -1;
65 141 100       668 if ( exists $args->{timeout} ) {
66 107         164 $timeout = $args->{timeout};
67 107 100       218 $class->_throw('Invalid child timeout.') unless _is_pos_int($timeout);
68             }
69 138         388 my $retries = 1;
70 138 100       381 if ( exists $args->{retries} ) {
71 5         13 $retries = $args->{retries};
72 5 100       15 $class->_throw('Invalid value for retries.') unless _is_pos_int($retries);
73             }
74 137         230 my $userdata = {};
75 137 100       370 if ( exists $args->{userdata} ) {
76 4         16 $class->_verify_userdata_arg( $args->{userdata} );
77 2         4 $userdata = { %{ $args->{userdata} } };
  2         204  
78             }
79              
80 135         491 my ( $command, $argstring ) = split( /\s+/, $args->{cmd}, 2 );
81 135 100       369 $argstring = '' unless defined $argstring;
82              
83             # recognizes simple args, quoted args, and quoted args with escaped quotes.
84 135         2048 my @args = ( $argstring =~ m/('(?: \\' | [^'] )*' | "(?: \\" | [^"] )*" | \S+ )/xg );
85 135         306 foreach my $arg (@args) {
86              
87             # remove quotes and escapes.
88 210         758 $arg =~ s/^['"]//;
89 210         394 $arg =~ s/["']$//;
90 210         478 $arg =~ s/\\(['"])/$1/g;
91             }
92              
93 135         2118 return bless {
94             _command => $command,
95             _argstring => $argstring,
96             _args => \@args,
97             _timestamp => time,
98             _uuid => $uuid,
99             _child_timeout => $timeout,
100             _started => undef,
101             _pid => undef,
102             _retries => $retries,
103             _userdata => $userdata,
104             }, $class;
105             }
106              
107             # Validate supplied hash bless into class if valid
108             sub reconstitute {
109 32     32 1 7269 my ( $class, $hash ) = @_;
110              
111 32 100       84 return unless defined $hash;
112 31 100       104 return $hash if ref $hash eq $class;
113 26 100       76 $class->_throw('Argument is not a hash reference.') unless ref {} eq ref $hash;
114              
115 24         47 foreach my $field (@fields) {
116 146 100       329 $class->_throw("Missing '$field' field in supplied hash") unless exists $hash->{$field};
117 135 100 100     480 next if $field eq '_pid' or $field eq '_started';
118 116 100       274 $class->_throw("Field '$field' has no value") unless defined $hash->{$field};
119             }
120 5 100       26 $class->_throw(q{The '_args' field must be an array}) unless ref [] eq ref $hash->{_args};
121              
122 3         5 my %object;
123 3         8 foreach my $field (@fields) {
124 30 100       67 if ( ref [] eq ref $hash->{$field} ) {
125 3         5 $object{$field} = [ @{ $hash->{$field} } ];
  3         9  
126             }
127             else {
128 27         60 $object{$field} = $hash->{$field};
129             }
130             }
131              
132 3         141 return bless \%object, $class;
133             }
134              
135             # Make a copy of the task description.
136             # Makes a one-level deep copy of the hash. If this structure is ever extended
137             # to support more complex attributes, this method will need to change.
138             #
139             # Returns the clone.
140             sub clone {
141 113     113 1 190 my $self = shift;
142              
143 113         152 my $new = bless { %{$self} }, __PACKAGE__;
  113         1367  
144              
145             # Don't add lexical in for, changing in place.
146 113         275 foreach ( grep { ref $_ } values %{$new} ) {
  1130         1889  
  113         385  
147 226 100       749 if ( ref [] eq ref $_ ) {
148 113         165 $_ = [ @{$_} ];
  113         416  
149             }
150             }
151 113         616 return $new;
152             }
153              
154             # Make a copy of the task description with changes.
155             # Makes a one-level deep copy of the hash. If this structure is ever extended
156             # to support more complex attributes, this method will need to change.
157             #
158             # Returns the modified clone.
159             sub mutate {
160 8     8 1 2719 my $self = shift;
161 8         31 my %parms = %{ shift() };
  8         60  
162              
163 8         26 my $new = $self->clone();
164              
165 8 100       26 if ( exists $parms{timeout} ) {
166 2 100       9 $self->_throw('Invalid child timeout.') unless _is_pos_int( $parms{timeout} );
167 1         3 $new->{_child_timeout} = $parms{timeout};
168             }
169 7 100       20 if ( exists $parms{retries} ) {
170 2 100       7 $self->_throw('Invalid value for retries.') unless _is_pos_int( $parms{retries} );
171 1         3 $new->{_retries} = $parms{retries};
172             }
173 6 100       22 if ( exists $parms{userdata} ) {
174 3         9 $self->_verify_userdata_arg( $parms{userdata} );
175 1         2 while ( my ( $k, $v ) = each %{ $parms{userdata} } ) {
  3         13  
176 2         5 $new->{_userdata}->{$k} = $v;
177             }
178             }
179              
180 4         15 return $new;
181             }
182              
183             # Accessors
184 767     767 1 7754 sub command { return $_[0]->{_command}; }
185 6     6 1 1302 sub full_command { return "$_[0]->{_command} $_[0]->{_argstring}"; }
186 12     12 1 108 sub argstring { return $_[0]->{_argstring}; }
187 381     381 1 766 sub args { return @{ $_[0]->{_args} }; }
  381         11122  
188 1     1 1 7 sub timestamp { return $_[0]->{_timestamp}; }
189 321     321 1 4648 sub uuid { return $_[0]->{_uuid}; }
190 15     15 1 470 sub child_timeout { return $_[0]->{_child_timeout}; }
191 2     2 1 1141 sub started { return $_[0]->{_started}; }
192 82     82 1 1800 sub pid { return $_[0]->{_pid}; }
193 10     10 1 72 sub retries_remaining { return $_[0]->{_retries}; }
194              
195             sub get_userdata {
196 8     8 1 506 my $self = shift;
197 8         14 my $key = shift;
198 8 100       24 $self->_throw('No userdata key specified') unless defined $key;
199 7 100       34 return unless exists $self->{_userdata}->{$key};
200 5         28 return $self->{_userdata}->{$key};
201             }
202              
203             sub get_arg {
204 80     80 1 314 my ( $self, $index ) = @_;
205 80         349 return $self->{_args}->[$index];
206             }
207              
208 16     16 1 88 sub set_pid { $_[0]->{_pid} = $_[1]; return; }
  16         107  
209 23     23 1 70 sub begin { $_[0]->{_started} = time; return; }
  23         50  
210              
211             sub decrement_retries {
212 6     6 1 21 my $self = shift;
213 6 100       38 return unless $self->{_retries};
214 5         9 $self->{_retries}--;
215 5         12 return;
216             }
217              
218             # Utility methods
219              
220             # Create a UUID from the supplied namespace and name.
221             # Based on code in RFC 4122, verified against Data::UUID
222             sub _make_name_based_uuid {
223 141     141   235 my ( $nsid, $name ) = @_;
224              
225 141         697 return sprintf( 'TQ:%s:%s', $nsid, $name );
226             }
227              
228             #
229             # Returns true if the supplied parameter is a positive integer.
230             sub _is_pos_int {
231 260     260   596 my $val = shift;
232 260 100       516 return unless defined $val;
233 259 100       1220 return unless $val =~ /^\d+$/;
234 252         897 return $val > 0;
235             }
236              
237             sub _is_valid_ns {
238 121     121   219 my $val = shift;
239 121   100     1226 return defined $val && length $val && $val !~ /:/;
240             }
241              
242             sub is_valid_taskid {
243 64     64 1 100 my $val = shift;
244 64   100     686 return defined $val && $val =~ /^TQ:[^:]+:\d+$/;
245             }
246              
247             sub _verify_userdata_arg {
248 7     7   10 my $class = shift;
249 7         11 my $arg = shift;
250 7 100       25 $class->_throw('Expected a hash reference for userdata value.') unless 'HASH' eq ref $arg;
251 5         7 my @bad_keys;
252 5         8 while ( my ( $k, $v ) = each %{$arg} ) {
  20         60  
253 15 100       43 push @bad_keys, $k if ref $v;
254             }
255 5 100       16 if (@bad_keys) {
256 2         12 @bad_keys = sort @bad_keys;
257 2         11 $class->_throw("Reference values not allowed as userdata. Keys containing references: @bad_keys");
258             }
259 3         7 return;
260             }
261              
262             1;
263              
264             __END__