| 5 | rodolico | 1 | #!/usr/bin/env perl
 | 
        
           |  |  | 2 |   | 
        
           |  |  | 3 | # All functions related to maniplating domains
 | 
        
           |  |  | 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 |   | 
        
           |  |  | 27 | package domain;
 | 
        
           |  |  | 28 |   | 
        
           |  |  | 29 | use warnings;
 | 
        
           |  |  | 30 | use strict;  
 | 
        
           |  |  | 31 |   | 
        
           |  |  | 32 | # define the version number
 | 
        
           |  |  | 33 | # see https://metacpan.org/pod/release/JPEACOCK/version-0.97/lib/version.pod
 | 
        
           |  |  | 34 | use version;
 | 
        
           |  |  | 35 | our $VERSION = version->declare("0.0.1");
 | 
        
           |  |  | 36 |   | 
        
           |  |  | 37 |   | 
        
           |  |  | 38 | use Data::Dumper;
 | 
        
           |  |  | 39 |   | 
        
           |  |  | 40 | use Exporter;
 | 
        
           |  |  | 41 |   | 
        
           |  |  | 42 | our @ISA = qw( Exporter );
 | 
        
           |  |  | 43 | our @EXPORT = qw( 
 | 
        
           | 7 | rodolico | 44 |                   &list
 | 
        
           | 5 | rodolico | 45 |                 );
 | 
        
           |  |  | 46 |   | 
        
           | 9 | rodolico | 47 | sub help {
 | 
        
           |  |  | 48 |    my @return;
 | 
        
           |  |  | 49 |    push @return, "domain update [domainname|-t domainname]";
 | 
        
           |  |  | 50 |    push @return, "\tUpdates capabilities on one or more domains, default is all domains";
 | 
        
           |  |  | 51 |    push @return, "domain list [--format|-f screen|tsv]";
 | 
        
           |  |  | 52 |    push @return, "\tLists all domains with some statistics about them as screen or tsv (default screen)";
 | 
        
           | 12 | rodolico | 53 |    push @return, "domain start domainname [node]";
 | 
        
           |  |  | 54 |    push @return, "\tstarts domainname on node. If node not set, will pick a node.";
 | 
        
           | 13 | rodolico | 55 |    push @return, "domain shutdown domainname";
 | 
        
           |  |  | 56 |    push @return, "\tInitiates a shutdown on a running domain and puts it in to manual mode";
 | 
        
           |  |  | 57 |    push @return, "domain migrate domainname [node]";
 | 
        
           |  |  | 58 |    push @return, "\tmigrates domain from current node to target. If target node not specified";
 | 
        
           |  |  | 59 |    push @return, "\twill be automatically selected";
 | 
        
           | 9 | rodolico | 60 |    return join( "\n", @return ) . "\n";
 | 
        
           |  |  | 61 | }
 | 
        
           |  |  | 62 |   | 
        
           | 7 | rodolico | 63 |   | 
        
           |  |  | 64 | sub list {
 | 
        
           | 12 | rodolico | 65 |    &main::readDB();
 | 
        
           |  |  | 66 |    print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::DEBUG > 2;
 | 
        
           | 7 | rodolico | 67 |   | 
        
           |  |  | 68 |    my @header;
 | 
        
           |  |  | 69 |    my @data;
 | 
        
           |  |  | 70 |   | 
        
           | 12 | rodolico | 71 |    foreach my $node ( keys %{$main::statusDB->{'nodePopulation'}} ) {
 | 
        
           |  |  | 72 |       foreach my $virt (keys %{$main::statusDB->{'nodePopulation'}->{$node}->{'running'}} ) {
 | 
        
           | 7 | rodolico | 73 |          unless ( @header ) {
 | 
        
           |  |  | 74 |             # if we don't have a header yet, create it from the keys in this one. Assumes every entry has same keys
 | 
        
           | 12 | rodolico | 75 |             @header = sort keys %{ $main::statusDB->{'virt'}->{$virt} };
 | 
        
           |  |  | 76 |             unshift @header, 'Domain';
 | 
        
           | 7 | rodolico | 77 |             unshift @header, 'Node';
 | 
        
           |  |  | 78 |          } # unless
 | 
        
           |  |  | 79 |          my @line;
 | 
        
           |  |  | 80 |          push @line, $node;
 | 
        
           |  |  | 81 |          push @line, $virt;
 | 
        
           | 12 | rodolico | 82 |          foreach my $column ( sort keys %{ $main::statusDB->{'virt'}->{$virt} } ) {
 | 
        
           |  |  | 83 |             push @line, $main::statusDB->{'virt'}->{$virt}->{$column};
 | 
        
           | 7 | rodolico | 84 |          }
 | 
        
           |  |  | 85 |          push @data, \@line;
 | 
        
           |  |  | 86 |       }
 | 
        
           |  |  | 87 |    }
 | 
        
           |  |  | 88 |   | 
        
           |  |  | 89 |    return &main::report( \@header, \@data );
 | 
        
           |  |  | 90 | }
 | 
        
           |  |  | 91 |   | 
        
           |  |  | 92 | sub update {
 | 
        
           | 12 | rodolico | 93 |    &main::readDB(1); # loading it for write, so lock
 | 
        
           | 8 | rodolico | 94 |    unless ( @_ ) {
 | 
        
           |  |  | 95 |       # they didn't pass in anything, so do everything
 | 
        
           | 12 | rodolico | 96 |       @_ = keys %{ $main::statusDB->{'virt'} }
 | 
        
           | 8 | rodolico | 97 |    } # unless
 | 
        
           |  |  | 98 |    print "Preparing to update " . join( "\n", @_ ) . "\n" if $main::DEBUG > 1;
 | 
        
           | 7 | rodolico | 99 |    while ( my $virt = shift ) {
 | 
        
           |  |  | 100 |       &parseDomain( $virt );
 | 
        
           |  |  | 101 |    } # while
 | 
        
           | 12 | rodolico | 102 |    &main::writeDB( $main::domainDBName, $main::statusDB->{'virt'} );
 | 
        
           | 9 | rodolico | 103 |    return "Updated\n";
 | 
        
           | 7 | rodolico | 104 | }
 | 
        
           |  |  | 105 |   | 
        
           |  |  | 106 | sub getXMLValue {
 | 
        
           |  |  | 107 |    my ( $key, $string ) = @_;
 | 
        
           | 9 | rodolico | 108 |    print "getXMLValue: looking for [$key] $string\n" if $main::DEBUG > 2;
 | 
        
           | 7 | rodolico | 109 |    my $start = "<$key";
 | 
        
           |  |  | 110 |    my $end = "</$key>";
 | 
        
           |  |  | 111 |    $string =~ m/$start([^>]*)>([^<]+)$end/;
 | 
        
           |  |  | 112 |    return ($1,$2);
 | 
        
           |  |  | 113 | }
 | 
        
           |  |  | 114 |   | 
        
           |  |  | 115 | sub parseDomain {
 | 
        
           |  |  | 116 |    my ($virt, $nodePopulations ) = @_;
 | 
        
           |  |  | 117 |   | 
        
           |  |  | 118 |    my @keysToSave = ( 'uuid', 'memory', 'vcpu','vnc' );
 | 
        
           |  |  | 119 |    my $filename = "$main::confDir/$virt.xml";
 | 
        
           |  |  | 120 |    my $xml = &getVirtConfig( $virt, $filename );
 | 
        
           |  |  | 121 |    my ($param,$value) = &getXMLValue( 'uuid', $xml );
 | 
        
           | 12 | rodolico | 122 |    $main::statusDB->{'virt'}->{$virt}->{'uuid'} = $value;
 | 
        
           | 7 | rodolico | 123 |    ($param,$value) = &getXMLValue( 'memory', $xml );
 | 
        
           | 12 | rodolico | 124 |    $main::statusDB->{'virt'}->{$virt}->{'memory'} = $value;
 | 
        
           | 7 | rodolico | 125 |    ($param,$value) = &getXMLValue( 'vcpu', $xml );
 | 
        
           | 12 | rodolico | 126 |    $main::statusDB->{'virt'}->{$virt}->{'vcpu'} = $value;
 | 
        
           | 7 | rodolico | 127 |   | 
        
           |  |  | 128 |    $xml =~ m/type='vnc' port='(\d+)'/;
 | 
        
           | 12 | rodolico | 129 |    $main::statusDB->{'virt'}->{$virt}->{'vnc'} = $1;
 | 
        
           | 7 | rodolico | 130 | }
 | 
        
           |  |  | 131 |   | 
        
           |  |  | 132 | # get the XML definition file of a running domain off of whatever
 | 
        
           |  |  | 133 | # node it is running on, and save it to disk
 | 
        
           |  |  | 134 | sub getVirtConfig {
 | 
        
           |  |  | 135 |    my ($virt,$filename) = @_;
 | 
        
           |  |  | 136 |    my $return;
 | 
        
           | 8 | rodolico | 137 |    print "In getVirtConfig looking for $virt with file $filename\n" if $main::DEBUG;
 | 
        
           | 7 | rodolico | 138 |    if ( -f $filename ) {
 | 
        
           |  |  | 139 |       open XML, "<$filename" or die "Could not read from $filename: $!\n";
 | 
        
           |  |  | 140 |       $return = join( '', <XML> );
 | 
        
           |  |  | 141 |       close XML;
 | 
        
           |  |  | 142 |    } else {
 | 
        
           | 12 | rodolico | 143 |       &main::readDB();
 | 
        
           |  |  | 144 |       foreach my $node ( keys %{$main::statusDB->{'nodePopulation'}} ) {
 | 
        
           | 9 | rodolico | 145 |          print "getVirtConfig Looking on $node for $virt\n" if $main::DEBUG > 1;;
 | 
        
           | 12 | rodolico | 146 |          if ( exists( $main::statusDB->{'nodePopulation'}->{$node}->{'running'}->{$virt} ) ) { # we found it
 | 
        
           | 9 | rodolico | 147 |             print "Found $virt on node $node\n" if $main::DEBUG;;
 | 
        
           | 7 | rodolico | 148 |             $return = `ssh $node 'virsh dumpxml $virt'`;
 | 
        
           | 9 | rodolico | 149 |             print "Writing config for $virt from $node into $filename\n" if $main::DEBUG;
 | 
        
           | 7 | rodolico | 150 |             open XML,">$filename" or die "Could not write to $filename: $!\n";
 | 
        
           |  |  | 151 |             print XML $return;
 | 
        
           |  |  | 152 |             close XML;
 | 
        
           |  |  | 153 |          } # if
 | 
        
           |  |  | 154 |       } # foreach
 | 
        
           |  |  | 155 |    } # if..else
 | 
        
           |  |  | 156 |    return $return;
 | 
        
           |  |  | 157 | } # sub getVirtConfig
 | 
        
           | 12 | rodolico | 158 |   | 
        
           |  |  | 159 | # start a domain
 | 
        
           |  |  | 160 | sub start {
 | 
        
           |  |  | 161 |    my ( $virt, $node ) = @_;
 | 
        
           |  |  | 162 |    $node = `hostname` unless $node;
 | 
        
           |  |  | 163 |    chomp $node;
 | 
        
           |  |  | 164 |    &main::readDB();
 | 
        
           |  |  | 165 |    for my $myNode ( keys %{$main::statusDB->{'nodePopulation'} } ) {
 | 
        
           |  |  | 166 |       die "$virt already running on $myNode, not starting\n" if ( $main::statusDB->{'nodePopulation'}->{$myNode}->{'running'}->{$virt} );
 | 
        
           |  |  | 167 |    }
 | 
        
           |  |  | 168 |    die "I do not have a definition for $virt\n" unless exists( $main::statusDB->{'virt'}->{$virt} );
 | 
        
           |  |  | 169 |    print Dumper( $main::statusDB->{'nodePopulation'} ) if $main::DEBUG > 2;
 | 
        
           |  |  | 170 |    my $filename = "$main::confDir/$virt.xml";
 | 
        
           |  |  | 171 |    return "ssh $node 'virsh create $filename'\n";
 | 
        
           |  |  | 172 | }
 | 
        
           | 13 | rodolico | 173 |   | 
        
           |  |  | 174 | sub shutdown {
 | 
        
           |  |  | 175 |    my $virt = shift;
 | 
        
           |  |  | 176 |    return "This code not written yet\n";
 | 
        
           |  |  | 177 | }
 | 
        
           |  |  | 178 |   | 
        
           |  |  | 179 | sub migrate {
 | 
        
           |  |  | 180 |    my ( $virt, $target ) = @_;
 | 
        
           |  |  | 181 |    return "I don't know how to migrate yet\n";
 | 
        
           |  |  | 182 | }
 |