Subversion Repositories havirt

Rev

Rev 44 | Go to most recent revision | 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
 
46 rodolico 89
# status
90
# Reports the current status of all nodes in the cluster.
91
# Gathers information about each node, including:
92
#   - Number of threads (vCPUs)
93
#   - Total memory
94
#   - Number of running domains
95
#   - Total vCPUs and memory used by running domains
96
#   - Node status (Maintenance or Online)
97
# Aggregates totals for all nodes and returns a formatted report.
10 rodolico 98
sub status {
99
   my $return = '';
13 rodolico 100
   &main::readDB();
25 rodolico 101
   my @header = ('Node','Threads','Memory','Domains','vcpu','mem_used', 'Status' );
10 rodolico 102
   my @data;
103
   my $usedmem = 0;
104
   my $usedcpu = 0;
105
   my $availmem = 0;
106
   my $availcpu = 0;
107
   my $totalDomains = 0;
25 rodolico 108
   my $maintenance = 0;
13 rodolico 109
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
10 rodolico 110
      my $memory = 0;
111
      my $vcpus = 0;
112
      my $count = 0;
13 rodolico 113
      foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
114
         $memory += $main::statusDB->{'virt'}->{$domain}->{'memory'};
115
         $vcpus += $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
10 rodolico 116
         $count++;
117
      }
25 rodolico 118
      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 119
      $usedmem += $memory;
120
      $usedcpu += $vcpus;
121
      $totalDomains += $count;
13 rodolico 122
      $availmem += $main::statusDB->{'node'}->{$node}->{memory};
123
      $availcpu += $main::statusDB->{'node'}->{$node}->{cpu_count};
46 rodolico 124
      $maintenance += $main::statusDB->{'node'}->{$node}->{'maintenance'} ? 0 : 1;
10 rodolico 125
   } # outer for
25 rodolico 126
   push @data, [ 'Total',$availcpu,$availmem,$totalDomains,$usedcpu,$usedmem, $maintenance ];
10 rodolico 127
   return &main::report( \@header, \@data );
128
}
26 rodolico 129
 
130
# perform various functions on iSCSI target definitions
131
# on all nodes
132
 
133
 
134
sub iscsi {
135
   my $action = shift;
136
   my @return;
137
   if ( $action && $action eq 'add' ) {
138
      &main::readDB(1);
139
      while ( my $target = shift ) {
140
         $main::statusDB->{'cluster'}->{'iscsi'}->{$target} = '';
141
      }
142
      &main::writeDB();
143
   } elsif ( $action && $action eq 'delete' ) {
144
      my $target = shift;
145
      &main::readDB(1);
146
      delete $main::statusDB->{'cluster'}->{'iscsi'}->{$target} if exists $main::statusDB->{'cluster'}->{'iscsi'}->{$target};
147
      &main::writeDB();
148
   } elsif ( $action && $action eq 'update' ) {
149
      &main::readDB();
150
      # if they did not give us a node, do all of them
151
      @_ = keys %{ $main::statusDB->{'node'} } unless @_;
152
      while ( my $node = shift ) { # process each node on stack
153
         if ( $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
154
            print "Not processing node $node since it is in maintenance mode\n" if $main::config->{'flags'}->{'verbose'};
155
         } else { # actually do the work
156
            push @return, &updateISCITargets( $node );
157
         }
158
      } # while
159
   }
160
   &main::readDB();
161
   push @return, "iSCSI targets are";
162
   if ( $main::statusDB->{'cluster'}->{'iscsi'} ) {
163
      push @return, join( "\n",  keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } );
164
   } else {
165
      push @return, "None Defined";
166
   }
167
   return join( "\n", @return ) . "\n";
168
}
169
 
170
# updates iSCSI targets on $node
171
# scans each target defined and compares it to the current session
172
# adding new targets if they exist
173
# NOTE: does not delete targets which no longer exist on server
174
sub updateISCITargets {
175
   my $node = shift;
176
   my $command;
177
   my %targets;
178
   my @return;
179
   push @return, "Processing iSCSI targets on $node";
180
   print Dumper( keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } ) if $main::config->{'flags'}->{'debug'};
