Subversion Repositories havirt

Rev

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