Kasdieninės ataskaitos apie virtualių mašinų būklę naudojant R ir PowerShell

Kasdieninės ataskaitos apie virtualių mašinų būklę naudojant R ir PowerShell

Įrašas

Laba diena. Jau pusę metų veikiame scenarijus (o tiksliau – scenarijų rinkinys), kuris generuoja ataskaitas apie virtualių mašinų (ir ne tik) būseną. Nusprendžiau pasidalinti savo kūrimo patirtimi ir pačiu kodu. Tikiuosi kritikos ir kad ši medžiaga kam nors bus naudinga.

Poreikio formavimas

Turime daug virtualių mašinų (apie 1500 VM, paskirstytų 3 vCenters). Gana dažnai kuriami nauji, o senieji ištrinami. Siekiant palaikyti tvarką, prie „vCenter“ buvo pridėti keli pasirinktiniai laukai, skirti VM padalyti į posistemes, nurodyti, ar jos yra bandomosios, kas ir kada jas sukūrė. Žmogiškasis veiksnys lėmė tai, kad daugiau nei pusė mašinų liko tuščiais laukais, o tai apsunkino darbą. Kartą per šešis mėnesius kažkas išsigando ir pradėjo atnaujinti šiuos duomenis, tačiau po pusantros savaitės rezultatas nustojo būti aktualus.
Iš karto patikslinu, kad visi supranta, kad mašinoms kurti turi būti taikomosios programos, jų kūrimo procesas ir pan. ir taip toliau. Ir tuo pačiu visi griežtai laikosi šio proceso ir viskas tvarkoje. Deja, čia taip nėra, bet tai ne straipsnio tema :)

Apskritai buvo priimtas sprendimas automatizuoti laukų pildymo teisingumo tikrinimą.
Nusprendėme, kad kasdienis laiškas su neteisingai užpildytų mašinų sąrašu visiems atsakingiems inžinieriams ir jų viršininkams būtų gera pradžia.

Šiuo metu vienas iš mano kolegų jau buvo įdiegęs scenarijų PowerShell, kuris kiekvieną dieną pagal grafiką rinko informaciją apie visus „vCenter“ įrenginius ir sugeneravo 3 csv dokumentus (kiekvienas savo vCenter), kurie buvo įkelti į bendras diskas. Buvo nuspręsta remtis šiuo scenarijumi ir papildyti jį čekiais naudojant R kalbą, su kuria turėjome šiek tiek patirties.

Galutinio užbaigimo procese sprendimas gavo informaciją paštu, duomenų bazę su pagrindine ir istorine lentele (apie tai plačiau vėliau), taip pat vSphere žurnalų analizę, siekiant rasti tikrus vm kūrėjus ir jų sukūrimo laiką.

Kuriant buvo naudojamos IDE RStudio Desktop ir PowerShell ISE.

Scenarijus paleidžiamas iš įprastos „Windows“ virtualios mašinos.

Bendrosios logikos aprašymas.

