Igapäevased aruanded virtuaalsete masinate seisundi kohta R ja PowerShelli abil

Igapäevased aruanded virtuaalsete masinate seisundi kohta R ja PowerShelli abil

Sissejuhatus

Tere päevast. Meie juures on juba kuus kuud töötanud skript (täpsemalt skriptide kogum), mis genereerib aruandeid virtuaalmasinate (ja mitte ainult) seisukorra kohta. Otsustasin jagada koostamise kogemust ja koodi. Ootan kriitikat ja loodan, et see materjal on kellelegi kasulik.

Nõudluse loomine

Virtuaalmasinaid on meil palju (umbes 1500 VM-i, mis on jaotatud kolme vCenteri vahel). Uusi luuakse ja vanu kustutatakse piisavalt tihti. Korra hoidmiseks lisati vCenterisse mitu kohandatud väljade, et jagada VM-e alamsüsteemide vahel, näidata, kas need on testmasinad ning ka seda, kes ja millal need lõid. Inimfaktor viis selleni, et enam kui pooltel masinatest ei olnud väljad täidetud, mis raskendas tööd. Iga kuue kuu tagant leidis keegi, et peab andmeid ajakohastama, kuid tulemus muutus juba nädalate jooksul mittetäielikuks.
Tõstan esile, et kõik mõistavad, et masinate loomiseks peavad olema taotlused, loomisprotsess ja nii edasi. Kõik järgivad seda protsessi rangelt ja kõiges on kord. Kahjuks ei ole meil nii, kuid see ei ole artikli teema 🙂

Igatahes, otsustati automatiseerida väljade täitmise õigsuse kontrollimine.
Otsustasime, et igapäevane kiri vale täitmisega masinatest kõigile vastutavatele inseneridele ja nende ülemustele oleks hea algus.

Selleks ajaks oli üks kolleeg juba rakendanud PowerShelli skripti, mis iga päev regulaarselt kogus teavet kõigi vCenterite masinate kohta ja genereeris 3 csv-dokumenti (igaühes oma vCenteri kohta), mis pandi üldisele kettale. Otsustati võtta see skript aluseks ja täiustada seda R-keele kontrollidega, millega oli teatud kogemus.

Töötamise käigus sai lahendus lisaks e-kirjade saatmisele ka andmebaasi koos põhja- ja ajalooliste tabelitega (sellest hiljem) ning analüüsiga vSphere'i logides, et leida masinate tegelikud loojaid ja nende loomise aega.

Arendamiseks kasutati IDE RStudio Desktop ja PowerShell ISE.

Skript käivitatakse tavalise Windowsi virtuaalmasina pealt.

Üldise loogika kirjeldus.

Skriptide üldine loogika kujunes välja järgmiselt.

  • Kogume andmeid virtuaalmasinate kohta PowerShelli skripti abil, mille kutsume välja R-i kaudu, ja ühendame tulemused ühte csv-faili. Tagasiside nende keelte vahel on tehtud sarnaselt. (me oleksime võinud edastada andmeid otse R-ist PowerShelli kui muutujad, kuid see oleks keeruline, ja vahepealsete csv-de olemasolu muudab tõrgete leidmise ja teistega vahepealsete tulemuste jagamise lihtsamaks).
  • Kasutades R-i, loome lubatud parameetrid väljade jaoks, mille väärtusi me kontrollime. — Loome Wordi dokumendi, mis sisaldab nende väljade väärtusi, et sisestada need informatiivses kirjas, mis vastab kolleegide küsimustele "Aga kuidas ma seda peaksin täitma?".
  • Laeme R-i abil kõikide VM-ide andmed csv-st, loome andmeraami, eemaldame mittevajalikud väljad ja koostame informatiivse xlsx-dokumendi, mis sisaldab kokkuvõtlikku teavet kõikide VM-ide kohta, mille paneme üldisele ressursile.
  • Rakendame kõik kontrollid väljade täitmise õigsuse osas andmeraamile kõikide VM-ide puhul ja koostame tabeli, mis sisaldab ainult VM-e, mille väljad on valesti täidetud (ja ainult need väljad).
  • Saadetud VM-ide nimekiri edastatakse teisele PowerShelli skriptile, mis vaatakse vCenteri logisid VM-ide loomise sündmuste osas, et võimaldada eeldatavat VM-i loomise aega ja eeldatavat loojat. See on juhtudel, kui keegi ei tunnista, kelle masin see on. See skript ei tööta kiiresti, eriti kui logisid on palju, seega vaatame ainult viimase kahe nädala logisid ja kasutame töövoogu, mis võimaldab teostada andmete otsimist mitme VM-i osas korraga. Skripti näites on selle mehhanismi kohta üksikasjalikud märkused. Salvestame tulemuse csv-failina, mille laadime uuesti R-i.
  • Loome kaunilt vormindatud xlsx-dokumendi, kus on punase värviga esile tõstetud valesti täidetud väljad, rakendatud filtrid mõnele veergudele ning lisatud täiendavad veerud, mis sisaldavad eeldatavaid loojaid ja VM-i loomise aega.
  • Kohandame e-kirja, kuhu lisame dokumendi, mis kirjeldab lubatud väljade väärtusi, samuti tabeli vale täitmisega VM-ide kohta. Tekstis toome välja vale loodud VM-ide koguarvu, lingi üldisele ressursile ja motivatsioonilise pildi. Kui vale täitmistega VM-e pole, saadame teise kirja koos rõõmsama motiveeriva pildiga.
  • Kandame kõikide VM-ide andmed SQL Serveri andmebaasi, arvestades sisseviidud ajalooliste tabelite mehhanismi (väga huvitav mehhanism – mille üle räägime lähemalt hiljem).

