Subversion Repositories havirt

Rev

Rev 46 | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 46 Rev 47
Line 36... Line 36...
36
# added source node as a parameter for migrate. If source node is passed in, will not run readDB
36
# added source node as a parameter for migrate. If source node is passed in, will not run readDB
37
#
37
#
38
# v1.2.3 20260101 RWR
38
# v1.2.3 20260101 RWR
39
# modified executeAndWait to return 0 on timeout, 1 on success
39
# modified executeAndWait to return 0 on timeout, 1 on success
40
# bugfix in diffArray to handle case where arr1 is smaller than arr2
40
# bugfix in diffArray to handle case where arr1 is smaller than arr2
-
 
41
#
-
 
42
# v1.2.4 20260102 RWR
-
 
43
# Created centralized execute() function for all command execution
-
 
44
# Refactored all backtick command executions to use execute() for easier debugging and maintenance
-
 
45
# All 7 command execution points in havirt.pm now use execute()
41
 
46
 
42
package havirt;
47
package havirt;
43
 
48
 
44
use warnings;
49
use warnings;
45
use strict;  
50
use strict;  
Line 52... Line 57...
52
   use File::Basename;
57
   use File::Basename;
53
   use lib dirname( abs_path( __FILE__ ) );
58
   use lib dirname( abs_path( __FILE__ ) );
54
}
59
}
55
 
60
 
56
use Data::Dumper qw(Dumper); # Import the Dumper() subroutine
61
use Data::Dumper qw(Dumper); # Import the Dumper() subroutine
-
 
62
use YAML::Tiny;
57
 
63
 
58
# define the version number
64
# define the version number
59
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
65
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
60
use version;
66
use version;
61
our $VERSION = version->declare("1.2.2");
67
our $VERSION = version->declare("1.2.4");
62
 
68
 
63
 
69
 
64
use Exporter;
70
use Exporter;
65
 
71
 
66
our @ISA = qw( Exporter );
72
our @ISA = qw( Exporter );
Line 68... Line 74...
68
                  &readDB
74
                  &readDB
69
                  &writeDB
75
                  &writeDB
70
                  &report
76
                  &report
71
                  &scan
77
                  &scan
72
                  &makeCommand
78
                  &makeCommand
-
 
79
                  &execute
73
                  &forceScan
80
                  &forceScan
74
                  &executeAndWait
81
                  &executeAndWait
75
                  &findDomain
82
                  &findDomain
76
                  &diffArray
83
                  &diffArray
77
                  &makeConfig
84
                  &makeConfig
Line 96... Line 103...
96
   while ( $lock && -f $lockFileName && $lockTime-- ) {
103
   while ( $lock && -f $lockFileName && $lockTime-- ) {
97
      sleep 1; # wait one second, then try again
104
      sleep 1; # wait one second, then try again
98
   }
105
   }
99
   if ( $lock ) {
106
   if ( $lock ) {
100
      die "Something has $main::config->{'status db filename'} locked, aborting\n" if -f $lockFileName;
107
      die "Something has $main::config->{'status db filename'} locked, aborting\n" if -f $lockFileName;
101
      `touch $lockFileName`;
108
      &execute("touch $lockFileName");
102
   }
109
   }
103
   my $yaml = YAML::Tiny->new( {} );
110
   my $yaml = YAML::Tiny->new( {} );
104
   if ( -f $main::config->{'status db filename'} ) {
111
   if ( -f $main::config->{'status db filename'} ) {
105
      $yaml = YAML::Tiny->read( $main::config->{'status db filename'} );
112
      $yaml = YAML::Tiny->read( $main::config->{'status db filename'} );
106
   }
113
   }
Line 166... Line 173...
166
   return $output;
173
   return $output;
167
}
174
}
168
 
175
 
169
# scans a node to determine which domains are running on it
176
# scans a node to determine which domains are running on it
170
# updates each domain to reflect when it was last seen
177
# updates each domain to reflect when it was last seen
-
 