Bendra scenarijų logika yra tokia.

  • Duomenis virtualiose mašinose renkame naudodami PowerShell scenarijų, kurį iškviečiame per R, ir sujungiame rezultatą į vieną csv. Atvirkštinė kalbų sąveika atliekama panašiai. (buvo galima tiesiogiai perkelti duomenis iš R į PowerShell kintamųjų pavidalu, bet tai sunku, o turint tarpinį csv failą lengviau derinti ir su kuo nors dalytis tarpiniais rezultatais).
  • Naudodami R formuojame galiojančius parametrus laukams, kurių reikšmes tikriname. – Kuriame word dokumentą, kuriame bus šių laukelių reikšmės, kurios bus įterptos į informacinį laišką, kuris bus atsakymas į kolegų klausimus „Ne, bet kaip tai užpildyti?
  • Įkeliame visų VM duomenis iš csv naudodami R, sukuriame duomenų rėmelį, pašaliname nereikalingus laukus ir sukuriame informacinį xlsx dokumentą, kuriame bus suvestinė informacija apie visas VM, kurią įkeliame į bendrinamą šaltinį.
  • Visų VM duomenų rėmui taikome visus laukų užpildymo teisingumo patikrinimus ir sukuriame lentelę, kurioje yra tik VM su neteisingai užpildytais laukais (ir tik šiais laukais).
  • Gautą VM sąrašą siunčiame į kitą PowerShell scenarijų, kuris peržiūrės VM kūrimo įvykių vCenter žurnalus, kurie leis mums nurodyti numatomą VM sukūrimo laiką ir numatomą kūrėją. Tai tinka tuo atveju, kai niekas nepripažįsta, kieno tai automobilis. Šis scenarijus neveikia greitai, ypač jei yra daug žurnalų, todėl žiūrime tik paskutines 2 savaites, taip pat naudojame darbo eigą, leidžiančią vienu metu ieškoti informacijos keliose VM. Scenarijaus pavyzdyje pateikiami išsamūs šio mechanizmo komentarai. Rezultatą įtraukiame į csv, kurį vėl įkeliame į R.
  • Sugeneruojame gražiai suformatuotą xlsx dokumentą, kuriame neteisingai užpildyti laukai bus paryškinti raudonai, kai kuriuose stulpeliuose bus pritaikyti filtrai, nurodyti papildomi stulpeliai, kuriuose yra numatyti kūrėjai ir VM sukūrimo laikas.
  • Sugeneruojame el. laišką, kuriame pridedame dokumentą, kuriame aprašomos galiojančios laukų reikšmės, bei lentelę su neteisingai užpildytais laukais. Tekste nurodome bendrą neteisingai sukurtų VM skaičių, nuorodą į bendrinamą šaltinį ir motyvacinį paveikslėlį. Jei nėra neteisingai užpildytų VM, išsiunčiame kitą laišką su džiugesniu motyvaciniu paveiksliuku.
  • Visų VM duomenis įrašome į SQL serverio duomenų bazę, atsižvelgdami į įdiegtą istorinių lentelių mechanizmą (labai įdomus mechanizmas – apie tai vėliau)

Tiesą sakant, scenarijai

Pagrindinis R kodo failas