181
   foreach my $server (keys %{ $main::statusDB->{'cluster'}->{'iscsi'} } ) {
182
      print "\n" . '-'x40 . "\nGetting targets on server $server\n" . '-'x40 . "\n" if $main::config->{'flags'}->{'verbose'};
183
      $command = &main::makeCommand( $node, "iscsiadm -m discovery -t st -p $server" );
184
      my @list = `$command`;
185
      chomp @list;
186
      # @list contains lines of type
187
      # 10.19.209.2:3260,1 iqn.2014-11.net.dailydata.castor:simon0
188
      # split them apart and add them to the hash
189
      foreach my $entry ( @list ) {
190
         my ( $portal, $targetName ) = split( ' ', $entry );
191
         # $portal has some extra info after a comma, so clean it up
192
         $portal =~ m/^([0-9:.]+)/;
193
         $portal = $1;
194
         # some targets return multiple IP's for a given name, so 
195
         # only add them if they are in this IP
196
         $targets{ $targetName } = $portal if $portal =~ m/^$server/;
197
         print "$targetName\t$targets{ $targetName }\n" if $main::config->{'flags'}->{'verbose'};
198
      } # foreach
199
   } # while
200
   print "\n" . '-'x40 . "\nGetting active sessions\n". '-'x40 . "\n" if $main::config->{'flags'}->{'verbose'};
201
   # now, get active sessions so we can filter them
202
   $command = &main::makeCommand( $node, "iscsiadm -m session" );
203
   my @activeSessions = `$command`;;
204
   chomp @activeSessions;
205
   foreach my $session ( @activeSessions ) {
206
      $session =~ m/^.*[^0-9:.]([0-9,:.]+).*(iqn\S*)/;
207
      my ( $portal,$targetName ) = ( $1,$2 );
208
      print "$portal\t$targetName" if $main::config->{'flags'}->{'verbose'};
209
      if ( exists( $targets{$targetName} ) ) {
210
         print "\tNOT updating\n" if $main::config->{'flags'}->{'verbose'};
211
         delete $targets{ $targetName };
212
      } else {
213
         print "Needs to be added\n" if $main::config->{'flags'}->{'verbose'};
214
      }
215
   }
216
 
217
   # check if we have any new entries and bail if not
218
   if ( scalar keys %targets ) {
219
      # We have new entries, so run them;
220
      foreach my $targetName ( sort keys %targets ) {
221
         my $portal = $targets{$targetName};
222
         push @return, "Adding $targetName";
223
         $command = &main::makeCommand( $node, "iscsiadm -m node --targetname '$targetName' --portal '$portal' --login" );
224
         if ( $main::config->{'flags'}->{'dryrun'} ) {
225
            push @return, $command;
226
         } else {
227
          `$command`;
228
         }
229
      }
230
   } else {
231
      push @return, "No new entries";
232
   }
233
   return join( "\n", @return ) . "\n";
39 rodolico 234
} # updateISCITargets
235
 
42 rodolico 236
# calculate stats about the cluster, including the amount of memory/cpu used, the standard deviation
237
# and variance. Used mainly to balance cluster
238
sub getClusterStats {
239
   my $return = {};
240
   $return->{'cluster'}->{'memory'} = 0;
241
   $return->{'cluster'}->{'used_memory'} = 0;
242
   $return->{'cluster'}->{'count'} = 0;
243
   $return->{'cluster'}->{'used_vcpu'} = 0;
244
   $return->{'cluster'}->{'domain_count'} = 0;
245
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
246
      # only count nodes which are not in maintenance as part of the cluster towards total memory available
247
      if ( ! $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
248
         $return->{'cluster'}->{'memory'} += $main::statusDB->{'node'}->{$node}->{'memory'};
249
         $return->{'cluster'}->{'vcpu'} += $main::statusDB->{'node'}->{$node}->{'cpu_count'};
250
         $return->{'cluster'}->{'count'}++;
251
      } else {
252
         $return->{'node'}->{$node}->{'maintenance'} = 1;
253
      }
254
      $return->{'node'}->{$node}->{'memory'} = $main::statusDB->{'node'}->{$node}->{'memory'};
255
      $return->{'node'}->{$node}->{'vcpu'} = $main::statusDB->{'node'}->{$node}->{'cpu_count'};
256
      $return->{'node'}->{$node}->{'used_memory'} = 0;
257
      $return->{'node'}->{$node}->{'count'} = 0;
258
      $return->{'node'}->{$node}->{'used_vcpu'} = 0;
259
      # get individual stats for every domain on the node
260
      foreach my $domain ( keys %{ $main::statusDB->{'nodePopulation'}->{$node}->{'running'} } ) {
261
         # track used memory, and count
262
         $return->{'node'}->{$node}->{'used_memory'} += $main::statusDB->{'virt'}->{$domain}->{'memory'};
263
         $return->{'node'}->{$node}->{'used_vcpu'} += $main::statusDB->{'virt'}->{$domain}->{'vcpu'};
264
         $return->{'node'}->{$node}->{'count'}++;
265
      }
266
      # calculate the average memory used in the node
267
      $return->{'node'}->{$node}->{'average_memory'} = $return->{'node'}->{$node}->{'used_memory'} / 
268
         (
269
            $main::statusDB->{'node'}->{$node}->{'maintenance'} ? 0.0001 : $main::statusDB->{'node'}->{$node}->{'memory'}
270
         );
271
      # add the used memory to the cluster
272
      $return->{'cluster'}->{'used_memory'} += $return->{'node'}->{$node}->{'used_memory'};
273
      $return->{'cluster'}->{'used_vcpu'} += $return->{'node'}->{$node}->{'used_vcpu'};
274
      $return->{'cluster'}->{'domain_count'} += $return->{'node'}->{$node}->{'count'};
275
   }
