File Coverage

lib/Rex/TaskList/Base.pm
Criterion Covered Total %
statement 181 221 81.9
branch 52 72 72.2
condition 27 45 60.0
subroutine 33 37 89.1
pod 0 20 0.0
total 293 395 74.1


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             package Rex::TaskList::Base;
6              
7 56     56   890 use v5.12.5;
  56         283  
8 56     56   324 use warnings;
  56         119  
  56         2899  
9              
10             our $VERSION = '1.14.2.2'; # TRIAL VERSION
11              
12             BEGIN {
13 56     56   546 use Rex::Shared::Var;
  56         140  
  56         3703  
14 56     56   444 share qw(@SUMMARY);
15             }
16              
17 56     56   386 use Data::Dumper;
  56         140  
  56         3275  
18 56     56   429 use Rex::Logger;
  56         125  
  56         526  
19 56     56   1534 use Rex::Task;
  56         160  
  56         605  
20 56     56   1808 use Rex::Config;
  56         102  
  56         400  
21 56     56   401 use Rex::Interface::Executor;
  56         789  
  56         365  
22 56     56   1573 use Rex::Fork::Manager;
  56         163  
  56         659  
23 56     56   1868 use Rex::Report;
  56         142  
  56         347  
24 56     56   2085 use Rex::Group;
  56         224  
  56         676  
25 56     56   1960 use Time::HiRes qw(time);
  56         133  
  56         301  
26 56     56   5441 use POSIX qw(floor);
  56         512  
  56         2281  