# Путь к рабочей директории (нужно для корректной работы через виндовый планировщик заданий)
setwd("C:ScriptsgetVm")
#### Подгружаем необходимые пакеты ####
library(tidyverse)
library(xlsx)
library(mailR)
library(rmarkdown)
##### Определяем пути к исходным файлам и другие переменные #####
source(file = "const.R", local = T, encoding = "utf-8")
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameVmCreationRules)) {file.remove(filenameVmCreationRules)}
#### Создаём вордовский документ с допустимыми полями
render("VM_name_rules.Rmd",
output_format = word_document(),
output_file = filenameVmCreationRules)
# Проверяем существование файла со всеми ВМ и удаляем, если есть
if (file.exists(allVmXlsxPath)) {file.remove(allVmXlsxPath)}
#### Забираем данные по всем машинам через PowerShell скрипт. На выходе получим csv.
system(paste0("powershell -File ", getVmPsPath))
# Полный df
fullXslx_df <- allVmXlsxPath %>% 
read.csv2(stringsAsFactors = FALSE)
# Проверяем корректность заполненных полей
full_df <- fullXslx_df %>%
mutate(
# Сначала убираем все лишние пробелы и табуляции, потом учитываем разделитель запятую, потом проверяем вхождение в допустимые значения,
isSubsystemCorrect = Subsystem %>% 
gsub("[[:space:]]", "", .) %>% 
str_split(., ",") %>% 
map(function(x) (all(x %in% AllowedValues$Subsystem))) %>%
as.logical(),
isOwnerCorrect = Owner %in% AllowedValues$Owner,
isCategoryCorrect = Category %in% AllowedValues$Category,
isCreatorCorrect = (!is.na(Creator) & Creator != ''),
isCreation.DateCorrect = map(Creation.Date, IsDate)
)
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameAll)) {file.remove(filenameAll)}
#### Формируем xslx файл с отчётом ####
# Общие данные на отдельный лист
full_df %>% write.xlsx(file=filenameAll,
sheetName=names[1],
col.names=TRUE,
row.names=FALSE,
append=FALSE)
#### Формируем xslx файл с неправильно заполненными полями ####
# Формируем df
incorrect_df <- full_df %>%
select(VM.Name, 
IP.s, 
Owner,
Subsystem,
Creator,
Category,
Creation.Date,
isOwnerCorrect, 
isSubsystemCorrect, 
isCategoryCorrect,
isCreatorCorrect,
vCenter.Name) %>%
filter(isSubsystemCorrect == F | 
isOwnerCorrect == F |
isCategoryCorrect == F |
isCreatorCorrect == F)
# Проверяем существование файла со всеми ВМ и удаляем, если есть.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}
# Сохраняем список VM с незаполненными полями в csv
incorrect_df %>%
select(VM.Name) %>%
write_csv2(path = filenameIncVM, append = FALSE)
# Фильтруем для вставки в почту
incorrect_df_filtered <- incorrect_df %>% 
select(VM.Name, 
IP.s, 
Owner, 
Subsystem, 
Category,
Creator,
vCenter.Name,
Creation.Date
)
# Считаем количество строк
numberOfRows <- nrow(incorrect_df)
#### Начало условия ####
# Дальше либо у нас есть неправильно заполненные поля, либо нет.
# Если есть - запускаем ещё один скрипт
if (numberOfRows > 0) {
# Проверяем существование файла с создателями и удаляем, если есть.
if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}
# Запускаем PowerShell скрипт, который найдёт создателей найденных VM. На выходе получим csv.
system(paste0("powershell -File ", getCreatorsPath))
# Читаем файл с создателями
creators_df <- creatorsFilePath %>%
read.csv2(stringsAsFactors = FALSE)
# Фильтруем для вставки в почту, добавляем данные из таблицы с создателями
incorrect_df_filtered <- incorrect_df_filtered %>% 
select(VM.Name, 
IP.s, 
Owner, 
Subsystem, 
Category,
Creator,
vCenter.Name,
Creation.Date
) %>% 
left_join(creators_df, by = "VM.Name") %>% 
rename(`Предполагаемый создатель` = CreatedBy, 
`Предполагаемая дата создания` = CreatedOn)  
# Формируем тело письма
emailBody <- paste0(
'<html>
<h3>Добрый день, уважаемые коллеги.</h3>
<p>Полную актуальную информацию по виртуальным машинам вы можете посмотреть на диске H: вот тут:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Также во вложении список ВМ с <strong>некорректно заполненными</strong> полями. Всего их <strong>', numberOfRows,'</strong>.</p>
<p>В таблице появилось 2 дополнительные колонки. <strong>Предполагаемый создатель</strong> и <strong>Предполагаемая дата создания</strong>, которые достаются из логов vCenter за последние 2 недели</p>
<p>Просьба создателей машин уточнить данные и заполнить поля корректно. Правила заполнения полей также во вложении</p>
<p><img src="data/meme.jpg"></p>
</html>'
)
# Проверяем существование файла
if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}
# Формируем красивую таблицу с форматами и т.д.
source(file = "email.R", local = T, encoding = "utf-8")
#### Формируем письмо с плохо подписанными машинами ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "ВМ с некорректно заполненными полями",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
attach.files = c(filenameIncorrect, filenameVmCreationRules),
debug = FALSE)
#### Дальше пойдёт блок, если нет проблем с ВМ ####
} else {
# Формируем тело письма
emailBody <- paste0(
'<html>
<h3>Добрый день, уважаемые коллеги</h3>
<p>Полную актуальную информацию по виртуальным машинам вы можете посмотреть на диске H: вот тут:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Также, на текущий момент, все поля ВМ корректно заполнены</p>
<p><img src="data/meme_correct.jpg"></p>
</html>'
)
#### Формируем письмо без плохо заполненных VM ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "Сводная информация",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
debug = FALSE)
}
####### Записываем данные в БД #####
source(file = "DB.R", local = T, encoding = "utf-8")

Scenarijus, skirtas gauti VM sąrašą PowerShell

