I've written a provisioning module for VCL that we use here at Duke that I'd like to share for possible inclusion in VCL. The module is based off VCL 2.1.

This module is designed to work against VirtualCenter/vCenter, but should be able to work directly with an ESX host as well. It works a little differently from the existing ESX modules in that it uses the VMware perl API directly instead of calling the helper apps. In addition, it also greatly reduces the amount of disk space and deployment time necessary.

Instead of cloning the VM for deployment, this module uses ESX to create a snapshot of the golden image disk, and uses that for the deployed VM. This means the only disk space used for the deployed VM is the differences between it and the master. Likewise, no time is needed to copy all the bits, so creating the VM takes seconds instead of minutes.


Internally we call the module 'esxduke' to distinguish it from the existing ESX module. Going forward is there a better name for this module? Also, what should be done from here to have this included in VCL?


Thanks,


Sean
#!/usr/bin/perl -w
###############################################################################
# Copyright 2010 Duke University and Apache Software Foundation
#
# 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.
###############################################################################

package VCL::Module::Provisioning::esxduke;

# Configure inheritance
use base qw(VCL::Module::Provisioning);

# Specify the version of this module
our $VERSION = '1.00';

# Specify the version of Perl to use
use 5.008000;

use strict;
use warnings;
use diagnostics;

use VCL::utils;
use Fcntl qw(:DEFAULT :flock);

# Use VMware's perl libraries
use VMware::VIRuntime;
use VMware::VILib;
use VMware::VIExt;



##############################################################################

=head1 CLASS ATTRIBUTES

=cut

=head2 %VMWARE_CONFIG

 Data type   : hash
 Description : %VMWARE_CONFIG is a hash containing the general VMWARE 
configuration
               for the management node this code is running on. Since the data 
is
                                        the same for every instance of the 
VMWARE class, a class attribute
                                        is used and the hash is shared among 
all instances. This also
                                        means that the data only needs to be 
retrieved from the database
                                        once.

=cut

#my %VMWARE_CONFIG;

##############################################################################

=head1 OBJECT METHODS

=cut

#/////////////////////////////////////////////////////////////////////////////

=head2 initialize

 Parameters  :
 Returns     :
 Description :

=cut

# A provisioning object is created for each request.  So this initialize is 
done once per request.
sub initialize {
        my $self = shift;
        if (!defined ($self) ) {
                return 0;
        }
        notify($ERRORS{'DEBUG'}, 0, "Duke vmware ESX module initialized");
        return 1;
} ## end sub initialize

sub _checkConnection{
        my $self = shift;
        if (!$VMware::VICommon::is_connected) {
                # Login to the ESX/VC server
                my $retval;
                eval {
                        local $SIG{__DIE__};   # We don't want vcld's die 
handler getting this
                        $retval = Util::connect('https://' . 
$self->data->get_vmhost_hostname . "/sdk/webService", 
$self->data->get_vmhost_profile_username(), 
$self->data->get_vmhost_profile_password());
                };
                if ($@ || !$retval) {
                        notify($ERRORS{'CRITICAL'}, 0, "Could not login to " . 
$self->data->get_vmhost_hostname . ": " . $@);
                        return 0;
                }
        }
        return 1;
}

#/////////////////////////////////////////////////////////////////////////////

=head2 provision

 Parameters  : hash
 Returns     : 1(success) or 0(failure)
 Description : loads virtual machine with requested image

=cut

sub load {
        my $self = shift;

        #check to make sure this call is for the esx module
        if (ref($self) !~ /esxduke/i) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
                return 0;
        }

        my $request_data = shift;
        my ($package, $filename, $line, $sub) = caller(0);
        notify($ERRORS{'DEBUG'}, 0, 
"****************************************************");

        # get various useful vars from the database
        my $request_id           = $self->data->get_request_id;
        my $reservation_id       = $self->data->get_reservation_id;
        my $vmhost_hostname      = $self->data->get_vmhost_hostname;
        my $image_name           = $self->data->get_image_name;
        my $computer_shortname   = $self->data->get_computer_short_name;
        my $vmclient_computerid  = $self->data->get_computer_id;
        my $vmclient_imageminram = $self->data->get_image_minram;
        my $image_os_name        = $self->data->get_image_os_name;
        my $image_os_type        = $self->data->get_image_os_type;
        my $image_identity       = $self->data->get_image_identity;
        my $user_name            = $self->data->get_user_login_id . "@" . 