276
   # calculate the deviation for each active node in the cluster
277
   $return->{'cluster'}->{'average_memory'} = $return->{'cluster'}->{'used_memory'} / $return->{'cluster'}->{'memory'};
278
 
279
   # get the deviation for each node
280
   # variance in the cluster is simply the average of all deviations
281
   $return->{'cluster'}->{'variance'} = 0;
282
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
283
      # deviation is the square of the difference between this node and the cluster overall
284
      $return->{'node'}->{$node}->{'deviation'} = (
285
         $return->{'node'}->{$node}->{'average_memory'} / $return->{'cluster'}->{'average_memory'} 
286
         ) ** 2;
287
      # we'll divide by number of active nodes after the loop
288
      $return->{'cluster'}->{'variance'} += $return->{'node'}->{$node}->{'deviation'};
289
   }
290
   $return->{'cluster'}->{'variance'} /= $return->{'cluster'}->{'count'};
291
   # now, determine how much memory needs to be added (plus) or removed (minus) for each node
292
   # memory_needed is calculated by taking the total amount of memory and multiplying it by the cluster average memory
293
   # then subtracting whatever is already used
294
   foreach my $node (sort keys %{ $main::statusDB->{'node'} } ) {
295
      if ( $main::statusDB->{'node'}->{$node}->{'maintenance'} ) {
296
         $return->{'node'}->{$node}->{'memory_needed'} = -1 * $return->{'node'}->{$node}->{'used_memory'};
297
      } else {
298
         $return->{'node'}->{$node}->{'memory_needed'} = int (
299
            ( $return->{'node'}->{$node}->{'memory'} * $return->{'cluster'}->{'average_memory'} ) -
300
            $return->{'node'}->{$node}->{'used_memory'} 
301
            );
302
      }
303
   }
304
   return $return;
305
}
306
 
307
sub humanReadable {
308
   my ( $value, $preferredUnits ) = @_;
309
   $value *= 1024;
310
   my @units =  ( '', 'k', 'M', 'G', 'T' );
311
   $preferredUnits = $units[-1] unless $preferredUnits;
312
   my $unit = 0;
313
   while ( $unit < @units && abs($value) > 1023 && lc $units[$unit] ne lc $preferredUnits ) {
314
      $unit++;
315
      $value /= 1024;
316
   }
317
   return sprintf( '%d%s', $value+0.5, $units[$unit] );
318
}
319
 
320
 
321
sub percent {
322
   my ($value, $accuracy) = @_;
323
   $accuracy = 0 unless $accuracy;
324
   return sprintf( '%2.' . $accuracy . 'f%%', $value * 100)
325
}
326
 
