Subversion Repositories havirt

Rev

Rev 2 | Rev 4 | 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
 
58
# global variables
59
my $scriptDir = $FindBin::RealBin;
60
my $scriptName = $FindBin::Script;
61
my $confDir = "$scriptDir/conf";
62
my $dbDir = "$scriptDir/var";
3 rodolico 63
our $nodeDBName = "$dbDir/node.yaml";
2 rodolico 64
my $domainDBName = "$dbDir/domains.yaml";
3 rodolico 65
our $nodePopulationDBName = "$dbDir/node_population.yaml";
2 rodolico 66
 
67
# these contain the values from the databases
68
# loaded on demand
3 rodolico 69
our $nodeDB;
70
our $virtDB;
71
our $nodePopulations;
2 rodolico 72
 
73
 
74
my $dryRun = 1;
75
my $DEBUG = 0;
76
 
77
sub help {
78
   print "$0 command [argument]\n";
79
   print "where command is one of\n";
80
   print "\tnode update [node] [node]... # update a given node (or ALL)\n";
81
   print "\tnode list # display tab delimited list of node specs\n";
82
   print "\tnode scan # find domains on all nodes\n ";
83
   print "\tdomain update ALL|RUNNING|[domain] [domain]... # update domains\n";
84
   print "\tdomain list ALL|RUNNING|[domain] [domain]... # display tab delimited list of domain specs\n";
85
   print "\tcluster status # report of memory and vcpu status on all nodes\n";
86
}
87
 
88
sub loadVirtDB {
89
   return if $virtDB;
90
   $virtDB = &readDB( $domainDBName );
91
}
92
 
93
sub loadNodePopulations {
94
   return if $nodePopulations;
95
   $nodePopulations = &readDB( $nodePopulationDBName );
96
}
97
 
98
sub domain {
99
   my $action = lc shift;
100
   my $return = '';
101
   &loadVirtDB();
102
   &loadNodePopulations();
103
   @_ = keys( %$virtDB ) if ( $_[0] && $_[0] eq 'ALL' );
104
   if ( $_[0] && $_[0] eq 'RUNNING' ) {
105
      my @running;
106
      foreach my $node ( keys %$nodePopulations ) {
107
         push @running, keys %{ $nodePopulations->{$node}->{'running'} };
108
      }
109
      @_ = @running;
110
   }
111
   if ( $action eq 'update' ) { # download xml to var and update database
112
      while ( my $virt = shift ) {
113
         &parseDomain( $virt );
114
      } # while
115
      &writeDB( $domainDBName, $virtDB );
116
   } elsif ( $action eq 'list' ) { # dump domain as a tab separated data file
117
      my @return;
118
      foreach my $node ( keys %$nodePopulations ) {
119
         foreach my $virt (keys %{$nodePopulations->{$node}->{'running'}} ) {
120
            push @return, &listDomain( $virt, $node );
121
         }
122
      }
123
      $return = join( "\n", sort @return ) . "\n";;
124
   }
125
   return $return;;
126
} # sub domain
127
 
128
sub listDomain {
129
   my ($virt,$node) = @_;
130
   my @return;
131
   push @return, $virt;
132
   push @return, $node;
133
   foreach my $column ( sort keys %{ $virtDB->{$virt} } ) {
134
      push @return, $virtDB->{$virt}->{$column};
135
   }
136
   return join( "\t", @return);
137
}
138
 
139
 
140
 
