Subversion Repositories havirt

Rev

Rev 42 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
8 rodolico 1
#!/usr/bin/env perl
2
 
3
# All functions related to maniplating/reporting on cluster
4
# part of havirt.
5
 
6
# Copyright 2024 Daily Data, Inc.
7
# 
8
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 
9
# conditions are met:
10
#
11
#   Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
12
#   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 
13
#   in the documentation and/or other materials provided with the distribution.
14
#   Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
15
#   from this software without specific prior written permission.
16
# 
17
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
18
# NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
19
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
20
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
22
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
23
 
24
# v0.0.1 20240602 RWR
25
# Initial setup
26 rodolico 26
#
27
# v1.2.0 20240826 RWR
28
# Added some code to migrate domains if node placed in maintenance mode
29
# Added a lot of 'verbose' print lines, and modified for new flag structure
30
#
42 rodolico 31
# v1.3.0 20250511 RWR
32
# Added balance function. If called, will attempt to balance a cluster so that the variance is lower than balance_max_variance 
33
# (new entry in config file). --dryrun will simply display the commands sent, and --nodryrun will execute them.
44 rodolico 34
#
35
# v1.3.0 20250514 RWR
36
# Modified so it will not issue error codes if we have done iterations but not perfectly in balance. Also, added message
37
# 'already in balance' if it is as good as we can get it.
38
# 
8 rodolico 39
 
26 rodolico 40
 
42 rodolico 41
 
8 rodolico 42
package cluster;
43
 
44
use warnings;
45
use strict;  
46
 
47
# define the version number
48
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
49
use version;
44 rodolico 50
our $VERSION = version->declare("1.3.1");
8 rodolico 51
 
52
 
53
use Data::Dumper;
54
 
55
use Exporter;
56
 
57
our @ISA = qw( Exporter );
58
our @EXPORT = qw( 
59
                  &list
26 rodolico 60
                  &iscsi
8 rodolico 61
                );
62
 
11 rodolico 63
sub help {
64
   my @return;
65
   push @return, 'cluster status';
66
   push @return, "\t[--format|-f screen|tsv] - displays some stats on cluster resources used";
39 rodolico 67
   push @return, 'cluster balance';
68
   push @return, "\tBalances resources by moving domains between nodes";
26 rodolico 69
   push @return, 'cluster iscsi';
70
   push @return, "\tdisplays list of all iSCSI targets 'known' by system";
71
   push @return, 'cluster iscsi add ip-or-dns-name';
72
   push @return, "\tAdds iscsi target to system";
73
   push @return, 'cluster iscsi delete  ip-or-dns-name';
74
   push @return, "\tDelete iSCSI target processed by system. ip-or-dns-name MUST be exact";
75
   push @return, 'cluster iscsi update [node ...]';
76
   push @return, "\tPerforms an update to add new iSCSI targets on one or more nodes";
77
   push @return, "\tScans all iSCSI targets, looking for new shares on each, then performs";
78
   push @return, "\ta login, adding it to the node. DOES NOT delete old targets at this";
79
   push @return, "\ttime. If no nodes passed in, will perform function on all nodes not";
80
   push @return, "\tin maintenance mode";
42 rodolico 81
   push @return, 'cluster balance';
82
   push @return, "\tAttempts to balance node memory usage by migrating domains to less used";
83
   push @return, "\tnodes. If a node is in maintenance mode, will attempt to move all domains";
84
   push @return, "\toff of it and balance them on the other nodes";
26 rodolico 85
 
11 rodolico 86
   return join( "\n", @return ) . "\n";
87
}
88
 
10 rodolico 89
sub status {
90
   my $return = '';
13 rodolico 91
   &main::readDB();
25 rodolico 92
   my @header = ('Node','Threads','Memory','Domains','vcpu','mem_used', 'Status' );
10 rodolico 93
   my @data;
94
   my $usedmem = 0;
95
   my $usedcpu = 0;
96
   my $availmem = 0;
97
   my $availcpu = 0;
98
   my $totalDomains = 0;
25 rodolico 99
   my $maintenance = 0;
13 rodolico 100
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
10 rodolico 101
      my $memory = 0;
102
      my $vcpus = 0;
103
      my $count = 0;
13 rodolico 104
      foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
105
         $memory += $main::statusDB->{'virt'}->{$domain}->{'memory'};
106
         $vcpus += $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
10 rodolico 107
         $count++;
108
      }