40 rodolico 327
# Creates a balance report to show the user what went on
328
# $cluster is a hash created by sub getClusterStats, and possibly modified by
329
# the calling process
330
sub showBalanceReport {
42 rodolico 331
   my $stats = shift;
332
   #die Dumper( $stats ) . "\n";
333
   my @header = ('Node','Threads','Memory','Domains','vcpu_alloc','mem_alloc', 'mem_needed', 'vcpu%', 'mem%', 'Status', 'StdDev' );
40 rodolico 334
   my @data;
42 rodolico 335
   foreach my $node ( sort keys %{ $stats->{'node'} } ) {
40 rodolico 336
      push @data, [
337
         $node, 
42 rodolico 338
         $stats->{'node'}->{$node}->{'vcpu'},
339
         &humanReadable( $stats->{'node'}->{$node}->{'memory'} ),
340
         $stats->{'node'}->{$node}->{'count'},
341
         $stats->{'node'}->{$node}->{'used_vcpu'},
342
         &humanReadable( $stats->{'node'}->{$node}->{'used_memory'} ),
343
         &humanReadable( $stats->{'node'}->{$node}->{'memory_needed'} ),
344
         &percent( $stats->{'node'}->{$node}->{'used_vcpu'} / $stats->{'node'}->{$node}->{'vcpu'} ),
345
         &percent( $stats->{'node'}->{$node}->{'used_memory'} / $stats->{'node'}->{$node}->{'memory'} ),
346
         $stats->{'node'}->{$node}->{'maintenance'} ? 'Maintenance' : '',
347
         $stats->{'node'}->{$node}->{'deviation'} < 1000 ? sprintf( "%2.2f", $stats->{'node'}->{$node}->{'deviation'} ) : 'undef'
40 rodolico 348
      ];
349
   }
350
   push @data, [
351
         'All', 
42 rodolico 352
         $stats->{'cluster'}->{'vcpu'},
353
         &humanReadable( $stats->{'cluster'}->{'memory'} ),
354
         $stats->{'cluster'}->{'domain_count'},
355
         $stats->{'cluster'}->{'used_vcpu'},
356
         &humanReadable( $stats->{'cluster'}->{'used_memory'} ),
41 rodolico 357
         '',
42 rodolico 358
         &percent( $stats->{'cluster'}->{'used_vcpu'} / $stats->{'cluster'}->{'vcpu'} ),
359
         &percent( $stats->{'cluster'}->{'used_memory'} / $stats->{'cluster'}->{'memory'} ),
360
         '',
40 rodolico 361
         ''
362
      ];
42 rodolico 363
   return &main::report( \@header, \@data ) . "Variance " . 
364
      ( $stats->{'cluster'}->{'variance'} < 100 ? sprintf( "%2.2f", $stats->{'cluster'}->{'variance'} + .005 ) : "undef" ) . "\n\n";
40 rodolico 365
}
366
 
42 rodolico 367
# simulates performing migrations. Simply moves entries from $from to $to in $main::statusDB->{'nodePopulation'}
368
sub doActions {
369
   my $actions = shift;
44 rodolico 370
   my $return = '';
42 rodolico 371
   for ( my $i = 0; $i < @$actions; $i++ ) {
372
      my ($domain, $source, $target, $size ) = split( "\t", $actions->[$i] );
373
      $return .= &main::migrate( $domain, $target, $source );
374
      delete $main::statusDB->{'nodePopulation'}->{$source}->{'running'}->{$domain};
375
      $main::statusDB->{'nodePopulation'}->{$target}->{'running'}->{$domain} = time;
376
   }
377
   &main::forceScan() unless $main::config->{'flags'}->{'dryrun'} || $main::config->{'flags'}->{'testing'};
378
   return $return;
379
}
380
 
