#!/usr/bin/env perl ##************************************************************** ## ## Copyright (C) 1990-2007, Condor Team, Computer Sciences Department, ## University of Wisconsin-Madison, WI. ## ## Licensed 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. ## ##************************************************************** # # VMWare Control Tool # V0.1 / 2007-Mar-01 / Jaeyoung Yoon / jyoon@cs.wisc.edu # use strict; use File::Path; use File::Copy; # program version information my $APPNAME = "VMWare Control Tool"; my $VERSION = "0.1"; my $verbose = undef; # Location of "vmrun" and "vmware-cmd" program # If these programs are in $PATH, just use basename. Otherwise use a full path my $vmrun_prog; my $vmwarecmd_prog; if (lc($^O) eq "mswin32") { # For MS Windows assume VMWare is in "C:\Program Files (x86)" if it exists # otherwise assume it's in "C:\Program Files", we find these directories # using environment variables. my $pf86 = "ProgramFiles(x86)"; my $progfiles = $ENV{$pf86}; if ( ! $progfiles) { $progfiles = $ENV{ProgramFiles}; } $vmrun_prog = $progfiles . '\VMware\VMware VIX\vmrun'; $vmwarecmd_prog = $progfiles . '\VMware\VMware VmPerl Scripting API\vmware-cmd'; }else { # For Linux $vmrun_prog = 'vmrun'; $vmwarecmd_prog = 'vmware-cmd'; } # Location of "mkisofs" or "cdmake.exe" program # If mkisofs program is in $PATH, just use basename. Otherwise use a full path my $cdmake; my $mkisofs; if (lc($^O) eq "mswin32") { # For MS Windows $cdmake = 'C:\condor\bin\cdmake.exe'; }else { # For Linux $mkisofs = 'mkisofs'; } # Location of dhcpd.lease # If dhcpd_lease is defined, we will use the file to find IP address of guest VM. # Otherwise, we will send a request to vmware tool. # If vmware tool is not installed inside VM, we can't get IP address of the VM. my $dhcpd_lease = undef; #$dhcpd_lease = '/etc/vmware/vmnet8/dhcpd/dhcpd.leases'; #$dhcpd_lease = 'C:\Documents and Settings\All Users\Application Data\VMware\vmnetdhcp.leases'; # stdout will be directed to stderr #open STDOUT, ">&STDERR" or die "Can't dup STDERR: $!"; #select STDERR; $| = 1; # make unbuffered #select STDOUT; $| = 1; # make unbuffered open OUTPUT, ">&STDOUT"; open STDOUT, ">&STDERR"; my $tmpdir = undef; my $progname = $0; # # Assume we're using Workstation or Player, but autodetect Server; # only Server has vmware-cmd. # my $VMWARE_VERSION = "workstation"; # open()ing a pipe on Windows always succeeds, even if the binary in # question doesn't exist, so just check if the binary exists. (We can't # do this on Linux because we don't have the full path to the binary.) if( lc($^O) eq "mswin32" ) { if( -x $vmwarecmd_prog ) { $VMWARE_VERSION = "server"; } } else { my $childPID = open( VMV, '-|', $vmwarecmd_prog, '-l' ); if( defined( $childPID ) ) { $VMWARE_VERSION = "server"; } close( VMV ); } if( defined($verbose) ) { print( "Using \$VMWARE_VERSION '$VMWARE_VERSION'.\n" ); } # # The behavior of vmrun isn't consistent from platform to platform, so we # have to examine the output to determine if it actually worked. Note # that VMWare Server's implementation of vmrun dies if passed -T, so be # sure never to do that. # my @VMRUN_CMD = ($vmrun_prog); while( $VMWARE_VERSION eq 'workstation' ) { my $wsList = `"$vmrun_prog" -T ws list`; if( $wsList =~ m/^Total running VMs:/ ) { push( @VMRUN_CMD, '-T', 'ws' ); last; } my $playerList = `"$vmrun_prog" -T player list`; if( $playerList =~ m/^Total running VMs:/ ) { push( @VMRUN_CMD, '-T', 'player' ); last; } last; } my $VMRUNCMD = '"' . join( '" "', @VMRUN_CMD ) . '"'; if( defined($verbose) ) { print( "Using \$VMRUNCMD '$VMRUNCMD'.\n" ); } sub usage() { print STDERR <) { next if /^#|^$/; if( /^lease (\d+\.\d+\.\d+\.\d+)/) { $tmp_ip = $1; } if( /$mac_address/ ) { $ip_address = $tmp_ip; } } close LEASEFILE; } unlink $tmp_lease_file; } if( defined($ip_address) ) { printverbose "getting IP address using lease file($dhcpd_lease)"; }else { # We failed to get IP address of guest VM # We will retry to get it by using vmware tool $resultline = `"$vmwarecmd_prog" "$vmconfig" getguestinfo ip`; chomp($resultline); if( $resultline ) { # result must be like "getguestinfo(ip) = 172.16.123.143" my @fields = split /=/, $resultline; shift @fields; $ip_address = shift @fields; # delete leading/trailing whitespace $ip_address =~ s/^\s+|\s+$//g; if( ! defined($ip_address) ) { $ip_address = undef; printwarn "Invalid format of getguestinfo ip($resultline)"; } } } if( defined($ip_address) ) { print OUTPUT "IP=$ip_address\n"; } } } } sub start { #start [vmconfig] Start a VM printverbose "start is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { !system @VMRUN_CMD, "start", $vmconfig, "nogui" or printerror "Can't create vm with $vmconfig"; } else { if( ! checkregister($vmconfig) ) { #!system $vmwarecmd_prog, "-s", "register", $vmconfig # or printerror "Can't register a new vm with $vmconfig"; system $vmwarecmd_prog, "-s", "register", $vmconfig; } # Now, a new vm is registered # Try to create a new vm !system $vmwarecmd_prog, $vmconfig, "start", "trysoft" or printerror "Can't create vm with $vmconfig"; } sleep(5); # Get pid of main process of this VM my $vmpid = getvmpid($vmconfig); print OUTPUT "PID=$vmpid\n"; } sub stop { #stop [vmconfig] Shutdown a VM printverbose "stop is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { # Try to stop vm !system @VMRUN_CMD, "stop", $vmconfig, "hard" or printerror "Can't stop vm with $vmconfig"; sleep(2); } else { # Get status my $vmstatus = status($vmconfig); if( $vmstatus eq "Running" ) { # Try to stop vm !system $vmwarecmd_prog, $vmconfig, "stop", "hard" or printerror "Can't stop vm with $vmconfig"; sleep(2); } unregister($vmconfig); } } sub killvm { #killvm [vmconfig] kill a VM printverbose "killvm is called"; my $matchstring = $_[0]; if ($VMWARE_VERSION eq "workstation") { # # Rather than error out and cause ugly warnings in the log, # we'd like to try stopping the VM again. Unfortunately, # the starter has already removed the execute directory by # the time it calls VMwareType::killVMFast(), so we don't # have a .vmx to pass to vmrun any longer. (The .vmx must # exist; we can't just pass the path.) # my $processList = `ps uxw`; my @processes = split( /\n/, $processList ); foreach my $process (@processes) { if( $process =~ m/vmware-vmx.*$matchstring/ ) { my( $user, $pid ) = split( /\s+/, $process ); printwarn( "Found '$process', trying to kill...\n" ); my $numSignalled = kill( 9, $pid ); if( $numSignalled < 1 ) { printerror( "Failed to signal process '$process'.\n" ); } else { printwarn( "Sent signal to process '$process'.\n" ); # Otherwise, the VM GAHP won't log the warnings. Change # back to zero once this problem is corrected. exit 1; } } } printwarn( "Found no process matching '$matchstring', assuming succesful prior termination." ); exit( 0 ); } if( ! defined($matchstring) ) { usage(); } printverbose "matching string is '$matchstring'"; my $os = $^O; if (lc($os) eq "mswin32") { # replace \ with \\ for regular expression $matchstring =~ s/\\/\\\\/g; # replace / with \\ for regular expression $matchstring =~ s/\//\\\\/g; } # Get the list of registered VMs chomp(my @vmstatus = `"$vmwarecmd_prog" -l`); # result must be like this # /home/condor/vmware/Centos.vmx # /home/condor/vmware/Centos2.vmx my $ori_line; foreach( @vmstatus ) { # delete leading/trailing whitespace s/^\s+|\s+$//g; $ori_line = $_; if (lc($os) eq "mswin32") { # replace / with \ $_ =~ s/\//\\/g; } if( $_ =~ m{$matchstring} ) { # this registed vm is matched # Get pid of this vm my $vmpid = getvmpid($ori_line); if( defined($vmpid) ) { printverbose "Killing process(pid=$vmpid)"; kill "KILL", $vmpid; } !system $vmwarecmd_prog, "-s", "unregister", $ori_line or printwarn "Can't unregister a vm($ori_line)"; } } if (lc($os) eq "linux") { # On Linux machines, we make certain no process for VM again. my @pidarr; my $psline; my @psarr = `ps -ef`; foreach $psline (@psarr) { if( $psline =~ m{$matchstring} && $psline =~ m/vmware-vmx/i && $psline !~ m{$0}) { @pidarr = split (/ +/, $psline); if( defined($pidarr[1]) ) { printverbose "Killing process(pid=$pidarr[1])"; kill 9, $pidarr[1]; } } } } } sub suspend { #suspend [vmconfig] Suspend a VM printverbose "suspend is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { !system @VMRUN_CMD, "suspend", $vmconfig, "soft" or printerror "Can't suspend vm with $vmconfig"; } else { # Get status my $vmstatus = status($vmconfig); if( $vmstatus ne "Running" ) { printerror "vm($vmconfig) is not running"; } !system $vmwarecmd_prog, $vmconfig, "suspend", "trysoft" or printerror "Can't suspend vm with $vmconfig"; } # We need to guarantee all memory data into a file # VMware does some part of suspending in background mode # So, we give some time to VMware sleep(5); } sub resume { #resume [vmconfig] Restore a suspended VM printverbose "resume is called"; my $vmconfig = checkvmconfig($_[0]); # Get status my $vmstatus = status($vmconfig); if( $vmstatus ne "Running" ) { start($vmconfig); } } sub snapshot { #snapshot [vmconfig] Create a snapshot of a VM printverbose "snapshot is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { !system @VMRUN_CMD, "snapshot", $vmconfig, "condor-snapshot" or printerror "Can't create snapshot for vm($vmconfig)"; } else { if( ! checkregister($vmconfig) ) { #!system $vmwarecmd_prog, "-s", "register", $vmconfig # or printerror "Can't register a vm with $vmconfig"; system $vmwarecmd_prog, "-s", "register", $vmconfig; } !system $vmrun_prog, "snapshot", $vmconfig or printerror "Can't create snapshot for vm($vmconfig)"; } sleep(1); } sub commit { #commit [vmconfig] Commit COW disks and delete the COW printverbose "commit is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { !system @VMRUN_CMD, "deleteSnapshot", $vmconfig, "condor-snapshot" or printerror "Can't combine snapshot disk with base disk for vm($vmconfig)"; } else { if( ! checkregister($vmconfig) ) { #!system $vmwarecmd_prog, "-s", "register", $vmconfig # or printerror "Can't register a vm with $vmconfig"; system $vmwarecmd_prog, "-s", "register", $vmconfig; } !system $vmrun_prog, "deleteSnapshot", $vmconfig or printerror "Can't combine snapshot disk with base disk for vm($vmconfig)"; } sleep(5); } sub revert { #revert [vmconfig] Set VM state to a snapshot printverbose "revert is called"; my $vmconfig = checkvmconfig($_[0]); if ($VMWARE_VERSION eq "workstation") { !system @VMRUN_CMD, "revertToSnapshot", $vmconfig, "condor-snapshot" or printerror "Can't revert VM state to a snapshot for vm($vmconfig)"; } else { if( ! checkregister($vmconfig) ) { #!system $vmwarecmd_prog, "-s", "register", $vmconfig # or printerror "Can't register a vm with $vmconfig"; system $vmwarecmd_prog, "-s", "register", $vmconfig; } !system $vmrun_prog, "revertToSnapshot", $vmconfig or printerror "Can't revert VM state to a snapshot for vm($vmconfig)"; } sleep(5); } sub createiso { #createiso [listfile] [ISO] Create an ISO image with files in a listfile printverbose "createiso is called"; my $isoconfig = $_[0]; my $isofile = $_[1]; if( ! defined($isofile) || ! defined($isoconfig)) { usage(); } unless( -e $isoconfig ) { printerror "File($isoconfig) does not exist"; } unless( -r $isoconfig ) { printerror "File($isoconfig) is not readable"; } # Create temporary directory $tmpdir = $isofile.".dir"; mkdir("$tmpdir") || printerror "Cannot mkdir newdir"; # Read config file open(ISOFILES, "$isoconfig") or printerror "Cannot open the file($isoconfig) : $!"; # Copy all files in config into the temporary directory while( ) { chomp; if( $_ ) { copy( "$_", "$tmpdir") or printerror "Cannot copy file($_) into directory($tmpdir) : $!"; } } close ISOFILES; my $cdlabel = "CONDOR"; # Using volume ID, application Label, Joliet if (lc($^O) eq "mswin32") { !system $cdmake, "-q", "-j", "-m", $tmpdir, $cdlabel, $isofile or printerror "Cannot create an ISO file($isofile)"; } else { !system $mkisofs, "-quiet", "-o", $isofile, "-input-charset", "iso8859-1", "-J", "-A", $cdlabel, "-V", $cdlabel, $tmpdir or printerror "Cannot create an ISO file($isofile)"; } rmtree("$tmpdir") or printwarn "Cannot delete temporary directory($tmpdir) and files in it"; sleep(1); } sub createconfig {} if ($#ARGV < 0 || $ARGV[0] eq "--help") { usage(); } elsif ($ARGV[0] eq "list") { list(); } elsif ($ARGV[0] eq "check") { check(); } elsif ($ARGV[0] eq "register") { register($ARGV[1]); } elsif ($ARGV[0] eq "unregister"){ unregister($ARGV[1]); } elsif ($ARGV[0] eq "start") { start($ARGV[1]); } elsif ($ARGV[0] eq "stop") { stop($ARGV[1]); } elsif ($ARGV[0] eq "killvm") { killvm($ARGV[1]); } elsif ($ARGV[0] eq "suspend") { suspend($ARGV[1]); } elsif ($ARGV[0] eq "resume") { resume($ARGV[1]); } elsif ($ARGV[0] eq "status") { status($ARGV[1], 1); } elsif ($ARGV[0] eq "getpid") { pidofvm($ARGV[1]); } elsif ($ARGV[0] eq "getvminfo") { getvminfo($ARGV[1]); } elsif ($ARGV[0] eq "snapshot") { snapshot($ARGV[1]); } elsif ($ARGV[0] eq "commit") { commit($ARGV[1]); } elsif ($ARGV[0] eq "revert") { revert($ARGV[1]); } elsif ($ARGV[0] eq "createiso") { createiso($ARGV[1], $ARGV[2]); } elsif ($ARGV[0] eq "createconfig") { createconfig($ARGV[1]); } else { printerror "Unknown command \"$ARGV[0]\". See $progname --help."; }