25 rodolico 109
      push @data, [ $node,$main::statusDB->{'node'}->{$node}->{cpu_count},$main::statusDB->{'node'}->{$node}->{memory},$count,$vcpus,$memory, $main::statusDB->{'node'}->{$node}->{maintenance} ? 'Maintenance' : 'Online' ];
10 rodolico 110
      $usedmem += $memory;
111
      $usedcpu += $vcpus;
112
      $totalDomains += $count;
13 rodolico 113
      $availmem += $main::statusDB->{'node'}->{$node}->{memory};
114
      $availcpu += $main::statusDB->{'node'}->{$node}->{cpu_count};
26 rodolico 115
      $maintenance += $main::statusDB->{'node'}->{$node}->{maintenance} ? 0 : 1;
10 rodolico 116
   } # outer for
25 rodolico 117
   push @data, [ 'Total',$availcpu,$availmem,$totalDomains,$usedcpu,$usedmem, $maintenance ];
10 rodolico 118
   return &main::report( \@header, \@data );
119
}
26 rodolico 120
 
121
# perform various functions on iSCSI target definitions
122
# on all nodes
123
 
124
 
125
sub iscsi {
126
   my $action = shift;
127
   my @return;
128
   if ( $action && $action eq 'add' ) {
129
      &main::readDB(1);
130
      while ( my $target = shift ) {
131
         $main::statusDB->{'cluster'}->{'iscsi'}->{$target} = '';
132
      }
133
      &main::writeDB();
134
   } elsif ( $action && $action eq 'delete' ) {
135
      my $target = shift;
136
      &main::readDB(1);
137
      delete $main::statusDB->{'cluster'}->{'iscsi'}->{$target} if exists $main::statusDB->{'cluster'}->{'iscsi'}->{$target};
138
      &main::writeDB();
139
   } elsif ( $action && $action eq 'update' ) {
140
      &main::readDB();
141
      # if they did not give us a node, do all of them
142
      @_ = keys %{ $main::statusDB->{'node'} } unless @_;
143
      while ( my $node = shift ) { # process each node on stack
144
         if ( $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
145
            print "Not processing node $node since it is in maintenance mode\n" if $main::config->{'flags'}->{'verbose'};
146
         } else { # actually do the work
147
            push @return, &updateISCITargets( $node );
148
         }
149
      } # while
150
   }
151
   &main::readDB();
152
   push @return, "iSCSI targets are";
153
   if ( $main::statusDB->{'cluster'}->{'iscsi'} ) {
154
      push @return, join( "\n",  keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } );
155
   } else {
156
      push @return, "None Defined";
157
   }
158
   return join( "\n", @return ) . "\n";
159
}
160
 
161
# updates iSCSI targets on $node
162
# scans each target defined and compares it to the current session
163
# adding new targets if they exist
164
# NOTE: does not delete targets which no longer exist on server
165
sub updateISCITargets {
166
   my $node = shift;
167
   my $command;
168
   my %targets;
169
   my @return;
170
   push @return, "Processing iSCSI targets on $node";
171
   print Dumper( keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } ) if $main::config->{'flags'}->{'debug'};
172
   foreach my $server (keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } ) {
173
      print "\n" . '-'x40 . "\nGetting targets on server $server\n" . '-'x40 . "\n" if $main::config->{'flags'}->{'verbose'};
174
      $command = &main::makeCommand( $node, "iscsiadm -m discovery -t st -p $server" );
175
      my @list = `$command`;
176
      chomp @list;
177
      # @list contains lines of type
178
      # 10.19.209.2:3260,1 iqn.2014-11.net.dailydata.castor:simon0
179
      # split them apart and add them to the hash
180
      foreach my $entry ( @list ) {
181
         my ( $portal, $targetName ) = split( ' ', $entry );
182
         # $portal has some extra info after a comma, so clean it up
183
         $portal =~ m/^([0-9:.]+)/;
184
         $portal = $1;
185
         # some targets return multiple IP's for a given name, so 
186
         # only add them if they are in this IP
187
         $targets{ $targetName } = $portal if $portal =~ m/^$server/;
188
         print "$targetName\t$targets{ $targetName }\n" if $main::config->{'flags'}->{'verbose'};
189
      } # foreach
190
   } # while