40 rodolico 381
# attempt to balance the domains on the active (maintenance = false) nodes
382
# basically, we take what is currently working, and calculate the variance
383
# of it (see https://en.wikipedia.org/wiki/Standard_deviation). If that is
384
# over about a 10, we move things around, if possible, then check our variance
385
# again.
39 rodolico 386
sub balance {
40 rodolico 387
   &main::readDB();
44 rodolico 388
   my $return = '';
40 rodolico 389
   # get the current cluster status
390
   my $cluster = &getClusterStats();
42 rodolico 391
   #die Dumper( $cluster ) . "\n";
40 rodolico 392
   # show user what it looks like at first
42 rodolico 393
   print "=== Starting Status ===\n\n" . &showBalanceReport( $cluster) unless $main::config->{'flags'}->{'quiet'};
394
   # we will do a loop to get the variance within our preferred range ($main::config->{ 'balance variance'})
395
   # however, we will only do a maximum number of iterations ($main::config->{ 'balance maxiterations'})
396
   my $iterations = defined $main::config->{ 'balance_max_iterations'} && $main::config->{ 'balance_max_iterations'} ? $main::config->{ 'balance_max_iterations'} : 10;
397
   $main::config->{ 'balance_max_variance'} = 1.1 unless defined $main::config->{ 'balance_max_variance'};
398
   # continue until our variance is where we want it, or we have tried too many times.
399
   while ( $iterations-- && $cluster->{'cluster'}->{'variance'} > $main::config->{ 'balance_max_variance'} ) {
400
      my $actions = &moveThings( $cluster );
44 rodolico 401
      if ( my $output = &doActions( $actions ) ) {
402
         $return .= $output;
403
      } else {
404
         last;
405
      }
42 rodolico 406
      #print Dumper( $actions ) . "\n"; die;
407
      # rerun stats
408
      $cluster = &getClusterStats();
409
      print &showBalanceReport( $cluster) if $main::config->{'flags'}->{'verbose'} > 1;
40 rodolico 410
   }
42 rodolico 411
   print "=== Ending Status ===\n\n" . &showBalanceReport( $cluster) unless $main::config->{'flags'}->{'quiet'};
44 rodolico 412
   return $return ? $return : "Already Balanced: No actions to take\n";
42 rodolico 413
} # balance
414
 
415
# finds node which needs to lose ($from) and gain ($to) the most. Then, goes through $from and finds the largest
416
# domain which will fit on $to until exhausted.
417
# as each domain is found, appends to $actions (array pointer). The format of each entry is a tab separated
418
# list of domain name, node from, node to, domain size
419
# returns the modified $actions
420
sub moveThings {
421
   my $stats = shift;
40 rodolico 422
 
42 rodolico 423
   my $actions = [];
424
   # find largest and smallest node differences
425
   my $transfer;
426
   my $from = '';
427
   my $to = '';
428
   # find smallest and largest "memory needed" in group. Note that if a node has too much, the number is negative and
429
   # for too little (ie, needs additional), the number is positive
430
   foreach my $node (keys %{$stats->{'node'} } ) {
431
      #print "Checking $node\n";
432
      if ( $from ) {
433
         $from = $node if $stats->{'node'}->{$from}->{'memory_needed'} > $stats->{'node'}->{$node}->{'memory_needed'};
434
         $to = $node if $stats->{'node'}->{$to}->{'memory_needed'} < $stats->{'node'}->{$node}->{'memory_needed'};
435
      } else { # just initialize everything to this node
436
         $from = $to = $node;
437
      } #if .. else
438
   } # foreach
439
   # 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
440
   # we need the smallest of what $from can give and $to can accept
441
   $transfer = abs( abs( $stats->{'node'}->{$from}->{'memory_needed'} ) > abs( $stats->{'node'}->{$to}->{'memory_needed'} ) ?
442
               $stats->{'node'}->{$to}->{'memory_needed'} : $stats->{'node'}->{$from}->{'memory_needed'} );
443
   # die "Transfer " . &humanReadable($transfer) ." bytes from $from to $to\n";
444
 
445
   # get array of domains running on $from, sorted by the size of the domain (descending, ie largest on top )
446
   # basically, get all keys from $main::statusDB->{'nodePopulation'}->{$from}->{'running'}, then sort them by looking them
447
   # up in $main::statusDB->{'virt'} and retrieving the amount of RAM
448
   my @sortedDomains = sort
449
      {
450
         $main::statusDB->{'virt'}->{$b}->{'memory'} <=> $main::statusDB->{'virt'}->{$a}->{'memory'}
451
      } keys %{ $main::statusDB->{'nodePopulation'}->{$from}->{'running'} };
452
   # now, "move" (fake move) largest domain that will fit into $to, and repeat until we can not do it anymore
453
   while ( $transfer ) {
454
      my $thisDomain = shift @sortedDomains;
455
      last unless $thisDomain; # we ran out of domains
456
      next unless $main::statusDB->{'virt'}->{$thisDomain}->{'memory'} <= $transfer;
457
      push @$actions, join( "\t", ( $thisDomain, $from, $to, $main::statusDB->{'virt'}->{$thisDomain}->{'memory'} ) );
458
      $transfer -= $main::statusDB->{'virt'}->{$thisDomain}->{'memory'};
459
   }
460
 
461
   return $actions;
39 rodolico 462
}