# Данные для подключения и другие переменные
$vCenterNames = @(
"vcenter01", 
"vcenter02", 
"vcenter03"
)
$vCenterUsername = "myusername"
$vCenterPassword = "mypassword"
$filename = "C:ScriptsgetVmdataallvmall-vm-$(get-date -f yyyy-MM-dd).csv"
$destinationSMB = "server.rumyfolder$vm"
$IP0=""
$IP1=""
$IP2=""
$IP3=""
$IP4=""
$IP5=""
# Подключение ко всем vCenter, что содержатся в переменной. Будет работать, если логин и пароль одинаковые (например, доменные)
Connect-VIServer -Server $vCenterNames -User $vCenterUsername -Password $vCenterPassword
write-host ""
# Создаём функцию с циклом по всем vCenter-ам
function Get-VMinventory {
# В этой переменной будет списко всех ВМ, как объектов
$AllVM = Get-VM | Sort Name
$cnt = $AllVM.Count
$count = 1
# Начинаем цикл по всем ВМ и собираем необходимые параметры каждого объекта
foreach ($vm in $AllVM) {
$StartTime = $(get-date)
$IP0 = $vm.Guest.IPAddress[0]
$IP1 = $vm.Guest.IPAddress[1]
$IP2 = $vm.Guest.IPAddress[2]
$IP3 = $vm.Guest.IPAddress[3]
$IP4 = $vm.Guest.IPAddress[4]
$IP5 = $vm.Guest.IPAddress[5]
If ($IP0 -ne $null) {If ($IP0.Contains(":") -ne 0) {$IP0=""}}
If ($IP1 -ne $null) {If ($IP1.Contains(":") -ne 0) {$IP1=""}}
If ($IP2 -ne $null) {If ($IP2.Contains(":") -ne 0) {$IP2=""}}
If ($IP3 -ne $null) {If ($IP3.Contains(":") -ne 0) {$IP3=""}}
If ($IP4 -ne $null) {If ($IP4.Contains(":") -ne 0) {$IP4=""}}
If ($IP5 -ne $null) {If ($IP5.Contains(":") -ne 0) {$IP5=""}}
$cluster = $vm | Get-Cluster | Select-Object -ExpandProperty name  
$Bootime = $vm.ExtensionData.Runtime.BootTime
$TotalHDDs = $vm.ProvisionedSpaceGB -as [int]
$CreationDate = $vm.CustomFields.Item("CreationDate") -as [string]
$Creator = $vm.CustomFields.Item("Creator") -as [string]
$Category = $vm.CustomFields.Item("Category") -as [string]
$Owner = $vm.CustomFields.Item("Owner") -as [string]
$Subsystem = $vm.CustomFields.Item("Subsystem") -as [string]
$IPS = $vm.CustomFields.Item("IP") -as [string]
$vCPU = $vm.NumCpu
$CorePerSocket = $vm.ExtensionData.config.hardware.NumCoresPerSocket
$Sockets = $vCPU/$CorePerSocket
$Id = $vm.Id.Split('-')[2] -as [int]
# Собираем все параметры в один объект
$Vmresult = New-Object PSObject
$Vmresult | add-member -MemberType NoteProperty -Name "Id" -Value $Id   
$Vmresult | add-member -MemberType NoteProperty -Name "VM Name" -Value $vm.Name  
$Vmresult | add-member -MemberType NoteProperty -Name "Cluster" -Value $cluster  
$Vmresult | add-member -MemberType NoteProperty -Name "Esxi Host" -Value $VM.VMHost  
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 1" -Value $IP0
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 2" -Value $IP1
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 3" -Value $IP2
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 4" -Value $IP3
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 5" -Value $IP4
$Vmresult | add-member -MemberType NoteProperty -Name "IP Address 6" -Value $IP5
$Vmresult | add-member -MemberType NoteProperty -Name "vCPU" -Value $vCPU
$Vmresult | Add-Member -MemberType NoteProperty -Name "CPU Sockets" -Value $Sockets
$Vmresult | Add-Member -MemberType NoteProperty -Name "Core per Socket" -Value $CorePerSocket
$Vmresult | add-member -MemberType NoteProperty -Name "RAM (GB)" -Value $vm.MemoryGB
$Vmresult | add-member -MemberType NoteProperty -Name "Total-HDD (GB)" -Value $TotalHDDs
$Vmresult | add-member -MemberType NoteProperty -Name "Power State" -Value $vm.PowerState
$Vmresult | add-member -MemberType NoteProperty -Name "OS" -Value $VM.ExtensionData.summary.config.guestfullname  
$Vmresult | Add-Member -MemberType NoteProperty -Name "Boot Time" -Value $Bootime
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Status" -Value $vm.ExtensionData.Guest.ToolsStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Version" -Value $vm.ExtensionData.Guest.ToolsVersion  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Version Status" -Value $vm.ExtensionData.Guest.ToolsVersionStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "VMTools Running Status" -Value $vm.ExtensionData.Guest.ToolsRunningStatus  
$Vmresult | add-member -MemberType NoteProperty -Name "Creation Date" -Value $CreationDate
$Vmresult | add-member -MemberType NoteProperty -Name "Creator" -Value $Creator
$Vmresult | add-member -MemberType NoteProperty -Name "Category" -Value $Category
$Vmresult | add-member -MemberType NoteProperty -Name "Owner" -Value $Owner
$Vmresult | add-member -MemberType NoteProperty -Name "Subsystem" -Value $Subsystem
$Vmresult | add-member -MemberType NoteProperty -Name "IP's" -Value $IPS
$Vmresult | add-member -MemberType NoteProperty -Name "vCenter Name" -Value $vm.Uid.Split('@')[1].Split(':')[0]  
# Считаем общее и оставшееся время выполнения и выводим на экран результаты. Использовалось для тестирования, но по факту оказалось очень удобно.
$elapsedTime = $(get-date) - $StartTime
$totalTime = "{0:HH:mm:ss}" -f ([datetime]($elapsedTime.Ticks*($cnt - $count)))
clear-host
Write-Host "Processing" $count "from" $cnt 
Write-host "Progress:" ([math]::Round($count/$cnt*100, 2)) "%" 
Write-host "You have about " $totalTime "for cofee"
Write-host ""
$count++
# Выводим результат, чтобы цикл "знал" что является результатом выполнения одного прохода
$Vmresult
}
}
# Вызываем получившуюся функцию и сразу выгружаем результат в csv
$allVm = Get-VMinventory | Export-CSV -Path $filename -NoTypeInformation -UseCulture -Force
# Пытаемся выложить полученный файл в нужное нам место и, в случае ошибки, пишем лог.
try
{
Copy-Item $filename -Destination $destinationSMB -Force -ErrorAction SilentlyContinue
}
catch
{
$error | Export-CSV -Path $filename".error" -NoTypeInformation -UseCulture -Force
}