178
# if testing flag is set, reads from testing directory file: $node_scan.testing
171
sub getDomainsOnNode {
179
sub getDomainsOnNode {
172
   my $node = shift;
180
   my $node = shift;
-
 
181
   my @nodeList;
-
 
182
   
-
 
183
   if ( $main::config->{'flags'}->{'testing'} ) {
-
 
184
      # Testing mode: read from file instead of executing command
-
 
185
      my $testFile = $main::config->{'script dir'} . "/tests/${node}_scan.testing";
-
 
186
      print "havirt.pm:getDomainsOnNode, reading from test file: $testFile\n" if $main::config->{'flags'}->{'debug'} > 2;
-
 
187
      if ( -f $testFile ) {
-
 
188
         open my $fh, '<', $testFile or die "Could not open test file $testFile: $!\n";
-
 
189
         @nodeList = grep { /^\s*\d/ } <$fh>;
-
 
190
         close $fh;
-
 
191
      } else {
-
 
192
         print "Warning: Test file $testFile not found, returning empty list\n" if $main::config->{'flags'}->{'verbose'};
-
 
193
      }
-
 
194
   } else {
-
 
195
      # Normal mode: execute virsh command
173
   my $command = &main::makeCommand( $node, 'virsh list' );
196
      my $command = &main::makeCommand( $node, 'virsh list' );
174
   print "havirt.pm:getDomainsOnNode, command is $command\n" if $main::config->{'flags'}->{'debug'} > 2;
197
      print "havirt.pm:getDomainsOnNode, command is $command\n" if $main::config->{'flags'}->{'debug'} > 2;
175
   my @nodeList = grep { /^\s*\d/ } `$command`;
198
      @nodeList = grep { /^\s*\d/ } &main::execute($command);
-
 
199
   }
-
 
200
   
176
   for ( my $i = 0; $i < @nodeList; $i++ ) {
201
   for ( my $i = 0; $i < @nodeList; $i++ ) {
177
      if ( $nodeList[$i] =~ m/\s*\d+\s*([^ ]+)/ ) {
202
      if ( $nodeList[$i] =~ m/\s*\d+\s*([^ ]+)/ ) {
178
         $nodeList[$i] = $1;
203
         $nodeList[$i] = $1;
179
      }
204
      }
180
   }
205
   }
Line 198... Line 223...
198
      print "findDomain, nodes = " . join( "\t", @node ) . "\n" if $main::config->{'flags'}->{'debug'} > 1;
223
      print "findDomain, nodes = " . join( "\t", @node ) . "\n" if $main::config->{'flags'}->{'debug'} > 1;
199
   }
224
   }
