Hyrje
Mirëmëngjes. Tani e gjashtë muaj kemi një skript (në fakt një grup skriptash) që gjeneron raporte mbi gjendjen e makinave virtuale (dhe jo vetëm). Vendosa të ndaja përvojën time të krijimit dhe kodin. Shpresoj për kritikë dhe se ky material mund të jetë i dobishëm për dikë.
Formimi i nevojës
Kemi shumë makina virtuale (rreth 1500 VM të shpërndara në 3 vCenter). Krijohen të reja dhe fshihen të vjetra mjaft shpesh. Për të ruajtur rendin, janë shtuar disa fusha të personalizuara në vCenter për të ndarë VM-të në Subsistemat, për të treguar nëse janë teste, si dhe kush i ka krijuar dhe kur. Faktori njerëzor ka bërë që më shumë se gjysma e makinave të mbeten me fusha të paplotësuara, çka e ka komplikuar punën. Një herë në gjashtë muaj, dikush nervozohej, fillonte një punë për aktualizimin e këtyre të dhënave, por rezultati ndalonte së qeni aktual pas rreth një deri në dy javë.
Le ta sqaroj menjëherë, se të gjithë e kuptojnë që duhet të ketë kërkesa për krijimin e makinave, proces për krijimin e tyre, etj. dhe kështu me radhë. Dhe për më tepër, të gjithë ndjekin këtë proces rreptësisht dhe gjithçka është në rend. Ne, fatkeqësisht, nuk jemi kështu, por kjo nuk është subjekti i artikullit 🙂
Në përgjithësi, u mor vendimi për të automatizuar kontrollin e saktësisë së plotësimit të fushave.
Vendosëm që një email përditësor me listën e makinave të mbushura gabim do të ishte një fillim i mirë për të gjithë inxhinierët përgjegjës dhe shefat e tyre.
Aktualisht, një nga kolegët kishte implementuar një skript në PowerShell, i cili çdo ditë, sipas një plani, grumbullonte informacion për të gjitha makinat e të gjitha vCenter-eve dhe formonte 3 dokumente csv (secili për vCenter-in e tij), të cilat vendoseshin në një disk të përbashkët. U mor vendimi të marrim këtë skript si bazë dhe ta plotësojmë me kontrolle duke përdorur gjuhën R, me të cilën kishte disa përvojë.
Gjatë procesit të përmirësimit, zgjidhja u pasurua me informimin përmes emailit, një baze të dhënash me tabelat kryesore dhe historike (për këtë më vonë), si dhe analizimin e logëve të vSphere për të gjetur krijuesit faktikë të vm dhe kohën e krijimit të tyre.
Për zhvillimin u përdorën IDE RStudio Desktop dhe PowerShell ISE.
Skripti fillon nga një makinë virtuale e zakonshme Windows.
Përshkrimi i logjikës së përgjithshme.
Logjika e përgjithshme e skripteve është si në vijim.
- Grumbullojmë të dhëna për makinat virtuale me një skript PowerShell, i cili thirret përmes R, rezultatin e bashkojmë në një csv të vetëm. Ndërveprimi i kundërt mes gjuhëve është bërë në mënyrë të ngjashme. (mund të dërgoheshin të dhënat drejtpërdrejt nga R në PowerShell në formën e variablave, por kjo është e komplikuar, dhe meqënëse kemi csv të përkohshme është më e lehtë për të debatuar dhe ndarë me dikë rezultatet e mesme).
- Me ndihmën e R formojmë parametrat e pranueshëm për fushat, vlerat e të cilave ne kontrollojmë. — Formojmë një dokument word, i cili do të përmbajë vlerat e këtyre fushave për t'i futur në një letër informacioni, e cila do të jetë përgjigje ndaj pyetjeve të kolegëve “Po si duhet ta plotësoj këtë?”.
- Ngarkojmë të dhënat për të gjitha VM-të nga csv me R, formojmë një dataframe, heqim fushat e panevojshme dhe formojmë një dokument informativ xlsx, i cili do të përmbajë informacion të përmbledhur për të gjitha VM-të, të cilin e ngarkojmë në një burim të përbashkët.
- Aplikojmë të gjitha kontrolllet e saktësisë së plotësimit të fushave në dataframe për të gjitha VM-të dhe formojmë një tabelë, e cila përmban vetëm VM-të me fusha të mbushura gabim (dhe vetëm këto fusha).
- Lista e marrë e VM-ve dërgohet në një tjetër skript PowerShell, i cili do të shikojë logët e vCenter për ngjarjet e krijimit të VM-ve, që do të lejojë të tregojmë kohën e supozuar të krijimit të VM-ve dhe krijuesin e supozuar. Kjo në rast se askush nuk e pranon se e ka makinën. Ky skript nuk punon shpejt, veçanërisht nëse ka shumë logë, prandaj shikojmë vetëm dy javët e fundit, dhe gjithashtu përdorim një proces të punës, i cili lejon kërkimin e informacionit për disa VM njëkohësisht. Në shembujt e skriptit ka komente të detajuara për këtë mekanizëm. Rezultatin e grumbullojmë në csv, i cili përsëri ngarkohet në R.
- Formojmë një dokument xlsx të dizajnuar bukur, në të cilin do të jenë fushat e mbushura gabim të shënuara me të kuqe, janë aplikuar filtra në disa kolona, si dhe janë treguar kolona shtesë, që përmbajnë krijuesit e supozuar dhe kohën e krijimit të VM-ve.
- Formojmë një email elektronik, në të cilin ngarkojmë dokumentin që përshkruan vlerat e pranueshme të fushave, si dhe tabelën me VM-të e mbushura gabim. Në tekst tregojmë numrin e përgjithshëm të VM-ve të mbushura gabim, lidhjen në burimin e përbashkët dhe një imazh motivues. Nëse nuk ka VM të mbushura gabim, dërgojmë një tjetër email me një imazh motivues më të gëzueshëm.
- Ruajmë të dhënat për të gjitha VM në databazën SQL Server duke marrë parasysh mekanizmin e tabelave historike të integruara (një mekanizëm shumë interesant — për të cilin do flasim më vonë).
Në fakt, skenaret.
Skedari kryesor me kodin në R.
# Путь к рабочей директории (нужно для корректной работы через виндовый планировщик заданий)
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)
# Kontroloni saktësinë e fushave të mbushura
full_df <- fullXslx_df %>%
mutate(
# Së pari hoqim të gjitha hapësirat dhe tabulacionet e tepërta, pastaj marrim parasysh ndarësin e presjes, pastaj kontrollojmë të dhënat e lejuara,
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)
)
# Kontroloni ekzistencën e skedarit me të gjitha VM-të dhe fshini, nëse është e nevojshme.
if (file.exists(filenameAll)) {file.remove(filenameAll)}
#### Formoni skedarin xslx me raportin ####
# Të dhënat e përgjithshme në një fletë të veçantë
full_df %>% write.xlsx(file=filenameAll,
sheetName=names[1],
col.names=TRUE,
row.names=FALSE,
append=FALSE)
#### Formoni skedarin xslx me fushat e mbushura gabimisht ####
# Formoni 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)
# Kontroloni ekzistencën e skedarit me të gjitha VM-të dhe fshini, nëse është e nevojshme.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}
# Ruani listën e VM-ve me fushat e paplotësuara në csv
incorrect_df %>%
select(VM.Name) %>%
write_csv2(path = filenameIncVM, append = FALSE)
# Filtroni për t'ia dërguar në email
incorrect_df_filtered <- incorrect_df %>%
select(VM.Name,
IP.s,
Owner,
Subsystem,
Category,
Creator,
vCenter.Name,
Creation.Date
)
# Numëroni rreshtat
numberOfRows <- nrow(incorrect_df)
#### Начало условия ####
# Дальше либо у нас есть неправильно заполненные поля, либо нет.
# Если есть - запускаем ещё один скрипт
if (numberOfRows > 0) {
# Kontroloni ekzistencën e skedarit me krijuesit dhe fshini, nëse është e nevojshme.
if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}
# Ekzekutoni skriptin PowerShell, i cili do të gjejë krijuesit e VM-ve të gjetura. Këtu do të marrim një csv.
system(paste0("powershell -File ", getCreatorsPath))
# Lexoni skedarin me krijuesit
creators_df <- creatorsFilePath %>%
read.csv2(stringsAsFactors = FALSE)
# Filtroni për t'ia dërguar në email, shtoni të dhënat nga tabela me krijuesit
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(`Krijuesi i propozuar` = CreatedBy,
`Data e propozuar e krijimit` = CreatedOn)
# Formoni trupin e email-it
emailBody <- paste0(
'<html>
<h3>Përshëndetje të dashur kolegë.</h3>
<p>Informacionin e plotë dhe aktual për makinat virtuale mund ta shihni në diskun H: këtu:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Po ashtu në aneks, lista e VM-ve me <strong>fushat e mbushura gabimisht.</strong> Në total ka <strong>', numberOfRows,'</strong>.</p>
<p>Në tabelë janë shtuar 2 kolona të tjera. <strong>Krijuesi i propozuar</strong> dhe <strong>Data e propozuar e krijimit</strong>, të cilat merren nga të dhënat e logëve të vCenter për dy javët e fundit.</p>
<p>Ju lutemi krijuesit e makinave të saktësojnë të dhënat dhe të plotësojnë fushat siç duhet. Rregullat për plotësimin e fushave janë gjithashtu në aneks.</p>
<p><img src="data/meme.jpg"></p>
</html>'
)
# Kontrolloni ekzistencën e skedarit
if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}
# Formoni një tabelë të bukur me formate etj.
source(file = "email.R", local = T, encoding = "utf-8")
#### Formoni një email me makinat me emër të keq ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "VM me fushat e mbushura gabimisht",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
attach.files = c(filenameIncorrect, filenameVmCreationRules),
debug = FALSE)
#### Këtu do të vazhdojë blloku, nëse nuk ka probleme me VM ####
} else {
# Formoni trupin e email-it
emailBody <- paste0(
'<html>
<h3>Përshëndetje të dashur kolegë</h3>
<p>Informacionin e plotë dhe aktual për makinat virtuale mund ta shihni në diskun H: këtu:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Po ashtu, për momentin, të gjitha fushat e VM-ve janë plotësisht të mbushura siç duhet.</p>
<p><img src="data/meme_correct.jpg"></p>
</html>'
)
#### Formoni një email pa VM të mbushura gabimisht ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "Informacion i përmbledhur",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
debug = FALSE)
}
####### Shkruani të dhënat në DB #####
source(file = "DB.R", local = T, encoding = "utf-8")
Skenari për të marrë listën e VM-ve në 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
}Skenari në PowerShell, që nxjerr nga logët krijuesit e makinerive virtuale dhe datat e krijimit të tyre.
# Путь к файлу, из которого будем доставать список 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"
Një vëmendje të veçantë meritojnë bibliotekat. , e cila e mundësoi krijimin e një bashkëngjitjeje të formatizuar bukur për email (siç e do menaxhmenti), dhe jo thjesht një tabelë CSV.
Krijimi i një dokumenti xlsx të bukur me listën e VM-ve të mbushura gabimisht.
# Создаём новую книгу
# Возможные значения : "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)Në fund rezultati duket më pak kështu:
Ishte gjithashtu një detaj interesant në konfigurimin e planifikuesit të Windows. Nuk arrita kurrë të gjej parametrat e duhur të privilegjeve dhe konfigurimeve që të gjitha të fillonin siç duhet. Në përfundim, u gjet një bibliotekë R, që vet krijon një detyrë për të ekzekutuar skenarin R dhe madje nuk harrohet për skedarin me logë. Pastaj mund të bëni disa rregullime manuale në detyrë.
Një pjesë kodi në R me dy shembuj, që krijon një detyrë në planifikuesin e Windows.
library(taskscheduleR)
myscript <- file.path(getwd(), "all_vm.R")
## ekzekutojmë skenarin pas 62 sekondash
taskscheduler_create(taskname = "getAllVm", rscript = myscript,
schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))
## ekzekutojmë skenarin çdo ditë në 09:10
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript,
schedule = "WEEKLY",
days = c("MON", "TUE", "WED", "THU", "FRI"),
starttime = "02:00")
## fshijmë detyrat
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")
# Shikojmë logët (katër rreshtat e fundit)
tail(readLines("all_vm.log"), sep = "n", n = 4)Veç në lidhje me DB.
Pas konfigurimit të skenarit, u shfaqën pyetje të tjera. Për shembull, dëshiroja të gjeja datën kur VM u fshi dhe logët në vCenter tashmë ishin fshirë. Duke qenë se skenari ruan skedarët në një dosje çdo ditë dhe nuk i pastron (i pastrojmë me dorë kur e kujtojmë), mund të shikojmë skedarët e vjetër dhe të gjejmë skedarin e parë në të cilin nuk ka këtë VM. Por kjo nuk është shumë e këndshme.
Desha të krijoja një DB historike.
Më ndihmoi funksionaliteti i MS SQL SERVER — system-versioned temporal table. Ai zakonisht përkthehet si tabelat temporale (jo tabelat e përkohshme).
Mund të lexoni në detaje në .
Në përmbledhje — krijojmë një tabelë, i themi se do të jetë me versionim dhe SQL Server krijon dy kolona shtesë datetime në këtë tabelë (datën e krijimit të regjistrimit dhe datën e përfundimit të jetës së regjistrimit) dhe një tabelë shtesë, në të cilën do të regjistrohen ndryshimet. Si rezultat, marrim informacion të актуалizuar dhe, përmes kërkesave jo të komplikuara, shembujt e të cilëve janë dhënë në dokumentacion, mund të shohim ose ciklin e jetës së një makine virtuale specifike, ose gjendjen e të gjitha VM-ve në një moment të caktuar.
Sa i përket performancës — transaksioni i ndalon regjistrimin në tabelën kryesore derisa të përfundojë transaksioni në tabelën temporale. Pra, në tabelat me një numër të madh operacionesh regjistrimi, ky funksionalitet duhet të inkuadrohet me kujdes, por në rastin tonë është një gjë shumë interesante.
Për të siguruar që mekanizmi të funksionojë saktësisht, duhej të shkruaja një copë kod në R, që do të krahasonte tabelën e re me të dhënat për të gjitha VM-të nga ajo që ruhet në DB dhe do të regjistronte vetëm rreshtat e ndryshuar. Kodi nuk është shumë i ndërlikuar, përdor bibliotekën compareDF, por do ta sjell edhe atë më poshtë.
Kodi në R për regjistrimin e të dhënave në DB.
# Подцепляем пакеты
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)Në përfundim
Si rezultat i implementimit të skenarit, për disa muaj u krijua dhe ruhet një rend. Ndonjëherë VM të mbushura gabimisht shfaqen, por skenari shërben si një përkujtues i mirë dhe një VM e rrallë zë vend në listë për 2 ditë radhazi.
Ishte gjithashtu krijuar një mbështetje për analizën e të dhënave historike.
Sigurisht, shumë nga kjo mund të realizohet jo "në mënyrë ad-hoc", por me softuer profesional, por detyra ishte interesante dhe mund të thuhet fakultative.
R përsëri e tregoi veten si një gjuhë universale të shkëlqyer, që është shumë e përshtatshme jo vetëm për zgjidhjen e problemeve statistikore, por gjithashtu shërben si një "lidhje" të shkëlqyer midis burimeve të tjera të të dhënave.
Burimi: habr.com
