You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@vcl.apache.org by ar...@apache.org on 2011/12/14 18:15:17 UTC
svn commit: r1214348 [1/2] - in
/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning: libvirt.pm
libvirt/ libvirt/KVM.pm
Author: arkurth
Date: Wed Dec 14 17:15:17 2011
New Revision: 1214348
URL: http://svn.apache.org/viewvc?rev=1214348&view=rev
Log:
VCL-545
Added libvirt.pm module to support general libvirt functionality. Added KVM.pm libvirt hypervisor driver module to allow KVM to be provisioned via libvirt.
Added:
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt/
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm (with props)
incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt/KVM.pm (with props)
Added: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm
URL: http://svn.apache.org/viewvc/incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm?rev=1214348&view=auto
==============================================================================
--- incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm (added)
+++ incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm Wed Dec 14 17:15:17 2011
@@ -0,0 +1,1664 @@
+#!/usr/bin/perl -w
+###############################################################################
+# $Id$
+###############################################################################
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements. See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+###############################################################################
+
+=head1 NAME
+
+VCL::Provisioning::libvirt - VCL provisioning module to support the libvirt toolkit
+
+=head1 SYNOPSIS
+
+ use VCL::Module::Provisioning::libvirt;
+ my $provisioner = (VCL::Module::Provisioning::libvirt)->new({data_structure => $self->data});
+
+=head1 DESCRIPTION
+
+ Provides support allowing VCL to provisioning resources supported by the
+ libvirt toolkit.
+ http://libvirt.org
+
+=cut
+
+##############################################################################
+package VCL::Module::Provisioning::libvirt;
+
+# Specify the lib path using FindBin
+use FindBin;
+use lib "$FindBin::Bin/../../..";
+
+# Configure inheritance
+use base qw(VCL::Module::Provisioning);
+
+# Specify the version of this module
+our $VERSION = '2.2.1';
+
+# Specify the version of Perl to use
+use 5.008000;
+
+use strict;
+use warnings;
+use diagnostics;
+use English qw( -no_match_vars );
+use File::Basename;
+use XML::Simple qw(:strict);
+
+use VCL::utils;
+
+##############################################################################
+
+=head1 OBJECT METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 initialize
+
+ Parameters : none
+ Returns : boolean
+ Description : Enumerates the libvirt driver modules directory:
+ lib/VCL/Module/Provisioning/libvirt/
+
+ Attempts to create and initialize an object for each hypervisor
+ driver module found in this directory. The first driver module
+ object successfully initialized is used. This object is made
+ accessible within this module via $self->driver. This allows
+ libvirt support driver modules to be added without having to
+ alter the code in libvirt.pm.
+
+=cut
+
+sub initialize {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $vmhost_type = $self->data->get_vmhost_type_name();
+ my $vmhost_username = $self->data->get_vmhost_profile_username();
+ my $vmhost_password = $self->data->get_vmhost_profile_password();
+
+ # Get the absolute path of the libvirt drivers directory
+ my $driver_directory_path = "$FindBin::Bin/../lib/VCL/Module/Provisioning/libvirt";
+ notify($ERRORS{'DEBUG'}, 0, "libvirt driver module directory path: $driver_directory_path");
+
+ # Get a list of all *.pm files in the libvirt drivers directory
+ my @driver_module_paths = $self->mn_os->find_files($driver_directory_path, '*.pm');
+
+ # Attempt to create an initialize an object for each driver module
+ # Use the first driver module successfully initialized
+ DRIVER: for my $driver_module_path (sort { lc($a) cmp lc($b) } @driver_module_paths) {
+ my $driver_name = fileparse($driver_module_path, qr/\.pm$/i);
+ my $driver_perl_package = ref($self) . "::$driver_name";
+
+ # Create and initialize the driver object
+ eval "use $driver_perl_package";
+ if ($EVAL_ERROR) {
+ notify($ERRORS{'WARNING'}, 0, "failed to load libvirt $driver_name driver module: $driver_perl_package, error: $EVAL_ERROR");
+ next DRIVER;
+ }
+ my $driver;
+ eval { $driver = ($driver_perl_package)->new({data_structure => $self->data, os => $self->os}) };
+ if ($driver) {
+ notify($ERRORS{'OK'}, 0, "libvirt $driver_name driver object created and initialized to control $node_name");
+ $self->{driver} = $driver;
+ $self->{driver}{driver} = $driver;
+ $self->{driver_name} = $driver_name;
+ last DRIVER;
+ }
+ elsif ($EVAL_ERROR) {
+ notify($ERRORS{'WARNING'}, 0, "libvirt $driver_name driver object could not be created: type: $driver_perl_package, error:\n$EVAL_ERROR");
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "libvirt $driver_name driver object could not be initialized to control $node_name");
+ }
+ }
+
+ # Make sure the driver module object was successfully initialized
+ if (!$self->driver()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to initialize libvirt provisioning module, driver object could not be created and initialized");
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, ref($self) . " provisioning module initialized");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 load
+
+ Parameters : none
+ Returns : boolean
+ Description : Loads the requested image on the domain:
+
+=over 3
+
+=cut
+
+sub load {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $reservation_id = $self->data->get_reservation_id();
+ my $image_name = $self->data->get_image_name();
+ my $computer_id = $self->data->get_computer_id();
+ my $computer_name = $self->data->get_computer_short_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $domain_name = $self->get_domain_name();
+ my $driver_name = $self->get_driver_name();
+ my $domain_xml_file_path = $self->get_domain_xml_file_path();
+
+=item *
+
+Destroy and delete any domains have already been defined for the computer
+assigned to this reservation.
+
+=cut
+
+ $self->delete_existing_domains() || return;
+
+=item *
+
+Construct the default libvirt XML definition for the domain.
+
+=cut
+
+ my $domain_xml_definition = $self->generate_domain_xml();
+ if (!$domain_xml_definition) {
+ notify($ERRORS{'WARNING'}, 0, "failed to load '$image_name' image on '$computer_name', unable to generate XML definition for '$domain_name' domain");
+ return;
+ }
+
+=item *
+
+Call the libvirt driver module's 'extend_domain_xml' subroutine if it is
+implemented. Pass the default domain XML definition hash reference as an
+argument. The 'extend_domain_xml' subroutine may add or modify XML values. This
+allows the driver module to customize the XML specific to that driver.
+
+=cut
+
+ if ($self->driver->can('extend_domain_xml')) {
+ $domain_xml_definition = $self->driver->extend_domain_xml($domain_xml_definition);
+ if (!$domain_xml_definition) {
+ notify($ERRORS{'WARNING'}, 0, "failed to load '$image_name' image on '$computer_name', $driver_name libvirt driver module failed to extend XML definition for '$domain_name' domain");
+ return;
+ }
+ }
+
+=item *
+
+Call the driver module's 'pre_define' subroutine if it is implemented. This
+subroutine completes any necessary tasks which are specific to the driver being
+used prior to defining the domain.
+
+=cut
+
+ if ($self->driver->can('pre_define') && !$self->driver->pre_define()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to load '$image_name' image on '$computer_name', $driver_name libvirt driver module failed to complete its steps prior to defining the domain");
+ return;
+ }
+
+=item *
+
+Create a text file on the node containing the domain XML definition.
+
+=cut
+
+ if (!$self->vmhost_os->create_text_file($domain_xml_file_path, $domain_xml_definition)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to load '$image_name' image on '$computer_name', unable to create XML file on $node_name: $domain_xml_file_path");
+ return;
+ }
+
+=item *
+
+Define the domain on the node by calling 'virsh define <XML file>'.
+
+=cut
+
+ my $command = "virsh define \"$domain_xml_file_path\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to define '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status eq '0') {
+ notify($ERRORS{'OK'}, 0, "defined '$domain_name' domain on $node_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to define '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+
+=item *
+
+Power on the domain.
+
+=cut
+
+ if (!$self->power_on($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to start '$domain_name' domain on $node_name");
+ return;
+ }
+
+=item *
+
+Call the domain guest OS module's 'post_load' subroutine if implemented.
+
+=cut
+
+ if ($self->os->can("post_load")) {
+ if ($self->os->post_load()) {
+ insertloadlog($reservation_id, $computer_id, "loadimagecomplete", "performed OS post-load tasks '$domain_name' domain on $node_name");
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to perform OS post-load tasks on '$domain_name' domain on node $node_name");
+ return;
+ }
+ }
+ else {
+ insertloadlog($reservation_id, $computer_id, "loadimagecomplete", "OS post-load tasks not necessary '$domain_name' domain on $node_name");
+ }
+
+=back
+
+=cut
+
+ return 1;
+} ## end sub load
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 capture
+
+ Parameters : none
+ Returns : boolean
+ Description : Captures the image currently loaded on the computer.
+
+=cut
+
+sub capture {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ return 1;
+} ## end sub capture
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 node_status
+
+ Parameters : $computer_id (optional)
+ Returns : string
+ Description : Checks the status of the computer in order to determine if the
+ computer is ready to be reserved or needs to be reloaded. A
+ string is returned depending on the status of the computer:
+ 'READY':
+ * Computer is ready to be reserved
+ * It is accessible
+ * It is loaded with the correct image
+ * OS module's post-load tasks have run
+ 'POST_LOAD':
+ * Computer is loaded with the correct image
+ * OS module's post-load tasks have not run
+ 'RELOAD':
+ * Computer is not accessible or not loaded with the correct
+ image
+
+=cut
+
+sub node_status {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ return 'RELOAD';
+} ## end sub node_status
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 does_image_exist
+
+ Parameters : $image_name (optional)
+ Returns : array (boolean)
+ Description : Checks if the requested image exists on the node or in the
+ repository. If the image exists, an array containing the image
+ file paths is returned. A boolean evaluation can be done on the
+ return value to simply determine if an image exists.
+
+=cut
+
+sub does_image_exist {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $image_name = shift || $self->data->get_image_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $master_image_file_path = $self->get_master_image_file_path($image_name);
+
+ # Get a semaphore in case another process is currently copying to create the master image
+ my $semaphore_id = "$node_name:$master_image_file_path";
+ my $semaphore_timeout_minutes = 60;
+ my $semaphore = $self->get_semaphore($semaphore_id, (60 * $semaphore_timeout_minutes), 5) || return;
+
+ # Check if the master image file exists on the VM host
+ if ($self->vmhost_os->file_exists($master_image_file_path)) {
+ notify($ERRORS{'DEBUG'}, 0, "$image_name image exists on $node_name: $master_image_file_path");
+ return 1;
+ }
+
+ # Attempt to find the image files in the repository
+ if ($self->find_repository_image_file_paths($image_name)) {
+ notify($ERRORS{'DEBUG'}, 0, "$image_name image exists in the repository mounted on $node_name");
+ return 1;
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "$image_name image does not exist $node_name");
+ return 0;
+} ## end sub does_image_exist
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_image_size
+
+ Parameters : $image_name (optional)
+ Returns : integer
+ Description : Returns the size of the image in megabytes.
+
+=cut
+
+sub get_image_size {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $image_name = shift || $self->data->get_image_name();
+
+ my $image_size_bytes = $self->get_image_size_bytes($image_name) || return;
+
+ # Convert bytes to MB
+ return int($image_size_bytes / 1024 ** 2);
+} ## end sub get_image_size
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_image_repository_search_paths
+
+ Parameters : $management_node_identifier (optional)
+ Returns : array
+ Description : Returns an array containing paths on the management node where an
+ image may reside. The paths may contain wildcards. This is used
+ to attempt to locate an image on another managment node in order
+ to retrieve it.
+
+=cut
+
+sub get_image_repository_search_paths {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ return ();
+} ## end sub get_image_repository_search_paths
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 power_status
+
+ Parameters : $domain_name (optional)
+ Returns : string
+ Description : Determines the power state of the domain. A string is returned
+ containing one of the following values:
+ * 'on'
+ * 'off'
+ * 'suspended'
+
+=cut
+
+sub power_status {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift || $self->get_domain_name();
+ if (!defined($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "domain name argument was not specified");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ # Get the domain info hash, make sure domain is defined
+ my $domain_info = $self->get_domain_info();
+ if (!defined($domain_info->{$domain_name})) {
+ notify($ERRORS{'DEBUG'}, 0, "unable to determine power status of '$domain_name' domain, it is not defined on $node_name");
+ return;
+ }
+
+ # enum virDomainState {
+ # VIR_DOMAIN_NOSTATE = 0 : no state
+ # VIR_DOMAIN_RUNNING = 1 : the domain is running
+ # VIR_DOMAIN_BLOCKED = 2 : the domain is blocked on resource
+ # VIR_DOMAIN_PAUSED = 3 : the domain is paused by user
+ # VIR_DOMAIN_SHUTDOWN = 4 : the domain is being shut down
+ # VIR_DOMAIN_SHUTOFF = 5 : the domain is shut off
+ # VIR_DOMAIN_CRASHED = 6 :
+ # VIR_DOMAIN_LAST = 7
+ # }
+
+ my $domain_state = $domain_info->{$domain_name}{state};
+ if (!defined($domain_state)) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine power status of '$domain_name' domain, the state attribute is not set");
+ return;
+ }
+ elsif ($domain_state =~ /running/i) {
+ return 'on';
+ }
+ elsif ($domain_state =~ /blocked/i) {
+ return 'blocked';
+ }
+ elsif ($domain_state =~ /paused/i) {
+ return 'suspended';
+ }
+ elsif ($domain_state =~ /(shutdown|off)/i) {
+ return 'off';
+ }
+ elsif ($domain_state =~ /crashed/i) {
+ return 'crashed';
+ }
+ else {
+ return $domain_state;
+ }
+
+} ## end sub power_status
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 power_on
+
+ Parameters : $domain_name (optional)
+ Returns : boolean
+ Description : Powers on the domain. Returns true if the domain was successfully
+ powered on or was already powered on.
+
+=cut
+
+sub power_on {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift || $self->get_domain_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ # Start the domain
+ my $command = "virsh start \"$domain_name\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to start '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status eq '0') {
+ notify($ERRORS{'OK'}, 0, "started '$domain_name' domain on $node_name");
+ return 1;
+ }
+ elsif (grep(/domain is already active/i, @$output)) {
+ notify($ERRORS{'DEBUG'}, 0, "'$domain_name' domain is already running on $node_name");
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to start '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+} ## end sub power_on
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 power_off
+
+ Parameters : $domain_name
+ Returns : boolean
+ Description : Powers off the domain. Returns true if the domain was
+ successfully powered off or was already powered off.
+
+=cut
+
+sub power_off {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift || $self->get_domain_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ # Start the domain
+ my $command = "virsh destroy \"$domain_name\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to destroy '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status eq '0') {
+ notify($ERRORS{'OK'}, 0, "destroyed '$domain_name' domain on $node_name");
+ return 1;
+ }
+ elsif (grep(/domain is not running/i, @$output)) {
+ notify($ERRORS{'DEBUG'}, 0, "'$domain_name' domain is not running on $node_name");
+ return 1;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to destroy '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+} ## end sub power_off
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 power_reset
+
+ Parameters : $domain_name (optional)
+ Returns : boolean
+ Description : Resets the power of the domain by powering it off and then back
+ on.
+
+=cut
+
+sub power_reset {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift || $self->get_domain_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ if (!$self->power_off()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to reset power of '$domain_name' domain on $node_name, domain could not be powered off");
+ return;
+ }
+
+ if (!$self->power_on()) {
+ notify($ERRORS{'WARNING'}, 0, "failed to reset power of '$domain_name' domain on $node_name, domain could not be powered on");
+ return;
+ }
+
+ notify($ERRORS{'OK'}, 0, "reset power of '$domain_name' domain on $node_name");
+ return 1;
+} ## end sub power_reset
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 post_maintenance_action
+
+ Parameters : none
+ Returns : boolean
+ Description : Performs tasks to the computer after it has been put into
+ maintenance mode.
+
+=cut
+
+sub post_maintenance_action {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ return 1;
+} ## end sub post_maintenance_action
+
+##############################################################################
+
+=head1 PRIVATE METHODS
+
+=cut
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 driver
+
+ Parameters : none
+ Returns : Libvirt driver object
+ Description : Returns a reference to the libvirt driver object which is created
+ when this libvirt.pm module is initialized.
+
+=cut
+
+sub driver {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ if (!$self->{driver}) {
+ notify($ERRORS{'WARNING'}, 0, "unable to return libvirt driver object, \$self->{driver} is not set");
+ return;
+ }
+ else {
+ return $self->{driver};
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_driver_name
+
+ Parameters : none
+ Returns : string
+ Description : Returns the name of the libvirt driver being used to control the
+ node. Example: 'KVM'
+
+=cut
+
+sub get_driver_name {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ if (!$self->{driver_name}) {
+ notify($ERRORS{'WARNING'}, 0, "unable to return libvirt driver name, \$self->{driver_name} is not set");
+ return;
+ }
+ else {
+ return $self->{driver_name};
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_name
+
+ Parameters : none
+ Returns : string
+ Description : Returns the name of the domain. This name is passed to various
+ virsh commands. It is also the name displayed in virt-manager.
+ Example: 'vclv99-197:vmwarewin7-Windows764bit1846-v3'
+
+=cut
+
+sub get_domain_name {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $computer_short_name = $self->data->get_computer_short_name();
+ my $image_id = $self->data->get_image_id();
+ my $image_name = $self->data->get_image_name();
+ my $image_revision = $self->data->get_imagerevision_revision();
+
+ return "$computer_short_name:$image_name";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_file_base_name
+
+ Parameters : none
+ Returns : string
+ Description : Returns the base name for files created for the current
+ reservation. A file extension is not included. This file name is
+ used for the domain's XML definition file and it's copy on write
+ image file. Example: 'vclv99-37_234-v23'
+
+=cut
+
+sub get_domain_file_base_name {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $computer_short_name = $self->data->get_computer_short_name();
+ my $image_id = $self->data->get_image_id();
+ my $image_revision = $self->data->get_imagerevision_revision();
+
+ return "$computer_short_name\_$image_id-v$image_revision";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_xml_directory_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the directory path on the node where domain definition
+ XML files reside. The directory used is: '/tmp/vcl'
+
+=cut
+
+sub get_domain_xml_directory_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+ return "/tmp/vcl";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_xml_file_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the domain XML definition file path on the node.
+ Example: '/tmp/vcl/vclv99-37_234-v23.xml'
+
+=cut
+
+sub get_domain_xml_file_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_xml_directory_path = $self->get_domain_xml_directory_path();
+ my $domain_file_name = $self->get_domain_file_base_name();
+
+ return "$domain_xml_directory_path/$domain_file_name.xml";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_master_image_directory_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the directory path on the node where the master image
+ files reside. Example: '/var/lib/libvirt/images'
+
+=cut
+
+sub get_master_image_directory_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $datastore_path = $self->data->get_vmhost_profile_datastore_path();
+ return $datastore_path;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_master_image_file_path
+
+ Parameters : $image_name (optional)
+ Returns : string
+ Description : Returns the path on the node where the master image file resides.
+ Example:
+ '/var/lib/libvirt/images/vmwarelinux-RHEL54Small2251-v1.qcow2'
+
+=cut
+
+sub get_master_image_file_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ return $self->{master_image_file_path} if $self->{master_image_file_path};
+
+ my $image_name = shift || $self->data->get_image_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $master_image_directory_path = $self->get_master_image_directory_path();
+
+ # Check if the master image file exists on the VM host
+ my @master_image_files_found = $self->vmhost_os->find_files($master_image_directory_path, "$image_name.*");
+ if (@master_image_files_found == 1) {
+ $self->{master_image_file_path} = $master_image_files_found[0];
+ notify($ERRORS{'DEBUG'}, 0, "found master image file on $node_name: $self->{master_image_file_path}");
+ return $self->{master_image_file_path};
+ }
+
+ # File was not found, construct it
+ my $vmdisk_format = $self->data->get_vmhost_profile_vmdisk_format();
+ return "$master_image_directory_path/$image_name.$vmdisk_format";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_copy_on_write_file_path
+
+ Parameters : none
+ Returns : string
+ Description : Returns the path on the node where the copy on write file for the
+ domain resides. Example:
+ '/var/lib/libvirt/images/vclv99-197_2251-v1.qcow2'
+
+=cut
+
+sub get_copy_on_write_file_path {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $vmhost_vmpath = $self->data->get_vmhost_profile_vmpath();
+ my $domain_file_name = $self->get_domain_file_base_name();
+ my $vmdisk_format = $self->data->get_vmhost_profile_vmdisk_format();
+
+ return "$vmhost_vmpath/$domain_file_name.$vmdisk_format";
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_existing_domains
+
+ Parameters : none
+ Returns : boolean
+ Description : Deletes existing domains which were previously created for the
+ computer assigned to the current reservation.
+
+=cut
+
+sub delete_existing_domains {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $computer_name = $self->data->get_computer_short_name();
+
+ my $domain_info = $self->get_domain_info();
+ for my $domain_name (keys %$domain_info) {
+ next if ($domain_name !~ /^$computer_name:/);
+
+ if (!$self->delete_domain($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete existing domains created for $computer_name on $node_name, '$domain_name' domain could not be deleted");
+ return;
+ }
+ }
+
+ # Delete existing XML files
+ my $domain_xml_directory_path = $self->get_domain_xml_directory_path();
+ $self->vmhost_os->delete_file("$domain_xml_directory_path/$computer_name\_*.xml");
+
+ notify($ERRORS{'OK'}, 0, "deleted existing domains created for $computer_name on $node_name");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_domain
+
+ Parameters : $domain_name
+ Returns : boolean
+ Description : Deletes a domain from the node.
+
+=cut
+
+sub delete_domain {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift;
+ if (!defined($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "domain name argument was not specified");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ # Make sure domain is defined
+ if (!$self->domain_exists($domain_name)) {
+ notify($ERRORS{'OK'}, 0, "'$domain_name' domain not deleted, it is not defined on $node_name");
+ return 1;
+ }
+
+ # Power off the domain
+ if ($self->power_status($domain_name) !~ /off/) {
+ if (!$self->power_off($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete '$domain_name' domain on $node_name, failed to power off domain");
+ return;
+ }
+ }
+
+ # Delete all snapshots created for the domain
+ my $snapshot_info = $self->get_snapshot_info($domain_name);
+ for my $snapshot_name (keys %$snapshot_info) {
+ if (!$self->delete_snapshot($domain_name, $snapshot_name)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete '$domain_name' domain on $node_name, its '$snapshot_name' snapshot could not be deleted");
+ return;
+ }
+ }
+
+ # Delete volumes assigned to to domain
+ my $domain_xml = $self->get_domain_xml($domain_name);
+ my $disks = $domain_xml->{devices}->[0]->{disk};
+ for my $disk (@$disks) {
+ my $volume_path = $disk->{source}->[0]->{file};
+ notify($ERRORS{'DEBUG'}, 0, "deleting volume assigned to domain: " . $disk->{source}->[0]->{file});
+
+ if (!$self->vmhost_os->delete_file($volume_path)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete '$domain_name' domain on $node_name, '$volume_path' volume could not be deleted");
+ return;
+ }
+ }
+
+ # Undefine the domain
+ my $command = "virsh undefine \"$domain_name\" --managed-save --snapshots-metadata";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to undefine '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to undefine '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "undefined '$domain_name' domain on $node_name");
+ }
+
+ notify($ERRORS{'OK'}, 0, "deleted '$domain_name' domain from $node_name");
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 generate_domain_xml
+
+ Parameters : none
+ Returns : string
+ Description : Generates a string containing the XML definition for the domain.
+
+=cut
+
+sub generate_domain_xml {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $image_name = $self->data->get_image_name();
+ my $image_display_name = $self->data->get_image_prettyname();
+ my $image_os_type = $self->data->get_image_os_type();
+ my $computer_name = $self->data->get_computer_short_name();
+
+ my $domain_name = $self->get_domain_name();
+ my $domain_type = $self->driver->get_domain_type();
+
+ my $copy_on_write_file_path = $self->get_copy_on_write_file_path();
+ my $disk_format = $self->data->get_vmhost_profile_vmdisk_format();
+ my $disk_driver_name = $self->driver->get_disk_driver_name();
+
+ my $eth0_source_device = $self->data->get_vmhost_profile_virtualswitch0();
+ my $eth1_source_device = $self->data->get_vmhost_profile_virtualswitch1();
+
+ my $eth0_mac_address;
+ my $is_eth0_mac_address_random = $self->data->get_vmhost_profile_eth0generated(0);
+ if ($is_eth0_mac_address_random) {
+ $eth0_mac_address = get_random_mac_address();
+ $self->data->set_computer_eth0_mac_address($eth0_mac_address);
+ }
+ else {
+ $eth0_mac_address = $self->data->get_computer_eth0_mac_address();
+ }
+
+ my $eth1_mac_address;
+ my $is_eth1_mac_address_random = $self->data->get_vmhost_profile_eth1generated(0);
+ if ($is_eth1_mac_address_random) {
+ $eth1_mac_address = get_random_mac_address();
+ $self->data->set_computer_eth1_mac_address($eth1_mac_address);
+ }
+ else {
+ $eth1_mac_address = $self->data->get_computer_eth1_mac_address();
+ }
+
+ my $cpu_count = $self->data->get_image_minprocnumber() || 1;
+
+ my $memory_mb = $self->data->get_image_minram();
+ if ($memory_mb < 512) {
+ $memory_mb = 512;
+ }
+ my $memory_kb = ($memory_mb * 1024);
+
+ # Per libvirt documentation:
+ # "The guest clock is typically initialized from the host clock.
+ # Most operating systems expect the hardware clock to be kept in UTC, and this is the default.
+ # Windows, however, expects it to be in so called 'localtime'."
+ my $clock_offset = ($image_os_type =~ /windows/) ? 'localtime' : 'utc';
+
+ my $xml = {
+ 'type' => $domain_type,
+ 'description' => [$image_display_name],
+ 'name' => [$domain_name],
+ 'on_poweroff' => ['preserve'],
+ 'on_reboot' => ['restart'],
+ 'on_crash' => ['preserve'],
+ 'os' => [
+ {
+ 'type' => {
+ 'content' => 'hvm'
+ }
+ }
+ ],
+ 'features' => [
+ {
+ 'acpi' => [{}],
+ 'apic' => [{}],
+ }
+ ],
+ 'memory' => [$memory_kb],
+ 'vcpu' => [$cpu_count],
+ 'cpu' => [
+ {
+ 'topology' => [
+ {
+ 'sockets' => $cpu_count,
+ 'cores' => '2',
+ 'threads' => '2',
+ }
+ ],
+ }
+ ],
+ 'clock' => [
+ {
+ 'offset' => $clock_offset,
+ }
+ ],
+ 'devices' => [
+ {
+ 'disk' => [
+ {
+ 'device' => 'disk',
+ 'type' => 'file',
+ 'driver' => {
+ 'name' => $disk_driver_name,
+ 'type' => $disk_format,
+ 'cache' => 'none',
+ },
+ 'source' => {
+ 'file' => $copy_on_write_file_path,
+ },
+ 'target' => {
+ 'bus' => 'ide',
+ 'dev' => 'vda'
+ },
+ }
+ ],
+ 'interface' => [
+ {
+ 'type' => 'bridge',
+ 'mac' => {
+ 'address' => $eth0_mac_address,
+ },
+ 'source' => {
+ 'bridge' => $eth0_source_device,
+ },
+ 'target' => {
+ 'dev' => 'vnet0',
+ },
+ 'model' => {
+ #'type' => 'rtl8139',
+ },
+ },
+ {
+ 'type' => 'bridge',
+ 'mac' => {
+ 'address' => $eth1_mac_address,
+ },
+ 'source' => {
+ 'bridge' => $eth1_source_device,
+ },
+ 'target' => {
+ 'dev' => 'vnet1',
+ },
+ 'model' => {
+ #'type' => 'rtl8139',
+ },
+ }
+ ],
+ 'graphics' => [
+ {
+ 'type' => 'vnc',
+ }
+ ],
+ 'video' => [
+ {
+ 'model' => {
+ 'type' => 'cirrus',
+ }
+ }
+ ],
+ }
+ ]
+ };
+
+ my $domain_xml_definition = XMLout($xml,
+ 'RootName' => 'domain',
+ 'KeyAttr' => []
+ );
+
+ return $domain_xml_definition;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_info
+
+ Parameters : none
+ Returns : hash reference
+ Description : Retrieves information about all of the domains defined on the
+ node and constructs a hash containing the information. Example:
+ "vclv99-197:vmwarewin7-Windows764bit1846-v3" => {
+ "id" => 135,
+ "state" => "paused"
+ },
+ "vclv99-37:vmwarewinxp-base234-v23" => {
+ "state" => "shut off"
+ }
+
+=cut
+
+sub get_domain_info {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ my $command = "virsh list --all";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to list defined domains on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to list defined domains on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "retrieved list of defined domains on $node_name:\n" . join("\n", @$output));
+ }
+
+ # [root@vclh3-10 images]# virsh list --all
+ # Id Name State
+ # ----------------------------------
+ # 14 test-name running
+ # - test-2gb shut off
+ # - vclv99-197: vmwarelinux-RHEL54Small2251-v1 shut off
+
+ my $defined_domains = {};
+ for my $line (@$output) {
+ my ($id, $name, $state) = $line =~ /^\s*([\d\-]+)\s(.+?)\s+(\w+|shut off)$/g;
+ next if (!defined($id));
+
+ $defined_domains->{$name}{state} = $state;
+ $defined_domains->{$name}{id} = $id if ($id =~ /\d/);
+ }
+
+ #notify($ERRORS{'DEBUG'}, 0, "retrieved domain info:\n" . format_data($defined_domains));
+ return $defined_domains;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_domain_xml
+
+ Parameters : $domain_name
+ Returns : hash reference
+ Description : Retrieves the XML definition of a domain already defined on the
+ node. Generates a hash using XML::Simple::XMLin.
+
+=cut
+
+sub get_domain_xml {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift;
+ if (!defined($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "domain name argument was not specified");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ my $command = "virsh dumpxml \"$domain_name\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to retrieve XML definition for '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieve XML definition for '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+
+ # Convert the XML to a hash using XML::Simple
+ my $xml = XMLin(join("\n", @$output), 'ForceArray' => 1, 'KeyAttr' => []);
+ if ($xml) {
+ #notify($ERRORS{'DEBUG'}, 0, "retrieved XML definition for '$domain_name' domain on $node_name");
+ notify($ERRORS{'DEBUG'}, 0, "retrieved XML definition for '$domain_name' domain on $node_name:\n" . format_data($xml));
+ return $xml;
+ }
+ else {
+ notify($ERRORS{'WARNING'}, 0, "failed to convert XML definition for '$domain_name' domain to hash:\n" . join("\n", @$output));
+ return;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 domain_exists
+
+ Parameters : $domain_name
+ Returns : boolean
+ Description : Determines if the domain is defined on the node.
+
+=cut
+
+sub domain_exists {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $domain_name = shift;
+ if (!defined($domain_name)) {
+ notify($ERRORS{'WARNING'}, 0, "domain name argument was not specified");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ # Get the domain info hash, make sure domain is defined
+ my $domain_info = $self->get_domain_info();
+ if (!defined($domain_info)) {
+ notify($ERRORS{'WARNING'}, 0, "unable to determine if '$domain_name' domain exists, domain information could not be retrieved from $node_name");
+ return;
+ }
+ elsif (!$domain_info) {
+ notify($ERRORS{'DEBUG'}, 0, "'$domain_name' domain does not exist, no domains are defined on $node_name");
+ return 0;
+ }
+ elsif (!defined($domain_info->{$domain_name})) {
+ notify($ERRORS{'OK'}, 0, "'$domain_name' is not defined on $node_name");
+ return 0;
+ }
+ else {
+ notify($ERRORS{'OK'}, 0, "'$domain_name' exists on $node_name");
+ return 1;
+ }
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_snapshot_info
+
+ Parameters : $domain_name
+ Returns : hash reference
+ Description : Retrieves snapshot information for the domain specified by the
+ argument and constructs a hash. The hash keys are the snapshot
+ names. Example:
+ "VCL snapshot" => {
+ "creation_time" => "2011-12-07 16:05:50 -0500",
+ "state" => "shutoff"
+ }
+
+=cut
+
+sub get_snapshot_info {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ my $domain_name = shift;
+ if (!$domain_name) {
+ notify($ERRORS{'WARNING'}, 0, "domain name argument was not supplied");
+ return;
+ }
+
+ my $command = "virsh snapshot-list \"$domain_name\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to list snapshots of '$domain_name' domain on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to list snapshots of '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "listed snapshots of '$domain_name' domain on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ }
+
+ #virsh # snapshot-list 'vclv99-197:vmwarelinux-RHEL54Small2251-v1'
+ # Name Creation Time State
+ #------------------------------------------------------------
+ # VCL snapshot 2011-11-21 17:10:05 -0500 shutoff
+
+ my $shapshot_info = {};
+ for my $line (@$output) {
+ my ($name, $creation_time, $state) = $line =~ /^\s*(.+?)\s+(\d{4}-\d{2}-\d{2} [^a-z]+)\s+(\w+)$/g;
+ next if (!defined($name));
+
+ $shapshot_info->{$name}{creation_time} = $creation_time;
+ $shapshot_info->{$name}{state} = $state;
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "retrieved snapshot info for '$domain_name' domain on $node_name:\n" . format_data($shapshot_info));
+ return $shapshot_info;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 create_snapshot
+
+ Parameters : $domain_name, $description
+ Returns : boolean
+ Description : Creates a snapshot of the domain specified by the argument.
+
+=cut
+
+sub create_snapshot {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ my $domain_name = shift;
+ if (!$domain_name) {
+ notify($ERRORS{'WARNING'}, 0, "unable to create snapshot on $node_name, domain argument was not supplied");
+ return;
+ }
+
+ my $description = shift || $self->get_domain_name();
+
+ my $command = "virsh snapshot-create-as \"$domain_name\" \"$description\"";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to create snapshot of domain '$domain_name' on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to create snapshot of domain '$domain_name' on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "created snapshot of domain '$domain_name' on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ }
+
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 delete_snapshot
+
+ Parameters : $domain_name, $snapshot
+ Returns : boolean
+ Description : Deletes a snapshot created of the domain specified by the
+ argument.
+
+=cut
+
+sub delete_snapshot {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $node_name = $self->data->get_vmhost_short_name();
+
+ my $domain_name = shift;
+ my $snapshot = shift;
+ if (!defined($domain_name) || !defined($snapshot)) {
+ notify($ERRORS{'WARNING'}, 0, "unable to delete snapshot on $node_name, domain and snapshot arguments not supplied");
+ return;
+ }
+
+ my $command = "virsh snapshot-delete \"$domain_name\" \"$snapshot\" --children";
+ my ($exit_status, $output) = $self->vmhost_os->execute($command);
+ if (!defined($output)) {
+ notify($ERRORS{'WARNING'}, 0, "failed to execute virsh command to delete '$snapshot' snapshot of domain '$domain_name' on $node_name");
+ return;
+ }
+ elsif ($exit_status ne '0') {
+ notify($ERRORS{'WARNING'}, 0, "failed to delete '$snapshot' snapshot of domain '$domain_name' on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ return;
+ }
+ else {
+ notify($ERRORS{'DEBUG'}, 0, "deleted '$snapshot' snapshot of domain '$domain_name' on $node_name\ncommand: $command\nexit status: $exit_status\noutput:\n" . join("\n", @$output));
+ }
+
+ return 1;
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 get_image_size_bytes
+
+ Parameters : $image_name (optional)
+ Returns : integer
+ Description : Returns the size of the image in bytes.
+
+=cut
+
+sub get_image_size_bytes {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ my $image_name = shift || $self->data->get_image_name();
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $master_image_file_path = $self->get_master_image_file_path($image_name);
+
+ my $image_size_bytes;
+
+ # Check if the master image file exists on the VM host
+ if ($self->vmhost_os->file_exists($master_image_file_path)) {
+ # Get a semaphore in case another process is currently copying to create the master image
+ my $semaphore_id = "$node_name:$master_image_file_path";
+ my $semaphore_timeout_minutes = 60;
+ my $semaphore = $self->get_semaphore($semaphore_id, (60 * $semaphore_timeout_minutes), 5) || return;
+
+ $image_size_bytes = $self->vmhost_os->get_file_size($master_image_file_path)
+ }
+
+ # Check the repository if the master image does not exist on the VM host or if failed to determine size
+ if (!$image_size_bytes) {
+ my @repository_image_file_paths = $self->find_repository_image_file_paths();
+ if (!@repository_image_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieved size of $image_name image, size could not be determined from $node_name and image files were not found in the repository");
+ return;
+ }
+
+ # Note - don't need semaphore because find_repository_image_file_paths gets one while it's checking
+
+ $image_size_bytes = $self->vmhost_os->get_file_size(@repository_image_file_paths);
+ if (!$image_size_bytes) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieved size of $image_name image from the repository mounted on $node_name");
+ return;
+ }
+ }
+
+ if (!$image_size_bytes) {
+ notify($ERRORS{'WARNING'}, 0, "failed to retrieved size of $image_name image on $node_name");
+ return;
+ }
+
+ notify($ERRORS{'DEBUG'}, 0, "retrieved size of $image_name image on $node_name:\n" . get_file_size_info_string($image_size_bytes));
+ return $image_size_bytes;
+} ## end sub get_image_size_bytes
+
+#/////////////////////////////////////////////////////////////////////////////
+
+=head2 find_repository_image_file_paths
+
+ Parameters : $image_name (optional)
+ Returns : array
+ Description : Locates valid image files stored in the image repository.
+ Searches for all files beginning with the image name and then
+ checks the results to remove any files which should not be
+ included. File extensions which are excluded: vmx, txt, xml
+ If multiple vmdk files are found it is assumed that the image is
+ one of the split vmdk formats and the <image name>.vmdk contains
+ the descriptor information. This file is excluded because it
+ causes qemu-img to fail.
+
+=cut
+
+sub find_repository_image_file_paths {
+ my $self = shift;
+ unless (ref($self) && $self->isa('VCL::Module')) {
+ notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a function, it must be called as a class method");
+ return;
+ }
+
+ # Attempt to get the image name argument
+ my $image_name = shift || $self->data->get_image_name();
+
+ # Return previosly retrieved result if defined
+ return @{$self->{repository_file_paths}{$image_name}} if $self->{repository_file_paths}{$image_name};
+
+ my $node_name = $self->data->get_vmhost_short_name();
+ my $vmhost_repository_directory_path = $self->data->get_vmhost_profile_repository_path();
+
+ if (!$vmhost_repository_directory_path) {
+ notify($ERRORS{'DEBUG'}, 0, "repository path is not configured in the VM host profile for $node_name");
+ return;
+ }
+
+ # Get a semaphore in case another process is currently copying the image to the repository
+ my $semaphore_id = "$node_name:$vmhost_repository_directory_path";
+ my $semaphore_timeout_minutes = 60;
+ my $semaphore = $self->get_semaphore($semaphore_id, (60 * $semaphore_timeout_minutes), 5) || return;
+
+ # Attempt to locate files in the repository matching the image name
+ my @matching_repository_file_paths = $self->vmhost_os->find_files($vmhost_repository_directory_path, "$image_name*.*");
+ if (!@matching_repository_file_paths) {
+ notify($ERRORS{'DEBUG'}, 0, "image $image_name does NOT exist in the repository on $node_name");
+ return ();
+ }
+
+ # Check the files found in the repository
+ # Attempt to determine which files are actual virtual disk files
+ my @virtual_disk_repository_file_paths;
+ for my $virtual_disk_repository_file_path (sort @matching_repository_file_paths) {
+ # Skip files which match known extensions which should be excluded
+ if ($virtual_disk_repository_file_path =~ /\.(vmx|txt|xml)/i) {
+ notify($ERRORS{'DEBUG'}, 0, "not including matching file because its extension is '$1': $virtual_disk_repository_file_path");
+ next;
+ }
+ elsif ($virtual_disk_repository_file_path !~ /\/[^\/]*\.[^\/]*$/i) {
+ notify($ERRORS{'DEBUG'}, 0, "not including matching directory: $virtual_disk_repository_file_path");
+ next;
+ }
+
+ push @virtual_disk_repository_file_paths, $virtual_disk_repository_file_path;
+ }
+
+ if (!@virtual_disk_repository_file_paths) {
+ notify($ERRORS{'WARNING'}, 0, "failed to locate any valid virtual disk files for image $image_name in repository on $node_name");
+ return;
+ }
+
+ # Check if a multi-file vmdk was found
+ # Remove the descriptor file - <image name>.vmdk
+ if (@virtual_disk_repository_file_paths > 1 && $virtual_disk_repository_file_paths[0] =~ /\.vmdk$/i) {
+ my @corrected_virtual_disk_repository_file_paths;
+ for my $virtual_disk_repository_file_path (@virtual_disk_repository_file_paths) {
+ if ($virtual_disk_repository_file_path =~ /$image_name\.vmdk$/) {
+ notify($ERRORS{'DEBUG'}, 0, "excluding file because it appears to be a vmdk descriptor file: $virtual_disk_repository_file_path");
+ next;
+ }
+ else {
+ push @corrected_virtual_disk_repository_file_paths, $virtual_disk_repository_file_path;
+ }
+
+ }
+ @virtual_disk_repository_file_paths = @corrected_virtual_disk_repository_file_paths;
+ }
+
+ # Save the result so this doesn't have to be done again
+ $self->{repository_file_paths}{$image_name} = \@virtual_disk_repository_file_paths;
+
+ notify($ERRORS{'DEBUG'}, 0, "retrieved " . scalar(@virtual_disk_repository_file_paths) . " repository file paths for image $image_name on $node_name");
+ return @virtual_disk_repository_file_paths
+}
+
+#/////////////////////////////////////////////////////////////////////////////
+
+1;
+__END__
+
+=head1 SEE ALSO
+
+L<http://cwiki.apache.org/VCL/>
+
+=cut
Propchange: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm
------------------------------------------------------------------------------
svn:eol-style = native
Propchange: incubator/vcl/trunk/managementnode/lib/VCL/Module/Provisioning/libvirt.pm
------------------------------------------------------------------------------
svn:keywords = Date Revision Author HeadURL Id