PowerShell scenarijus, kuris iš žurnalų ištraukia virtualių mašinų kūrėjus ir jų sukūrimo datą

# Путь к файлу, из которого будем доставать список VM
$VMfilePath = "C:ScriptsgetVmcreators_VMcreators_VM_$(get-date -f yyyy-MM-dd).csv"
# Путь к файлу, в который будем записывать результат
$filePath = "C:ScriptsgetVmdatacreatorscreators-$(get-date -f yyyy-MM-dd).csv"
# Создаём вокрфлоу
Workflow GetCreators-Wf
{
# Параметры, которые можно будет передать при вызове скрипта
param([string[]]$VMfilePath)
# Параметры, которые доступны только внутри workflow
$vCenterUsername = "myusername"
$vCenterPassword = "mypassword"
$daysToLook = 14
$start = (get-date).AddDays(-$daysToLook)
$finish = get-date
# Значения, которые будут вписаны в csv для машин, по которым не будет ничего найдено
$UnknownUser = "UNKNOWN"
$UnknownCreatedTime = "0000-00-00"
# Определяем параметры подключения и выводной файл, которые будут доступны во всём скрипте.
$vCenterNames = @(
"vcenter01", 
"vcenter02", 
"vcenter03"
)
# Получаем список VM из csv и загружаем соответствующие объекты
$list = Import-Csv $VMfilePath -UseCulture | select -ExpandProperty VM.Name
# Цикл, который будет выполняться параллельно (по 5 машин за раз)
foreach -parallel ($row in $list)
{
# Это скрипт, который видит только свои переменные и те, которые ему переданы через $Using
InlineScript {
# Время начала выполнения отдельного блока
$StartTime = $(get-date)
Write-Host ""
Write-Host "Processing $Using:row started at $StartTime"
Write-Host ""
# Подключение оборачиваем в переменную, чтобы информация о нём не мешалась в консоли
$con = Connect-VIServer -Server $Using:vCenterNames -User $Using:vCenterUsername -Password $Using:vCenterPassword
# Получаем объект vm
$vm = Get-VM -Name $Using:row
# Ниже 2 одинаковые команды. Одна с фильтром по времени, вторая - без. Можно пользоваться тем,
$Event = $vm | Get-VIEvent -Start $Using:start -Finish $Using:finish -Types Info | Where { $_.Gettype().Name -eq "VmBeingDeployedEvent" -or $_.Gettype().Name -eq "VmCreatedEvent" -or $_.Gettype().Name -eq "VmRegisteredEvent" -or $_.Gettype().Name -eq "VmClonedEvent"}
# $Event = $vm | Get-VIEvent -Types Info | Where { $_.Gettype().Name -eq "VmBeingDeployedEvent" -or $_.Gettype().Name -eq "VmCreatedEvent" -or $_.Gettype().Name -eq "VmRegisteredEvent" -or $_.Gettype().Name -eq "VmClonedEvent"}
# Заполняем параметры в зависимости от того, удалось ли в логах найти что-то
If (($Event | Measure-Object).Count -eq 0){
$User = $Using:UnknownUser
$Created = $Using:UnknownCreatedTime
$CreatedFormat = $Using:UnknownCreatedTime
} Else {
If ($Event.Username -eq "" -or $Event.Username -eq $null) {
$User = $Using:UnknownUser
} Else {
$User = $Event.Username
} # Else
$CreatedFormat = $Event.CreatedTime
# Один из коллег отдельно просил, чтобы время было в таком формате, поэтому дублируем его. А в БД пойдёт нормальный формат.
$Created = $Event.CreatedTime.ToString('yyyy-MM-dd')
} # Else
Write-Host "Creator for $vm is $User. Creating object."
# Создаём объект. Добавляем параметры.
$Vmresult = New-Object PSObject
$Vmresult | add-member -MemberType NoteProperty -Name "VM Name" -Value $vm.Name  
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedBy" -Value $User
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedOn" -Value $CreatedFormat
$Vmresult | add-member -MemberType NoteProperty -Name "CreatedOnFormat" -Value $Created           
# Выводим результаты
$Vmresult
} # Inline
} # ForEach
}
$Creators = GetCreators-Wf $VMfilePath
# Записываем результат в файл
$Creators | select 'VM Name', CreatedBy, CreatedOn | Export-Csv -Path $filePath -NoTypeInformation -UseCulture -Force
Write-Host "CSV generetion finisghed at $(get-date). PROFIT"

