Subversion Repositories havirt

Rev

Rev 25 | Rev 29 | Go to most recent revision | Show entire file | Ignore whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 25 Rev 26
Line 22... Line 22...
22
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
22
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
 
23
 
24
 
24
 
25
# v0.0.1 20240602 RWR
25
# v0.0.1 20240602 RWR
26
# Initial setup
26
# Initial setup
-
 
27
#
-
 
28
# v1.2.0 20240826 RWR
-
 
29
# Added some code to migrate domains if node placed in maintenance mode
-
 
30
# Added a lot of 'verbose' print lines, and modified for new flag structure
-
 
31
#
27
 
32
 
28
package havirt;
33
package havirt;
29
 
34
 
30
use warnings;
35
use warnings;
31
use strict;  
36
use strict;  
Line 42... Line 47...
42
use Data::Dumper qw(Dumper); # Import the Dumper() subroutine
47
use Data::Dumper qw(Dumper); # Import the Dumper() subroutine
43
 
48
 
44
# define the version number
49
# define the version number
45
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
50
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
46
use version;
51
use version;
47
our $VERSION = version->declare("1.0.0");
52
our $VERSION = version->declare("1.2.0");
48
 
53
 
49
 
54
 
50
use Exporter;
55
use Exporter;
51
 
56
 
52
our @ISA = qw( Exporter );
57
our @ISA = qw( Exporter );
Line 91... Line 96...
91
      $yaml = YAML::Tiny->read( $main::config->{'status db filename'} );
96
      $yaml = YAML::Tiny->read( $main::config->{'status db filename'} );
92
   }
97
   }
93
   $main::statusDB = $yaml->[0];
98
   $main::statusDB = $yaml->[0];
94
}
99
}
95
 
100
 
-
 
101
# Write the statusDB file out, overwriting the current one
-
 
102
# remove the lock file, if it exists
96
sub writeDB {
103
sub writeDB {
97
   my $yaml = YAML::Tiny->new( $main::statusDB );
104
   my $yaml = YAML::Tiny->new( $main::statusDB );
98
   $yaml->write( $main::config->{'status db filename'} );
105
   $yaml->write( $main::config->{'status db filename'} );
99
   unlink "$main::config->{'status db filename'}.lock" if -f "$main::config->{'status db filename'}.lock"; # release any lock we might have on it
106
   unlink "$main::config->{'status db filename'}.lock" if -f "$main::config->{'status db filename'}.lock"; # release any lock we might have on it
100
}
107
}
101
 
108
 
-
 
109
# create a report and send to STDOUT.
102
sub report {
110
sub report {
103
   if ( $main::config->{'flags'}->{'format'} eq 'tsv' ) {
111
   if ( $main::config->{'flags'}->{'format'} eq 'tsv' ) {
104
      return &report_tsv( @_ );
112
      return &report_tsv( @_ );
105
   } else {
113
   } else {
106
      return &report_screen( @_ );
114
      return &report_screen( @_ );
107
   }
115
   }
108
}
116
}
109
 
117
 
-
 
118
# report as a tab separated values, no encapulation
110
sub report_tsv {
119
sub report_tsv {
111
   my ( $header, $data ) = @_;
120
   my ( $header, $data ) = @_;
112
   my @output;
121
   my @output;
113
   push @output, join( "\t", @$header );
122
   push @output, join( "\t", @$header );
114
   for( my $line = 0; $line < @$data; $line++ ) {
123
   for( my $line = 0; $line < @$data; $line++ ) {
115
      push @output, join( "\t", @{$data->[$line]} );
124
      push @output, join( "\t", @{$data->[$line]} );
116
   } # for
125
   } # for
117
   return join( "\n", @output ) . "\n";
126
   return join( "\n", @output ) . "\n";
118
}
127
}
119
 
128
 
-
 
129
# report suitable for screen, with fixed width columns
120
sub report_screen {
130
sub report_screen {
121
   my ( $header, $data ) = @_;
131
   my ( $header, $data ) = @_;
122
   my @output;
132
   my @output;
123
   my @widths;
133
   my @widths;
124
   my $column;
134
   my $column;
Line 146... Line 156...
146
   } # for row
156
   } # for row
147
   return $output;
157
   return $output;
148
}
158
}
149
 
159
 
150
# scans a node to determine which domains are running on it
160
# scans a node to determine which domains are running on it
-
 
