Subversion Repositories havirt

Rev

Rev 3 | Rev 5 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
2 rodolico 1
#! /usr/bin/env perl
2
 
3
use strict;
4
use warnings;
3 rodolico 5
 
6
# havirt
7
# Basically an extension of virsh which will perform actions on virtuals 
8
# running on multiple, connected hypervisors (virsh calls them nodes)
9
# existing as a cluster of hypervisors where virtuals can be shut down,
10
# started and migrated at need.
11
#
12
# Progam consists of one executable (havirt) and multiple Perl Modules
13
# (*.pm), each of which encompasses a function. However, this is NOT
14
# written as an Object Oriented system.
15
#
16
# havirt --help gives a brief help screen.
17
 
18
# Copyright 2024 Daily Data, Inc.
19
# 
20
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following 
21
# conditions are met:
22
#
23
#   Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
24
#   Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer 
25
#   in the documentation and/or other materials provided with the distribution.
26
#   Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived
27
#   from this software without specific prior written permission.
28
# 
29
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT
30
# NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
31
# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
32
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
33
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
34
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35
 
36
 
2 rodolico 37
#use experimental "switch";
38
 
39
# requires File::Slurp. 
40
# In Debian derivatives
41
# apt install libfile-slurp-perl
42
 
43
# apt install libxml-libxml-perl libyaml-tiny-perl
44
 
45
 
46
BEGIN {
47
   use FindBin;
48
   use File::Spec;
49
   # use libraries from the directory this script is in
50
   use lib File::Spec->catdir($FindBin::Bin);
51
}
52
 
3 rodolico 53
use havirt; # Load all our shared stuff
54
 
2 rodolico 55
use Data::Dumper;
56
use YAML::Tiny;
57
 
4 rodolico 58
# define the version number
59
# see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
60
use version;
61
our $VERSION = version->declare("0.0.1");
62
 
63
 
64
# see https://perldoc.perl.org/Getopt/Long.html
65
use Getopt::Long;
66
# allow -vvn (ie, --verbose --verbose --dryrun)
67
Getopt::Long::Configure ("bundling");
68
 
69
 
2 rodolico 70
# global variables
71
my $scriptDir = $FindBin::RealBin;
72
my $scriptName = $FindBin::Script;
73
my $confDir = "$scriptDir/conf";
74
my $dbDir = "$scriptDir/var";
3 rodolico 75
our $nodeDBName = "$dbDir/node.yaml";
4 rodolico 76
our $domainDBName = "$dbDir/domains.yaml";
3 rodolico 77
our $nodePopulationDBName = "$dbDir/node_population.yaml";
2 rodolico 78
 
79
# these contain the values from the databases
80
# loaded on demand
3 rodolico 81
our $nodeDB;
82
our $virtDB;
83
our $nodePopulations;
2 rodolico 84
 
4 rodolico 85
# options variables
86
our $reportFormat = 'screen';
87
our $targetNode = '';
88
our $dryRun = 1;
89
our $DEBUG = 0;
90
my $help = 0;
91
my $version = 0;
2 rodolico 92
 
93
sub help {
94
   print "$0 command [argument]\n";
95
   print "where command is one of\n";
4 rodolico 96
   print "\tnode update [-t NODE] # update a given node (defaults to all)\n";
2 rodolico 97
   print "\tnode list # display tab delimited list of node specs\n";
4 rodolico 98
   print "\tnode scan [-t NODE] # update list of domains on node (defaults to all)\n ";
2 rodolico 99
   print "\tdomain update ALL|RUNNING|[domain] [domain]... # update domains\n";
100
   print "\tdomain list ALL|RUNNING|[domain] [domain]... # display tab delimited list of domain specs\n";
101
   print "\tcluster status # report of memory and vcpu status on all nodes\n";
4 rodolico 102
   print "Some flags can be used where appropriate\n";
103
   print "\t--help|-h # show this screen\n";
104
   print "\t--version|-v # show version of program\n";
105
   print "\t--format|-f screen|tsv # output of list commands is either padded for screen or Tab Delim\n";
106
   print "\t--target|-t NODE # the action use NODE for the target of actions\n";
107
   print "\t--dryrun|-n # does not perform the actions, simply shows what commands would be executed\n";
108
   print "\t--debug|d # increases verbosity, does not actually perform actions (--dry-run assumed)\n";
2 rodolico 109
}
110
 
111
sub loadVirtDB {
112
   return if $virtDB;
113
   $virtDB = &readDB( $domainDBName );
114
}
115
 
116
sub domain {
117
   my $action = lc shift;
118
   my $return = '';
119
   &loadVirtDB();
120
   &loadNodePopulations();
121
   @_ = keys( %$virtDB ) if ( $_[0] && $_[0] eq 'ALL' );
122
   if ( $_[0] && $_[0] eq 'RUNNING' ) {
123
      my @running;
124
      foreach my $node ( keys %$nodePopulations ) {
125
         push @running, keys %{ $nodePopulations->{$node}->{'running'} };
126
      }
127
      @_ = @running;
128
   }
129
   if ( $action eq 'update' ) { # download xml to var and update database
130
      while ( my $virt = shift ) {
131
         &parseDomain( $virt );
132
      } # while
133
      &writeDB( $domainDBName, $virtDB );
134
   } elsif ( $action eq 'list' ) { # dump domain as a tab separated data file
135
      my @return;
136
      foreach my $node ( keys %$nodePopulations ) {
137
         foreach my $virt (keys %{$nodePopulations->{$node}->{'running'}} ) {
138
            push @return, &listDomain( $virt, $node );
139
         }
140
      }
141
      $return = join( "\n", sort @return ) . "\n";;
142
   }
143
   return $return;;
144
} # sub domain
145
 