Biblioteka nusipelno ypatingo dėmesio xlsx, kuri leido padaryti laiško priedą aiškiai suformatuotą (kaip patinka vadovybei), o ne tik CSV lentele.

Sukuriamas gražus xlsx dokumentas su neteisingai užpildytų mašinų sąrašu

# Создаём новую книгу
# Возможные значения : "xls" и "xlsx"
wb<-createWorkbook(type="xlsx")
# Стили для имён рядов и колонок в таблицах
TABLE_ROWNAMES_STYLE <- CellStyle(wb) + Font(wb, isBold=TRUE)
TABLE_COLNAMES_STYLE <- CellStyle(wb) + Font(wb, isBold=TRUE) +
Alignment(wrapText=TRUE, horizontal="ALIGN_CENTER") +
Border(color="black", position=c("TOP", "BOTTOM"), 
pen=c("BORDER_THIN", "BORDER_THICK"))
# Создаём новый лист
sheet <- createSheet(wb, sheetName = names[2])
# Добавляем таблицу
addDataFrame(incorrect_df_filtered, 
sheet, startRow=1, startColumn=1,  row.names=FALSE, byrow=FALSE,
colnamesStyle = TABLE_COLNAMES_STYLE,
rownamesStyle = TABLE_ROWNAMES_STYLE)
# Меняем ширину, чтобы форматирование было автоматическим
autoSizeColumn(sheet = sheet, colIndex=c(1:ncol(incorrect_df)))
# Добавляем фильтры
addAutoFilter(sheet, cellRange = "C1:G1")
# Определяем стиль
fo2 <- Fill(foregroundColor="red")
cs2 <- CellStyle(wb, 
fill = fo2, 
dataFormat = DataFormat("@"))
# Находим ряды с неверно заполненным полем Владельца и применяем к ним определённый стиль
rowsOwner <- getRows(sheet, rowIndex = (which(!incorrect_df$isOwnerCorrect) + 1))
cellsOwner <- getCells(rowsOwner, colIndex = which( colnames(incorrect_df_filtered) == "Owner" )) 
lapply(names(cellsOwner), function(x) setCellStyle(cellsOwner[[x]], cs2))
# Находим ряды с неверно заполненным полем Подсистемы и применяем к ним определённый стиль
rowsSubsystem <- getRows(sheet, rowIndex = (which(!incorrect_df$isSubsystemCorrect) + 1))
cellsSubsystem <- getCells(rowsSubsystem, colIndex = which( colnames(incorrect_df_filtered) == "Subsystem" )) 
lapply(names(cellsSubsystem), function(x) setCellStyle(cellsSubsystem[[x]], cs2))
# Аналогично по Категории
rowsCategory <- getRows(sheet, rowIndex = (which(!incorrect_df$isCategoryCorrect) + 1))
cellsCategory <- getCells(rowsCategory, colIndex = which( colnames(incorrect_df_filtered) == "Category" )) 
lapply(names(cellsCategory), function(x) setCellStyle(cellsCategory[[x]], cs2))
# Создатель
rowsCreator <- getRows(sheet, rowIndex = (which(!incorrect_df$isCreatorCorrect) + 1))
cellsCreator <- getCells(rowsCreator, colIndex = which( colnames(incorrect_df_filtered) == "Creator" )) 
lapply(names(cellsCreator), function(x) setCellStyle(cellsCreator[[x]], cs2))
# Сохраняем файл
saveWorkbook(wb, filenameIncorrect)