161
# updates each domain to reflect when it was last seen
151
sub getDomainsOnNode {
162
sub getDomainsOnNode {
152
   my $node = shift;
163
   my $node = shift;
153
   my $command = &main::makeCommand( $node, 'virsh list' );
164
   my $command = &main::makeCommand( $node, 'virsh list' );
154
   print "havirt.pm:getDomainsOnNode, command is $command\n" if $main::config->{'flags'}->{'debug'} > 2;
165
   print "havirt.pm:getDomainsOnNode, command is $command\n" if $main::config->{'flags'}->{'debug'} > 2;
155
   my @nodeList = grep { /^\s*\d/ } `$command`;
166
   my @nodeList = grep { /^\s*\d/ } `$command`;
Line 175... Line 186...
175
   &readDB();
186
   &readDB();
176
   unless ( @node ) {
187
   unless ( @node ) {
177
      @node = keys %{$main::statusDB->{'node'} };
188
      @node = keys %{$main::statusDB->{'node'} };
178
      print "findDomain, nodes = " . join( "\t", @node ) . "\n" if $main::config->{'flags'}->{'debug'} > 1;
189
      print "findDomain, nodes = " . join( "\t", @node ) . "\n" if $main::config->{'flags'}->{'debug'} > 1;
179
   }
190
   }
-
 
191
   if ( $main::config->{'flags'}->{'paranoid'} ) { # we will scan all nodes just to make sure
180
   foreach my $thisNode ( @node ) {
192
      foreach my $thisNode ( @node ) {
181
      my $command = &main::makeCommand( $thisNode, 'virsh list' );
193
         my $command = &main::makeCommand( $thisNode, 'virsh list' );
182
      my $output = `$command`;
194
         my $output = `$command`;
183
      print "findDomain, $thisNode list =\n" . $output . "\n" if $main::config->{'flags'}->{'debug'} > 1;;
195
         print "findDomain, $thisNode list =\n" . $output . "\n" if $main::config->{'flags'}->{'debug'} > 1;;
184
      return $thisNode if ( $output =~ m/$domainName/ );
196
         return $thisNode if ( $output =~ m/$domainName/ );
-
 
197
      }
-
 
198
   } else { # not paranoid mode, so just look through the status file
-
 
199
      foreach my $thisNode ( @node ) {
-
 
200
         if ( $main::statusDB->{'nodePopulation'}->{$thisNode}->{'running'}->{$domainName} ) {
-
 
201
            return $thisNode;
-
 
202
         }
-
 
203
      }
185
   }
204
   }
186
   return '';
205
   return '';
187
}
206
}
188
 
207
 
189
# check one or more nodes and determine which domains are running on them.
208
# check one or more nodes and determine which domains are running on them.
190
# defaults to everything in the node database, but the -t can have it run on only one
209
# defaults to everything in the node database, but the -t can have it run on only one
191
# this is the function that should be run every few minutes on one of the servers
210
# this is the function that should be run every few minutes on one of the servers
192
sub scan {
211
sub scan {
193
   my @targets = @_;
212
   my @targets = @_;
194
   if ( -f $main::config->{'last scan filename'} && ! $main::config->{'flags'}->{'yes'} ) {
213
   if ( -f $main::config->{'last scan filename'} && ! $main::config->{'flags'}->{'force'} ) {
195
      my $lastScan = time - ( stat( $main::config->{'last scan filename'} ) ) [9];
214
      my $lastScan = time - ( stat( $main::config->{'last scan filename'} ) ) [9];
196
      return "Scan was run $lastScan seconds ago\n" unless $lastScan > $main::config->{'minum scan time'};
215
      return "Scan was run $lastScan seconds ago\n" unless $lastScan > $main::config->{'min scan time'};
197
   }
216
   }
198
   `touch $main::config->{'last scan filename'}`;
217
   `touch $main::config->{'last scan filename'}`;
199
   &main::readDB(1);
218
   &main::readDB(1);
200
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
219
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
201
   if ( $main::config->{'flags'}->{'target'} ) {
220
   if ( $main::config->{'flags'}->{'target'} ) {
202
      push @targets, $main::config->{'flags'}->{'target'};
221
      push @targets, $main::config->{'flags'}->{'target'};
203
   }
222
   }
204
   @targets = keys %{$main::statusDB->{'node'}} unless @targets;
223
   @targets = keys %{$main::statusDB->{'node'}} unless @targets;
205
   print "Scanning " . join( "\n", @targets ) . "\n" if $main::config->{'flags'}->{'debug'};
224
   print "Scanning " . join( "\n", @targets ) . "\n" if $main::config->{'flags'}->{'debug'};
206
   foreach my $node (@targets) {
225
   foreach my $node (@targets) {
-
 
226
      print "Scanning $node\n" if $main::config->{'flags'}->{'verbose'};
207
      $main::statusDB->{'nodePopulation'}->{$node}->{'running'} = &getDomainsOnNode( $node );
227
      $main::statusDB->{'nodePopulation'}->{$node}->{'running'} = &getDomainsOnNode( $node );
208
      $main::statusDB->{'nodePopulation'}->{$node}->{'lastchecked'} = time;
228
      $main::statusDB->{'nodePopulation'}->{$node}->{'lastchecked'} = time;
-
 
229
      print "Found " . @{$main::statusDB->{'nodePopulation'}->{$node}->{'running'}} . " domains on node $node\n" if $main::config->{'flags'}->{'verbose'};
209
      foreach my $domain ( keys %{$main::statusDB->{'nodePopulation'}->{$node}->{'running'}} ) {
230
      foreach my $domain ( keys %{$main::statusDB->{'nodePopulation'}->{$node}->{'running'}} ) {
210
         # make sure there is an entry for all of these domains
231
         # make sure there is an entry for all of these domains
211
         $main::statusDB->{'virt'}->{$domain} = {} unless exists( $main::statusDB->{'virt'}->{$domain} );
232
         $main::statusDB->{'virt'}->{$domain} = {} unless exists( $main::statusDB->{'virt'}->{$domain} );
212
      }
233
      }
213
      print Dumper( $main::statusDB->{'nodePopulation'}->{$node} ) if $main::config->{'flags'}->{'debug'} > 2;
234
      print Dumper( $main::statusDB->{'nodePopulation'}->{$node} ) if $main::config->{'flags'}->{'debug'} > 2;
Line 231... Line 252...
231
      return "ssh $node '$command'";
252
      return "ssh $node '$command'";
232
   }
253
   }
233
}
254
}
234
 