27              
28             sub new {
29 61     61 0 160 my $that = shift;
30 61   33     428 my $proto = ref($that) || $that;
31 61         195 my $self = {@_};
32              
33 61         165 bless( $self, $proto );
34              
35 61         342 $self->{IN_TRANSACTION} = 0;
36 61         763 $self->{DEFAULT_AUTH} = Rex::Config->get_default_auth();
37 61         205 $self->{tasks} = {};
38              
39 61         272 return $self;
40             }
41              
42             sub create_task {
43 212     212 0 459 my $self = shift;
44 212         426 my $task_name = shift;
45 212         351 my $options = pop;
46 212         387 my $desc = pop;
47              
48 212 50       607 if ( exists $self->{tasks}->{$task_name} ) {
49 0         0 Rex::Logger::info( "Task $task_name already exists. Overwriting...",
50             "warn" );
51             }
52              
53 212         824 Rex::Logger::debug("Creating task: $task_name");
54              
55 212         336 my $func;
56 212 50       743 if ( ref($desc) eq "CODE" ) {
57 0         0 $func = $desc;
58 0         0 $desc = "";
59             }
60             else {
61 212         376 $func = pop;
62             }
63              
64             # matching against a task count of 2 because of the two internal tasks (filtered below)
65 212 100       338 if ( ( scalar( keys %{ $self->{tasks} } ) ) == 2 ) {
  212         846  
66 39         416 my $requested_env = Rex::Config->get_environment;
67 39         523 my @environments = Rex::Commands->get_environments;
68              
69 39 50 33     609 if ( $task_name ne 'Commands:Box:get_sys_info'
      33        
      33        
70             && $task_name ne 'Test:run'
71             && $requested_env ne ''
72 0         0 && !grep { $_ eq $requested_env } @environments )
73             {
74 0         0 Rex::Logger::info(
75             "Environment '$requested_env' has been requested, but it could not be found in the Rexfile. This is most likely only by mistake.",
76             'warn'
77             );
78 0         0 Rex::Logger::info(
79             "If it is intentional, you can suppress this warning by specifying an empty environment: environment '$requested_env' => sub {};",
80             'warn'
81             );
82             }
83             }
84              
85 212         473 my @server = ();
86              
87 212 100       478 if ($::FORCE_SERVER) {
88              
89 1 50       3 if ( ref $::FORCE_SERVER eq "ARRAY" ) {
90 0         0 my $group_name_arr = $::FORCE_SERVER;
91              
92 0         0 for my $group_name ( @{$group_name_arr} ) {
  0         0  
93 0 0       0 if ( !Rex::Group->is_group($group_name) ) {
94 0         0 Rex::Logger::debug("Using late group-lookup");
95              
96             push @server, sub {
97 0 0   0   0 if ( !Rex::Group->is_group($group_name) ) {
98 0         0 Rex::Logger::info( "No group $group_name defined.", "error" );
99 0         0 exit 1;
100             }
101              
102             return
103 0         0 map { Rex::Group::Entry::Server->new( name => $_ )->get_servers; }
  0         0  
104             Rex::Group->get_group($group_name);
105 0         0 };
106             }
107             else {
108              
109             push( @server,
110 0         0 map { Rex::Group::Entry::Server->new( name => $_ ); }
  0         0  
111             Rex::Group->get_group($group_name) );
112              
113             }
114             }
115             }
116             else {
117 1         7 my @servers = split( /\s+/, $::FORCE_SERVER );
118             push( @server,
119 1         4 map { Rex::Group::Entry::Server->new( name => $_ ); } @servers );
  2         11  
120              
121 1         35 Rex::Logger::debug("\tserver: $_") for @server;
122             }
123              
124             }
125              
126             else {
127              
128 211 100       656 if ( scalar(@_) >= 1 ) {
129 13 100       67 if ( $_[0] eq "group" ) {
130 6         10 my $groups;
131 6 100       14 if ( ref( $_[1] ) eq "ARRAY" ) {
132 1         2 $groups = $_[1];
133             }
134             else {
135 5         20 $groups = [ $_[1] ];
136             }
137              
138 6         12 for my $group ( @{$groups} ) {
  6         14  
139 7 100       30 if ( Rex::Group->is_group($group) ) {
140 6         18 my @group_server = Rex::Group->get_group($group);
141              
142             # check if the group is empty. this is mostly due to a failure.
143             # so report it, and exit.
144 6 50 33     22 if ( scalar @group_server == 0
145             && Rex::Config->get_allow_empty_groups() == 0 )
146             {
147 0         0 Rex::Logger::info(
148             "The group $group is empty. This is mostly due to a failure.",
149             "warn" );
150 0         0 Rex::Logger::info(
151             "If this is an expected behaviour, please add the feature flag 'empty_groups'.",
152             "warn"
153             );
154 0         0 CORE::exit(1);
155             }
156 6         19 push( @server, @group_server );
157             }
158             else {
159 1         4 Rex::Logger::debug("Using late group-lookup");
160              
161             push @server, sub {
162 1 50   1   7 if ( !Rex::Group->is_group($group) ) {
163 0         0 Rex::Logger::info( "No group $group defined.", "error" );
164 0         0 exit 1;
165             }
166              
167             return map {
168 1 50 33     7 if ( ref $_ && $_->isa("Rex::Group::Entry::Server") ) {
  1         11  
169 1         6 $_->get_servers;
170             }
171             else {
172 0         0 Rex::Group::Entry::Server->new( name => $_ )->get_servers;
173             }
174             } Rex::Group->get_group($group);
175 1         10 };
176              
177             }
178             }
179             }
180             else {
181 7         29 for my $entry (@_) {
182 10 100 66     125 push(
183             @server,
184             (
185             ref $entry && $entry->isa("Rex::Group::Entry::Server")
186             ? $entry
187             : Rex::Group::Entry::Server->new( name => $entry )
188             )
189             );
190             }
191             }
192             }
193              
194             }
195              
196             my %task_hash = (
197             func => $func,
198             server => [@server],
199             desc => $desc,
200             no_ssh => ( $options->{"no_ssh"} ? 1 : 0 ),
201             hidden => ( $options->{"dont_register"} ? 1 : 0 ),
202             exit_on_connect_fail => (
203             exists $options->{exit_on_connect_fail}
204             ? $options->{exit_on_connect_fail}
205 212 100       2190 : 1
    100          
    100          
206             ),
207             before => [],
208             after => [],
209             around => [],
210             after_task_finished => [],
211             before_task_start => [],
212             name => $task_name,
213             executor => Rex::Interface::Executor->create,
214             connection_type => Rex::Config->get_connection_type,
215             );
216              
217 212 100       909 if ( $self->{DEFAULT_AUTH} ) {
218             $task_hash{auth} = {
219 207   50     1189 user => Rex::Config->get_user || undef,
      100        
      100        
      100        
      50        
220             password => Rex::Config->get_password || undef,
221             private_key => Rex::Config->get_private_key || undef,
222             public_key => Rex::Config->get_public_key || undef,
223             sudo_password => Rex::Config->get_sudo_password || undef,
224             };
225             }
226              
227 212 100       871 if ( exists $Rex::Commands::auth_late{$task_name} ) {
228 1         4 $task_hash{auth} = $Rex::Commands::auth_late{$task_name};
229             }
230              
231 212         2202 $self->{tasks}->{$task_name} = Rex::Task->new(%task_hash);
232              
233 212         1479 return $self->{tasks}->{$task_name};
234             }
235              
236             sub get_tasks {
237 17     17 0 93 my $self = shift;
238 60         219 return grep { $self->{tasks}->{$_}->hidden() == 0 }
239 17         50 sort { $a cmp $b } keys %{ $self->{tasks} };
  71         213  
  17         160  
240             }
241              
242             sub get_all_tasks {
243 96     96 0 1872 my $self = shift;
244 96         169 my $regexp = shift;
245              
246 247         1605 return grep { $_ =~ $regexp }
247 96         154 keys %{ $self->{tasks} };
  96         470  
248             }
249              
250             sub get_tasks_for {
251 2     2 0 5 my $self = shift;
252 2         6 my $host = shift;
253              
254 2         3 my @tasks;
255 2         3 for my $task_name ( keys %{ $self->{tasks} } ) {
  2         9  
256 6         11 my @servers = @{ $self->{tasks}->{$task_name}->server() };
  6         20  
257              
258 6 100 66     15 if ( ( grep { /^$host$/ } @servers ) || $#servers == -1 ) {
  28         108  
259 3         56 push @tasks, $task_name;
260             }
261             }
262              
263 2         18 my @ret = sort { $a cmp $b } @tasks;
  1         6  
264 2         11 return @ret;
265             }
266              
267             sub get_task {
268 273     273 0 3825 my ( $self, $task ) = @_;
269 273         992 return $self->{tasks}->{$task};
270             }
271              
272             sub clear_tasks {
273 7     7 0 11 my $self = shift;
274 7         51 $self->{tasks} = {};
275             }
276              
277             sub get_desc {
278 1     1 0 3 my $self = shift;
279 1         3 my $task = shift;
280              
281 1         7 return $self->{tasks}->{$task}->desc();
282             }
283              
284             sub is_task {
285 192     192 0 359 my $self = shift;
286 192         299 my $task = shift;
287              
288 192 100       526 if ( exists $self->{tasks}->{$task} ) { return 1; }
  126         438  
289 66         233 return 0;
290             }
291              
292 16     16 0 179 sub current_task { shift->{__current_task__} }
293              
294             sub run {
295 138     138 0 758 my ( $self, $task, %options ) = @_;
296              
297 138 50       928 if ( !ref $task ) {
298 0         0 $task = Rex::TaskList->create()->get_task($task);
299             }
300              
301 138         1818 my $fm = Rex::Fork::Manager->new( max => $self->get_thread_count($task) );
302 138         810 my $all_servers = $task->server;
303              
304 138         948 for my $server (@$all_servers) {
305 138         966 my $child_coderef = $self->build_child_coderef( $task, $server, %options );
306              
307 138 100       1249 if ( $self->{IN_TRANSACTION} ) {
308              
309             # Inside a transaction -- no forking and no chance to get zombies.
310             # This only happens if someone calls do_task() from inside a transaction.
311 3         10 $child_coderef->();
312             }
313             else {
314             # Not inside a transaction, so lets fork
315             # Add $forked_sub to the fork queue
316 135         1048 $fm->add($child_coderef);
317             }
318             }
319              
320 107         5453 Rex::Logger::debug("Waiting for children to finish");
321 107         1598 my $ret = $fm->wait_for_all;
322 107         3906 Rex::reconnect_lost_connections();
323              
324 107         18624 return $ret;
325             }
326              
327             sub build_child_coderef {
328 138     138 0 647 my ( $self, $task, $server, %options ) = @_;
329              
330             return sub {
331 32     32   2921 Rex::Logger::init();
332 32         1382 Rex::Logger::info( "Running task " . $task->name . " on $server" );
333              
334 32         211 my $return_value = eval {
335             $task->clone->run(
336             $server,
337             in_transaction => $self->{IN_TRANSACTION},
338             params => $options{params},
339             args => $options{args},
340 32         919 );
341             };
342              
343 32 100       844 if ( $self->{IN_TRANSACTION} ) {
344 3 100       228 die $@ if $@;
345             }
346             else {
347 29         226 my $e = $@;
348 29 100 100     290 my $exit_code = $@ ? ( $? || 1 ) : 0;
349              
350 29         322 push @SUMMARY,
351             {
352             task => $task->name,
353             server => $server->to_s,
354             exit_code => $exit_code,
355             error_message => $e,
356             };
357             }
358              
359 30         377 Rex::Logger::debug("Destroying all cached os information");
360 30         381 Rex::Logger::shutdown();
361              
362 30         304 return $return_value;
363 138         2654 };
364             }
365              
366             sub modify {
367 85     85 0 262 my ( $self, $type, $task, $code, $package, $file, $line ) = @_;
368              
369 85 100 100     443 if ( $package ne "main" && $package ne "Rex::CLI" ) {
370 5 100       26 if ( $task !~ m/:/ ) {
371              
372             #do we need to detect for base -Rex ?
373 4         9 $package =~ s/^Rex:://;
374             }
375             }
376              
377 85         247 $package =~ s/::/:/g;
378              
379 85         305 my @all_tasks = map { $self->get_task($_); } grep {
380 85 100 100     238 if ( $package ne "main" && $package ne "Rex:CLI" ) {
  85         399  
381 5         46 $_ =~ m/^\Q$package\E:/;
382             }
383             else {
384 80         192 $_;
385             }
386             } $self->get_all_tasks($task);
387              
388 85 50       296 if ( !@all_tasks ) {
389 0         0 Rex::Logger::info(
390             "Can't add $type $task, as it is not yet defined\nsee $file line $line");
391 0         0 return;
392             }
393              
394 85         183 for my $taskref (@all_tasks) {
395 85         296 $taskref->modify( $type => $code );
396             }
397             }
398              
399             sub set_default_auth {
400 0     0 0 0 my ( $self, $auth ) = @_;
401 0         0 $self->{DEFAULT_AUTH} = $auth;
402             }
403              
404             sub is_default_auth {
405 1     1 0 2 my ($self) = @_;
406 1         17 return $self->{DEFAULT_AUTH};
407             }
408              
409             sub set_in_transaction {
410 6     6 0 29 my ( $self, $val ) = @_;
411 6         301 $self->{IN_TRANSACTION} = $val;
412             }
413              
414             sub is_transaction {
415 0     0 0 0 my ($self) = @_;
416 0         0 return $self->{IN_TRANSACTION};
417             }
418              
419             sub get_exit_codes {
420 0     0 0 0 my ($self) = @_;
421 0         0 return map { $_->{exit_code} } @SUMMARY;
  0         0  
422             }
423              
424             sub get_thread_count {
425 138     138 0 607 my ( $self, $task ) = @_;
426 138   33     2394 my $threads = $task->parallelism || Rex::Config->get_parallelism;
427 138         578 my $server_count = scalar @{ $task->server };
  138         2246  
428              
429 138 50       1435 return $1 if $threads =~ /^(\d+)$/;
430 0 0       0 return floor( $server_count / $1 ) if $threads =~ /^max\s?\/(\d+)$/;
431 0 0       0 return floor( $server_count * $1 / 100 ) if $threads =~ /^max (\d+)%$/;
432 0 0       0 return $server_count if $threads eq 'max';
433              
434 0         0 Rex::Logger::info(
435             "Unrecognized thread count requested: '$threads'. Falling back to a single thread.",
436             'warn'
437             );
438 0         0 return 1;
439             }
440              
441 51     51 0 2079 sub get_summary { @SUMMARY }
442              
443             1;