Достаточно часто на форумах посвященных виртуализации можно встретить вопросы по организации выключения виртуалок и хостов VMware ESX по сигналу от источника бесперебойного питания. В этой статье я расскажу о том, как я это реализовал у нас в офисе.
Для своего решения я использовал обычный комп с установленной Microsoft Windows 2003 Server, на котором у меня развернут VMware VirtualCenter Server. Там же установлен клиент ИБП и VMware VI Remote CLI.
Алгоритм решения следующий:
- От ИПБ поступает сигнал
- Клиент ИПБ обрабатывает этот сигнал и вызывает скрипт (листинг ниже).
- Скрипт обходит все подключенные хосты VMware ESXi, запоминает их MAC-адреса в файл, и переводит все запущенные виртуальные машины в состояние Suspend и выключает хосты.
- Далее выключается наш управляющий сервер.
#!/usr/bin/perl -w # # Copyright (c) 2009 Igor Nikolaev use strict; use warnings; use FindBin; use lib "$FindBin::Bin/../"; use VMware::VIRuntime; use AppUtil::HostUtil; my $url = "<a href="https://vcs/sdk/webService">https://vcs/sdk/webService</a>"; my $username = "username"; my $password = "password"; Vim::login(service_url => $url, user_name => $username, password => $password); shutdown_hosts(); Vim::logout(); # This subroutine is used to suspend the virtual machines, it is called from # enter_maintenance, reboot, shutdown_host subroutines sub suspend_vm { my $target_host = shift; my $vm_views = Vim::get_views(mo_ref_array => $target_host->vm); foreach (@$vm_views) { if ($_->runtime->powerState->val eq 'poweredOn') { Util::trace(0, "\nSuspending virtual machine: '" . $_->name . "'..."); eval { $_->SuspendVM(); }; if ($@) { if (ref($@) eq 'SoapFault') { if (ref(<a href="mailto:$@->detail">$@->detail</a>) eq 'InvalidPowerState') { Util::trace(0,"\nVM should be powered on"); } elsif (ref(<a href="mailto:$@->detail">$@->detail</a>) eq 'NotSupported') { Util::trace(0,"\nVirtual machine is marked as a template."); } else { Util::trace(0,"\nVM cannot be suspended \n" . $@. ""); } } else { Util::trace(0,"\nVM cannot be suspended \n" . $@. ""); } } else { Util::trace(0, "\n\nVirtual machine '" . $_->name . "' Suspended successfully\n"); } } } } # This is used for shutting down the host. All the powered On VM's # are first suspended if the suspend flag is set to '1' else if suspend flag # is set to '0' then the operation will not be performed if any of the virtual # machine is powered On. sub shutdown_host { my $target_host = shift; my $suspend_result = suspend_vm($target_host); if ($@) { return; } if ($suspend_result == 1) { return; } eval { $target_host->ShutdownHost(force => 1); }; if ($@) { if (ref($@) eq 'SoapFault') { if (ref(<a href="mailto:$@->detail">$@->detail</a>) eq 'InvalidState') { Util::trace(0,"\nThe operation is not allowed in the current state"); } else { Util::trace(0,"\nCan't shutdown host\n" . $@ ."" ); } } } else { Util::trace(0, "\nShutdown of host '" . $target_host->name . "' done successfully\n"); } } # This subroutine is used to shutdown all the hosts. It also saves MAC-addresses of the hosts in a file. # This file is used to startup hosts with wake-on-lan sub shutdown_hosts { my %filter = (); my $host_views = HostUtils::get_hosts('HostSystem', undef, undef, %filter); if ($host_views) { open (FILE, '>vmstartup.txt'); foreach (@$host_views) { my $name = $_->name; my $mac = $_->config->network->vnic->[0]->spec->mac; print FILE "# Host $name\n"; print FILE "$mac\n\n"; shutdown_host($_); } close (FILE); } }
Алгоритм автоматического влючения хостов следующий:
- От ИПБ поступает сигнал и включает управляющий сервер
- После загрузги сервер запускает скрипт (листинг ниже)
- Скрипт считывает файл с MAC-адресами хостов, каторые били записаны при их выключении.
- Скрипт пробегает по списку MAC-адресов и посылает каждому хосту Wake-On-Lan пакет.
#!/usr/bin/perl -w # # Copyright (c) 2009 Igor Nikolaev # # This is a modification of perl program written by <a href="mailto:ken.yap@acm.org">ken.yap@acm.org</a> # # Perl version by <a href="mailto:ken.yap@acm.org">ken.yap@acm.org</a> after DOS/Windows C version posted by # <a href="mailto:Steve_Marfisi@3com.com">Steve_Marfisi@3com.com</a> on the Netboot mailing list # Released under GNU Public License, 2000-01-05 # use Getopt::Std; use Socket; open(F, "vmstartup.txt"); while (<F>) { next if /^\s*#/; # skip comments ($mac, $ip) = split; next if !defined($mac) or $mac eq ''; if (!defined($ip) or $ip eq '') { &send_broadcast_packet($mac); } else { &send_wakeup_packet($mac, $ip); } } close(F); sub send_broadcast_packet { ($mac) = @_; if ($mac !~ /[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}/i) { print "Malformed MAC address $mac\n"; return; } print "Sending wakeup packet to MAC address $mac\n"; # Remove colons $mac =~ tr/://d; # Magic packet is 6 bytes of FF followed by the MAC address 16 times $magic = ("\xff" x 6) . (pack('H12', $mac) x 16); # Create socket socket(S, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or die "socket: $!\n"; # Enable broadcast setsockopt(S, SOL_SOCKET, SO_BROADCAST, 1) or die "setsockopt: $!\n"; # Send the wakeup packet defined(send(S, $magic, 0, sockaddr_in(0x2fff, INADDR_BROADCAST))) or print "send: $!\n"; close(S); } sub send_wakeup_packet { ($mac, $ip) = @_; if (!defined($iaddr = inet_aton($ip))) { print "Cannot resolve $ip\n"; return; } if ($mac !~ /[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}:[\da-f]{2}/i) { print "Malformed MAC address $mac\n"; return; } # Inject entry into ARP table, in case it's not there already system("arp -s $ip $mac") == 0 or print "Warning: arp command failed, you need to be root\n"; print "Sending wakeup packet to $ip at MAC address $mac\n"; # Remove colons $mac =~ tr/://d; # Magic packet is 6 bytes of FF followed by the MAC address 16 times $magic = ("\xff" x 6) . (pack('H12', $mac) x 16); # Create socket socket(S, PF_INET, SOCK_DGRAM, getprotobyname('udp')) or die "socket: $!\n"; # Send the wakeup packet defined(send(S, $magic, 0, sockaddr_in(0x2fff, $iaddr))) or print "send: $!\n"; close(S); }
Вот, собственно и все. Всем удачи. Оставляйте свои комментарии если будут какие-нибудь вопросы, пожелания, дополнения.