255
 
235
# force a node scan, even if time has not expired
256
# force a node scan, even if time has not expired
-
 
257
# do this by setting force to 1, calling scan, then resetting
-
 
258
# it to old value
236
sub forceScan {
259
sub forceScan {
237
   my $save = $main::config->{'flags'}->{'yes'};
260
   my $save = $main::config->{'flags'}->{'force'};
238
   $main::config->{'flags'}->{'yes'} = 1;
261
   $main::config->{'flags'}->{'force'} = 0;
239
   &main::scan();
262
   &main::scan();
240
   $main::config->{'flags'}->{'yes'} = $save;
263
   $main::config->{'flags'}->{'force'} = $save;
241
}
264
}
242
 
265
 
243
 
266
 
244
# executes command $command, then repeatedly runs virsh list
267
# executes command $command, then repeatedly runs virsh list
245
# on $scanNode, grep'ing for $scanDomain
268
# on $scanNode, grep'ing for $scanDomain
246
# $condition is 1 (true) or 0 (false)
269
# $condition is 1, to wait for domain to start
-
 
270
# or 0 (false) to wait for it to shut down
247
sub executeAndWait {
271
sub executeAndWait {
248
   my ( $command, $scanNode, $scanDomain, $condition ) = @_;
272
   my ( $command, $scanNode, $scanDomain, $condition ) = @_;
249
   my $waitSeconds = 5; # number of seconds to wait before checking again
273
   my $waitSeconds = 5; # number of seconds to wait before checking again
250
   my $maxIterations = 60 / $waitSeconds; # maximum number of tries
274
   my $maxIterations = 60 / $waitSeconds; # maximum number of tries
251
   print "Running [$command], then waiting $waitSeconds to check if complete\n" if $main::config->{'flags'}->{'debug'};
275
   print "Running [$command], then waiting $waitSeconds to check if complete\n" if $main::config->{'flags'}->{'debug'};
Line 263... Line 287...
263
} 
287
} 
264
 
288
 
265
# find the differences between two arrays (passed by reference)
289
# find the differences between two arrays (passed by reference)
266
# first sorts the array, then walks through them one by one
290
# first sorts the array, then walks through them one by one
267
# @$arr1 MUST be larger than @$arr2
291
# @$arr1 MUST be larger than @$arr2
-
 
