| 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.
 | 
        
           | 26 | rodolico | 35 | #
 | 
        
           |  |  | 36 | # v1.2.0 20240826 RWR
 | 
        
           |  |  | 37 | # Modified the flag structure to conform to standard (ie, dryrun instead of yes)
 | 
        
           |  |  | 38 | # Added a lot of 'verbose' print lines, and modified for new flag structure
 | 
        
           |  |  | 39 | # Added a config file, which is auto-generated if it doesn't exist
 | 
        
           | 3 | rodolico | 40 |   | 
        
           | 2 | rodolico | 41 | #use experimental "switch";
 | 
        
           |  |  | 42 |   | 
        
           |  |  | 43 | # requires File::Slurp. 
 | 
        
           |  |  | 44 | # In Debian derivatives
 | 
        
           |  |  | 45 | # apt install libfile-slurp-perl
 | 
        
           |  |  | 46 |   | 
        
           |  |  | 47 | # apt install libxml-libxml-perl libyaml-tiny-perl
 | 
        
           |  |  | 48 |   | 
        
           |  |  | 49 |   | 
        
           |  |  | 50 | BEGIN {
 | 
        
           |  |  | 51 |    use FindBin;
 | 
        
           |  |  | 52 |    use File::Spec;
 | 
        
           |  |  | 53 |    # use libraries from the directory this script is in
 | 
        
           | 6 | rodolico | 54 |    use Cwd 'abs_path';
 | 
        
           |  |  | 55 |    use File::Basename;
 | 
        
           |  |  | 56 |    use lib dirname( abs_path( __FILE__ ) );
 | 
        
           | 2 | rodolico | 57 | }
 | 
        
           |  |  | 58 |   | 
        
           | 3 | rodolico | 59 | use havirt; # Load all our shared stuff
 | 
        
           |  |  | 60 |   | 
        
           | 2 | rodolico | 61 | use Data::Dumper;
 | 
        
           |  |  | 62 | use YAML::Tiny;
 | 
        
           |  |  | 63 |   | 
        
           | 4 | rodolico | 64 | # define the version number
 | 
        
           |  |  | 65 | # see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
 | 
        
           |  |  | 66 | use version;
 | 
        
           | 26 | rodolico | 67 | our $VERSION = version->declare("1.2.0");
 | 
        
           | 4 | rodolico | 68 |   | 
        
           |  |  | 69 |   | 
        
           |  |  | 70 | # see https://perldoc.perl.org/Getopt/Long.html
 | 
        
           |  |  | 71 | use Getopt::Long;
 | 
        
           |  |  | 72 | # allow -vvn (ie, --verbose --verbose --dryrun)
 | 
        
           |  |  | 73 | Getopt::Long::Configure ("bundling");
 | 
        
           |  |  | 74 |   | 
        
           |  |  | 75 |   | 
        
           | 25 | rodolico | 76 | our $config;
 | 
        
           |  |  | 77 | my $configFileName = $FindBin::RealBin . '/config.yaml';
 | 
        
           | 2 | rodolico | 78 |   | 
        
           | 12 | rodolico | 79 | # Big hash that contains all information about system
 | 
        
           |  |  | 80 | our $statusDB;
 | 
        
           | 2 | rodolico | 81 |   | 
        
           |  |  | 82 | sub help {
 | 
        
           |  |  | 83 |    print "$0 command [argument]\n";
 | 
        
           |  |  | 84 |    print "where command is one of\n";
 | 
        
           | 14 | rodolico | 85 |    print "\tnode [help] # work with a node\n";
 | 
        
           |  |  | 86 |    print "\tdomain [help] # work with individual domains\n";
 | 
        
           |  |  | 87 |    print "\tcluster [help] # report of memory and vcpu status on all nodes\n";
 | 
        
           | 4 | rodolico | 88 |    print "Some flags can be used where appropriate\n";
 | 
        
           |  |  | 89 |    print "\t--help|-h # show this screen\n";
 | 
        
           |  |  | 90 |    print "\t--version|-v # show version of program\n";
 | 
        
           | 26 | rodolico | 91 |    print "\t--format screen|tsv # output of list commands is either padded for screen or Tab Delim\n";
 | 
        
           |  |  | 92 |    print "\t--force|f force an action, even if not normally done\n";
 | 
        
           | 4 | rodolico | 93 |    print "\t--target|-t NODE # the action use NODE for the target of actions\n";
 | 
        
           |  |  | 94 |    print "\t--dryrun|-n # does not perform the actions, simply shows what commands would be executed\n";
 | 
        
           | 9 | rodolico | 95 |    print "\t--debug|d # increases verbosity, with -ddd, totally outragious\n";
 | 
        
           | 15 | rodolico | 96 |    print "\t--quiet|q # do not print anything except major errors\n";
 | 
        
           | 2 | rodolico | 97 | }
 | 
        
           |  |  | 98 |   | 
        
           | 25 | rodolico | 99 | &makeConfig( $config,$configFileName ) unless -f $configFileName;
 | 
        
           |  |  | 100 | $config = readConfig( $configFileName );
 | 
        
           | 2 | rodolico | 101 |   | 
        
           | 4 | rodolico | 102 | # handle any command line parameters that may have been passed in
 | 
        
           |  |  | 103 |   | 
        
           |  |  | 104 | GetOptions (
 | 
        
           | 25 | rodolico | 105 |    $config->{'flags'}, 
 | 
        
           | 26 | rodolico | 106 |       'debug|d+',  # integer, can be incremented like -ddd
 | 
        
           |  |  | 107 |       'dryrun|n!', # negatable with --nodryrun
 | 
        
           |  |  | 108 |       'force|f',
 | 
        
           |  |  | 109 |       'format=s',  # must be --format (no short form)
 | 
        
           |  |  | 110 |       'help|h', 
 | 
        
           |  |  | 111 |       'quiet|q',
 | 
        
           | 25 | rodolico | 112 |       'target|t=s',
 | 
        
           | 26 | rodolico | 113 |       'verbose|v+',# integer, can be incremented like -vv
 | 
        
           |  |  | 114 |       'version|V'  # note, the short form is a capital 'V'
 | 
        
           | 4 | rodolico | 115 | ) or die "Error parsing command line\n";
 | 
        
           |  |  | 116 |   | 
        
           | 33 | rodolico | 117 | print "Parameters are " . Dumper( $config ) . "\n" if $config->{'flags'}->{'debug'};
 | 
        
           |  |  | 118 | die if $config->{'flags'}->{'debug'} > 3;
 | 
        
           | 25 | rodolico | 119 |   | 
        
           | 6 | rodolico | 120 | my $command = shift; # the first one is the actual subsection
 | 
        
           |  |  | 121 | my $action = shift; # second is action to run
 | 
        
           | 14 | rodolico | 122 | $action = 'help' unless $action;
 | 
        
           | 4 | rodolico | 123 |   | 
        
           | 25 | rodolico | 124 | if ( $config->{'flags'}->{'help'} || $command eq 'help' || ! $command ) { &help() ; exit; }
 | 
        
           |  |  | 125 | if ( $config->{'flags'}->{'version'} ) { use File::Basename; print basename($0) . " v$VERSION\n"; exit; }
 | 
        
           | 4 | rodolico | 126 |   | 
        
           | 25 | rodolico | 127 | print "Command = $command\nAction = $action\n" if $config->{'flags'}->{'debug'};
 | 
        
           | 4 | rodolico | 128 |   | 
        
           | 26 | rodolico | 129 |   | 
        
           | 8 | rodolico | 130 | # we allow a three part command for some actions on a domain, ie start, shutdown, migrate and destroy
 | 
        
           |  |  | 131 | # for simplicity, if the command is one of the above, allow the user to enter like that, but we will
 | 
        
           |  |  | 132 | # restructure the command as if they had used the full, three part (ie, with domain as the command)
 | 
        
           |  |  | 133 | # so, the following are equivilent
 | 
        
           |  |  | 134 | # havirt domain start nameofdomain
 | 
        
           |  |  | 135 | # havirt start nameofdomain
 | 
        
           |  |  | 136 |   | 
        
           |  |  | 137 | if ( $command eq 'start' || $command eq 'shutdown' || $command eq 'migrate' || $command eq 'destroy' ) { # shortcut for working with domains
 | 
        
           |  |  | 138 |    push @ARGV, $action; # this is the domain we are working with
 | 
        
           |  |  | 139 |    $action = $command; # this is what we want to do (start, shutdown, etc...)
 | 
        
           |  |  | 140 |    $command = 'domain'; # keywork domain, for correct module
 | 
        
           | 2 | rodolico | 141 | }
 | 
        
           |  |  | 142 |   | 
        
           | 8 | rodolico | 143 | # ok, this is some serious weirdness. $command is actually the name of a module, and $action is a method
 | 
        
           |  |  | 144 | # defined in the module.
 | 
        
           | 2 | rodolico | 145 |   | 
        
           | 8 | rodolico | 146 | # I use 'require' which loads at runtime, not compile time, so it will only load a module if needed.
 | 
        
           |  |  | 147 | # then, I check to see if a method named in $action is defined within that module and, if so
 | 
        
           |  |  | 148 | # execute it and return the result. If not, gives an error message.
 | 
        
           |  |  | 149 |   | 
        
           |  |  | 150 | # This means to add functionality, we simply add a method (sub) in a given module, or create a whole
 | 
        
           |  |  | 151 | # new module.
 | 
        
           |  |  | 152 |   | 
        
           |  |  | 153 |   | 
        
           |  |  | 154 | # we have to concat here since the double colon causes some interpretation problems
 | 
        
           |  |  | 155 | my $execute = $command . '::' . $action;
 | 
        
           | 11 | rodolico | 156 | # load the module, die if it doesn't exist.
 | 
        
           | 25 | rodolico | 157 | if (-f $config->{'script dir'} . "/$command.pm" ) {
 | 
        
           | 11 | rodolico | 158 |    require "$command.pm";
 | 
        
           |  |  | 159 |    Module->import( $command ); # for require, you must manually import
 | 
        
           |  |  | 160 | } else {
 | 
        
           |  |  | 161 |    die "Error, I don't know the command [$command]\n";
 | 
        
           |  |  | 162 | }
 | 
        
           | 8 | rodolico | 163 |   | 
        
           |  |  | 164 | if ( defined &{\&{$execute}} ) { # check if module::sub exists (ie, $command::action)
 | 
        
           | 12 | rodolico | 165 |   my $message =  &{\&{$execute}}(@ARGV); # yes, it exists, so call it with any remaining arguments
 | 
        
           | 26 | rodolico | 166 |   print $message unless $main::config->{'flags'}->{'quiet'};
 | 
        
           | 8 | rodolico | 167 | } else { # method $action does not exist in module $command, so just a brief error message
 | 
        
           | 11 | rodolico | 168 |   die "Error, could not find action [$action] for module [$command]\n";
 | 
        
           | 8 | rodolico | 169 | } 
 | 
        
           |  |  | 170 |   | 
        
           | 2 | rodolico | 171 | 1;
 |