200
   if ( $main::config->{'flags'}->{'paranoid'} ) { # we will scan all nodes just to make sure
225
   if ( $main::config->{'flags'}->{'paranoid'} ) { # we will scan all nodes just to make sure
201
      foreach my $thisNode ( @node ) {
226
      foreach my $thisNode ( @node ) {
202
         my $command = &main::makeCommand( $thisNode, 'virsh list' );
227
         my $command = &main::makeCommand( $thisNode, 'virsh list' );
203
         my $output = `$command`;
228
         my $output = &main::execute($command);
204
         print "findDomain, $thisNode list =\n" . $output . "\n" if $main::config->{'flags'}->{'debug'} > 1;;
229
         print "findDomain, $thisNode list =\n" . $output . "\n" if $main::config->{'flags'}->{'debug'} > 1;;
205
         return $thisNode if ( $output =~ m/$domainName/ );
230
         return $thisNode if ( $output =~ m/$domainName/ );
206
      }
231
      }
207
   } else { # not paranoid mode, so just look through the status file
232
   } else { # not paranoid mode, so just look through the status file
208
      foreach my $thisNode ( @node ) {
233
      foreach my $thisNode ( @node ) {
Line 221... Line 246...
221
   my @targets = @_;
246
   my @targets = @_;
222
   if ( -f $main::config->{'last scan filename'} && ! $main::config->{'flags'}->{'force'} ) {
247
   if ( -f $main::config->{'last scan filename'} && ! $main::config->{'flags'}->{'force'} ) {
223
      my $lastScan = time - ( stat( $main::config->{'last scan filename'} ) ) [9];
248
      my $lastScan = time - ( stat( $main::config->{'last scan filename'} ) ) [9];
224
      return "Scan was run $lastScan seconds ago\n" unless $lastScan > $main::config->{'min scan time'};
249
      return "Scan was run $lastScan seconds ago\n" unless $lastScan > $main::config->{'min scan time'};
225
   }
250
   }
226
   `touch $main::config->{'last scan filename'}`;
251
   &main::execute("touch $main::config->{'last scan filename'}");
227
   &main::readDB(1);
252
   &main::readDB(1);
228
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
253
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
229
   if ( $main::config->{'flags'}->{'target'} ) {
254
   if ( $main::config->{'flags'}->{'target'} ) {
230
      push @targets, $main::config->{'flags'}->{'target'};
255
      push @targets, $main::config->{'flags'}->{'target'};
231
   }
256
   }
Line 251... Line 276...
251
# if node is the node we're on, we don't need to do a remote call
276
# if node is the node we're on, we don't need to do a remote call
252
# if node is null, we'll assume we do the command here
277
# if node is null, we'll assume we do the command here
253
# otherwise, we'll do an ssh to the node and run the command there
278
# otherwise, we'll do an ssh to the node and run the command there
254
sub makeCommand {
279
sub makeCommand {
255
   my ( $node, $command ) = @_;
280
   my ( $node, $command ) = @_;
256
   my $me = `hostname`;
281
   my $me = &execute('hostname');
257
   chomp $me;
282
   chomp $me;
258
   if ( ! $node || $node eq $me ) {
283
   if ( ! $node || $node eq $me ) {
259
      return $command;
284
      return $command;
260
   } else {
285
   } else {
261
      return "ssh $node '$command'";
286
      return "ssh $node '$command'";
262
   }
287
   }
263
}
288
}
264
 
289
 
-
 
290
# executes a shell command and returns the output
-
 
291
# centralizes all command execution for easier debugging and modification
-
 
292
# usage: execute($command) or @output = execute($command)
-
 
293
sub execute {
-
 
294
   my $command = shift;
-
 
295
   print "havirt.pm:execute running: [$command]\n" if $main::config->{'flags'}->{'debug'} > 2;
-
 
296
   return `$command`;
-
 
297
}
-
 
298
 
265
# force a node scan, of all domains, even if time has not expired
299
# force a node scan, of all domains, even if time has not expired
266
# and/or target is set. do this by setting force to 1 and target to null
300
# and/or target is set. do this by setting force to 1 and target to null
267
# then calling scan,
301
# then calling scan,
268
# after run, reset it to old value
302
# after run, reset it to old value
269
sub forceScan {
303
sub forceScan {
Line 280... Line 314...
280
# executes command $command, then repeatedly runs virsh list
314
# executes command $command, then repeatedly runs virsh list
281
# on $scanNode, grep'ing for $scanDomain
315
# on $scanNode, grep'ing for $scanDomain
282
# $condition is 1, to wait for domain to start
316
# $condition is 1, to wait for domain to start
283
# or 0 (false) to wait for it to shut down
317
# or 0 (false) to wait for it to shut down
284
sub executeAndWait {
318
sub executeAndWait {
285
   my ( $command, $scanNode, $scanDomain, $condition ) = @_;
319
   my ( $command, $scanNode, $scanDomain, $condition, $timeOut, $pollEvery ) = @_;
286
   my $waitSeconds = 15; # number of seconds to wait before checking again
320
   $pollEvery = 15 unless defined $pollEvery; # number of seconds to wait before checking again
287
   my $maxIterations = 60 / $waitSeconds; # maximum number of tries
321
   $timeOut = 60 unless defined $timeOut; # maximum number of tries
-
 
322
   $timeOut = int( $timeOut / $pollEvery ) unless $timeOut > $pollEvery;
288
   print "Running [$command], then waiting $waitSeconds to check if complete\n" if $main::config->{'flags'}->{'debug'};
323
   print "Running [$command], then waiting $pollEvery seconds to check if complete\n" if $main::config->{'flags'}->{'debug'};
289
   `$command`;
324
   &main::execute($command);
290
   my $waitCommand = &makeCommand( $scanNode, "virsh list | grep $scanDomain" );
325
   my $waitCommand = &makeCommand( $scanNode, "virsh list | grep $scanDomain" );
291
   my $output = '';
326
   my $output = '';
292
   do {
327
   do {
293
      return 0 unless ( $maxIterations-- ); # we've waited too long, so probably not working
328
      return 0 unless ( $timeOut-- ); # we've waited too long, so probably not working
294
      print '. ';
329
      print '. ';
295
      sleep 1;
330
      sleep $pollEvery;
296
      $output = `$waitCommand`;
331
      $output = &main::execute($waitCommand);
297
      print "[$waitCommand] returned [$output]\n" if $main::config->{'flags'}->{'debug'} > 1;
332
      print "[$waitCommand] returned [$output]\n" if $main::config->{'flags'}->{'debug'} > 1;
298
   } until ( $condition ? $output : !$output );
333
   } until ( $condition ? $output : !$output );
299
   return 1; # made it successful
334
   return 1; # made it successful
300
} 
335
} 
301
 
336
 
302
# find the differences between two arrays (passed by reference)
337
# find the differences between two arrays (passed by reference)
303
# first sorts the array, then walks through them one by one
338
# first sorts the array, then walks through them one by one
304
# @$arr1 MUST be larger than @$arr2
339
# @$arr1 MUST be larger than @$arr2
305
# used by domain.pm:list to find non-running domains for output
340
# used by domain.pm:list to find non-running domains for output
-
 
341
# returns elements in arr1 that are not in arr2
306
sub diffArray {
342
sub diffArray {
307
   my ( $arr1, $arr2 ) = @_;
343
   my ( $arr1, $arr2 ) = @_;
308
   my @result;
344
   my @result;
309
 
345
 
310
   @$arr1 = sort @$arr1;
346
   @$arr1 = sort @$arr1;
311
   @$arr2 = sort @$arr2;
347
   @$arr2 = sort @$arr2;
312
   my $i=0;
348
   my $i=0;
313
   my $j=0;
349
   my $j=0;
314
 
350
 
315
   while ( $i < @$arr1 && $j < @$arr2) {
351
   while ( $i < @$arr1 ) {
316
      if ( $arr1->[$i] eq $arr2->[$j] ) {
352
      if ( $j < @$arr2 && $arr1->[$i] eq $arr2->[$j] ) {
-
 
353
         # Match found, skip both
317
         $i++;
354
         $i++;
318
         $j++;
355
         $j++;
319
      } elsif ( $arr1->[$i] lt $arr2->[$j] ) {
356
      } elsif ( $j >= @$arr2 || $arr1->[$i] lt $arr2->[$j] ) {
-
 
357
         # arr1[i] not in arr2, or arr2 exhausted
320
         push @result, $arr1->[$i];
358
         push @result, $arr1->[$i];
321
         $i++;
359
         $i++;
322
      } else {
360
      } else {
323
         push @result, $arr2->[$j];
361
         # arr1[i] > arr2[j], advance j to keep looking for match
324
         $j++;
362
         $j++;
325
      }
363
      }
326
   }
364
   }
327
   return \@result;
365
   return \@result;
328
}
366
}
Line 433... Line 471...
433
   my $command = &main::makeCommand( $node, "virsh migrate --live --persistent --verbose  $virt qemu+ssh://$target/system" );
471
   my $command = &main::makeCommand( $node, "virsh migrate --live --persistent --verbose  $virt qemu+ssh://$target/system" );
434
   if ( $main::config->{'flags'}->{'dryrun'} ) { # they want us to actually do it
472
   if ( $main::config->{'flags'}->{'dryrun'} ) { # they want us to actually do it
435
      $return = $command;
473
      $return = $command;
436
   } else {
474
   } else {
437
      print "Migrating $virt to $node\n" if $main::config->{'flags'}->{'verbose'};
475
      print "Migrating $virt to $node\n" if $main::config->{'flags'}->{'verbose'};
438
      $return = ( &main::executeAndWait( $command, $node, $virt, 0 ) ? 'Success' : 'Time Out waiting for shutdown');
476
      $return = ( &main::executeAndWait( $command, $node, $virt, 0, 60, 15 ) ? 'Success' : 'Time Out waiting for shutdown');
439
      #&main::forceScan(); Removed since we're doing it at a higher level
477
      #&main::forceScan(); Removed since we're doing it at a higher level
440
   }
478
   }
441
   return "$return\n";
479
   return "$return\n";
442
}
480
}
443
 
481