146
sub listDomain {
147
   my ($virt,$node) = @_;
148
   my @return;
149
   push @return, $virt;
150
   push @return, $node;
151
   foreach my $column ( sort keys %{ $virtDB->{$virt} } ) {
152
      push @return, $virtDB->{$virt}->{$column};
153
   }
154
   return join( "\t", @return);
155
}
156
 
157
 
158
 
159
# get the XML definition file of a running domain off of whatever
160
# node it is running on, and save it to disk
161
sub getVirtConfig {
162
   my ($virt,$filename) = @_;
163
   my $return;
164
   print "In getVirtConfig looking for $virt with file $filename\n" if $DEBUG;
165
   if ( -f $filename ) {
166
      open XML, "<$filename" or die "Could not read from $filename: $!\n";
167
      $return = join( '', <XML> );
168
      close XML;
169
   } else {
170
      &loadNodePopulations();
171
      #die Dumper( $nodePopulations );
172
      foreach my $node ( keys %$nodePopulations ) {
173
         print "getVirtConfig Looking on $node for $virt\n";
174
         if ( exists( $nodePopulations->{$node}->{'running'}->{$virt} ) ) { # we found it
175
            print "Found $virt on node $node\n";
176
            $return = `ssh $node 'virsh dumpxml $virt'`;
177
            open XML,">$filename" or die "Could not write to $filename: $!\n";
178
            print XML $return;
179
            close XML;
180
         } # if
181
      } # foreach
182
   } # if..else
183
   return $return;
184
} # sub getVirtConfig
185
 
186
sub getXMLValue {
187
   my ( $key, $string ) = @_;
188
   my $start = "<$key";
189
   my $end = "</$key>";
190
   $string =~ m/$start([^>]*)>([^<]+)$end/;
191
   return ($1,$2);
192
}
193
 
194
sub parseDomain {
195
   my ($virt, $nodePopulations ) = @_;
196
 
197
   my @keysToSave = ( 'uuid', 'memory', 'vcpu' );
198
   my $filename = "$confDir/$virt.xml";
199
   my $xml = &getVirtConfig( $virt, $filename );
200
   my ($param,$value) = &getXMLValue( 'uuid', $xml );
201
   $virtDB->{$virt}->{'uuid'} = $value;
202
   ($param,$value) = &getXMLValue( 'memory', $xml );
203
   $virtDB->{$virt}->{'memory'} = $value;
204
   ($param,$value) = &getXMLValue( 'vcpu', $xml );
205
   $virtDB->{$virt}->{'vcpu'} = $value;
206
 
207
   $xml =~ m/type='vnc' port='(\d+)'/;
208
   $virtDB->{$virt}->{'vnc'} = $1;
209
}
210
 
211
sub cluster {
212
   my $action = lc shift;
213
   my $return = '';
214
   if ( $action eq 'status' ) {
215
      &loadVirtDB();
216
      &loadNodePopulations();
217
      &loadNodeDB();
218
      print "Node\tThreads\tMemory\tDomains\tvcpu\tmem_used\n";
219
      my $usedmem = 0;
220
      my $usedcpu = 0;
221
      my $availmem = 0;
222
      my $availcpu = 0;
223
      my $totalDomains = 0;
224
      foreach my $node (sort keys %$nodeDB ) {
225
         my $memory = 0;
226
         my $vcpus = 0;
227
         my $count = 0;
228
         foreach my $domain ( keys %{ $nodePopulations->{$node}->{'running'} } ) {
229
            $memory += $virtDB->{$domain}->{'memory'};
230
            $vcpus += $virtDB->{$domain}->{'vcpu'};
231
            $count++;
232
         }
233
         $return .= "$node\t$nodeDB->{$node}->{cpu_count}\t$nodeDB->{$node}->{memory}\t$count\t$vcpus\t$memory\n";
234
         $usedmem += $memory;
235
         $usedcpu += $vcpus;
236
         $totalDomains += $count;
237
         $availmem += $nodeDB->{$node}->{memory};
238
         $availcpu += $nodeDB->{$node}->{cpu_count};
239
      } # outer for
240
      $return .= "Total\t$availcpu\t$availmem\t$totalDomains\t$usedcpu\t$usedmem\n";
241
   }
242
   return $return;
243
}
244
 
245
 
4 rodolico 246
# handle any command line parameters that may have been passed in
247
 
248
GetOptions (
249
   'format|f=s' => \$reportFormat,
250
   'target|t=s' => \$targetNode,
251
   'dryrun|n!' => \$dryRun,
252
   'debug|d+' => \$DEBUG,
253
   'help|h' => \$help,
254
   'version|v' => \$version
255
) or die "Error parsing command line\n";
256
 
257
 
258
if ( $help ) { &help() ; exit; }
259
if ( $version ) { use File::Basename; print basename($0) . " v$VERSION\n"; exit; }
260
 
2 rodolico 261
my $command = shift; # the first one is the actual subsection
262
my $action = shift; # second is action to run
263
 
4 rodolico 264
#print "Parameters are\nreportFormat\t$reportFormat\ntargetNode\t$targetNode\ndryRun\t$dryRun\nDEBUG\t$DEBUG\n";
265
#print "Command = $command\nAction = $action\n";
266
#die;
267
 
2 rodolico 268
if ( $command eq 'node' ) {
3 rodolico 269
   require node;
270
   Module->import( qw/node/ );
4 rodolico 271
#   print &node::list( $action, @ARGV );
272
   print &{\&{"node::$action"}}();
2 rodolico 273
} elsif ( $command eq 'domain' ) {
274
   print &domain( $action, @ARGV );
275
} elsif ( $command eq 'cluster' ) {
276
   print &cluster( $action, @ARGV );
277
} else {
278
   &help();
279
}
280
 
281
 
282
1;