Tegelikult skriptid.

Peamine fail R-koodiga.

# Путь к рабочей директории (нужно для корректной работы через виндовый планировщик заданий)
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)

# Kontrollime täidetud väljade õigsust
full_df <- fullXslx_df %>%
  mutate(
    # Esiteks eemaldame kõik liigsed tühikud ja tabulaatorid, seejärel arvestame eraldajana koma, seejärel kontrollime lubatud väärtuste olemasolu,
    isSubsystemCorrect = Subsystem %&gt;% 
      gsub("[[:space:]]", "", .) %&gt;% 
      str_split(., ",") %&gt;% 
      map(function(x) (all(x %in% AllowedValues$Subsystem))) %&gt;%
      as.logical(),
    isOwnerCorrect = Owner %in% AllowedValues$Owner,
    isCategoryCorrect = Category %in% AllowedValues$Category,
    isCreatorCorrect = (!is.na(Creator) &amp; Creator != ''),
    isCreation.DateCorrect = map(Creation.Date, IsDate)
  )

# Kontrollime, kas fail kõigi VM-ide olemasolu on olemas ja kustutame, kui see on.
if (file.exists(filenameAll)) {file.remove(filenameAll)}

#### Koostame xslx faili aruande ####
# Ühisandmed eraldi lehele
full_df %&gt;% write.xlsx(file=filenameAll,
                       sheetName=names[1],
                       col.names=TRUE,
                       row.names=FALSE,
                       append=FALSE)

#### Koostame xslx faili valesti täidetud väljadega ####
# Koostame df
incorrect_df <- full_df %>%
  select(VM.Name, 
         IP.s, 
         Owner,
         Subsystem,
         Creator,
         Category,
         Creation.Date,
         isOwnerCorrect, 
         isSubsystemCorrect, 
         isCategoryCorrect,
         isCreatorCorrect,
         vCenter.Name) %&gt;%
  filter(isSubsystemCorrect == F | 
           isOwnerCorrect == F |
           isCategoryCorrect == F |
           isCreatorCorrect == F)

# Kontrollime, kas fail kõigi VM-ide olemasolu on olemas ja kustutame, kui see on.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}

# Salvestame VM loendi, millel on täitmata väljad csv-sse
incorrect_df %&gt;%
  select(VM.Name) %&gt;%
  write_csv2(path = filenameIncVM, append = FALSE)

# Filtreerime postitamiseks
incorrect_df_filtered <- incorrect_df %>% 
  select(VM.Name, 
         IP.s, 
         Owner, 
         Subsystem, 
         Category,
         Creator,
         vCenter.Name,
         Creation.Date
  )

# Arvutame ridade arvu
numberOfRows <- nrow(incorrect_df)

#### Начало условия ####
# Дальше либо у нас есть неправильно заполненные поля, либо нет.
# Если есть - запускаем ещё один скрипт

