"/sbin/powerOffVms" в ESX 4.1

в 8:59, , рубрики: bash, ESX, ESXi, виртуализация, метки: , ,

Руководство в конторе, где я работаю, поставило задачу отработать автоматическое выключение серверов, когда бесперебойник переходит на работу от аккумуляторов. Часть серверов бегает на 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, а там баша нету (пришлось добавить). Все это можно описать в следующей статье, если вам, конечно, интересно.

Автор: ol_x

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js