191
   print "\n" . '-'x40 . "\nGetting active sessions\n". '-'x40 . "\n" if $main::config->{'flags'}->{'verbose'};
192
   # now, get active sessions so we can filter them
193
   $command = &main::makeCommand( $node, "iscsiadm -m session" );
194
   my @activeSessions = `$command`;;
195
   chomp @activeSessions;
196
   foreach my $session ( @activeSessions ) {
197
      $session =~ m/^.*[^0-9:.]([0-9,:.]+).*(iqn\S*)/;
198
      my ( $portal,$targetName ) = ( $1,$2 );
199
      print "$portal\t$targetName" if $main::config->{'flags'}->{'verbose'};
200
      if ( exists( $targets{$targetName} ) ) {
201
         print "\tNOT updating\n" if $main::config->{'flags'}->{'verbose'};
202
         delete $targets{ $targetName };
203
      } else {
204
         print "Needs to be added\n" if $main::config->{'flags'}->{'verbose'};
205
      }
206
   }
207
 
208
   # check if we have any new entries and bail if not
209
   if ( scalar keys %targets ) {
210
      # We have new entries, so run them;
211
      foreach my $targetName ( sort keys %targets ) {
212
         my $portal = $targets{$targetName};
213
         push @return, "Adding $targetName";
214
         $command = &main::makeCommand( $node, "iscsiadm -m node --targetname '$targetName' --portal '$portal' --login" );
215
         if ( $main::config->{'flags'}->{'dryrun'} ) {
216
            push @return, $command;
217
         } else {
218
          `$command`;
219
         }
220
      }
221
   } else {
222
      push @return, "No new entries";
223
   }
224
   return join( "\n", @return ) . "\n";
39 rodolico 225
} # updateISCITargets
226
 
42 rodolico 227
# calculate stats about the cluster, including the amount of memory/cpu used, the standard deviation
228
# and variance. Used mainly to balance cluster
229
sub getClusterStats {
230
   my $return = {};
231
   $return->{'cluster'}->{'memory'} = 0;
232
   $return->{'cluster'}->{'used_memory'} = 0;
233
   $return->{'cluster'}->{'count'} = 0;
234
   $return->{'cluster'}->{'used_vcpu'} = 0;
235
   $return->{'cluster'}->{'domain_count'} = 0;
236
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
237
      # only count nodes which are not in maintenance as part of the cluster towards total memory available
238
      if ( ! $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
239
         $return->{'cluster'}->{'memory'} += $main::statusDB->{'node'}->{$node}->{'memory'};
240
         $return->{'cluster'}->{'vcpu'} += $main::statusDB->{'node'}->{$node}->{'cpu_count'};
241
         $return->{'cluster'}->{'count'}++;
242
      } else {
243
         $return->{'node'}->{$node}->{'maintenance'} = 1;
244
      }
245
      $return->{'node'}->{$node}->{'memory'} = $main::statusDB->{'node'}->{$node}->{'memory'};
246
      $return->{'node'}->{$node}->{'vcpu'} = $main::statusDB->{'node'}->{$node}->{'cpu_count'};
247
      $return->{'node'}->{$node}->{'used_memory'} = 0;
248
      $return->{'node'}->{$node}->{'count'} = 0;
249
      $return->{'node'}->{$node}->{'used_vcpu'} = 0;
250
      # get individual stats for every domain on the node
251
      foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
252
         # track used memory, and count
253
         $return->{'node'}->{$node}->{'used_memory'} += $main::statusDB->{'virt'}->{$domain}->{'memory'};
254
         $return->{'node'}->{$node}->{'used_vcpu'} += $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
255
         $return->{'node'}->{$node}->{'count'}++;
256
      }
257
      # calculate the average memory used in the node
