"/sbin/powerOffVms" в ESX 4.1

от автора

Руководство в конторе, где я работаю, поставило задачу отработать автоматическое выключение серверов, когда бесперебойник переходит на работу от аккумуляторов. Часть серверов бегает на windows (о них я даже не думал) и часть на esx/esxi, которые беспокоили меня больше всего, ибо опыта работы с никсами у меня очень и очень мало, особенно написания всяческих скриптов. Но задача поставлена и надо ее решать.
Начал потихоньку изучать этот вопрос, и я прям обрадовался, когда вышел на бинарник в esxi 5.x powerOffVms, который завершает работу гостевых систем при включенной у них соответствующий опции. Но энтузиазма поубавилось, когда такой штуки не обнаружилось в esx версии. В общем, было принято решение реализовать эту фичу на bash в esx (как раз понять, чем он и для чего дышит).

Все, что пойдет сейчас ниже, может быть реализовано различными способами, которые, возможно, будут правильнее, но кидать задуманное уже не хотелось.

Первое, что я сделал, это выключение конкретной виртуальной машины. Для этого нужно было узнать ее состояние, и какими же командами esx это делается. Возвращаясь к мануалам, имеем нужное…

vim-cmd vmsvc/power.getstate состояние машины
vim-cmd vmsvc/power.shutdown завершение работы гостевой ОС
vim-cmd vmsvc/power.off выключение питания гостевой ОС

В дальнейшем хотелось наглядности, а если быть точнее, то в ходе выполнения скрипта вывод имени машины, которую можно было выдрать из

vim-cmd vmsvc/get.summary

Зная id виртуальной машины, получаем ее имя для наглядности и пытаемся завершить работу гостевой ОС. Но чтобы корректно завершить работу ОС, необходимы установленные vmware tools и тычка «guest shutdown». А еще все это дело проверить, выключилась ли машина или подвисла и сколько времени проверять процесс завершения работы. Очередной раз курим мануалы и находим такую штуку, как stopDelay, который можно выставить через vsphere client, а по умолчанию он равен 120 секундам. Его можно выдрать вот отсюда:

vim-cmd hostsvc/hostconfig

Но и тут оказался нюанс: если настройка порядка загрузки не производилась, то и не будет там значения этой задержки.
Итак, что мы имеем…

Получаем имя машины

# get vm name via VMID # $1 - VMID function GetVMName () {                vmName=$(vim-cmd vmsvc/get.summary $1 | grep "name" | sed 's/.*"\(.*\)"[^"]*$/\1/') } 

Получаем массив значений задержек выключения. Обращаю еще раз внимание, что массив может быть не полон, если настройка порядка включения не производилась. Далее будет «костыль», как решить эту проблему.

# get stop delay options of vms function GetStopDelay () {         OUT=$(vim-cmd hostsvc/hostconfig | grep "stopDelay" | sed 's/[^-0-9]//g')         stopDelay=( $OUT ) } 

Завершение работы виртуальной машины. Функция получает id машины и значение задержки, через сколько времени будет выключено питание, если процесс завершения работы подвис.

# vm shutdown # passing parameters to the function # echo "VMShutDown $1 $2" # $1 - VMId, $2 - stopDelay function VMShutDown () {           GetVMName $1           stopTime=0           STATE=$(vim-cmd vmsvc/power.getstate $1 | grep "Power")         if [ "$STATE" = "Powered off" ]         then                 echo "VM $1 ($vmName) is stopped. "                 return 1         fi                  echo "Call VM $1 ($vmName) shutdown..."         vim-cmd vmsvc/power.shutdown $1           sleep 5           if [ "$stopTime" -eq 0]         then                 echo "Waiting for VM $1 ($vmName) shutdown..."         fi           while [ "$STATE" != "Powered off" ]         do                 if [ "$stopTime" -ge "$2" ]                 then                         echo "Shutdown of VM $1 ($vmName) causes to fail. Call power off!"                         vim-cmd vmsvc/power.off $1                         return 2                 fi                       STATE=$(vim-cmd vmsvc/power.getstate $1 | grep "Power")                 stopTime=$(($stopTime+5))                 sleep 5         done           echo "VM $VM ($vmName) shutdown is successfully"         return 3 } 

Так как в планах было хоть какая, но универсальность скрипта, выключение конкретной машины оформилось в этой функции…

# specific VM Shutdown # $1 - VMId function SpecificVMShutDown () {         GetVMName $1               GetBootOrder           element=1                  for VM in ${bootOrder[@]}         do                 if [ "$VM" -eq "$1" ]                 then                         GetVmStopDelay "${stopDelay[0]}" "${stopDelay["$element"]}"                         VMShutDown $VM $currentDelay                         return 1                         fi                               element=$(($element+1))         done           echo "VMId $1 is not found!!!" }  

Что я тут делаю, получаю порядок включения виртуальных машин в функции GetBootOrder, возвращающая массив значений, индексы которых полностью соответствуют индексам массива из GetStopDelay. И они оба могут быть не полными (причину я указал выше). Тут я иду на маленькую хитрость, иначе говоря костыль. Добавляю в массивы данные по дефолту для тех машин, которых нет в массиве. Чтобы понять, какие же машины не настраивались вообще, надо было сначала получить весь их список с помощью:

vim-cmd vmsvc/getallvms

# Get full list of VMs ID function GetAllVMs () {         OUT=$(vim-cmd vmsvc/getallvms |grep -o '^[0-9]*')         allVMs=( $OUT ) } 

И добавление отсутствующих значений в массивы, если такие имеются…