292
# used by domain.pm:list to find non-running domains for output
268
sub diffArray {
293
sub diffArray {
269
   my ( $arr1, $arr2 ) = @_;
294
   my ( $arr1, $arr2 ) = @_;
270
   my @result;
295
   my @result;
271
 
296
 
272
   @$arr1 = sort @$arr1;
297
   @$arr1 = sort @$arr1;
Line 297... Line 322...
297
   $config->{'script name'} = $FindBin::Script;
322
   $config->{'script name'} = $FindBin::Script;
298
   $config->{'db dir'} = $config->{'script dir'} . '/var';
323
   $config->{'db dir'} = $config->{'script dir'} . '/var';
299
   $config->{'conf dir'} = $config->{'script dir'} . '/conf';
324
   $config->{'conf dir'} = $config->{'script dir'} . '/conf';
300
   $config->{'status db filename'} = $config->{'db dir'} . '/status.yaml';
325
   $config->{'status db filename'} = $config->{'db dir'} . '/status.yaml';
301
   $config->{'last scan filename'} = $config->{'script dir'} . '/var/lastscan';
326
   $config->{'last scan filename'} = $config->{'script dir'} . '/var/lastscan';
302
   $config->{'minum scan time'} = 5 * 60; # five minutes
327
   $config->{'min scan time'} = 5 * 60; # five minutes
303
   $config->{'node reserved memory'} = 8 * 1024 * 1024; # 8 gigabytes
328
   $config->{'node reserved memory'} = 8 * 1024 * 1024; # 8 gigabytes
304
   $config->{'node reserved vcpu' } = 0; # turn off reserved vcpu
329
   $config->{'node reserved vcpu' } = 0; # turn off reserved vcpu
-
 
330
   $config->{'paranoid'} = 1; # rescan all nodes on any action which will modify it
-
 
331
   $config->{'flags'}->{'debug'} = 0;
-
 
332
   $config->{'flags'}->{'dryrun'} = 1;
-
 
333
   $config->{'flags'}->{'force'} = 0;
305
   $config->{'flags'}->{'format'} = 'screen';
334
   $config->{'flags'}->{'format'} = 'screen';
306
   $config->{'flags'}->{'yes'} = 0;
335
   #$config->{'flags'}->{'help'} = 0; # used, but don't put in config file
307
   $config->{'flags'}->{'quiet'} = 0;
336
   $config->{'flags'}->{'quiet'} = 0;
308
   $config->{'flags'}->{'target'} = '';
337
   $config->{'flags'}->{'target'} = '';
309
   $config->{'flags'}->{'dryrun'} = 1;
338
   $config->{'flags'}->{'verbose'} = 1;
310
   $config->{'flags'}->{'debug'} = 0;
-
 
311
   $config->{'flags'}->{'help'} = 0;
-
 
312
   $config->{'flags'}->{'version'} = 0;
339
   #$config->{'flags'}->{'version'} = 0; # used, but don't put in config file
313
   my $yaml = YAML::Tiny->new( $config );
340
   my $yaml = YAML::Tiny->new( $config );
314
   $yaml->write( $filename );
341
   $yaml->write( $filename );
315
}
342
}
316
 
343
 
317
# read the config file and return it
344
# read the config file and return it
Line 322... Line 349...
322
      $yaml = YAML::Tiny->read( $filename );
349
      $yaml = YAML::Tiny->read( $filename );
323
   }
350
   }
324
   return $yaml->[0];
351
   return $yaml->[0];
325
}
352
}
326
 
353
 
327
# find available resource on a node
354
# find available resource on a node, total RAM and threads
328
sub resource {
355
sub resource {
329
   my $node = shift;
356
   my $node = shift;
330
   die "Can not find node $node in havirt.pm:resource\n"
357
   die "Can not find node $node in havirt.pm:resource\n"
331
      unless $main::statusDB->{'node'}->{$node};
358
      unless $main::statusDB->{'node'}->{$node};
332
   my $return = {
359
   my $return = {
Line 338... Line 365...
338
         if defined $main::statusDB->{'node'}->{$node}->{$key};
365
         if defined $main::statusDB->{'node'}->{$node}->{$key};
339
   } # foreach
366
   } # foreach
340
   return $return;
367
   return $return;
341
}
368
}
342
 
369
 
-
 
370
# determine resources used on a node, total RAM and VCPU
343
sub getAvailableResources {
371
sub getAvailableResources {
344
   my $node = shift;
372
   my $node = shift;
345
   &readDB();
373
   &readDB();
346
   die "Can not find node $node in havirt.pm:resource\n"
374
   die "Can not find node $node in havirt.pm:resource\n" unless $main::statusDB->{'node'}->{$node};
347
      unless $main::statusDB->{'node'}->{$node};
-
 
348
   my $totalResources = &resource( $node );
375
   my $totalResources = &resource( $node );
349
   print Dumper( $totalResources ) if $main::config->{'flags'}->{'debug'};
376
   print Dumper( $totalResources ) if $main::config->{'flags'}->{'debug'};
350
   foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
377
   foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
351
      $totalResources->{'memory'} -= $main::statusDB->{'virt'}->{$domain}->{'memory'};
378
      $totalResources->{'memory'} -= $main::statusDB->{'virt'}->{$domain}->{'memory'};
352
      $totalResources->{'cpu_count'} -= $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
379
      $totalResources->{'cpu_count'} -= $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
Line 361... Line 388...
361
   my $node = shift;
388
   my $node = shift;
362
   &readDB();
389
   &readDB();
363
   my @return;
390
   my @return;
364
   my $nodeResources = &getAvailableResources( $node );
391
   my $nodeResources = &getAvailableResources( $node );
365
   print "In havirt.pm:validateResources, checking if enough room on $node for\n" . join( "\n", @_ ) . "\n"
392
   print "In havirt.pm:validateResources, checking if enough room on $node for\n" . join( "\n", @_ ) . "\n"
366
      if ( $main::config->{'flags'}->{'debug'} );
393
      if $main::config->{'flags'}->{'debug'};
-
 
394
   print "Checking resources on $node\n" if $main::config->{'flags'}->{'verbose'};
367
   # subtract the reserved memory from the node
395
   # subtract the reserved memory from the node
368
   $nodeResources->{'memory'} -= $main::config->{'node reserved memory'};
396
   $nodeResources->{'memory'} -= $main::config->{'node reserved memory'};
369
   $nodeResources->{'cpu_count'} -= $main::config->{'node reserved vcpu'} if $main::config->{'node reserved vcpu'};
397
   $nodeResources->{'cpu_count'} -= $main::config->{'node reserved vcpu'} if $main::config->{'node reserved vcpu'};
370
   while ( my $domain = shift ) {
398
   while ( my $domain = shift ) {
371
      $nodeResources->{'memory'} -= $main::statusDB->{'virt'}->{$domain}->{'memory'};
399
      $nodeResources->{'memory'} -= $main::statusDB->{'virt'}->{$domain}->{'memory'};
Line 381... Line 409...
381
 
409
 
382
# migrate domain from current node it is on to $target
410
# migrate domain from current node it is on to $target
383
sub migrate {
411
sub migrate {
384
   my ( $virt, $target ) = @_;
412
   my ( $virt, $target ) = @_;
385
   my $return;
413
   my $return;
386
   my $node;
-
 
387
   # these are replaced by the safer findDomain
-
 
388
   #&main::forceScan();
-
 
389
   #&main::readDB();
-
 
390
   $node = &main::findDomain( $virt );
414
   my $node  = &main::findDomain( $virt );
391
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
415
   print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::config->{'flags'}->{'debug'} > 2;
392
   die "I can not find $virt on any node\n" unless $node;
416
   die "I can not find $virt on any node\n" unless $node;
393
   die "Domain $virt in maintenance mode, can not migrate it\n" if $main::statusDB->{'virt'}->{$virt}->{'maintenance'};
417
   die "Domain $virt in maintenance mode, can not migrate it\n" if $main::statusDB->{'virt'}->{$virt}->{'maintenance'};
394
   die "Node $target in maintenance mode, can not migrate anything to it\n" if $main::statusDB->{'node'}->{$target}->{'maintenance'};
418
   die "Node $target in maintenance mode, can not migrate anything to it\n" if $main::statusDB->{'node'}->{$target}->{'maintenance'};
395
   die "$virt already on $target\n" if $target eq $node;
419
   die "$virt already on $target\n" if $target eq $node;
396
   my $command = &main::makeCommand( $node, "virsh migrate --live --persistent --verbose  $virt qemu+ssh://$target/system" );
420
   my $command = &main::makeCommand( $node, "virsh migrate --live --persistent --verbose  $virt qemu+ssh://$target/system" );
397
   if ( $main::config->{'flags'}->{'yes'} ) { # they want us to actually do it
421
   if ( $main::config->{'flags'}->{'dryrun'} ) { # they want us to actually do it
-
 
422
      $return = $command;
-
 
423
   } else {
398
      $return = ( &main::executeAndWait( $command, $node, $virt, 0 ) ? 'Success' : 'Time Out waiting for shutdown');
424
      $return = ( &main::executeAndWait( $command, $node, $virt, 0 ) ? 'Success' : 'Time Out waiting for shutdown');
399
      &main::forceScan();
425
      &main::forceScan();
400
   } else {
-
 
401
      $return = $command;
-
 
402
   }
426
   }
403
   return "$return\n";
427
   return "$return\n";
404
}
428
}
405
 
429