Subversion Repositories sysadmin_scripts

Rev

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

Rev Author Line No. Line
143 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
5
#use experimental "switch";
6
 
7
# requires File::Slurp. 
8
# In Debian derivatives
9
# apt install libfile-slurp-perl
10
 
11
# apt install libxml-libxml-perl libyaml-tiny-perl
12
 
13
 
14
BEGIN {
15
   use FindBin;
16
   use File::Spec;
17
   # use libraries from the directory this script is in
18
   use lib File::Spec->catdir($FindBin::Bin);
19
}
20
 
21
use Data::Dumper;
22
use YAML::Tiny;
23
 
24
# global variables
25
my $scriptDir = $FindBin::RealBin;
26
my $scriptName = $FindBin::Script;
27
my $confDir = "$scriptDir/conf";
28
my $dbDir = "$scriptDir/var";
29
my $nodeDBName = "$dbDir/node.yaml";
144 rodolico 30
my $domainDBName = "$dbDir/domains.yaml";
143 rodolico 31
my $nodePopulationDBName = "$dbDir/node_population.yaml";
32
 
33
# these contain the values from the databases
34
# loaded on demand
35
my $nodeDB;
36
my $virtDB;
37
my $nodePopulations;
38
 
39
 
40
my $dryRun = 1;
41
my $DEBUG = 0;
42
 
43
sub help {
44
   print "$0 command [argument]\n";
45
   print "where command is one of\n";
46
   print "\tnode update [node] [node]... # update a given node (or ALL)\n";
47
   print "\tnode list # display tab delimited list of node specs\n";
144 rodolico 48
   print "\tnode scan # find domains on all nodes\n ";
49
   print "\tdomain update ALL|RUNNING|[domain] [domain]... # update domains\n";
50
   print "\tdomain list ALL|RUNNING|[domain] [domain]... # display tab delimited list of domain specs\n";
145 rodolico 51
   print "\tcluster status # report of memory and vcpu status on all nodes\n";
143 rodolico 52
}
53
 
54
sub readDB {
55
   my ($filename) = @_;
56
   my $yaml = YAML::Tiny->new( {} );
57
   if ( -f $filename ) {
58
      $yaml = YAML::Tiny->read( $filename );
59
   }
60
   return $yaml->[0];
61
}
62
 
63
sub writeDB {
64
   my ($filename,$data) = @_;
65
   my $yaml = YAML::Tiny->new( $data );
66
   $yaml->write( $filename );
67
}
68
 
69
sub loadVirtDB {
70
   return if $virtDB;
144 rodolico 71
   $virtDB = &readDB( $domainDBName );
143 rodolico 72
}
73
 
74
sub loadNodePopulations {
75
   return if $nodePopulations;
76
   $nodePopulations = &readDB( $nodePopulationDBName );
77
}
78
 
79
sub loadNodeDB {
80
   return if $nodeDB;
81
   $nodeDB = &readDB( $nodeDBName );
82
}
83
 
144 rodolico 84
sub domain {
143 rodolico 85
   my $action = lc shift;
86
   my $return = '';
87
   &loadVirtDB();
88
   &loadNodePopulations();
89
   @_ = keys( %$virtDB ) if ( $_[0] && $_[0] eq 'ALL' );
90
   if ( $_[0] && $_[0] eq 'RUNNING' ) {
91
      my @running;
92
      foreach my $node ( keys %$nodePopulations ) {
93
         push @running, keys %{ $nodePopulations->{$node}->{'running'} };
94
      }
95
      @_ = @running;
96
   }
97
   if ( $action eq 'update' ) { # download xml to var and update database
98
      while ( my $virt = shift ) {
144 rodolico 99
         &parseDomain( $virt );
143 rodolico 100
      } # while
144 rodolico 101
      &writeDB( $domainDBName, $virtDB );
102
   } elsif ( $action eq 'list' ) { # dump domain as a tab separated data file
143 rodolico 103
      my @return;
104
      foreach my $node ( keys %$nodePopulations ) {
105
         foreach my $virt (keys %{$nodePopulations->{$node}->{'running'}} ) {
144 rodolico 106
            push @return, &listDomain( $virt, $node );
143 rodolico 107
         }
108
      }
109
      $return = join( "\n", sort @return ) . "\n";;
110
   }
111
   return $return;;
144 rodolico 112
} # sub domain
143 rodolico 113
 