$self->data->get_user_affiliation_name;

        my $virtualswitch0   = $self->data->get_vmhost_profile_virtualswitch0;
        my $virtualswitch1   = $self->data->get_vmhost_profile_virtualswitch1;
        my $vmclient_eth0MAC = $self->data->get_computer_eth0_mac_address;
        my $vmclient_eth1MAC = $self->data->get_computer_eth1_mac_address;
        my $vmclient_OSname  = $self->data->get_image_os_name;

        my $vmhost_username = $self->data->get_vmhost_profile_username();
        my $vmhost_password = $self->data->get_vmhost_profile_password();

        $vmhost_hostname =~ /([-_a-zA-Z0-9]*)(\.?)/;
        my $vmhost_shortname = $1;

        $self->_checkConnection();

        notify($ERRORS{'OK'},    0, "Entered ESX Duke module, loading 
$image_name on $computer_shortname (on $vmhost_hostname) for $user_name 
(reservation $reservation_id)");

        my $newVm;
        my $newVmPath;
        my $task;
        my $vmView = $self->_getvmView(0);
        my $imageDatastore;
        my $datacenter;

        my $fileManager = VIExt::get_file_manager();

        # These should be somehow configurable in the future
        my $resourcePoolName = "VCL-TEST";
        my $folderName = "VCL-TEST";
        my $datastoreName = "VCL-TEST-01";

        if(open(CONF, "/etc/vcl/vcld.conf")){
                my  @conf=<CONF>;
                close(CONF);
                foreach $line (@conf) {
                  #resourcePool
                  if($line =~ /^RESOURCEPOOLNAME=([\S]*)/){
                                chomp($line);
                                $resourcePoolName=$1;
                        }
                        #folderName
                        if($line =~ /^FOLDERNAME=([\S]*)/){
                                chomp($line);
                                $folderName=$1;
                        }
                        #datastoreName
                        if($line =~ /^DATASTORENAME=([\S]*)/){
                                chomp($line);
                                $datastoreName=$1;
                        }
                }
        }

        # Get some info on our base image
        my $imageView = $self->_getimageView();
        if (! $imageView) {
                return 0;
        }

        my $parentView = Vim::get_view(mo_ref => $imageView->{parent});
        while (ref($parentView) ne 'Datacenter') {
                $parentView = Vim::get_view(mo_ref => $parentView->{parent});
        }
        $datacenter = $parentView;

        $imageDatastore = Vim::get_view(mo_ref => 
@{$imageView->{datastore}}[0]);

        my $resourcePool = Vim::find_entity_view(view_type => 'ResourcePool', 
filter => {'name' => $resourcePoolName}, begin_entity => $datacenter);
        if (! defined($resourcePool)) {
                notify($ERRORS{'WARNING'}, 0, "ERROR: esxduke->load could not 
find resource pool $resourcePoolName");
                return 0;
        }
        my $folder = Vim::find_entity_view(view_type => 'Folder', filter => 
{'name' => $folderName}, begin_entity => $datacenter);
        if (! defined($folder)) {
                notify($ERRORS{'WARNING'}, 0, "ERROR: esxduke->load could not 
find folder $folderName");
                return 0;
        }

        if ($vmView) {
                notify($ERRORS{'DEBUG'}, 0, "Removing old copy of 
$computer_shortname");
                if (!$self->_removeOldVM()) {
                        return 0;
                }
        }

        # Copy file down
        my $service = Vim::get_vim_service();
        my ($mode, $datacenter_foo, $datastore, $filepath) = 
VIExt::parse_remote_path($imageView->{config}->{files}->{vmPathName});
        #my ($mode, $datacenter_foo, $datastore, $filepath) = 
VIExt::parse_remote_path($imageView->{config}->{files}->{vmPathName});
        $filepath =~ m/(.*)\/.*/;
        my $imageDir = $1;
        my $resp = VIExt::http_get_file($mode, $filepath, $datastore, 
$datacenter->{name});
        if (!defined($resp) || !$resp->is_success) {
                notify($ERRORS{'WARNING'}, 0, "ERROR: esxduke->load could not 
get config for $image_name");
                return 0;
        }
        # Edit file
        my $newvmx = $resp->content;
        $newvmx =~ s/^displayName = .*$/displayName = 
\"$computer_shortname\"/gm;
        $newvmx =~ s/^extendedConfigFile = .*$/extendedConfigFile = 
\"$computer_shortname.vmxf\"/gm;
        $newvmx =~ s/^scsi0:0.filename = .*$/scsi0:0.filename = \"FOO\"/gm;
        $newvmx =~ s/^nvram = .*$/nvram = \"$computer_shortname.nvram\"/gm;

        $newvmx =~ s/^uuid.location = .*$//gm;
        $newvmx =~ s/^uuid.bios = .*$//gm;
        $newvmx =~ s/^sched.swap.derivedName = .*$//gm;
        $newvmx =~ s/^sched.mem.max = .*$//gm;
        $newvmx =~ s/^annotation = .*$//gm;
        my $timeStr = localtime;
        $newvmx .= "annotation = \"$image_name created for $user_name at 
$timeStr\"\n";

        $newvmx =~ s/^ethernet0.networkName = .*$/ethernet0.networkName = 
\"$virtualswitch0\"/gm;
        $newvmx =~ s/^ethernet1.networkName = .*$/ethernet1.networkName = 
\"$virtualswitch1\"/gm;

        if (!$VMWARE_MAC_ETH0_GENERATED && defined($vmclient_eth0MAC)) {
                # Make sure the address type is set right and we have a 
.address line
                if ($newvmx !~ /^ethernet0.addressType = \"static\"$/m) {
                        $newvmx .= "ethernet0.addressType = \"static\"\n";
                        $newvmx .= "ethernet0.address = \"XXX\"\n";
                }
                $newvmx =~ s/^ethernet0.address = .*$/ethernet0.address = 
\"$vmclient_eth0MAC\"/gm;
        } else {
                $newvmx =~ s/^ethernet0.addressType = 
\"static\"$/ethernet0.addressType = \"generated\"/gm;
        }
        if (!$VMWARE_MAC_ETH1_GENERATED && defined($vmclient_eth1MAC)) {
                # Make sure the address type is set right and we have a 
.address line
                if ($newvmx !~ /^ethernet1.addressType = \"static\"$/m) {
                        $newvmx .= "ethernet1.addressType = \"static\"\n";
                        $newvmx .= "ethernet1.address = \"XXX\"\n";
                }
                $newvmx =~ s/^ethernet1.address = .*$/ethernet1.address = 
\"$vmclient_eth1MAC\"/gm;
        } else {
                $newvmx =~ s/^ethernet1.addressType = 
\"static\"$/ethernet1.addressType = \"generated\"/gm;
        }

        my $imageDSUUID;
        if (defined($imageDatastore->{vmfs}->{uuid})) {
                $imageDSUUID = $imageDatastore->{vmfs}->{uuid};
        } else {
                # Use the name if its NFS, not preferred, but I don't know how 
to get the UUID for an NFS datastore
                $imageDSUUID = $imageDatastore->info->name;
        }

        # This is ugly, but I don't know a better way to make sure I catch all 
the disks.  If anyone has a better way, please let me know
        my @vmxlines = split(/\n/, $newvmx);
        for $line (@vmxlines) {
                # Make sure the scsi filename isn't an absolute path before we 
make it an absolute path
                if ($line !~ /scsi\d:\d{1,2}.fileName = "\/.*"/) {
                        $line =~ s/scsi(\d:\d{1,2}).fileName = 
"(.*)"/scsi$1.fileName = "\/vmfs\/volumes\/$imageDSUUID\/$imageDir\/$2"/;
                }
        }
        $newvmx = join("\n", @vmxlines) . "\n";

        # Upload
        # Need to make sure this directory doesn't already exist...
        _deleteDirectory($datastoreName, $computer_shortname, $datacenter);
        eval {
                $fileManager->MakeDirectory(name => "[$datastoreName] " . 
$computer_shortname, datacenter => $datacenter);
        };
        if ($@) {
                notify($ERRORS{'WARNING'}, 0, "ERROR: Unable to create 
directory [$datastoreName] " . $computer_shortname . ": " . 
($...@->fault_string));
                return 0;
        }

        # Based on VIExt::http_put_file
        my $serviceURI = URI::URL->new($service->{vim_soap}->{url});
        my $userAgent = $service->{vim_soap}->{user_agent};

        $newVmPath = $computer_shortname . "/" . $computer_shortname . ".vmx";
        my $attempt = 1;
        while ($attempt <= 3) {
                my $req = VIExt::build_http_request("PUT", "folder", 
$serviceURI, $newVmPath, $datastoreName, $datacenter->{name});
                $req->header('Content-Type', 'application/octet-stream');
                $req->header('Content-Length', length($newvmx));
                $req->content($newvmx);
                $resp = $userAgent->request($req);
                if (!$resp || !$resp->is_success) {
                        notify($ERRORS{'WARNING'}, 0, "ERROR: Unable to upload 
[$datastoreName] $newVmPath: " . $resp->message . ": " . $resp->content);
                        $attempt += 1;
                        sleep 3;
                        next;
                } else {
                        last;
                }
        }

        if (!$resp || !$resp->is_success) {
                return 0;
        }
        if ($attempt > 1) {
                notify($ERRORS{'OK'}, 0, "Uploaded new vmx file on attempt 
number $attempt");
        }

        $newVmPath = "[$datastoreName]" . " $newVmPath";

        $attempt = 1;
        my $result;
        while ($attempt <= 3) {
                $task = $folder->RegisterVM_Task(path => $newVmPath, name => 
$computer_shortname, asTemplate => "false", pool => $resourcePool);
                $result = _checkTask($task, "registering $computer_shortname");
                if (!$result) {
                        $attempt += 1;
                        sleep 3;
                        next;
                } else {
                        last;
                }
        }

        if (!$resp) {
                return 0;
        }
        if ($attempt > 1) {
                notify($ERRORS{'OK'}, 0, "Registered VM on attempt number 
$attempt");
        }

        $vmView = $self->_getvmView();

        $task = $vmView->CreateSnapshot_Task(name => 'linked clone from 
vcl-image-' . $self->data->get_image_name, memory => "false", quiesce => 
"true");
        if (!_checkTask($task, "creating snapshot of $computer_shortname")) {
                return 0;
        }

        if ($vmclient_OSname =~ /winxp/) {
                # I am hard coding this for now... I am sure we can pass it in 
or read it from the db
                _customizeVm('vcl-xp', $computer_shortname , $vmView);
        }

        $task = $vmView->PowerOnVM_Task();
        if (!_checkTask($task, "powering on $computer_shortname")) {
                return 0;
        }


        #Set some variable
        my $wait_loops = 0;
        my $arpstatus  = 0;
        my $client_ip;

        if ($VMWARE_MAC_ETH0_GENERATED) {
                # allowing vmware to generate the MAC address
                # find out what MAC got assigned
                # find out what IP address is assigned to this MAC
                my $devices = $vmView->config->hardware->device;
                my $mac_addr;
                foreach my $dev (@$devices) {
                        next unless ($dev->isa("VirtualEthernetCard"));
                        notify($ERRORS{'DEBUG'}, 0, "deviceinfo->summary: 
$dev->deviceinfo->summary");
                        notify($ERRORS{'DEBUG'}, 0, "virtualswitch0: 
$virtualswitch0");
                        if ($dev->deviceInfo->summary eq $virtualswitch0) {
                                $mac_addr = $dev->macAddress;
                                last;
                        }
                }
                if (!$mac_addr) {
                        notify($ERRORS{'WARNING'}, 0, "Failed to find MAC 
address");
                        return 0;
                }
                notify($ERRORS{'DEBUG'}, 0, "Queried MAC address is $mac_addr");

                # Query ARP table for $mac_addr to find the IP (waiting for 
machine to come up if necessary)
                # The DHCP negotiation should add the appropriate ARP entry for 
us
                while (!$arpstatus) {
                        my $arpoutput = `arp -n`;
                        if ($arpoutput =~ 
/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?$mac_addr/mi) {
                                $client_ip = $1;
                                $arpstatus = 1;
                                notify($ERRORS{'OK'}, 0, "$computer_shortname 
now has ip $client_ip");
                        }
                        else {
                                if ($wait_loops > 24) {
                                        notify($ERRORS{'WARNING'}, 0, "waited 
acceptable amount of time for dhcp, please check $computer_shortname on 
$vmhost_shortname");
                                        return 0;
                                }
                                else {
                                        $wait_loops++;
                                        notify($ERRORS{'OK'}, 0, "going to 
sleep 10 seconds, waiting for computer to DHCP. Try $wait_loops");
                                        sleep 10;
                                }
                        } ## end else [ if ($arpoutput =~ 
/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}).*?$mac_addr/mi)
                } ## end while (!$arpstatus)



                notify($ERRORS{'OK'}, 0, "Found IP address $client_ip");

                # Delete existing entry for $computer_shortname in /etc/hosts 
(if any)
                notify($ERRORS{'OK'}, 0, "Removing old hosts entry");
                my $sedoutput = `sed -i "/.*\\b$computer_shortname\$/d" 
/etc/hosts`;
                notify($ERRORS{'DEBUG'}, 0, $sedoutput);

                # Add new entry to /etc/hosts for $computer_shortname
                `echo -e "$client_ip\t$computer_shortname" >> /etc/hosts`;
        } ## end if ($VMWARE_MAC_ETH0_GENERATED)
        else {
                notify($ERRORS{'DEBUG'}, 0, "IP is known for 
$computer_shortname");
        }
        # Start waiting for SSH to come up
        my $sshdstatus = 0;
        $wait_loops = 0;
        my $sshd_status = "off";
        notify($ERRORS{'DEBUG'}, 0, "Waiting for ssh to come up on 
$computer_shortname");
        while (!$sshdstatus) {
                $sshd_status = _sshd_status($computer_shortname, $image_name, 
$image_os_type);
                if ($sshd_status eq "on") {
                        $sshdstatus = 1;
                        notify($ERRORS{'OK'}, 0, "$computer_shortname now has 
active sshd running");
                }
                else {
                        #either sshd is off or N/A, we wait
                        if ($wait_loops > 150) {
                                notify($ERRORS{'WARNING'}, 0, "waited 
acceptable amount of time for sshd to become active, please check 
$computer_shortname on $vmhost_shortname");
                                #need to check power, maybe reboot it. for now 
fail it
                                return 0;
                        }
                        else {
                                $wait_loops++;
                                # to give post config a chance
                                notify($ERRORS{'DEBUG'}, 0, "going to sleep 10 
seconds, waiting for computer to start SSH. Try $wait_loops");
                                sleep 10;
                        }
                }    # else
        }    #while

        # Set IP info
        if ($IPCONFIGURATION ne "manualDHCP") {
                #not default setting
                if ($IPCONFIGURATION eq "dynamicDHCP") {
                        insertloadlog($reservation_id, $vmclient_computerid, 
"dynamicDHCPaddress", "collecting dynamic IP address for node");
                        notify($ERRORS{'DEBUG'}, 0, "Attempting to query 
vmclient for its public IP...");
#### Sean
### Why call getdynamicaddress?  It doesn't work.  Should we instead just write 
a local method 
#### to get the ip from another means?  
#### Liz
                        my $assignedIPaddress = 
getdynamicaddress($computer_shortname, $vmclient_OSname, $image_os_type);
                        if ($assignedIPaddress) {
                                #update computer table
                                notify($ERRORS{'DEBUG'}, 0, " Got dynamic 
address from vmclient, attempting to update database");
                                if 
(update_computer_address($vmclient_computerid, $assignedIPaddress)) {
                                        notify($ERRORS{'DEBUG'}, 0, " 
succesfully updated IPaddress of node $computer_shortname");
                                }
                                else {
                                        notify($ERRORS{'CRITICAL'}, 0, "could 
not update dynamic address $assignedIPaddress for $computer_shortname 
$image_name");
                                        return 0;
                                }
                        } ## end if ($assignedIPaddress)
                        else {
                                notify($ERRORS{'CRITICAL'}, 0, "could not fetch 
dynamic address from $computer_shortname $image_name");
                                insertloadlog($reservation_id, 
$vmclient_computerid, "failed", "could not collect dynamic IP address for 
node");
                                return 0;
                        }
                } ## end if ($IPCONFIGURATION eq "dynamicDHCP")
                elsif ($IPCONFIGURATION eq "static") {
                        notify($ERRORS{'CRITICAL'}, 0, "STATIC ASSIGNMENT NOT 
SUPPORTED. See vcld.conf");
                        return 0;
                        #insertloadlog($reservation_id, $vmclient_computerid, 
"staticIPaddress", "setting static IP address for node");
                        #if (setstaticaddress($computer_shortname, 
$vmclient_OSname, $vmclient_publicIPaddress)) {
                        #       # good set static address
                        #}
                }
        } ## end if ($IPCONFIGURATION ne "manualDHCP")

        # Perform post load tasks
        return 1;
} ## end sub load

#/////////////////////////////////////////////////////////////////////////

=head2 node_status

 Parameters  : $nodename, $log
 Returns     : array of related status checks
 Description : checks on sshd, currentimage

=cut

sub node_status {
        my $self = shift;

        my ($package, $filename, $line, $sub) = caller(0);

        my $vmpath             = 0;
        my $datastorepath      = 0;
        my $requestedimagename = 0;
        my $vmhost_type        = 0;
        my $vmhost_hostname    = 0;
        my $vmhost_imagename   = 0;
        my $image_os_type      = 0;
        my $vmclient_shortname = 0;
        my $request_forimaging = 0;
        my $identity_keys      = 0;
        my $log                = 0;
        my $computer_node_name = 0;


        # Check if subroutine was called as a class method
        if (ref($self) !~ /esxduke/i) {
                notify($ERRORS{'OK'}, 0, "subroutine was called as a function");
                if (ref($self) eq 'HASH') {
                        $log = $self->{logfile};
                        #notify($ERRORS{'DEBUG'}, $log, "self is a hash 
reference");

                        $vmpath             = 
$self->{vmhost}->{vmprofile}->{vmpath};
                        $datastorepath      = 
$self->{vmhost}->{vmprofile}->{datastorepath};
                        $requestedimagename = 
$self->{imagerevision}->{imagename};
                        $vmhost_type        = 
$self->{vmhost}->{vmprofile}->{vmtype}->{name};
                        $vmhost_hostname    = $self->{vmhost}->{hostname};
                        $vmhost_imagename   = $self->{vmhost}->{imagename};
                        $image_os_type      = $self->{image}->{OS}->{type};
                        $computer_node_name = $self->{computer}->{hostname};
                        $identity_keys      = $self->{managementnode}->{keys};

                } ## end if (ref($self) eq 'HASH')
                    # Check if node_status returned an array ref
                elsif (ref($self) eq 'ARRAY') {
                        notify($ERRORS{'DEBUG'}, $log, "self is a array 
reference");
                }

                $vmclient_shortname = $1 if ($computer_node_name =~ 
/([-_a-zA-Z0-9]*)(\.?)/);
        } ## end if (ref($self) !~ /esx/i)
        else {

                # try to contact vm
                # $self->data->get_request_data;
                # get state of vm
                $vmpath             = $self->data->get_vmhost_profile_vmpath;
                $datastorepath      = 
$self->data->get_vmhost_profile_datastore_path;
                $requestedimagename = $self->data->get_image_name;
                $vmhost_type        = $self->data->get_vmhost_type;
                $vmhost_hostname    = $self->data->get_vmhost_hostname;
                $vmhost_imagename   = $self->data->get_vmhost_image_name;
                $image_os_type      = $self->data->get_image_os_type;
                $vmclient_shortname = $self->data->get_computer_short_name;
                $request_forimaging = $self->data->get_request_forimaging();
        } ## end else [ if (ref($self) !~ /esx/i)

        notify($ERRORS{'OK'},    0, "Entering node_status, checking status of 
$vmclient_shortname");
        notify($ERRORS{'DEBUG'}, 0, "request_for_imaging: $request_forimaging");
        notify($ERRORS{'DEBUG'}, 0, "requeseted image name: 
$requestedimagename");

        my ($hostnode, $identity);

        # Create a hash to store status components
        my %status;

        # Initialize all hash keys here to make sure they're defined
        $status{status}       = 0;
        $status{currentimage} = 0;
        $status{ping}         = 0;
        $status{ssh}          = 0;
        $status{vmstate}      = 0;    #on or off
        $status{image_match}  = 0;

        if ($vmhost_type eq "blade") {
                $hostnode = $1 if ($vmhost_hostname =~ /([-_a-zA-Z0-9]*)(\.?)/);
                $identity = $IDENTITY_bladerhel;    #if($vm{vmhost}{imagename} 
=~ /^(rhel|rh3image|rh4image|fc|rhfc)/);
        }
        else {
                #using FQHN
                $hostnode = $vmhost_hostname;
                $identity = $IDENTITY_linux_lab if ($vmhost_imagename =~ 
/^(realmrhel)/);
        }

        if (!$identity) {
                notify($ERRORS{'CRITICAL'}, 0, "could not set ssh identity 
variable for image $vmhost_imagename type= $vmhost_type host= 
$vmhost_hostname");
        }

        # Check if node is pingable
        notify($ERRORS{'DEBUG'}, 0, "checking if $vmclient_shortname is 
pingable");
        if (_pingnode($vmclient_shortname)) {
                $status{ping} = 1;
                notify($ERRORS{'OK'}, 0, "$vmclient_shortname is pingable 
($status{ping})");
        }
        else {
                notify($ERRORS{'OK'}, 0, "$vmclient_shortname is not pingable 
($status{ping})");
                $status{status} = 'RELOAD';
                return $status{status};
        }

        #
        #my $vmx_directory = "$requestedimagename$vmclient_shortname";
        #my $myvmx         = 
"$vmpath/$requestedimagename$vmclient_shortname/$requestedimagename$vmclient_shortname.vmx";
        #my $mybasedirname = $requestedimagename;
        #my $myimagename   = $requestedimagename;

        notify($ERRORS{'DEBUG'}, 0, "Trying to ssh...");

        #can I ssh into it
        my $sshd = _sshd_status($vmclient_shortname, $requestedimagename, 
$image_os_type);


        #is it running the requested image
        if ($sshd eq "on") {

                notify($ERRORS{'DEBUG'}, 0, "SSH good, trying to query image 
name");

                $status{ssh} = 1;
                $identity = $IDENTITY_bladerhel;
                my @sshcmd = run_ssh_command($vmclient_shortname, $identity, 
"cat currentimage.txt");
                $status{currentimage} = $sshcmd[1][0];

                notify($ERRORS{'DEBUG'}, 0, "Image name: 
$status{currentimage}");

                if ($status{currentimage}) {
                        chomp($status{currentimage});
                        if ($status{currentimage} =~ /$requestedimagename/) {
                                $status{image_match} = 1;
                                notify($ERRORS{'OK'}, 0, "$vmclient_shortname 
is loaded with requestedimagename $requestedimagename");
                        }
                        else {
                                notify($ERRORS{'OK'}, 0, "$vmclient_shortname 
reports current image is currentimage= $status{currentimage} 
requestedimagename= $requestedimagename");
                        }
                } ## end if ($status{currentimage})
        } ## end if ($sshd eq "on")

        # Determine the overall machine status based on the individual status 
results
        if ($status{ssh} && $status{image_match}) {
                $status{status} = 'READY';
        }
        else {
                $status{status} = 'RELOAD';
        }

        notify($ERRORS{'DEBUG'}, 0, "status set to $status{status}");


        if ($request_forimaging) {
                $status{status} = 'RELOAD';
                notify($ERRORS{'OK'}, 0, "request_forimaging set, setting 
status to RELOAD");
        }

        notify($ERRORS{'DEBUG'}, 0, "returning node status hash reference 
(\$node_status->{status}=$status{status})");
        return \%status;

} ## end sub node_status

sub does_image_exist {
        my $self = shift;
        if (ref($self) !~ /esxduke/i) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
                return 0;
        }

        my $image_view = $self->_getimageView(0);
        if (! $image_view) {
                return 0;
        }
        return 1;

} ## end sub does_image_exist

#/////////////////////////////////////////////////////////////////////////////

=head2  getimagesize

 Parameters  : imagename
 Returns     : 0 failure or size of image
 Description : in size of Kilobytes

=cut

# Need to implement
sub get_image_size {
        my $self = shift;
        if (ref($self) !~ /esxduke/i) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
                return 0;
        }

        # Just a placeholder - not sure if this value even matters
        return 1;
} ## end sub get_image_size

sub power_off {
        my $self = shift;
        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }

        my $task;
        my $computer_shortname = $self->data->get_computer_short_name;
        my $vmView = $self->_getvmView();
        if (!$vmView) {
                return 0;
        }
        if ($vmView->{runtime}->{powerState}->val eq "poweredOff") {
                # VM is turned off, so just return happily
                return 1;
        }
        $task = $vmView->PowerOffVM_Task();
        if (!_checkTask($task, "powering off " . $computer_shortname)) {
                return 0;
        }
        return 1;
}

sub power_on {
        my $self = shift;
        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }

        my $task;
        my $computer_shortname = $self->data->get_computer_short_name;
        my $vmView = $self->_getvmView();
        if (!$vmView) {
                return 0;
        }
        if ($vmView->{runtime}->{powerState}->val eq "poweredOn") {
                # VM is turned on, so just return happily
                return 1;
        }
        $task = $vmView->PowerOnVM_Task();
        if (!_checkTask($task, "powering on " . $computer_shortname)) {
                return 0;
        }
        return 1;
}

sub power_reset {
        my $self = shift;
        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }

        my $task;
        my $computer_shortname = $self->data->get_computer_short_name;
        my $vmView = $self->_getvmView();
        if (!$vmView) {
                return 0;
        }
        $task = $vmView->ResetVM_Task();
        if (!_checkTask($task, "reseting on " . $computer_shortname)) {
                return 0;
        }
        return 1;
}

sub power_status {
        my $self = shift;
        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }

        my $task;
        my $computer_shortname = $self->data->get_computer_short_name;
        my $vmView = $self->_getvmView();
        if (!$vmView) {
                return;
        }
        $vmView->update_view_data();
        if ($vmView->runtime->powerState->val eq 'poweredOn') {
                return "on";
        } elsif ($vmView->runtime->powerState->val eq 'poweredOff') {
                return "off";
        } else {
                notify($ERRORS{'WARNING'}, 0, "Unable to determin power status. 
 VMware reports powerState: " . $vmView->runtime->powerState->val);
                return;
        }
}

#/////////////////////////////////////////////////////////////////////////////

=head2 capture

 Parameters  : $request_data_hash_reference
 Returns     : 1 if sucessful, 0 if failed
 Description : Creates a new vmware image.

=cut

sub capture {
        my $self = shift;
        if (ref($self) !~ /esxduke/i) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine was called as a 
function, it must be called as a class method");
                return 0;
        }

        my ($package, $filename, $line, $sub) = caller(0);

        # Store some hash variables into local variables
        # to pass to write_current_image routine
        my $request_data = $self->data->get_request_data;

        if (!$request_data) {
                notify($ERRORS{'WARNING'}, 0, "unable to retrieve request data 
hash");
                return 0;
        }

        # Store some hash variables into local variables
        my $request_id     = $self->data->get_request_id;
        my $reservation_id = $self->data->get_reservation_id;

        my $image_id       = $self->data->get_image_id;
        my $image_os_name  = $self->data->get_image_os_name;
        my $image_identity = $self->data->get_image_identity;
        my $image_os_type  = $self->data->get_image_os_type;
        my $image_name     = $self->data->get_image_name();
        
        my $computer_id        = $self->data->get_computer_id;
        my $computer_shortname = $self->data->get_computer_short_name;
        my $computer_nodename  = $computer_shortname;
        my $computer_hostname  = $self->data->get_computer_hostname;
        my $computer_type      = $self->data->get_computer_type;

        my $vmtype_name             = $self->data->get_vmhost_type_name;
        my $vmhost_vmpath           = $self->data->get_vmhost_profile_vmpath;
        my $vmprofile_vmdisk        = $self->data->get_vmhost_profile_vmdisk;
        my $vmprofile_datastorepath = 
$self->data->get_vmhost_profile_datastore_path;
        my $vmhost_hostname         = $self->data->get_vmhost_hostname;
        my $host_type               = $self->data->get_vmhost_type;
        my $vmhost_imagename        = $self->data->get_vmhost_image_name;

        my ($hostIdentity, $hostnodename);
        if ($host_type eq "blade") {
                $hostnodename = $1 if ($vmhost_hostname =~ 
/([-_a-zA-Z0-9]*)(\.?)/);
                $hostIdentity = $IDENTITY_bladerhel;
        }
        else {
                #using FQHN
                $hostnodename = $vmhost_hostname;
                $hostIdentity = $IDENTITY_linux_lab if ($vmhost_imagename =~ 
/^(realmrhel)/);
        }
        # Assemble a consistent prefix for notify messages
        my $notify_prefix = "req=$request_id, res=$reservation_id:";


        # Print some preliminary information
        notify($ERRORS{'OK'}, 0, "$notify_prefix new name: $image_name");
        notify($ERRORS{'OK'}, 0, "$notify_prefix computer_name: 
$computer_shortname");
        notify($ERRORS{'OK'}, 0, "$notify_prefix vmhost_hostname: 
$vmhost_hostname");
        notify($ERRORS{'OK'}, 0, "$notify_prefix vmtype_name: $vmtype_name");

        my $vmView = $self->_getvmView();
        if (!$vmView) {
                return 0;
        }

        my $imageFolderName = "VCL-TEST";
        my $imageDatastoreName = "VCL-IMAGES-TEST-01";

        if(open(CONF, "/etc/vcl/vcld.conf")){
                my  @conf=<CONF>;
                close(CONF);
                foreach $line (@conf) {
                        #folderName
                        if($line =~ /^IMAGEFOLDERNAME=([\S]*)/){
                                chomp($line);
                                $imageFolderName=$1;
                        }
                        #datastoreName
                        if($line =~ /^IMAGEDATASTORENAME=([\S]*)/){
                                chomp($line);
                                $imageDatastoreName=$1;
                        }
                }
        }

        my $parentView = Vim::get_view(mo_ref => $vmView->{parent});
        while (ref($parentView) ne 'Datacenter') {
                $parentView = Vim::get_view(mo_ref => $parentView->{parent});
        }
        my $datacenter = $parentView;
        my $datastoreView;
        my $datastores = Vim::get_views(mo_ref_array => 
$datacenter->{datastore});
        foreach (@$datastores) {
                if ($_->{info}->{name} eq $imageDatastoreName) {
                        $datastoreView = $_;
                        last;
                }
        }
        if (!defined($datastoreView)) {
                notify($ERRORS{'CRITICAL'}, 0, "Unable to find datastore 
$imageDatastoreName");
                return 0;
        }
        my $folder = Vim::find_entity_view(view_type => 'Folder', filter => 
{'name' => $imageFolderName}, begin_entity => $datacenter);
        if (! defined($folder)) {
                notify($ERRORS{'CRITICAL'}, 0, "ERROR: esxduke->capture could 
not find folder $imageFolderName");
                return 0;
        }

        # Modify currentimage.txt
        if (write_currentimage_txt($self->data)) {
                notify($ERRORS{'OK'}, 0, "$notify_prefix currentimage.txt 
updated on $computer_shortname");
        }
        else {
                notify($ERRORS{'WARNING'}, 0, "$notify_prefix unable to update 
currentimage.txt on $computer_shortname");
                return 0;
        }

        # Set some vm paths and names
        my $vmx_directory  = "$reservation_id$computer_shortname";
        my $vmx_image_name = "$reservation_id$computer_shortname";
        my $vmx_path       = 
"$vmhost_vmpath/$vmx_directory/$vmx_image_name.vmx";
        
        my @sshcmd;
        
        # Check if pre_capture() subroutine has been implemented by the OS 
module
        if ($self->os->can("pre_capture")) {
                # Call OS pre_capture() - it should perform all OS steps 
necessary to capture an image
                # pre_capture() should shut down the computer when it is done
                notify($ERRORS{'OK'}, 0, "calling OS module's pre_capture() 
subroutine");
                
                if (!$self->os->pre_capture({end_state => 'off'})) {
                        notify($ERRORS{'WARNING'}, 0, "OS module pre_capture() 
failed");
                        return 0;
                }
        
        }
        # Get the power status, make sure computer is off
        my $power_status = $self->power_status();
        notify($ERRORS{'DEBUG'}, 0, "retrieved power status: $power_status");
        if ($power_status eq 'off') {
                notify($ERRORS{'OK'}, 0, "verified $computer_nodename power is 
off");
        }
        elsif ($power_status eq 'on') {
                notify($ERRORS{'WARNING'}, 0, "$computer_nodename power is 
still on, turning computer off");
                
                # Attempt to power off computer
                if ($self->power_off()) {
                        notify($ERRORS{'OK'}, 0, "$computer_nodename was 
powered off");
                }
                else {
                        notify($ERRORS{'WARNING'}, 0, "failed to power off 
$computer_nodename");
                        return 0;
                }
        }
        else {
                notify($ERRORS{'WARNING'}, 0, "failed to determine power status 
of $computer_nodename");
                return 0;
        }

        # Clone with new image name: $image_name
        
        my $relocateSpec = VirtualMachineRelocateSpec->new(datastore => 
$datastoreView, transform => 
VirtualMachineRelocateTransformation->new('sparse'));
        my $cloneSpec = VirtualMachineCloneSpec->new(powerOn => 0, template => 
1, location => $relocateSpec);

        notify($ERRORS{'OK'}, 0, "Cloning $computer_shortname to 
vcl-image-$image_name");

        my $task = $vmView->CloneVM_Task(folder => $folder, name => 
"vcl-image-" . $image_name, spec => $cloneSpec);
        return _checkTask($task, "Cloning $computer_shortname");

} ## end sub capture


sub _getvmView {
        my $self = shift;
        my $is_warning = shift;
        my $notify_level = $ERRORS{'WARNING'};

        if (defined($is_warning) && !$is_warning) {
                $notify_level = $ERRORS{'DEBUG'};
        }

        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }
        if (defined($self->{_vmView})) {
                return $self->{_vmView};
        }

        $self->_checkConnection();
        my $computer_shortname   = $self->data->get_computer_short_name;
        my $vmView = Vim::find_entity_view(view_type => 'VirtualMachine', 
filter => {'name' => $computer_shortname });
        if (!$vmView) {
                notify($notify_level, 0, "Could not find VM 
$computer_shortname");
                return 0;
        }
        $self->{_vmView} = $vmView;

        return $vmView;
}

sub _getimageView {
        my $self = shift;
        my $is_crit = shift;
        my $notify_level = $ERRORS{'CRITICAL'};

        if (defined($is_crit) && !$is_crit) {
                $notify_level = $ERRORS{'DEBUG'};
        }

        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }
        if (defined($self->{_imageView})) {
                return $self->{_imageView};
        }

        $self->_checkConnection();
        my $image_name = $self->data->get_image_name;
        my $imageView = Vim::find_entity_view(view_type => 'VirtualMachine', 
filter => {'name' => "vcl-image-$image_name"});
        if (!$imageView) {
                notify($notify_level, 0, "Could not find image 
vcl-image-$image_name");
                return 0;
        }
        if ($imageView->{snapshot}) {
                # This is always crit. If we're called from does_image_exist we 
want to log the weird state of the image existing but being invalid
                notify($ERRORS{'CRITICAL'}, 0, "ERROR: vcl-image-$image_name is 
an invalid image, it has a snapshot");
                return 0;
        }
        $self->{_imageView} = $imageView;

        return $imageView;
}

sub _removeOldVM {

        my $self = shift;
        unless (ref($self) && $self->isa('VCL::Module')) {
                notify($ERRORS{'CRITICAL'}, 0, "subroutine can only be called 
as a VCL::Module module object method");
                return 0;
        }

        my $task;
        my (@deviceSpecs, $vmDevices, $vmConfigSpec);

        my $vmView = $self->_getvmView();

        if ($vmView->{runtime}->{powerState}->val ne "poweredOff") {
                notify($ERRORS{'DEBUG'}, 0, "Powering off " . $vmView->{name});
                $task = $vmView->PowerOffVM_Task();
                if (!_checkTask($task, "powering off " . $vmView->{name})) {
                        return 0;
                }
        }

        # We simply unregister the VM.  The _deleteDirectory function will
        # clean up the files.  If we were to Destroy the VM, VMware would
        # wipe out the disk of the image, which we need to avoid.
        $vmView->UnregisterVM();

        $self->{_vmView} = undef;
        return 1;

}

sub _checkTask {
        my ($task, $taskName) = @_;

        my $taskView = Vim::get_view(mo_ref => $task);
        while (1) {
                # Sometimes FileManager->DeleteDatastoreFile() gives us an odd 
task, so this is to give us a shot of making it a normal task
                if (!defined $taskView->{info}) {
                        my $attempt = 1;
                        while ($attempt <= 3) {
                                $taskView->update_view_data();
                                if (defined $taskView->{info}) {
                                        last;
                                } else {
                                        $attempt += 1;
                                        sleep 1;
                                }
                        }
                        if (!defined $taskView->{info}) {
                                notify($ERRORS{'WARNING'}, 0, "ERROR: Could not 
get valid task for $taskName");
                                return 0;
                        }
                }

                if ($taskView->info->state->val eq "success") {
                        return 1;
                } elsif ($taskView->info->state->val eq "error") {
                        notify($ERRORS{'WARNING'}, 0, "ERROR: $taskName: " . 
$taskView->info->error->localizedMessage);
                        return 0;
                }
                sleep 1;
                $taskView->update_view_data();
        }
}

sub _customizeVm {
        my ($customSpec, $nodeName, $view ) = @_;

        my $customView = Vim::get_view(mo_ref => 
Vim::get_service_content()->customizationSpecManager);
        my $customizationSpec = $customView->GetCustomizationSpec ('name' => 
$customSpec)->spec;

        # We need to set the machine name in this spec
        _setMachineName($nodeName, $customizationSpec);
        my $task = $view->CustomizeVM_Task(spec => $customizationSpec);
        if (_checkTask($task, "customizing $nodeName")){
                notify($ERRORS{'OK'}, 0, "success setting custom specs");
        }
}

sub _setMachineName {
        my ($vmName, $customizationSpec) = @_;

        my @machineName = split(/\./,$vmName);
        my $cust_name = CustomizationFixedName->new (name => $machineName[0]);
        $customizationSpec->identity->userData->computerName ($cust_name);
        return;
}

sub _deleteDirectory {
        my ($dsName, $dirname, $datacenter) = @_;
        my $ds;

        foreach my $ds2 (@{$datacenter->{'datastore'}}) {
                my $ds_view = Vim::get_view(mo_ref => $ds2);
                if ($ds_view->info->name eq $dsName) {
                        $ds = $ds_view;
                        last; 
                }
        }
        
        if (!defined($ds)) {
                notify($ERRORS{'WARNING'}, 0, "Could not find datastore 
$dsName");
                return 0;
        }
        
        my $ds_browser = Vim::get_view(mo_ref => $ds->browser);

        my $search_spec = HostDatastoreBrowserSearchSpec->new(matchPattern => 
[$dirname]);
        my $browse_result = $ds_browser->SearchDatastore(datastorePath => 
"[$dsName]", searchSpec => $search_spec);
        # Only do the delete if the file exists
        if (defined $browse_result->file) {
                eval {
                        my $fileManager = Vim::get_view(mo_ref => 
Vim::get_service_content()->fileManager);
                        my $task = $fileManager->DeleteDatastoreFile_Task(name 
=> "[$dsName] $dirname", datacenter => $datacenter);
                        if (!_checkTask($task, "deleting [$dsName] $dirname")) {
                                return 0;
                        }
                };
                if ($@) {
                        notify($ERRORS{'WARNING'}, 0, "Error trying to delete 
[$dsName] $dirname: " . ($...@->fault_string));
                }
        }
}



initialize();

END {
        Util::disconnect();
}

#/////////////////////////////////////////////////////////////////////////////

1;
__END__

=head1 SEE ALSO

L<http://cwiki.apache.org/VCL/>

=cut

Reply via email to