258
      $return->{'node'}->{$node}->{'average_memory'} = $return->{'node'}->{$node}->{'used_memory'} / 
259
         (
260
            $main::statusDB->{'node'}->{$node}->{'maintenance'} ? 0.0001 : $main::statusDB->{'node'}->{$node}->{'memory'}
261
         );
262
      # add the used memory to the cluster
263
      $return->{'cluster'}->{'used_memory'} += $return->{'node'}->{$node}->{'used_memory'};
264
      $return->{'cluster'}->{'used_vcpu'} += $return->{'node'}->{$node}->{'used_vcpu'};
265
      $return->{'cluster'}->{'domain_count'} += $return->{'node'}->{$node}->{'count'};
266
   }
267
   # calculate the deviation for each active node in the cluster
268
   $return->{'cluster'}->{'average_memory'} = $return->{'cluster'}->{'used_memory'} / $return->{'cluster'}->{'memory'};
269
 
270
   # get the deviation for each node
271
   # variance in the cluster is simply the average of all deviations
272
   $return->{'cluster'}->{'variance'} = 0;
273
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
274
      # deviation is the square of the difference between this node and the cluster overall
275
      $return->{'node'}->{$node}->{'deviation'} = (
276
         $return->{'node'}->{$node}->{'average_memory'} / $return->{'cluster'}->{'average_memory'} 
277
         ) ** 2;
278
      # we'll divide by number of active nodes after the loop
279
      $return->{'cluster'}->{'variance'} += $return->{'node'}->{$node}->{'deviation'};
280
   }
281
   $return->{'cluster'}->{'variance'} /= $return->{'cluster'}->{'count'};
282
   # now, determine how much memory needs to be added (plus) or removed (minus) for each node
283
   # memory_needed is calculated by taking the total amount of memory and multiplying it by the cluster average memory
284
   # then subtracting whatever is already used
285
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
286
      if ( $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
287
         $return->{'node'}->{$node}->{'memory_needed'} = -1 * $return->{'node'}->{$node}->{'used_memory'};
288
      } else {
289
         $return->{'node'}->{$node}->{'memory_needed'} = int (
290
            ( $return->{'node'}->{$node}->{'memory'} * $return->{'cluster'}->{'average_memory'} ) -
291
            $return->{'node'}->{$node}->{'used_memory'} 
292
            );
293
      }
294
   }
295
   return $return;
296
}
297
 
298
sub humanReadable {
299
   my ( $value, $preferredUnits ) = @_;
300
   $value *= 1024;
301
   my @units =  ( '', 'k', 'M', 'G', 'T' );
302
   $preferredUnits = $units[-1] unless $preferredUnits;
303
   my $unit = 0;
304
   while ( $unit < @units && abs($value) > 1023 && lc $units[$unit] ne lc $preferredUnits ) {
305
      $unit++;
306
      $value /= 1024;
307
   }
308
   return sprintf( '%d%s', $value+0.5, $units[$unit] );
309
}
310
 
311
 
312
sub percent {
313
   my ($value, $accuracy) = @_;
314
   $accuracy = 0 unless $accuracy;
315
   return sprintf( '%2.' . $accuracy . 'f%%', $value * 100)
316
}
317
 