if (numberOfRows > 0) {

  # Kontrollime, kas faili loojate olemasolu on olemas ja kustutame, kui see on.
  if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}

  # Käivitame PowerShelli skripti, mis leiab leitud VM-ide loojad. Tulemuseks saame csv.
  system(paste0("powershell -File ", getCreatorsPath))

  # Loeme loojate faili
  creators_df <- creatorsFilePath %>%
    read.csv2(stringsAsFactors = FALSE)

  # Filtreerime postitamiseks, lisame andmed loojate tabelist
  incorrect_df_filtered <- incorrect_df_filtered %>% 
    select(VM.Name, 
           IP.s, 
           Owner, 
           Subsystem, 
           Category,
           Creator,
           vCenter.Name,
           Creation.Date
    ) %&gt;% 
    left_join(creators_df, by = "VM.Name") %&gt;% 
    rename(`Eeldatav looja` = CreatedBy, 
           `Eeldatav loomise kuupäev` = CreatedOn)  

  # Koostame kirja sisu
  emailBody &lt;- paste0(
    &#039;<html>
                    <h3>Tere päevast, austatud kolleegid.</h3>
                    <p>Kogu ajakohast teavet virtuaalmasinate kohta saate vaadata kettal H: siin:<p>
                    <p>\server.ruVM', sourceFileFormat, '</p>
                    <p>Samuti on manuses loetelu VM-idest, millel on <strong>valeandmed</strong> väljad. Kokku on neid <strong>', numberOfRows,'</strong>.</p>
                    <p>Tabelisse on lisandunud 2 uut veergu. <strong>Eeldatav looja</strong> ja <strong>Eeldatav loomise kuupäev</strong>, mis pärinevad vCenteri logidest viimase 2 nädala jooksul</p>
                    <p>Palun masinate loojatel andmed täpsustada ja väljad korrektselt täita. Väljade täitmise reeglid on samuti manusena</p>
                    <p><img src="data/meme.jpg"></p>
                    </html>'
  )

  # Kontrollime faili olemasolu
  if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}

  # Koostame kauni tabeli formaatide jms. kohta
  source(file = "email.R", local = T, encoding = "utf-8")

  #### Koostame kirja halvasti allkirjastatud masinatest ####
  send.mail(from = emailParams$from,
            to = emailParams$to,
            subject = "VM-id valeandmetega",
            body = emailBody,
            encoding = "utf-8",
            html = TRUE,
            inline = TRUE,
            smtp = emailParams$smtpParams,
            authenticate = TRUE,
            send = TRUE,
            attach.files = c(filenameIncorrect, filenameVmCreationRules),
            debug = FALSE)

  #### Edasi liigume plokki, kui VM-iga pole probleeme ####
} else {

  # Koostame kirja sisu
  emailBody &lt;- paste0(
    &#039;<html>
    <h3>Tere päevast, austatud kolleegid</h3>
   <p>Kogu ajakohast teavet virtuaalmasinate kohta saate vaadata kettal H: siin:<p>
    <p>\server.ruVM', sourceFileFormat, '</p>
    <p>Samuti on hetkel kõik VM-i väljad korrektselt täidetud</p>
    <p><img src="data/meme_correct.jpg"></p>
    </html>'
  )

  #### Koostame kirja ilma halvasti täidetud VM-ideta ####
  send.mail(from = emailParams$from,
            to = emailParams$to,
            subject = "Kokkuvõte",
            body = emailBody,
            encoding = "utf-8",
            html = TRUE,
            inline = TRUE,
            smtp = emailParams$smtpParams,
            authenticate = TRUE,
            send = TRUE,
            debug = FALSE)
}

####### Salvestame andmed andmebaasi #####

source(file = "DB.R", local = T, encoding = "utf-8")

PowerShelli skript, mis saadab vm-i nimekirja.

# Данные для подключения и другие переменные
$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
    }

PowerShelli skript, mis tõmbab välja virtuaalsete masinate loojate logidest ja nende loomise kuupäevad.

# Путь к файлу, из которого будем доставать список 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"

Erilist tähelepanu väärib raamatukogu. xlsx, mis võimaldas teha e-kirja lisandeid visuaalselt vormindatuna (nagu juhatus eelistab), mitte lihtsalt csv tabelina.

Ilusa xlsx dokumendi koostamine vale täitmisega masinate loendiga.

# Создаём новую книгу
# Возможные значения : "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)

Väljund tuleb umbes selline:

Igapäevased aruanded virtuaalsete masinate seisundi kohta R ja PowerShelli abil

