| 1 | #!/usr/bin/perl -w
|
|---|
| 2 |
|
|---|
| 3 | # A perl object to provide a simple, unified method of handling some
|
|---|
| 4 | # VMware Server VM management functions using the perl and VIX API's.
|
|---|
| 5 | # Copyright Brad Henry <brad@samba.org> 2006
|
|---|
| 6 | # Released under the GNU GPL version 3 or later.
|
|---|
| 7 |
|
|---|
| 8 | # VMware Perl API
|
|---|
| 9 | use VMware::VmPerl;
|
|---|
| 10 | use VMware::VmPerl::VM;
|
|---|
| 11 | use VMware::VmPerl::ConnectParams;
|
|---|
| 12 |
|
|---|
| 13 | # VMware C bindings
|
|---|
| 14 | use VMware::Vix::Simple;
|
|---|
| 15 | use VMware::Vix::API::Constants;
|
|---|
| 16 |
|
|---|
| 17 | # Create a class to abstract from the Vix and VMPerl APIs.
|
|---|
| 18 | { package VMHost;
|
|---|
| 19 | my $perl_vm = VMware::VmPerl::VM::new();
|
|---|
| 20 | my $perl_vm_credentials;
|
|---|
| 21 | my $vix_vm;
|
|---|
| 22 | my $vix_vm_host;
|
|---|
| 23 |
|
|---|
| 24 | my $err_code = 0;
|
|---|
| 25 | my $err_str = "";
|
|---|
| 26 |
|
|---|
| 27 | my $hostname;
|
|---|
| 28 | my $port;
|
|---|
| 29 | my $username;
|
|---|
| 30 | my $password;
|
|---|
| 31 | my $vm_cfg_path;
|
|---|
| 32 | my $guest_admin_username;
|
|---|
| 33 | my $guest_admin_password;
|
|---|
| 34 |
|
|---|
| 35 | sub error {
|
|---|
| 36 | my $old_err_code = $err_code;
|
|---|
| 37 | my $old_err_str = $err_str;
|
|---|
| 38 | $err_code = 0;
|
|---|
| 39 | $err_str = "";
|
|---|
| 40 | return ($old_err_code, $old_err_str);
|
|---|
| 41 | }
|
|---|
| 42 |
|
|---|
| 43 | # Power on the guest if it isn't already running.
|
|---|
| 44 | # Returns 0 when the guest is already running, and
|
|---|
| 45 | # if not, it waits until it is started.
|
|---|
| 46 | sub start_guest {
|
|---|
| 47 | my $vm_power_state = $perl_vm->get_execution_state();
|
|---|
| 48 | if (!defined($vm_power_state)) {
|
|---|
| 49 | ($err_code, $err_str) = $perl_vm->get_last_error();
|
|---|
| 50 | return ($err_code);
|
|---|
| 51 | }
|
|---|
| 52 | if ($vm_power_state == VMware::VmPerl::VM_EXECUTION_STATE_OFF
|
|---|
| 53 | || $vm_power_state ==
|
|---|
| 54 | VMware::VmPerl::VM_EXECUTION_STATE_SUSPENDED)
|
|---|
| 55 | {
|
|---|
| 56 | if (!$perl_vm->start()) {
|
|---|
| 57 | ($err_code, $err_str) =
|
|---|
| 58 | $perl_vm->get_last_error();
|
|---|
| 59 | return ($err_code);
|
|---|
| 60 | }
|
|---|
| 61 | while ($perl_vm->get_tools_last_active() == 0) {
|
|---|
| 62 | sleep(60);
|
|---|
| 63 | }
|
|---|
| 64 | }
|
|---|
| 65 | return ($err_code);
|
|---|
| 66 | }
|
|---|
| 67 |
|
|---|
| 68 | sub host_connect {
|
|---|
| 69 | # When called as a method, the first parameter passed is the
|
|---|
| 70 | # name of the method. Called locally, this function will lose
|
|---|
| 71 | # the first parameter.
|
|---|
| 72 | shift @_;
|
|---|
| 73 | ($hostname, $port, $username, $password, $vm_cfg_path,
|
|---|
| 74 | $guest_admin_username, $guest_admin_password) = @_;
|
|---|
| 75 |
|
|---|
| 76 | # Connect to host using vmperl api.
|
|---|
| 77 | $perl_vm_credentials =
|
|---|
| 78 | VMware::VmPerl::ConnectParams::new($hostname, $port,
|
|---|
| 79 | $username, $password);
|
|---|
| 80 | if (!$perl_vm->connect($perl_vm_credentials, $vm_cfg_path)) {
|
|---|
| 81 | ($err_code, $err_str) = $perl_vm->get_last_error();
|
|---|
| 82 | undef $perl_vm;
|
|---|
| 83 | return ($err_code);
|
|---|
| 84 | }
|
|---|
| 85 |
|
|---|
| 86 | # Connect to host using vix api.
|
|---|
| 87 | ($err_code, $vix_vm_host) =
|
|---|
| 88 | VMware::Vix::Simple::HostConnect(
|
|---|
| 89 | VMware::Vix::Simple::VIX_API_VERSION,
|
|---|
| 90 | VMware::Vix::Simple::VIX_SERVICEPROVIDER_VMWARE_SERVER,
|
|---|
| 91 | $hostname, $port, $username, $password,
|
|---|
| 92 | 0, VMware::Vix::Simple::VIX_INVALID_HANDLE);
|
|---|
| 93 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 94 | $err_str =
|
|---|
| 95 | VMware::Vix::Simple::GetErrorText($err_code);
|
|---|
| 96 | undef $perl_vm;
|
|---|
| 97 | undef $vix_vm;
|
|---|
| 98 | undef $vix_vm_host;
|
|---|
| 99 | return ($err_code);
|
|---|
| 100 | }
|
|---|
| 101 |
|
|---|
| 102 | # Power on our guest os if it isn't already running.
|
|---|
| 103 | $err_code = start_guest();
|
|---|
| 104 | if ($err_code != 0) {
|
|---|
| 105 | my $old_err_str = $err_str;
|
|---|
| 106 | $err_str = "Starting guest power after connect " .
|
|---|
| 107 | "failed: " . $old_err_str;
|
|---|
| 108 | undef $perl_vm;
|
|---|
| 109 | undef $vix_vm;
|
|---|
| 110 | undef $vix_vm_host;
|
|---|
| 111 | return ($err_code);
|
|---|
| 112 | }
|
|---|
| 113 |
|
|---|
| 114 | # Open VM.
|
|---|
| 115 | ($err_code, $vix_vm) =
|
|---|
| 116 | VMware::Vix::Simple::VMOpen($vix_vm_host, $vm_cfg_path);
|
|---|
| 117 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 118 | $err_str =
|
|---|
| 119 | VMware::Vix::Simple::GetErrorText($err_code);
|
|---|
| 120 | undef $perl_vm;
|
|---|
| 121 | undef $vix_vm;
|
|---|
| 122 | undef $vix_vm_host;
|
|---|
| 123 | return ($err_code);
|
|---|
| 124 | }
|
|---|
| 125 |
|
|---|
| 126 | # Login to $vix_vm guest OS.
|
|---|
| 127 | $err_code = VMware::Vix::Simple::VMLoginInGuest($vix_vm,
|
|---|
| 128 | $guest_admin_username, $guest_admin_password,
|
|---|
| 129 | 0);
|
|---|
| 130 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 131 | $err_str =
|
|---|
| 132 | VMware::Vix::Simple::GetErrorText($err_code);
|
|---|
| 133 | undef $perl_vm;
|
|---|
| 134 | undef $vix_vm;
|
|---|
| 135 | undef $vix_vm_host;
|
|---|
| 136 | return ($err_code);
|
|---|
| 137 | }
|
|---|
| 138 | return ($err_code);
|
|---|
| 139 | }
|
|---|
| 140 |
|
|---|
| 141 | sub host_disconnect {
|
|---|
| 142 | undef $perl_vm;
|
|---|
| 143 |
|
|---|
| 144 | $perl_vm = VMware::VmPerl::VM::new();
|
|---|
| 145 | if (!$perl_vm) {
|
|---|
| 146 | $err_code = 1;
|
|---|
| 147 | $err_str = "Error creating new VmPerl object";
|
|---|
| 148 | }
|
|---|
| 149 |
|
|---|
| 150 | undef $vix_vm;
|
|---|
| 151 | VMware::Vix::Simple::HostDisconnect($vix_vm_host);
|
|---|
| 152 | VMware::Vix::Simple::ReleaseHandle($vix_vm_host);
|
|---|
| 153 | return ($err_code);
|
|---|
| 154 | }
|
|---|
| 155 |
|
|---|
| 156 | sub host_reconnect {
|
|---|
| 157 | $err_code = host_disconnect();
|
|---|
| 158 | if ($err_code != 0) {
|
|---|
| 159 | my $old_err_str = $err_str;
|
|---|
| 160 | $err_str = "Disconnecting from host failed: " .
|
|---|
| 161 | $old_err_str;
|
|---|
| 162 | return ($err_code);
|
|---|
| 163 | }
|
|---|
| 164 |
|
|---|
| 165 | $err_code = host_connect(NULL, $hostname, $port, $username,
|
|---|
| 166 | $password, $vm_cfg_path, $guest_admin_username,
|
|---|
| 167 | $guest_admin_password);
|
|---|
| 168 | if ($err_code != 0) {
|
|---|
| 169 | my $old_err_str = $err_str;
|
|---|
| 170 | $err_str = "Re-connecting to host failed: " .
|
|---|
| 171 | $old_err_str;
|
|---|
| 172 | return ($err_code);
|
|---|
| 173 | }
|
|---|
| 174 | return ($err_code);
|
|---|
| 175 | }
|
|---|
| 176 |
|
|---|
| 177 | sub create_snapshot {
|
|---|
| 178 | my $snapshot;
|
|---|
| 179 |
|
|---|
| 180 | ($err_code, $snapshot) =
|
|---|
| 181 | VMware::Vix::Simple::VMCreateSnapshot($vix_vm,
|
|---|
| 182 | "Snapshot", "Created by vm_setup.pl", 0,
|
|---|
| 183 | VMware::Vix::Simple::VIX_INVALID_HANDLE);
|
|---|
| 184 |
|
|---|
| 185 | VMware::Vix::Simple::ReleaseHandle($snapshot);
|
|---|
| 186 |
|
|---|
| 187 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 188 | $err_str =
|
|---|
| 189 | VMware::Vix::Simple::GetErrorText($err_code);
|
|---|
| 190 | return $err_code;
|
|---|
| 191 | }
|
|---|
| 192 |
|
|---|
| 193 | $err_code = host_reconnect();
|
|---|
| 194 | if ($err_code != 0) {
|
|---|
| 195 | my $old_err_str = $err_str;
|
|---|
| 196 | $err_str = "Reconnecting to host after creating " .
|
|---|
| 197 | "snapshot: " . $old_err_str;
|
|---|
| 198 | return ($err_code);
|
|---|
| 199 | }
|
|---|
| 200 | return ($err_code);
|
|---|
| 201 | }
|
|---|
| 202 |
|
|---|
| 203 | sub revert_snapshot {
|
|---|
| 204 | # Because of problems with VMRevertToSnapshot(), we have to
|
|---|
| 205 | # rely on the guest having set 'Revert to Snapshot' following
|
|---|
| 206 | # a power-off event.
|
|---|
| 207 | $err_code = VMware::Vix::Simple::VMPowerOff($vix_vm, 0);
|
|---|
| 208 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 209 | $err_str =
|
|---|
| 210 | VMware::Vix::Simple::GetErrorText($err_code);
|
|---|
| 211 | return $err_code;
|
|---|
| 212 | }
|
|---|
| 213 |
|
|---|
| 214 | # host_reconnect() will power-on a guest in a non-running state.
|
|---|
| 215 | $err_code = host_reconnect();
|
|---|
| 216 | if ($err_code != 0) {
|
|---|
| 217 | my $old_err_str = $err_str;
|
|---|
| 218 | $err_str = "Reconnecting to host after reverting " .
|
|---|
| 219 | "snapshot: " . $old_err_str;
|
|---|
| 220 | return ($err_code);
|
|---|
| 221 | }
|
|---|
| 222 | return ($err_code);
|
|---|
| 223 | }
|
|---|
| 224 |
|
|---|
| 225 | # $dest_path must exist. It doesn't get created.
|
|---|
| 226 | sub copy_files_to_guest {
|
|---|
| 227 | shift @_;
|
|---|
| 228 | my (%files) = @_;
|
|---|
| 229 |
|
|---|
| 230 | my $src_file;
|
|---|
| 231 | my $dest_file;
|
|---|
| 232 |
|
|---|
| 233 | foreach $src_file (keys(%files)) {
|
|---|
| 234 | $dest_file = $files{$src_file};
|
|---|
| 235 | $err_code =
|
|---|
| 236 | VMware::Vix::Simple::VMCopyFileFromHostToGuest(
|
|---|
| 237 | $vix_vm, $src_file, $dest_file, 0,
|
|---|
| 238 | VMware::Vix::Simple::VIX_INVALID_HANDLE);
|
|---|
| 239 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 240 | $err_str = "Copying $src_file: " .
|
|---|
| 241 | VMware::Vix::Simple::GetErrorText(
|
|---|
| 242 | $err_code);
|
|---|
| 243 | return $err_code;
|
|---|
| 244 | }
|
|---|
| 245 | }
|
|---|
| 246 | return $err_code;
|
|---|
| 247 | }
|
|---|
| 248 |
|
|---|
| 249 | sub copy_to_guest {
|
|---|
| 250 | # Read parameters $src_path, $dest_path.
|
|---|
| 251 | shift @_;
|
|---|
| 252 | my ($src_path, $dest_dir) = @_;
|
|---|
| 253 |
|
|---|
| 254 | my $len = length($dest_dir);
|
|---|
| 255 | my $idx = rindex($dest_dir, '\\');
|
|---|
| 256 | if ($idx != ($len - 1)) {
|
|---|
| 257 | $err_code = -1;
|
|---|
| 258 | $err_str = "Destination $dest_dir must be a " .
|
|---|
| 259 | "directory path";
|
|---|
| 260 | return ($err_code);
|
|---|
| 261 | }
|
|---|
| 262 |
|
|---|
| 263 | # Create the directory $dest_path on the guest VM filesystem.
|
|---|
| 264 | my $cmd = "cmd.exe ";
|
|---|
| 265 | my $cmd_args = "/C MKDIR " . $dest_dir;
|
|---|
| 266 | $err_code = run_on_guest(NULL, $cmd, $cmd_args);
|
|---|
| 267 | if ( $err_code != 0) {
|
|---|
| 268 | my $old_err_str = $err_str;
|
|---|
| 269 | $err_str = "Creating directory $dest_dir on host: " .
|
|---|
| 270 | $old_err_str;
|
|---|
| 271 | return ($err_code);
|
|---|
| 272 | }
|
|---|
| 273 |
|
|---|
| 274 | # If $src_filepath specifies a file, create it in $dest_path
|
|---|
| 275 | # and keep the same name.
|
|---|
| 276 | # If $src_path is a directory, create the files it contains in
|
|---|
| 277 | # $dest_path, keeping the same names.
|
|---|
| 278 | $len = length($src_path);
|
|---|
| 279 | my %files;
|
|---|
| 280 | $idx = rindex($src_path, '/');
|
|---|
| 281 | if ($idx == ($len - 1)) {
|
|---|
| 282 | # $src_path is a directory.
|
|---|
| 283 | if (!opendir (DIR_HANDLE, $src_path)) {
|
|---|
| 284 | $err_code = -1;
|
|---|
| 285 | $err_str = "Error opening directory $src_path";
|
|---|
| 286 | return $err_code;
|
|---|
| 287 | }
|
|---|
| 288 |
|
|---|
| 289 | foreach $file (readdir DIR_HANDLE) {
|
|---|
| 290 | my $src_file = $src_path . $file;
|
|---|
| 291 |
|
|---|
| 292 | if (!opendir(DIR_HANDLE2, $src_file)) {
|
|---|
| 293 | # We aren't interested in subdirs.
|
|---|
| 294 | my $dest_path = $dest_dir . $file;
|
|---|
| 295 | $files{$src_file} = $dest_path;
|
|---|
| 296 | } else {
|
|---|
| 297 | closedir(DIR_HANDLE2);
|
|---|
| 298 | }
|
|---|
| 299 | }
|
|---|
| 300 | } else {
|
|---|
| 301 | # Strip if preceeding path from $src_path.
|
|---|
| 302 | my $src_file = substr($src_path, ($idx + 1), $len);
|
|---|
| 303 | my $dest_path = $dest_dir . $src_file;
|
|---|
| 304 |
|
|---|
| 305 | # Add $src_path => $dest_path to %files.
|
|---|
| 306 | $files{$src_path} = $dest_path;
|
|---|
| 307 | }
|
|---|
| 308 |
|
|---|
| 309 | $err_code = copy_files_to_guest(NULL, %files);
|
|---|
| 310 | if ($err_code != 0) {
|
|---|
| 311 | my $old_err_str = $err_str;
|
|---|
| 312 | $err_str = "Copying files to host after " .
|
|---|
| 313 | "populating %files: " . $old_err_str;
|
|---|
| 314 | return ($err_code);
|
|---|
| 315 | }
|
|---|
| 316 | return ($err_code);
|
|---|
| 317 | }
|
|---|
| 318 |
|
|---|
| 319 | sub run_on_guest {
|
|---|
| 320 | # Read parameters $cmd, $cmd_args.
|
|---|
| 321 | shift @_;
|
|---|
| 322 | my ($cmd, $cmd_args) = @_;
|
|---|
| 323 |
|
|---|
| 324 | $err_code = VMware::Vix::Simple::VMRunProgramInGuest($vix_vm,
|
|---|
| 325 | $cmd, $cmd_args, 0,
|
|---|
| 326 | VMware::Vix::Simple::VIX_INVALID_HANDLE);
|
|---|
| 327 | if ($err_code != VMware::Vix::Simple::VIX_OK) {
|
|---|
| 328 | $err_str = VMware::Vix::Simple::GetErrorText(
|
|---|
| 329 | $err_code);
|
|---|
| 330 | return ($err_code);
|
|---|
| 331 | }
|
|---|
| 332 |
|
|---|
| 333 | return ($err_code);
|
|---|
| 334 | }
|
|---|
| 335 |
|
|---|
| 336 | sub get_guest_ip {
|
|---|
| 337 | my $guest_ip = $perl_vm->get_guest_info('ip');
|
|---|
| 338 |
|
|---|
| 339 | if (!defined($guest_ip)) {
|
|---|
| 340 | ($err_code, $err_str) = $perl_vm->get_last_error();
|
|---|
| 341 | return NULL;
|
|---|
| 342 | }
|
|---|
| 343 |
|
|---|
| 344 | if (!($guest_ip)) {
|
|---|
| 345 | $err_code = 1;
|
|---|
| 346 | $err_str = "Guest did not set the 'ip' variable";
|
|---|
| 347 | return NULL;
|
|---|
| 348 | }
|
|---|
| 349 | return $guest_ip;
|
|---|
| 350 | }
|
|---|
| 351 |
|
|---|
| 352 | sub DESTROY {
|
|---|
| 353 | host_disconnect();
|
|---|
| 354 | undef $perl_vm;
|
|---|
| 355 | undef $vix_vm_host;
|
|---|
| 356 | }
|
|---|
| 357 | }
|
|---|
| 358 |
|
|---|
| 359 | return TRUE;
|
|---|