40 rodolico 318
# Creates a balance report to show the user what went on
319
# $cluster is a hash created by sub getClusterStats, and possibly modified by
320
# the calling process
321
sub showBalanceReport {
42 rodolico 322
   my $stats = shift;
323
   #die Dumper( $stats ) . "\n";
324
   my @header = ('Node','Threads','Memory','Domains','vcpu_alloc','mem_alloc', 'mem_needed', 'vcpu%', 'mem%', 'Status', 'StdDev' );
40 rodolico 325
   my @data;
42 rodolico 326
   foreach my $node ( sort keys %{ $stats->{'node'} } ) {
40 rodolico 327
      push @data, [
328
         $node, 
42 rodolico 329
         $stats->{'node'}->{$node}->{'vcpu'},
330
         &humanReadable( $stats->{'node'}->{$node}->{'memory'} ),
331
         $stats->{'node'}->{$node}->{'count'},
332
         $stats->{'node'}->{$node}->{'used_vcpu'},
333
         &humanReadable( $stats->{'node'}->{$node}->{'used_memory'} ),
334
         &humanReadable( $stats->{'node'}->{$node}->{'memory_needed'} ),
335
         &percent( $stats->{'node'}->{$node}->{'used_vcpu'} / $stats->{'node'}->{$node}->{'vcpu'} ),
336
         &percent( $stats->{'node'}->{$node}->{'used_memory'} / $stats->{'node'}->{$node}->{'memory'} ),
337
         $stats->{'node'}->{$node}->{'maintenance'} ? 'Maintenance' : '',
338
         $stats->{'node'}->{$node}->{'deviation'} < 1000 ? sprintf( "%2.2f", $stats->{'node'}->{$node}->{'deviation'} ) : 'undef'
40 rodolico 339
      ];
340
   }
341
   push @data, [
342
         'All', 
42 rodolico 343
         $stats->{'cluster'}->{'vcpu'},
344
         &humanReadable( $stats->{'cluster'}->{'memory'} ),
345
         $stats->{'cluster'}->{'domain_count'},
346
         $stats->{'cluster'}->{'used_vcpu'},
347
         &humanReadable( $stats->{'cluster'}->{'used_memory'} ),
41 rodolico 348
         '',
42 rodolico 349
         &percent( $stats->{'cluster'}->{'used_vcpu'} / $stats->{'cluster'}->{'vcpu'} ),
350
         &percent( $stats->{'cluster'}->{'used_memory'} / $stats->{'cluster'}->{'memory'} ),
351
         '',
40 rodolico 352
         ''
353
      ];
42 rodolico 354
   return &main::report( \@header, \@data ) . "Variance " . 
355
      ( $stats->{'cluster'}->{'variance'} < 100 ? sprintf( "%2.2f", $stats->{'cluster'}->{'variance'} + .005 ) : "undef" ) . "\n\n";
40 rodolico 356
}
357
 
42 rodolico 358
# simulates performing migrations. Simply moves entries from $from to $to in $main::statusDB->{'nodePopulation'}
359
sub doActions {
360
   my $actions = shift;
44 rodolico 361
   my $return = '';
42 rodolico 362
   for ( my $i = 0; $i < @$actions; $i++ ) {
363
      my ($domain, $source, $target, $size ) = split( "\t", $actions->[$i] );
364
      $return .= &main::migrate( $domain, $target, $source );
365
      delete $main::statusDB->{'nodePopulation'}->{$source}->{'running'}->{$domain};
366
      $main::statusDB->{'nodePopulation'}->{$target}->{'running'}->{$domain} = time;
367
   }
368
   &main::forceScan() unless $main::config->{'flags'}->{'dryrun'} || $main::config->{'flags'}->{'testing'};
369
   return $return;
370
}
371
 
40 rodolico 372
# attempt to balance the domains on the active (maintenance = false) nodes
373
# basically, we take what is currently working, and calculate the variance
374
# of it (see https://en.wikipedia.org/wiki/Standard_deviation). If that is
375
# over about a 10, we move things around, if possible, then check our variance
376
# again.
39 rodolico 377
sub balance {
40 rodolico 378
   &main::readDB();
44 rodolico 379
   my $return = '';
40 rodolico 380
   # get the current cluster status
381
   my $cluster = &getClusterStats();
42 rodolico 382
   #die Dumper( $cluster ) . "\n";
40 rodolico 383
   # show user what it looks like at first
42 rodolico 384
   print "=== Starting Status ===\n\n" . &showBalanceReport( $cluster) unless $main::config->{'flags'}->{'quiet'};
385
   # we will do a loop to get the variance within our preferred range ($main::config->{ 'balance variance'})
386
   # however, we will only do a maximum number of iterations ($main::config->{ 'balance maxiterations'})
387
   my $iterations = defined $main::config->{ 'balance_max_iterations'} && $main::config->{ 'balance_max_iterations'} ? $main::config->{ 'balance_max_iterations'} : 10;
388
   $main::config->{ 'balance_max_variance'} = 1.1 unless defined $main::config->{ 'balance_max_variance'};
389
   # continue until our variance is where we want it, or we have tried too many times.
390
   while ( $iterations-- && $cluster->{'cluster'}->{'variance'} > $main::config->{ 'balance_max_variance'} ) {
391
      my $actions = &moveThings( $cluster );
44 rodolico 392
      if ( my $output = &doActions( $actions ) ) {
393
         $return .= $output;
394
      } else {
395
         last;
396
      }
42 rodolico 397
      #print Dumper( $actions ) . "\n"; die;
398
      # rerun stats
399
      $cluster = &getClusterStats();
400
      print &showBalanceReport( $cluster) if $main::config->{'flags'}->{'verbose'} > 1;
40 rodolico 401
   }
42 rodolico 402
   print "=== Ending Status ===\n\n" . &showBalanceReport( $cluster) unless $main::config->{'flags'}->{'quiet'};
44 rodolico 403
   return $return ? $return : "Already Balanced: No actions to take\n";
42 rodolico 404
} # balance
405
 