141
# get the XML definition file of a running domain off of whatever
142
# node it is running on, and save it to disk
143
sub getVirtConfig {
144
   my ($virt,$filename) = @_;
145
   my $return;
146
   print "In getVirtConfig looking for $virt with file $filename\n" if $DEBUG;
147
   if ( -f $filename ) {
148
      open XML, "<$filename" or die "Could not read from $filename: $!\n";
149
      $return = join( '', <XML> );
150
      close XML;
151
   } else {
152
      &loadNodePopulations();
153
      #die Dumper( $nodePopulations );
154
      foreach my $node ( keys %$nodePopulations ) {
155
         print "getVirtConfig Looking on $node for $virt\n";
156
         if ( exists( $nodePopulations->{$node}->{'running'}->{$virt} ) ) { # we found it
157
            print "Found $virt on node $node\n";
158
            $return = `ssh $node 'virsh dumpxml $virt'`;
159
            open XML,">$filename" or die "Could not write to $filename: $!\n";
160
            print XML $return;
161
            close XML;
162
         } # if
163
      } # foreach
164
   } # if..else
165
   return $return;
166
} # sub getVirtConfig
167
 
168
sub getXMLValue {
169
   my ( $key, $string ) = @_;
170
   my $start = "<$key";
171
   my $end = "</$key>";
172
   $string =~ m/$start([^>]*)>([^<]+)$end/;
173
   return ($1,$2);
174
}
175
 
176
sub parseDomain {
177
   my ($virt, $nodePopulations ) = @_;
178
 
179
   my @keysToSave = ( 'uuid', 'memory', 'vcpu' );
180
   my $filename = "$confDir/$virt.xml";
181
   my $xml = &getVirtConfig( $virt, $filename );
182
   my ($param,$value) = &getXMLValue( 'uuid', $xml );
183
   $virtDB->{$virt}->{'uuid'} = $value;
184
   ($param,$value) = &getXMLValue( 'memory', $xml );
185
   $virtDB->{$virt}->{'memory'} = $value;
186
   ($param,$value) = &getXMLValue( 'vcpu', $xml );
187
   $virtDB->{$virt}->{'vcpu'} = $value;
188
 
189
   $xml =~ m/type='vnc' port='(\d+)'/;
190
   $virtDB->{$virt}->{'vnc'} = $1;
191
}
192
 
193
sub cluster {
194
   my $action = lc shift;
195
   my $return = '';
196
   if ( $action eq 'status' ) {
197
      &loadVirtDB();
198
      &loadNodePopulations();
199
      &loadNodeDB();
200
      print "Node\tThreads\tMemory\tDomains\tvcpu\tmem_used\n";
201
      my $usedmem = 0;
202
      my $usedcpu = 0;
203
      my $availmem = 0;
204
      my $availcpu = 0;
205
      my $totalDomains = 0;
206
      foreach my $node (sort keys %$nodeDB ) {
207
         my $memory = 0;
208
         my $vcpus = 0;
209
         my $count = 0;
210
         foreach my $domain ( keys %{ $nodePopulations->{$node}->{'running'} } ) {
211
            $memory += $virtDB->{$domain}->{'memory'};
212
            $vcpus += $virtDB->{$domain}->{'vcpu'};
213
            $count++;
214
         }
215
         $return .= "$node\t$nodeDB->{$node}->{cpu_count}\t$nodeDB->{$node}->{memory}\t$count\t$vcpus\t$memory\n";
216
         $usedmem += $memory;
217
         $usedcpu += $vcpus;
218
         $totalDomains += $count;
219
         $availmem += $nodeDB->{$node}->{memory};
220
         $availcpu += $nodeDB->{$node}->{cpu_count};
221
      } # outer for
222
      $return .= "Total\t$availcpu\t$availmem\t$totalDomains\t$usedcpu\t$usedmem\n";
223
   }
224
   return $return;
225
}
226
 
227
 
228
#my $config = &readConf( $confFile );
229
 
230
my $command = shift; # the first one is the actual subsection
231
my $action = shift; # second is action to run
232
 
233
if ( $command eq 'node' ) {
3 rodolico 234
   require node;
235
   Module->import( qw/node/ );
236
   print &node::node( $action, @ARGV );
2 rodolico 237
} elsif ( $command eq 'domain' ) {
238
   print &domain( $action, @ARGV );
239
} elsif ( $command eq 'cluster' ) {
240
   print &cluster( $action, @ARGV );
241
} else {
242
   &help();
243
}
244
 
245
 
246
1;