Samuti oli huvitav nüanss Windowsi ajakava seadistamisel. Ei suutnud kuidagi leida õigeid õiguste ja seadete parameetreid, et kõik käivituks nagu peab. Lõpuks leiti R-raamatukogu, mis loob automaatselt R-skripti käivitamise ülesande ja ei unusta ka logifaili. Hiljem saab manuaalselt ülesannet muuta.

Tükike R-koodi kahe näitega, mis loob ülesande Windowsi ajakavas.

library(taskscheduleR)
myscript <- file.path(getwd(), "all_vm.R")

## käivitame skripti 62 sekundi pärast
taskscheduler_create(taskname = "getAllVm", rscript = myscript, 
                     schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))

## käivitame skripti iga päev kell 09:10
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript, 
                     schedule = "WEEKLY", 
                     days = c("MON", "TUE", "WED", "THU", "FRI"),
                     starttime = "02:00")

## kustutame ülesanded
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")

# Vaata logisid (viimased 4 rida)
tail(readLines("all_vm.log"), sep ="n", n = 4)

Eraldi andmebaasi kohta.

Pärast skripti seadistamist kerkisid esile teised küsimused. Näiteks tahtsin leida kuupäeva, mil VM kustutati, kuid vCenteri logid olid juba kadunud. Kuna skript lisab failid kaustasse iga päev ja ei puhasta neid (puhastame käsitsi, kui meelde tuleb), saab vaadata vanu faile ja leida esimene fail, milles seda VM-i pole. Aga see ei ole eriti hea lahendus.

Soovisin luua ajaloolist andmebaasi.

Abiks tuli MS SQL SERVERi funktsionaalsus — süsteemiversiooniga ajutised tabelid. Seda tõlgitakse tavaliselt kui ajutised tabelid (mitte ajaliselt tabelid).

Seda saab põhjalikult lugeda Microsofti ametlikust dokumentatsioonist.

Lühidalt öeldes — loome tabeli, ütleme, et see on versioneeritud, ja SQL Server loob sellele tabelile 2 täiendavat datetime veergu (kirje loomise kuupäeva ja kirje kehtivuse lõpu kuupäeva) ja täiendava tabeli, kuhu salvestatakse muudatused. Tulemuseks on, et saame ajakohast teavet ja lihtsate päringute kaudu, mille näited on dokumentatsioonis, saame näha kas konkreetse virtuaalmasina elutsüklit või kõigi virtuaalmasinate seisundit kindlal ajahetkel.

Tõhususe vaatenurgast — kirje kirjutamise tehing põhietabelisse ei lõpe, enne kui ei lõpe kirje kirjutamise tehing ajutisse tabelisse. St. tabelites, kus on palju kirjutamisoperatsioone, tuleb seda funktsionaalsust rakendada ettevaatlikult, kuid meie puhul on see tõeliselt lahe asi.

Selle mehhanismi korrektseks toimimiseks pidi R'is kirjutama väikese koodi, mis võrreldaks uut tabelit kõigi virtuaalmasinate andmetega, mis on andmebaasis, ja salvestaks sinna ainult muudetud read. Kood ei ole eriti keeruline, see kasutab compareDF teeki, aga toon selle ka allpool.

R kood andmete salvestamiseks andmebaasi

# Подцепляем пакеты
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)

Kokkuvõttes

Skripti juurutamise tulemuseks on, et mõne kuu jooksul on saavutatud ja hoitakse korrastatud seisundit. Aeg-ajalt ilmuvad valeandmed virtuaalmasinatele, kuid skript on hea meeldetuletus ja harva satub virtuaalmasin nimekirja 2 päeva järjest.

Olid tehtud ettevalmistused ajalooliste andmete analüüsimiseks.

On selge, et paljusid asju saaks teha mitte 'käepärast', vaid eriprogrammide abil, kuid ülesanne oli huvitav ja võib öelda, et valikuline.

R on jälle näidanud end suurepärase universaalse keelena, mis sobib suurepäraselt mitte ainult statistiliste probleemide lahendamiseks, vaid on ka suurepärane 'vahekiht' teiste andmeallikate vahel.

Igapäevased aruanded virtuaalsete masinate seisundi kohta R ja PowerShelli abil

Allikas: habr.com

Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid 🔥 Osta usaldusväärne hostimine veebilehtede jaoks DDoS-i kaitsega, VPS VDS serverid | ProHoster