406
# finds node which needs to lose ($from) and gain ($to) the most. Then, goes through $from and finds the largest
407
# domain which will fit on $to until exhausted.
408
# as each domain is found, appends to $actions (array pointer). The format of each entry is a tab separated
409
# list of domain name, node from, node to, domain size
410
# returns the modified $actions
411
sub moveThings {
412
   my $stats = shift;
40 rodolico 413
 
42 rodolico 414
   my $actions = [];
415
   # find largest and smallest node differences
416
   my $transfer;
417
   my $from = '';
418
   my $to = '';
419
   # find smallest and largest "memory needed" in group. Note that if a node has too much, the number is negative and
420
   # for too little (ie, needs additional), the number is positive
421
   foreach my $node (keys %{$stats->{'node'} } ) {
422
      #print "Checking $node\n";
423
      if ( $from ) {
424
         $from = $node if $stats->{'node'}->{$from}->{'memory_needed'} > $stats->{'node'}->{$node}->{'memory_needed'};
425
         $to = $node if $stats->{'node'}->{$to}->{'memory_needed'} < $stats->{'node'}->{$node}->{'memory_needed'};
426
      } else { # just initialize everything to this node
427
         $from = $to = $node;
428
      } #if .. else
429
   } # foreach
430
   # this is a poor mans min. we want to transfer the least number of bytes, ie what $from can spare, or what $to can accept
431
   # we need the smallest of what $from can give and $to can accept
432
   $transfer = abs( abs( $stats->{'node'}->{$from}->{'memory_needed'} ) > abs( $stats->{'node'}->{$to}->{'memory_needed'} ) ?
433
               $stats->{'node'}->{$to}->{'memory_needed'} : $stats->{'node'}->{$from}->{'memory_needed'} );
434
   # die "Transfer " . &humanReadable($transfer) ." bytes from $from to $to\n";
435
 
436
   # get array of domains running on $from, sorted by the size of the domain (descending, ie largest on top )
437
   # basically, get all keys from $main::statusDB->{'nodePopulation'}->{$from}->{'running'}, then sort them by looking them
438
   # up in $main::statusDB->{'virt'} and retrieving the amount of RAM
439
   my @sortedDomains = sort
440
      {
441
         $main::statusDB->{'virt'}->{$b}->{'memory'} <=> $main::statusDB->{'virt'}->{$a}->{'memory'}
442
      } keys %{ $main::statusDB->{'nodePopulation'}->{$from}->{'running'} };
443
   # now, "move" (fake move) largest domain that will fit into $to, and repeat until we can not do it anymore
444
   while ( $transfer ) {
445
      my $thisDomain = shift @sortedDomains;
446
      last unless $thisDomain; # we ran out of domains
447
      next unless $main::statusDB->{'virt'}->{$thisDomain}->{'memory'} <= $transfer;
448
      push @$actions, join( "\t", ( $thisDomain, $from, $to, $main::statusDB->{'virt'}->{$thisDomain}->{'memory'} ) );
449
      $transfer -= $main::statusDB->{'virt'}->{$thisDomain}->{'memory'};
450
   }
451
 
452
   return $actions;
39 rodolico 453
}