# Find missed VMs in boot order # Add missed VM to boot order array function FindMissedVMs () {         GetAllVMs           for aVM in ${allVMs[@]}         do                 exists=0                 for oVM in ${bootOrder[@]}                 do                         if [ "$aVM" -eq "$oVM" ]                         then                                 exists=1                                 break                         fi                 done                                  if [ "$exists" -eq 0 ]                 then                         bootOrder=( "${bootOrder[@]}" "$aVM" )                         stopDelay=( "${stopDelay[@]}" "-1" )                 fi         done } 

Ну и сама функция получения порядка загрузки виртуальных машин…

# get boot order of vms function GetBootOrder () {         OUT=$(vim-cmd hostsvc/hostconfig | grep "key = 'vim.VirtualMachine:" | sed 's/[^-0-9]//g')         bootOrder=( $OUT )                  GetStopDelay         FindMissedVMs }  

Снова вернемся к выключению виртуальной машины, а точнее к функции GetVmStopDelay, которая определяет, какую же задержку будем использовать. Тут все просто.

# use default or optional delay # $1 - default delay, $2 optional delay function GetVmStopDelay () {         currentDelay="$1"         if [ "$2" -gt 0 ]         then                 currentDelay="$2"                fi } 

На выходе получаем выключение конкретной виртуальной машины и проверка всего этого дела, в случае подвисания выключается питание (на мой практике такого еще не было).

Далее я поставил себе задачу выключить машины согласно порядка загрузки. Тут сильно уже думать не надо было. Получаем массив со значениями очереди, пробегаем по каждому и выключаем машинку выше указанным способом.

# order shutdown all VMS function OrderShutDown () {         echo "Call order shutdown all VMs"           GetBootOrder           element=1           for VM in ${bootOrder[@]}         do                 #echo "${stopDelay["$element"]}"                 GetVMName $VM                 echo "Beginning shutdown process: $vmName (VMID: $VM)..."                   GetVmStopDelay "${stopDelay[0]}" "${stopDelay["$element"]}"                          VMShutDown $VM $currentDelay                 element=$(($element+1))         done           echo "Order shutdown has been executed" } 

Вот тут надо обратить внимание, если машин много, то процесс выключения может занять некоторое время. А время при переходе на аккумулятор – наше все. Поэтому было решено сделать функцию, которая вызовет выключение всех машин, когда порядок не важен.

# verbose shutdown all VMS function VerboseShutDown () {         echo "Call verbose shutdown all VMs"                  GetBootOrder         for VM in ${bootOrder[@]}         do                 STATE=$(vim-cmd vmsvc/power.getstate $VM | grep "Power")                 if [ "$STATE" != "Powered off" ]                 then                         GetVMName $VM                         echo "Call VM $VM ($vmName) shutdown"                         vim-cmd vmsvc/power.shutdown $VM                 fi         done         ControlVerboseShutDown }  

В отличии от предыдущей функции вызывается выключение машины без проверки, прямой командой «vim-cmd vmsvc/power.shutdown». Но как быть, если завершение работы какой-то из машин подвисло. Надо проверить результат работы… Тут реализуется еще одна функция ControlVerboseShutDown, которая перепроверит состояние машин через заданный промежуток времени, т.е. для каждой виртуальной машины согласно своему stopDelay. Изобретать уже ничего не надо, все написано – все изучено.

# control process off verbose shutdown function ControlVerboseShutDown () {         echo "Checking verbose shutdown all VMs"           executed=0         stopTime=0           while [ "$executed" -eq 0 ]         do                 errorCount=0                 element=1                   for VM in ${bootOrder[@]}                 do                         GetVMName $VM                                  STATE=$(vim-cmd vmsvc/power.getstate $VM | grep "Power")                         if [ "$STATE" = "Powered off" ]                         then                                     #echo "VM $VM ($vmName) is powered off. Checking next"                                 element=$(($element+1))                                 continue                         fi                           if [ "$stopTime" -eq 0]                         then                                 echo "Waiting for VM $1 ($vmName) shutdown..."                         fi                                                  GetVmStopDelay "${stopDelay[0]}" "${stopDelay["$element"]}"                                                  if [ "$stopTime" -ge "$currentDelay" ]                         then                                 echo "Shutdown of VM $VM ($vmName) causes to fail. Call power Off!"                                 vim-cmd vmsvc/power.off $VM                         fi                           errorCount=$(($errorCount+1))                         element=$(($element+1))                 done                                  if [ "$errorCount" -eq 0 ]                 then                         echo "Verbose shutdown has been executed"                         executed=1                         return 1                         fi                                  stopTime=$(($stopTime+10))                 sleep 10                 echo "Remaining time: $stopTime"         done } 

Ну и немного универсальности, чтобы вызывать сам скрипт с параметрами…

while getopts ":os:v" optname do         case "$optname" in         "o")                 OrderShutDown                 exit 1         ;;         "s")                 VMID=$OPTARG                 SpecificVMShutDown "$VMID"                 exit 2         ;;         "v")                 VerboseShutDown                 exit 3         ;;         esac done 

Вот и получился почти полный аналог powerOffVms esxi 5.1. В процессе написания изучились, так сказать, основы bash, утилиты grep и sed и немножко регулярных выражений.
Эту задачу, конечно, можно решить другими способами:

PowerShell scripts for PowerCLI
vMA with bash scripting
pyshpere api
VIX API

Но на этом я не остановился и запустил все это дело на esxi 5.1, а там баша нету (пришлось добавить). Все это можно описать в следующей статье, если вам, конечно, интересно.

ссылка на оригинал статьи http://habrahabr.ru/post/188726/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *