Einleitung
Guten Tag. Seit einem halben Jahr läuft bei uns ein Skript (genauer gesagt, eine Reihe von Skripten), das Berichte über den Zustand virtueller Maschinen (und nicht nur darüber) generiert. Ich möchte meine Erfahrungen bei der Erstellung und den Quellcode teilen. Ich freue mich über Kritik und hoffe, dass dieses Material für einige von Nutzen sein kann.
Bedarfsermittlung
Wir haben viele virtuelle Maschinen (etwa 1500 VM, verteilt auf 3 vCenter). Neue werden regelmäßig erstellt und alte gelöscht. Um die Übersichtlichkeit zu wahren, wurden mehrere benutzerdefinierte Felder im vCenter hinzugefügt, um die VMs in Subsysteme zu unterteilen, zu kennzeichnen, ob sie Testumgebungen sind und um anzugeben, von wem und wann sie erstellt wurden. Der menschliche Faktor hat dazu geführt, dass über die Hälfte der Maschinen mit leeren Feldern geblieben ist, was die Arbeit erschwerte. Alle sechs Monate kam es vor, dass jemand die Nerven verlor und die Aktualisierung dieser Daten anstieß, aber das Ergebnis war nach anderthalb Wochen schon nicht mehr aktuell.
Ich möchte klarstellen, dass alle wissen, dass Anträge für die Erstellung von Maschinen gestellt werden müssen, sowie der Prozess zu deren Erstellung usw. Leider ist das bei uns nicht der Fall, aber das ist nicht Thema des Artikels 🙂
Es wurde beschlossen, die Überprüfung der korrekten Ausfüllung der Felder zu automatisieren.
Es wurde entschieden, dass ein täglicher Bericht mit einer Liste der falsch ausgefüllten Maschinen an alle zuständigen Ingenieure und deren Vorgesetzte ein guter Anfang sein wird.
Zu diesem Zeitpunkt hatte einer der Kollegen bereits ein PowerShell-Skript implementiert, das täglich nach einem Zeitplan Informationen zu allen Maschinen in allen vCenter-Instanzen sammelte und drei CSV-Dokumente (jeweils für sein vCenter) erstellte, die auf einem gemeinsamen Laufwerk abgelegt wurden. Es wurde beschlossen, dieses Skript als Grundlage zu verwenden und es mit Überprüfungen in der Programmiersprache R zu ergänzen, mit der bereits etwas Erfahrung vorlag.
Im Zuge der Weiterentwicklung wurde die Lösung um E-Mail-Benachrichtigungen, eine Datenbank mit Haupt- und Historientabellen (darüber später) sowie um die Analyse von vSphere-Logs zum Auffinden der tatsächlichen Ersteller von VMs und deren Erstellungszeit ergänzt.
Für die Entwicklung wurden die IDEs RStudio Desktop und PowerShell ISE verwendet.
Das Skript wird von einer herkömmlichen Windows-VM aus gestartet.
Beschreibung der allgemeinen Logik.
Die allgemeine Logik der Skripte gestaltet sich wie folgt.
- Wir sammeln Daten zu den virtuellen Maschinen mit einem PowerShell-Skript, das wir über R aufrufen. Das Ergebnis wird in einer einzigen CSV-Datei zusammengeführt. Die umgekehrte Interaktion zwischen den Sprachen erfolgt auf ähnliche Weise. (Es wäre möglich, die Daten direkt von R nach PowerShell als Variablen zu senden, aber das ist komplex. Zudem erleichtert es das Debuggen und das Teilen von Zwischenergebnissen mit anderen, wenn man Zwischen-CSV-Dateien hat.)
- Mit R erstellen wir zulässige Parameter für die Felder, deren Werte wir überprüfen. Wir erstellen ein Word-Dokument, das die Werte dieser Felder zur Einfügung in das Informationsschreiben enthalten wird, das als Antwort auf die Fragen der Kollegen dient: 'Wie soll ich das ausfüllen?'
- Wir laden die Daten zu allen VMs aus der CSV-Datei mit R, erstellen ein DataFrame, entfernen nicht benötigte Felder und erstellen ein Informationsdokument im XLSX-Format, das eine Zusammenfassung aller VMs enthält, das wir auf einer gemeinsamen Plattform bereitstellen.
- Für das DataFrame aller VMs wenden wir alle Prüfungen zur Richtigkeit der ausgefüllten Felder an und erstellen eine Tabelle, die nur VMs mit falsch ausgefüllten Feldern (und nur diesen Feldern) enthält.
- Die erhaltene Liste der VMs wird an ein anderes PowerShell-Skript gesendet, das die vCenter-Logs nach Ereignissen zur Erstellung der VMs durchsucht. Dies ermöglicht die Angabe der vermuteten Erstellungszeit und des vermuteten Schöpfers. Dies gilt für den Fall, dass niemand angibt, wem die Maschine gehört. Dieses Skript arbeitet nicht schnell, insbesondere wenn es viele Logs gibt, sodass wir uns nur die letzten 2 Wochen ansehen und außerdem einen Workflow verwenden, der die gleichzeitige Suche nach Informationen zu mehreren VMs ermöglicht. Im Beispielskript sind ausführliche Kommentare zu diesem Mechanismus enthalten. Das Ergebnis wird in einer CSV-Datei zusammengefasst, die wir erneut in R hochladen.
- Wir erstellen ein schön formatiertes xlsx-Dokument, in dem die falsch ausgefüllten Felder rot hervorgehoben, Filter auf einige Spalten angewendet und zusätzliche Spalten mit den vermuteten Erstellern und der Erstellungszeit der VMs angegeben werden.
- Wir erstellen eine E-Mail, in die wir ein Dokument einfügen, das die zulässigen Werte der Felder beschreibt, sowie eine Tabelle mit fehlerhaft ausgefüllten VMs. Im Text geben wir die Gesamtanzahl der falsch erstellten VMs, einen Link zur allgemeinen Ressource und ein motivierendes Bild an. Wenn keine fehlerhaft ausgefüllten VMs vorhanden sind, senden wir eine andere E-Mail mit einem fröhlicheren motivierenden Bild.
- Wir dokumentieren die Daten aller VMs in einer SQL Server-Datenbank unter Berücksichtigung des implementierten Mechanismus historischer Tabellen (ein sehr interessanter Mechanismus — mehr dazu später).
Hier sind die Skripte.
Die Hauptdatei mit dem Code in 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)
# Überprüfen der Korrektheit der ausgefüllten Felder
full_df <- fullXslx_df %>%
mutate(
# Zuerst entfernen wir alle überflüssigen Leerzeichen und Tabulatoren, dann berücksichtigen wir das Komma als Trennzeichen, anschließend prüfen wir die Eingaben auf zulässige Werte,
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)
)
# Überprüfen der Existenz der Datei mit allen VMs und löschen, falls vorhanden.
if (file.exists(filenameAll)) {file.remove(filenameAll)}
#### Erstellen der xslx-Datei mit dem Bericht ####
# Allgemeine Daten auf einem separaten Blatt
full_df %>% write.xlsx(file=filenameAll,
sheetName=names[1],
col.names=TRUE,
row.names=FALSE,
append=FALSE)
#### Erstellen der xslx-Datei mit falsch ausgefüllten Feldern ####
# Erstellen des 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)
# Überprüfen, ob die Datei mit allen VMs existiert und löschen, falls ja.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}
# Speichern der Liste der VMs mit unvollständigen Feldern in csv
incorrect_df %>%
select(VM.Name) %>%
write_csv2(path = filenameIncVM, append = FALSE)
# Filtern für den Versand per E-Mail
incorrect_df_filtered <- incorrect_df %>%
select(VM.Name,
IP.s,
Owner,
Subsystem,
Category,
Creator,
vCenter.Name,
Creation.Date
)
# Zähle die Anzahl der Zeilen
numberOfRows <- nrow(incorrect_df)
#### Начало условия ####
# Дальше либо у нас есть неправильно заполненные поля, либо нет.
# Если есть - запускаем ещё один скрипт
if (numberOfRows > 0) {
# Überprüfen, ob die Datei mit den Erstellern existiert und löschen, falls ja.
if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}
# Führen Sie das PowerShell-Skript aus, das die Ersteller der gefundenen VMs findet. Das Ergebnis ist eine csv-Datei.
system(paste0("powershell -File ", getCreatorsPath))
# Lesen Sie die Datei mit den Erstellern
creators_df <- creatorsFilePath %>%
read.csv2(stringsAsFactors = FALSE)
# Filtern für den Versand per E-Mail, hinzufügen von Daten aus der Tabelle mit den Erstellern
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(`Vorgesehener Ersteller` = CreatedBy,
`Vorgesehener Erstellungsdatum` = CreatedOn)
# Generieren Sie den E-Mail-Text
emailBody <- paste0(
'<html>
<h3>Guten Tag, liebe Kollegen.</h3>
<p>Die aktuellen Informationen zu den virtuellen Maschinen finden Sie auf dem Laufwerk H: hier:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Im Anhang finden Sie auch die Liste der VMs mit <strong>nicht korrekt ausgefüllten</strong> Felder. Insgesamt sind es <strong>', numberOfRows,'</strong>.</p>
<p>In der Tabelle sind 2 zusätzliche Spalten hinzugekommen. <strong>Vorgesehener Ersteller</strong> und <strong>Vorgesehener Erstellungsdatum</strong>, die aus den vCenter-Logs der letzten 2 Wochen stammen</p>
<p>Die Ersteller der Maschinen werden gebeten, die Daten zu überprüfen und die Felder korrekt auszufüllen. Die Regeln für das Ausfüllen der Felder sind ebenfalls im Anhang enthalten.</p>
<p><img src="data/meme.jpg"></p>
</html>'
)
# Überprüfen der Dateiexistenz
if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}
# Erstellung einer ansprechenden Tabelle mit Formaten usw.
source(file = "email.R", local = T, encoding = "utf-8")
#### Erstellung der E-Mail für schlecht ausgefüllte Maschinen ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "VM mit inkorrekt ausgefüllten Feldern",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
attach.files = c(filenameIncorrect, filenameVmCreationRules),
debug = FALSE)
#### Der nächste Block behandelt, falls keine Probleme mit der VM vorliegen ####
} else {
# Erstellung des E-Mail-Textes
emailBody <- paste0(
'<html>
<h3>Guten Tag, verehrte Kollegen</h3>
<p>Die aktuellen Informationen zu den virtuellen Maschinen finden Sie auf dem Laufwerk H: hier:<p>
<p>\server.ruVM', sourceFileFormat, '</p>
<p>Zum aktuellen Zeitpunkt sind alle Felder der VM korrekt ausgefüllt</p>
<p><img src="data/meme_correct.jpg"></p>
</html>'
)
#### Erstellung der E-Mail ohne schlecht ausgefüllte VMs ####
send.mail(from = emailParams$from,
to = emailParams$to,
subject = "Zusammenfassende Informationen",
body = emailBody,
encoding = "utf-8",
html = TRUE,
inline = TRUE,
smtp = emailParams$smtpParams,
authenticate = TRUE,
send = TRUE,
debug = FALSE)
}
####### Aufzeichnen der Daten in der Datenbank #####
source(file = "DB.R", local = T, encoding = "utf-8")
Skript zur Abfrage der VM-Liste in 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-Skript, das die Ersteller der virtuellen Maschinen und deren Erstellungsdaten aus den Protokollen extrahiert.
# Путь к файлу, из которого будем доставать список 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"
Besonderes Augenmerk verdient die Bibliothek , die es ermöglichte, den Anhang an die E-Mail übersichtlich formatiert zu gestalten (wie es das Management gerne hat), und nicht einfach als CSV-Tabelle.
Die Erstellung eines ansprechenden xlsx-Dokuments mit der Liste der fehlerhaft ausgefüllten Maschinen.
# Создаём новую книгу
# Возможные значения : "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)Das Ergebnis sieht ungefähr so aus:
Es gab auch einen interessanten Aspekt bei der Einrichtung des Windows-Planers. Es war schwierig, die richtigen Berechtigungen und Einstellungen zu finden, damit alles wie gewünscht gestartet wird. Am Ende wurde eine R-Bibliothek gefunden, die die Aufgabe zum Ausführen des R-Skripts erstellt und dabei sogar die Log-Datei berücksichtigt. Anschließend kann die Aufgabe manuell angepasst werden.
Ein Stück Code in R mit zwei Beispielen, das eine Aufgabe im Windows-Planer erstellt.
library(taskscheduleR)
myscript <- file.path(getwd(), "all_vm.R")
## Skript nach 62 Sekunden starten
taskscheduler_create(taskname = "getAllVm", rscript = myscript,
schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))
## Skript jeden Tag um 09:10 Uhr starten
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript,
schedule = "WEEKLY",
days = c("MON", "TUE", "WED", "THU", "FRI"),
starttime = "02:00")
## Aufgaben löschen
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")
# Logs anzeigen (letzte 4 Zeilen)
tail(readLines("all_vm.log"), sep ="n", n = 4)Details zur Datenbank
Nach der Einrichtung des Skripts traten weitere Fragen auf. Zum Beispiel wollte ich das Datum finden, an dem die VM gelöscht wurde, aber die Protokolle in vCenter sind bereits gelöscht. Da das Skript jeden Tag Dateien in einen Ordner speichert und nicht aufräumt (wir machen das manuell, wenn wir daran denken), können wir die alten Dateien durchsehen und die erste Datei finden, in der diese VM nicht mehr vorhanden ist. Aber das ist nicht optimal.
Ich wollte eine historische Datenbank erstellen.
Hierbei kam die Funktionalität von MS SQL SERVER — system-versioned temporal table — hilfreich. Diese werden üblicherweise als zeitgesteuerte (nicht zeitraumgestellte) Tabellen übersetzt.
Detaillierte Informationen finden Sie in der .
Kurz gesagt — wir erstellen eine Tabelle, geben an, dass sie versioniert sein soll, und SQL Server fügt dieser Tabelle zwei zusätzliche datetime-Spalten hinzu (das Erstellungsdatum des Datensatzes und das Ablaufdatum des Datensatzes) sowie eine zusätzliche Tabelle, in die die Änderungen geschrieben werden. So erhalten wir aktuelle Informationen und können durch einfache Abfragen, deren Beispiele in der Dokumentation gegeben sind, entweder den Lebenszyklus einer bestimmten virtuellen Maschine oder den Zustand aller VMs zu einem bestimmten Zeitpunkt einsehen.
In Bezug auf die Leistung wird die Schreibtransaktion in die Haupttabelle nicht abgeschlossen, solange die Schreibtransaktion in die temporäre Tabelle nicht abgeschlossen ist. Das heißt, bei Tabellen mit vielen Schreiboperationen sollte diese Funktionalität mit Vorsicht implementiert werden. In unserem Fall ist es jedoch eine wirklich coole Sache.
Um sicherzustellen, dass der Mechanismus korrekt funktioniert, musste ich ein kleines Stück Code in R schreiben, das die neue Tabelle mit den Daten aller VMs vergleicht und nur die geänderten Zeilen in die Datenbank schreibt. Der Code ist nicht besonders komplex und verwendet die Bibliothek compareDF, die ich ebenfalls unten angeben werde.
R-Code zur Datenspeicherung in der Datenbank
# Подцепляем пакеты
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)Gesamt
Dank der Implementierung des Skripts wurde in wenigen Monaten Ordnung geschaffen und aufrechterhalten. Manchmal erscheinen falsch ausgefüllte VMs, aber das Skript dient als gutes Erinnerungswerkzeug, sodass selten eine VM an zwei aufeinanderfolgenden Tagen in die Liste gelangt.
Es wurde auch eine Grundlage für die Analyse historischer Daten geschaffen.
Es ist klar, dass vieles davon nicht "von Hand" umgesetzt werden kann, sondern mit spezialisierter Software, aber die Aufgabe war interessant und kann als optional betrachtet werden.
R hat sich erneut als hervorragende Allzwecksprache gezeigt, die nicht nur für statistische Aufgaben geeignet ist, sondern auch als ausgezeichnete "Schnittstelle" zwischen anderen Datenquellen fungiert.
Quelle: habr.com