Išvestis atrodo maždaug taip:

Kasdieninės ataskaitos apie virtualių mašinų būklę naudojant R ir PowerShell

Taip pat buvo įdomus niuansas nustatant „Windows“ planuoklį. Nebuvo įmanoma rasti tinkamų teisių ir nustatymų, kad viskas prasidėtų taip, kaip turėtų. Dėl to buvo rasta R biblioteka, kuri pati sukuria užduotį paleisti R scenarijų ir net nepamiršta apie žurnalo failą. Tada galite taisyti užduotį rankiniu būdu.

R kodo dalis su dviem pavyzdžiais, sukurianti užduotį „Windows“ planuoklyje

library(taskscheduleR)
myscript <- file.path(getwd(), "all_vm.R")
## запускаем скрипт через 62 секунды
taskscheduler_create(taskname = "getAllVm", rscript = myscript, 
schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))
## запускаем скрипт каждый день в 09:10
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript, 
schedule = "WEEKLY", 
days = c("MON", "TUE", "WED", "THU", "FRI"),
starttime = "02:00")
## удаляем задачи
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")
# Смотрим логи (последние 4 строчки)
tail(readLines("all_vm.log"), sep ="n", n = 4)

Atskirai apie duomenų bazę

Nustačius scenarijų, ėmė ryškėti kitos problemos. Pavyzdžiui, norėjau rasti datą, kada buvo ištrinta VM, bet žurnalai „vCenter“ jau buvo susidėvėję. Kadangi scenarijus kiekvieną dieną deda failus į aplanką ir jo nevalo (valome rankomis, kai prisimename), galite peržiūrėti senus failus ir rasti pirmą failą, kuriame šios VM nėra. Bet tai nėra puiku.

Norėjau sukurti istorinę duomenų bazę.

Į pagalbą atėjo MS SQL SERVER funkcionalumas – sisteminės versijos laikinė lentelė. Paprastai jis verčiamas kaip laikinos (ne laikinos) lentelės.

Išsamiai galite perskaityti adresu oficiali Microsoft dokumentacija.

Trumpai tariant, mes sukuriame lentelę, sakome, kad turėsime ją su versijų nustatymu, o SQL Server šioje lentelėje sukuria 2 papildomus datos ir laiko stulpelius (įrašo sukūrimo data ir įrašo galiojimo data) ir papildomą lentelę, į kurią keičiasi. bus parašyta. Dėl to gauname naujausią informaciją ir per paprastas užklausas, kurių pavyzdžiai pateikiami dokumentacijoje, galime matyti arba konkrečios virtualios mašinos gyvavimo ciklą, arba visų VM būseną tam tikru momentu. laiku.

Našumo požiūriu įrašymo į pagrindinę lentelę operacija nebus baigta, kol nebus baigta įrašymo operacija į laikinąją lentelę. Tie. lentelėse su daugybe rašymo operacijų šią funkciją reikėtų diegti atsargiai, tačiau mūsų atveju tai tikrai šaunus dalykas.

Kad mechanizmas veiktų tinkamai, turėjau pridėti nedidelį kodo fragmentą į R, kuris palygintų naują lentelę su visų VM su duomenų bazėje saugomais duomenimis ir į ją įrašytų tik pakeistas eilutes. Kodas nėra ypač protingas; jame naudojama palyginimo biblioteka, bet aš taip pat pateiksiu jį žemiau.

R kodas duomenims įrašyti į duomenų bazę