144 rodolico 114
sub listDomain {
143 rodolico 115
   my ($virt,$node) = @_;
116
   my @return;
117
   push @return, $virt;
118
   push @return, $node;
119
   foreach my $column ( sort keys %{ $virtDB->{$virt} } ) {
120
      push @return, $virtDB->{$virt}->{$column};
121
   }
122
   return join( "\t", @return);
123
}
124
 
125
 
126
 
144 rodolico 127
# get the XML definition file of a running domain off of whatever
143 rodolico 128
# node it is running on, and save it to disk
129
sub getVirtConfig {
130
   my ($virt,$filename) = @_;
131
   my $return;
132
   print "In getVirtConfig looking for $virt with file $filename\n" if $DEBUG;
133
   if ( -f $filename ) {
134
      open XML, "<$filename" or die "Could not read from $filename: $!\n";
135
      $return = join( '', <XML> );
136
      close XML;
137
   } else {
138
      &loadNodePopulations();
139
      #die Dumper( $nodePopulations );
140
      foreach my $node ( keys %$nodePopulations ) {
141
         print "getVirtConfig Looking on $node for $virt\n";
142
         if ( exists( $nodePopulations->{$node}->{'running'}->{$virt} ) ) { # we found it
143
            print "Found $virt on node $node\n";
144
            $return = `ssh $node 'virsh dumpxml $virt'`;
145
            open XML,">$filename" or die "Could not write to $filename: $!\n";
146
            print XML $return;
147
            close XML;
148
         } # if
149
      } # foreach
150
   } # if..else
151
   return $return;
152
} # sub getVirtConfig
153
 
154
sub getXMLValue {
155
   my ( $key, $string ) = @_;
156
   my $start = "<$key";
157
   my $end = "</$key>";
158
   $string =~ m/$start([^>]*)>([^<]+)$end/;
159
   return ($1,$2);
160
}
161
 
144 rodolico 162
sub parseDomain {
143 rodolico 163
   my ($virt, $nodePopulations ) = @_;
164
 
165
   my @keysToSave = ( 'uuid', 'memory', 'vcpu' );
166
   my $filename = "$confDir/$virt.xml";
167
   my $xml = &getVirtConfig( $virt, $filename );
168
   my ($param,$value) = &getXMLValue( 'uuid', $xml );
169
   $virtDB->{$virt}->{'uuid'} = $value;
170
   ($param,$value) = &getXMLValue( 'memory', $xml );
171
   $virtDB->{$virt}->{'memory'} = $value;
172
   ($param,$value) = &getXMLValue( 'vcpu', $xml );
173
   $virtDB->{$virt}->{'vcpu'} = $value;
174
 
175
   $xml =~ m/type='vnc' port='(\d+)'/;
176
   $virtDB->{$virt}->{'vnc'} = $1;
177
}
178
 
144 rodolico 179
sub getDomainsOnNode {
143 rodolico 180
   my $node = shift;
181
   my @nodeList = grep { /^\s*\d/ } `ssh $node 'virsh list'`;
182
   for ( my $i = 0; $i < @nodeList; $i++ ) {
183
      if ( $nodeList[$i] =~ m/\s*\d+\s*([^ ]+)/ ) {
184
         $nodeList[$i] = $1;
185
      }
186
   }
187
   my %hash = map{ $_ => time } @nodeList;
188
   return \%hash;
189
}
190
 
191
sub node {
192
   my $action = lc shift;
193
 
194
   my %conversion = ( 
195
     'CPU frequency' => 'clock',
196
     'CPU model' => 'cpu_model',
197
     'CPU socket(s)' => 'cpu_socket',
198
     'CPU(s)' => 'cpu_count',
199
     'Core(s) per socket' => 'cpu_cores',
200
     'Memory size' => 'memory',
201
     'NUMA cell(s)' => 'numa_cells',
202
     'Thread(s) per core' => 'threads_per_core'
203
   );
204
 
205
 
206
   print "In node, action is $action\n" if $DEBUG;
207
   my $return = '';
208
   &loadNodeDB();
209
   if ( $action eq 'update' ) { # read information for nodes and update database
210
      @_ = keys %$nodeDB if ( $_[0] eq 'ALL' );
211
      while ( my $nodename = shift ) {
212
         print "Updating $nodename\n" if $DEBUG;
213
         $return = `ssh $nodename 'virsh nodeinfo'`;
214
         print "Output of ssh $nodename 'virsh nodeinfo' is\n" . $return if $DEBUG;
215
         my @nodeinfo = split( "\n", $return );
216
         for ( my $i = 0; $i < @nodeinfo; $i++ ) {
217
            my ($key, $value) = split( /:\s+/, $nodeinfo[$i] );
218
            if ( $value =~ m/^(\d+)\s+[a-z]+$/i ) {
219
               $value = $1;
220
            }
221
            $key = $conversion{$key} if exists( $conversion{$key} );
222
            $nodeDB->{$nodename}->{$key} = $value;
223
         } # for
224
      } # while
225
      print "nodeDB state after update\n" . Dumper( $nodeDB ) if $DEBUG;
226
      &writeDB( $nodeDBName, $nodeDB );
227
   } elsif ( $action eq 'list' ) { # dump database as a tab separated file with headers
228
      my @return;
229
      foreach my $node ( sort keys %$nodeDB ) {
230
         @return[0] = "Node\t" . join( "\t", sort keys %{ $nodeDB->{$node} } ) unless @return;
231
         my @line;
232
         push @line, $node;
233
         foreach my $column (sort keys %{ $nodeDB->{$node} }) {
234
            push @line, $nodeDB->{$node}->{$column};
235
         }
236
         push @return, join( "\t", @line );
237
      }
238
      $return = join( "\n", @return ) . "\n";
239
   } elsif ( $action eq 'scan' ) {
240
      foreach my $node ( keys %$nodeDB ) {
144 rodolico 241
         $nodePopulations->{$node}->{'running'} = &getDomainsOnNode( $node );
143 rodolico 242
         $nodePopulations->{$node}->{'lastchecked'} = time;
243
      }
244
      &writeDB( $nodePopulationDBName,$nodePopulations );
245
   } # if..elsif
246
   return $return;
247
}
248
 
145 rodolico 249
 
250
sub cluster {
251
   my $action = lc shift;
252
   my $return = '';
253
   if ( $action eq 'status' ) {
254
      &loadVirtDB();
255
      &loadNodePopulations();
256
      &loadNodeDB();
257
      print "Node\tThreads\tMemory\tDomains\tvcpu\tmem_used\n";
258
      my $usedmem = 0;
259
      my $usedcpu = 0;
260
      my $availmem = 0;
261
      my $availcpu = 0;
262
      my $totalDomains = 0;
263
      foreach my $node (sort keys %$nodeDB ) {
264
         my $memory = 0;
265
         my $vcpus = 0;
266
         my $count = 0;
267
         foreach my $domain ( keys %{ $nodePopulations->{$node}->{'running'} } ) {
268
            $memory += $virtDB->{$domain}->{'memory'};
269
            $vcpus += $virtDB->{$domain}->{'vcpu'};
270
            $count++;
271
         }
272
         $return .= "$node\t$nodeDB->{$node}->{cpu_count}\t$nodeDB->{$node}->{memory}\t$count\t$vcpus\t$memory\n";
273
         $usedmem += $memory;
274
         $usedcpu += $vcpus;
275
         $totalDomains += $count;
276
         $availmem += $nodeDB->{$node}->{memory};
277
         $availcpu += $nodeDB->{$node}->{cpu_count};
278
      } # outer for
279
      $return .= "Total\t$availcpu\t$availmem\t$totalDomains\t$usedcpu\t$usedmem\n";
280
   }
281
   return $return;
282
}
283
 
284
 
143 rodolico 285
#my $config = &readConf( $confFile );
286
 
287
my $command = shift; # the first one is the actual subsection
288
my $action = shift; # second is action to run
289
 
290
if ( $command eq 'node' ) {
291
   print &node( $action, @ARGV );
144 rodolico 292
} elsif ( $command eq 'domain' ) {
293
   print &domain( $action, @ARGV );
145 rodolico 294
} elsif ( $command eq 'cluster' ) {
295
   print &cluster( $action, @ARGV );
143 rodolico 296
} else {
297
   &help();
298
}
299
 
300
 
301
1;