# Подцепляем пакеты
library(odbc)
library(compareDF)
# Формируем коннект
con <- dbConnect(odbc(),
Driver = "ODBC Driver 13 for SQL Server",
Server = DBParams$server,
Database = DBParams$database,
UID = DBParams$UID,
PWD = DBParams$PWD,
Port = 1433)
#### Проверяем есть ли таблица. Если нет - создаём. ####
if (!dbExistsTable(con, DBParams$TblName)) {
#### Создаём таблицу ####
create <- dbSendStatement(
con,
paste0(
'CREATE TABLE ',
DBParams$TblName,
'(
[Id] [int] NOT NULL PRIMARY KEY CLUSTERED,
[VM.Name] [varchar](255) NULL,
[Cluster] [varchar](255) NULL,
[Esxi.Host] [varchar](255) NULL,
[IP.Address.1] [varchar](255) NULL,
[IP.Address.2] [varchar](255) NULL,
[IP.Address.3] [varchar](255) NULL,
[IP.Address.4] [varchar](255) NULL,
[IP.Address.5] [varchar](255) NULL,
[IP.Address.6] [varchar](255) NULL,
[vCPU] [int] NULL,
[CPU.Sockets] [int] NULL,
[Core.per.Socket] [int] NULL,
[RAM..GB.] [int] NULL,
[Total.HDD..GB.] [int] NULL,
[Power.State] [varchar](255) NULL,
[OS] [varchar](255) NULL,
[Boot.Time] [varchar](255) NULL,
[VMTools.Status] [varchar](255) NULL,
[VMTools.Version] [int] NULL,
[VMTools.Version.Status] [varchar](255) NULL,
[VMTools.Running.Status] [varchar](255) NULL,
[Creation.Date] [varchar](255) NULL,
[Creator] [varchar](255) NULL,
[Category] [varchar](255) NULL,
[Owner] [varchar](255) NULL,
[Subsystem] [varchar](255) NULL,
[IP.s] [varchar](255) NULL,
[vCenter.Name] [varchar](255) NULL,
DateFrom datetime2 GENERATED ALWAYS AS ROW START NOT NULL,
DateTo datetime2 GENERATED ALWAYS AS ROW END NOT NULL,
PERIOD FOR SYSTEM_TIME (DateFrom, DateTo)
) ON [PRIMARY]
WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = ', DBParams$TblHistName,'));'
)
)
# Отправляем подготовленный запрос
dbClearResult(create)
} # if
#### Начало работы с таблицей ####
# Обозначаем таблицу, с которой будем работать
allVM_db_con <- tbl(con, DBParams$TblName) 
#### Сравниваем таблицы ####
# Собираем данные с таблицы (убираем служебные временные поля)
allVM_db <- allVM_db_con %>% 
select(c(-"DateTo", -"DateFrom")) %>% 
collect()
# Создаём таблицу со сравнением объектов. Сравниваем по Id
# Удалённые объекты там будут помечены через -, созданные через +, изменённые через - и +
ctable_VM <- fullXslx_df %>% 
compare_df(allVM_db, 
c("Id"))
#### Удаление строк ####
# Выдираем Id виртуалок, записи о которых надо удалить 
remove_Id <- ctable_VM$comparison_df %>% 
filter(chng_type == "-") %>%
select(Id)
# Проверяем, что есть записи (если записей нет - и удалять ничего не нужно)
if (remove_Id %>% nrow() > 0) {
# Конструируем шаблон для запроса на удаление данных
delete <- dbSendStatement(con, 
paste0('
DELETE 
FROM ',
DBParams$TblName,
' WHERE "Id"=?
') # paste
) # send
# Создаём запрос на удаление данных
dbBind(delete, remove_Id)
# Отправляем подготовленный запрос
dbClearResult(delete)
} # if
#### Добавление строк ####
# Выделяем таблицу, содержащую строки, которые нужно добавить.
allVM_add <- ctable_VM$comparison_df %>% 
filter(chng_type == "+") %>% 
select(-chng_type)
# Проверяем, есть ли строки, которые нужно добавить и добавляем (если нет - не добавляем)
if (allVM_add %>% nrow() > 0) {
# Пишем таблицу со всеми необходимыми данными
dbWriteTable(con,
DBParams$TblName,
allVM_add,
overwrite = FALSE,
append = TRUE)
} # if
#### Не забываем сделать дисконнект ####
dbDisconnect(con)

Iš viso

Įgyvendinus scenarijų, per kelis mėnesius buvo atkurta ir išlaikyta tvarka. Kartais pasirodo neteisingai užpildytos VM, tačiau scenarijus yra geras priminimas ir reta VM patenka į sąrašą 2 dienas iš eilės.

Taip pat buvo parengtas istorinių duomenų analizės pagrindas.

Aišku, kad daug ką galima įgyvendinti ne ant kelio, o su specializuota programine įranga, tačiau užduotis buvo įdomi ir, galima sakyti, neprivaloma.

R dar kartą pasirodė esanti puiki universali kalba, kuri puikiai tinka ne tik statistinėms problemoms spręsti, bet ir veikia kaip puikus „sluoksnis“ tarp kitų duomenų šaltinių.

Kasdieninės ataskaitos apie virtualių mašinų būklę naudojant R ir PowerShell

Šaltinis: www.habr.com

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