R və PowerShell ilə virtual maşınların vəziyyəti haqqında gündəlik hesabatlar

R və PowerShell ilə virtual maşınların vəziyyəti haqqında gündəlik hesabatlar

Giriş

Salam. Artıq altı aydır ki, bizdə virtual maşınların (və yalnız onların) vəziyyətinə dair hesabatlar yaradan skript (daha doğrusu, skriptlər toplusu) işləyir. Yaratma təcrübəmi və kodu paylaşmaq istədim. Tənqidlərə açıqam və ümid edirəm ki, bu material kiməsə faydalı olacaq.

Ehtiyacın formalaşdırılması

Bizdə çox sayda virtual maşın var (təxminən 1500 VM, 3 vCenter arasında yayılmışdır). Yeni maşınların yaradılması və köhnələrinin silinməsi tez-tez baş verir. Nizam-intizamı qorumaq üçün vCenter-ə bir neçə xüsusi sahə əlavə edildi ki, bunlar vasitəsilə VM-ləri alt sistemlərə ayırmaq, onların test məqsədli olub-olmadığını göstərmək və kim tərəfindən, nə zaman yaradıldığını qeyd etmək mümkün olsun. İnsan amili nəticəsində maşınların yarıdan çoxu doldurulmamış sahələrlə qaldı ki, bu da işimizi çətinləşdirdi. Hər altı ayda kimlərsə əsəbləşərək bu məlumatların yenilənməsi işinə başlayırdı, lakin nəticə artıq bir neçə həftədən sonra köhnəlirdi.
Bununla bərabər, hamının maşınların yaradılması üçün müraciətlərin olmalı olduğunu, onların yaradılması prosesi olduğunu, və s.-ni anladığını aydınlaşdırım. Və buna baxmayaraq, hamı bu prosesi dəqiq izləyir və hər şey qaydasındadır. Bizdə, təəssüf ki, belə deyil, amma bu məqalənin mövzusu deyil 🙂

Ümumiyyətlə, doldurulmuş sahələrin düzgünlüyünü yoxlamaq üçün avtomatlaşdırma qərarı qəbul edildi.
Hər gün bütün məsul mühəndislərə və onların rəhbərlərinə səhv doldurulmuş maşınların siyahısını göndərən bir e-poçt mesajının yaxşı bir başlanğıc olacağına qərar verdik.

Bu anda artıq bir həmkar PowerShell skriptini tətbiq etmişdi ki, bu skript hər gün cədvələ uyğun olaraq bütün vCenter-lər üzrə bütün maşınların məlumatlarını toplayırdı və hər biri öz vCenter-ı üçün 3 csv sənəd yaradırdı, hansılar ki, ortaq diskə yerləşdirilirdi. Bu skripti əsas götürüb, R dili ilə yoxlamalar əlavə etmək qərarına gəldik, həmin dil ilə bağlı müəyyən təcrübəmiz var idi.

İşin gedişində, sistem e-poçt vasitəsilə məlumatlandırma, əsas və tarixi cədvələ malik olan bir məlumat bazası (bunun haqqında sonra danışacağam), eləcə də VM-lərin faktiki yaradıcısını və onların yaradılma vaxtını tapmaq üçün vSphere loqlarının analizi ilə daha da zənginləşdi.

İnkişaf üçün RStudio Desktop və PowerShell ISE IDE-lərindən istifadə edildi.

Skript adi bir Windows virtual maşınından işə salınır.

Ümumi məntiqin təsviri.

Skriptlərin ümumi məntiqi aşağıdakı kimidir.

  • Məlumatları PowerShell skripti vasitəsilə yığırıq, onu R ilə çağırırıq və nəticələri bir csv faylında birləşdiririk. Dillər arasında geridönüş əlaqəsi bənzəri şəkildə yaradılıb. (Əslində R-dən PowerShell-ə birbaşa dəyişənlər kimi məlumat ötürmək olardı, amma bu çətindir, eləcə də ara csv-lərlə işləmək daha asandır və bu, müvəqqəti nəticələri başqaları ilə bölüşməyə imkan tanıyır).
  • R vasitəsilə yoxlanılacaq sahələrin dəyərləri üçün etibarlı parametrləri formalaşdırırıq. - Məlumatlarını yollamağa cavab olaraq iş yoldaşlarının "Amma mən bunu necə doldurmalıyam?" suallarına cavab vermək üçün bu sahələrin dəyərlərini əks etdirən word sənədini yaradırıq.
  • Bütün VM-lərin məlumatlarını csv-dən R ilə yükləyərək dataframe yaradırıq, lazımsız sahələri çıxarırıq və bütün VM-lər haqqında xülasə məlumat saxlayan informasiya xlsx sənədini hazırlayırıq, bu sənədi ümumi resursa yerləşdiririk.
  • Bütün VM-lərə aid dataframe-ə sahələrin doğru doldurulması ilə bağlı bütün yoxlamaları tətbiq edərək yalnız düzgün doldurulmamış sahələri olan VM-ləri ehtiva edən cədvəl hazırlayırıq.
  • Alınan VM siyahısını başqa bir PowerShell skriptinə göndəririk ki, bu da vCenter loglarını VM-lərin yaradılması üçün hadisələri araşdıracaq. Bu, kiminsə öz maşınının sahibi olduğunu etiraf etmədiyi halda, VM-in təsdiq olunan yaradılma vaxtını və sahibini göstərməyə imkan verəcək. Bu skript qısa sürətdə işləyir, xüsusilə loglar çoxdursa, buna görə yalnız son 2 həftəni izləyirik, eləcə də eyni anda bir neçə VM üçün məlumat axtarışını yerinə yetirməyə imkan tanıyan iş axınını istifadə edirik. Skriptdə bu mexanizmə dair ətraflı şərhlər var. Nəticəni yenidən R-ə yüklədiyimiz csv faylında birləşdiririk.
  • Gözəl formatlanmış bir xlsx sənədi hazırlayırıq, burada səhv doldurulmuş sahələr qırmızı rəngdə vurğulanır, bəzi sütunlara filtrlər tətbiq edilir və ayrıca sütunlar yaradılır ki, bunlar potensial yaradıcılara və VM-in yaradılma vaxtına dair məlumatları ehtiva edir.
  • Bir elektron poçtu hazırlayırıq, burada sahələrin qəbul edilən dəyərlərini təsvir edən sənəd və yanlış doldurulmuş VM-lərin cədvəli əlavə edirik. Mətnin içində səhv yaradılmış VM-lərin ümumi sayını, ümumi resursun linkini və motivasiyaedici şəkil əlavə edirik. Əgər yanlış doldurulmuş VM-lər yoxdursa — daha xoş motivasiyaedici şəkil ilə başqa bir poçt göndəririk.
  • Bütün VM-lərin məlumatlarını SQL Server verilənlər bazasında qeyd edirik, tarixi cədvəllər mexanizmini nəzərə alaraq (çox maraqlı bir mexanizm — bunun haqqında daha ətraflı sonra).

Əslində skriptlər

R dilində kodun əsas faylı

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

# Doldurulmuş sahələrin dəqiqliyini yoxlayırıq
full_df <- fullXslx_df %>%
  mutate(
    # Əvvəlcə bütün lazım olmayan boşluqları və sekmeleri aradan qaldırırıq, sonra ayırıcı kimi vergül nəzərə alınır, daha sonra icazə verilən dəyərlərə uyğunluq yoxlanılır,
    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)
  )

# Bütün VM-lərlə bağlı faylın mövcudluğunu yoxlayırıq və varsa, silirik.
if (file.exists(filenameAll)) {file.remove(filenameAll)}

#### Hesabatla xslx faylını formalayırıq ####
# Ümumi məlumat ayrı bir səhifəyə
full_df %&gt;% write.xlsx(file=filenameAll,
                       sheetName=names[1],
                       col.names=TRUE,
                       row.names=FALSE,
                       append=FALSE)

#### Yanlış doldurulmuş sahələrlə bağlı xslx faylını formalayırıq ####
# df formalaşdırırıq
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)

# Bütün VM-lərlə bağlı faylın mövcudluğunu yoxlayırıq və varsa, silirik.
if (file.exists(filenameIncVM)) {file.remove(filenameIncVM)}

# Boş sahələrlə VM siyahısını csv-yə saxlayırıq
incorrect_df %&gt;%
  select(VM.Name) %&gt;%
  write_csv2(path = filenameIncVM, append = FALSE)

# E-poçta daxil etmək üçün filtr edirik
incorrect_df_filtered <- incorrect_df %>% 
  select(VM.Name, 
         IP.s, 
         Owner, 
         Subsystem, 
         Category,
         Creator,
         vCenter.Name,
         Creation.Date
  )

# Sətir sayı hesablayırıq
numberOfRows <- nrow(incorrect_df)

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

if (numberOfRows > 0) {

  # Yaradanlarla bağlı faylın mövcudluğunu yoxlayırıq və varsa, silirik.
  if (file.exists(creatorsFilePath)) {file.remove(creatorsFilePath)}

  # Tapılan VM-lərin yaradanlarını tapacaq PowerShell skriptini işə salırıq. Nəticədə csv alacağıq.
  system(paste0("powershell -File ", getCreatorsPath))

  # Yaradıcılarla bağlı faylı oxuyuruq
  creators_df <- creatorsFilePath %>%
    read.csv2(stringsAsFactors = FALSE)

  # E-poçta daxil etmək üçün filtr edirik, yaradıcılarla bağlı məlumatları əlavə edirik
  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(`Tövsiyə Edilən Yaradıcı` = CreatedBy, 
           `Tövsiyə Edilən Yaradılış Tarixi` = CreatedOn)  

  # E-poçtun mətni
  emailBody &lt;- paste0(
    &#039;<html>
                    <h3>Salam, hörmətli həmkarlar.</h3>
                    <p>Virtual maşınlar haqqında tam, güncəl məlumatı H: diskində burada görə bilərsiniz:<p>
                    <p>\server.ruVM', sourceFileFormat, '</p>
                    <p>Həmçinin əlavə olunmuş sənəd, <strong>düzgün doldurulmamış</strong> sahələri əhatə edir. Ümumilikdə, <strong>', numberOfRows,'</strong>.</p>
                    <p>Cədvəldə 2 əlavə sütun var. <strong>Təxmini yaradıcısı</strong> və <strong>Təxmini yaradılma tarixi</strong>, bunlar vCenter-in son 2 həftəlik loglarından götürülür</p>
                    <p>Maşın yaradıcılara məlumatları dəqiqləşdirmək və sahələri düzgün doldurmaq xahiş olunur. Sahələrin doldurulması qaydaları da əlavə olunmuşdur.</p>
                    <p><img src="data/meme.jpg"></p>
                    </html>'
  )

  # Faylın mövcudluğunu yoxlayırıq
  if (file.exists(filenameIncorrect)) {file.remove(filenameIncorrect)}

  # Formatlar və s. ilə gözəl cədvəl yaradılır
  source(file = "email.R", local = T, encoding = "utf-8")

  #### Pis imzalanmış maşınlarla bağlı məktub formalaşdırırıq ####
  send.mail(from = emailParams$from,
            to = emailParams$to,
            subject = "Düzgün doldurulmamış sahələri olan VM-lər",
            body = emailBody,
            encoding = "utf-8",
            html = TRUE,
            inline = TRUE,
            smtp = emailParams$smtpParams,
            authenticate = TRUE,
            send = TRUE,
            attach.files = c(filenameIncorrect, filenameVmCreationRules),
            debug = FALSE)

  #### VM-lərlə bağlı problem olmadıqda buraya keçilir ####
} else {

  # Məktubun mətni formalaşdırılır
  emailBody &lt;- paste0(
    &#039;<html>
    <h3>Salam, hörmətli həmkarlar</h3>
   <p>Virtual maşınlar haqqında tam, güncəl məlumatı H: diskində burada görə bilərsiniz:<p>
    <p>\server.ruVM', sourceFileFormat, '</p>
    <p>Həmçinin, cari zamanda, bütün VM sahələri düzgün doldurulub</p>
    <p><img src="data/meme_correct.jpg"></p>
    </html>'
  )

  #### Düzgün doldurulmamış VM-lər olmadan məktub formalaşdırırıq ####
  send.mail(from = emailParams$from,
            to = emailParams$to,
            subject = "Xülasə məlumat",
            body = emailBody,
            encoding = "utf-8",
            html = TRUE,
            inline = TRUE,
            smtp = emailParams$smtpParams,
            authenticate = TRUE,
            send = TRUE,
            debug = FALSE)
}

####### Məlumatları DB-yə yazırıq #####

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

PowerShell-də vm siyahısının əldə olunması skripti

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

Virtual maşınların yaradıcıları və onların yaradılma tarixlərini loglardan çıxaran PowerShell skripti

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

Xüsusi diqqəti cəlb edən kitabxana xlsx, bu da əlavə məktubu daha gözəl formatlanmış (idarə heyətinin sevdiyi kimi) etdi, sadəcə csv cədvəli deyil.

Yanlış doldurulmuş maşınların siyahısını gözəl xlsx sənədinə çevirmək

# Создаём новую книгу
# Возможные значения : "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əticə belə görünəcək:

R və PowerShell ilə virtual maşınların vəziyyəti haqqında gündəlik hesabatlar

Həmçinin Windows scheduler-i tənzimləməklə bağlı maraqlı bir məsələ var idi. Bütün hüquq və tənzimləmə parametrlərini düzgün seçə bilmirdik ki, hər şey istədikləri kimi işə düşsün. Nəticədə R kitabxanası tapıldı ki, bu da R skriptinin icrasını avtomatik olaraq yaradır və hətta log faylı üçün unutmur. Sonra əl ilə tapşırığı düzəldə bilərik.

Windows planlayıcısında tapşırığı yaradan R kodunun bir hissəsi, iki nümunə ilə

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

## skripti 62 saniyədən sonra işə salırıq
taskscheduler_create(taskname = "getAllVm", rscript = myscript, 
                     schedule = "ONCE", starttime = format(Sys.time() + 62, "%H:%M"))

## skripti hər gün 09:10-da işə salırıq
taskscheduler_create(taskname = "getAllVmDaily", rscript = myscript, 
                     schedule = "WEEKLY", 
                     days = c("MON", "TUE", "WED", "THU", "FRI"),
                     starttime = "02:00")

## tapşırıqları silirik
taskscheduler_delete(taskname = "getAllVm")
taskscheduler_delete(taskname = "getAllVmDaily")

# Logları yoxlayırıq (son 4 sətir)
tail(readLines("all_vm.log"), sep ="n", n = 4)

Verilənlər bazası haqqında ayrıca

Skripti tənzimlədikdən sonra digər suallar ortaya çıxdı. Məsələn, VM-nin silindiyi tarixi tapmaq istəyirdik, lakin vCenter-də loglar artıq itmişdi. Skript faylları hər gün bir qovluğa yığıb, təmizlənmir (əlimizlə təmizləyirik, xatırlayanda), bu səbəbdən köhnə faylları gözdən keçirib, o VM-in olmadığı ilk faylı tapa bilərik. Lakin bu o qədər də yaxşı deyil.

Tarix bazası yaratmaq istədim.

Bunun üçün MS SQL SERVER-in sistem-əsaslı temporal cədvəl funksionallığı köməyə gəldi. Bu, adətən, zamanla (nəqədər də olsa) cədvəl kimi tərcümə edilir.

Bunun haqqında daha ətraflı oxumaq olar Microsoft-un rəsmi sənədində.

Qısa desək, cədvəl yaradırıq, onun versiyalı olacağını deyirik və SQL Server bu cədvəldə iki əlavə datetime kolonası (yazının yaradılma tarixi və yazının sona yetmə tarixi) və dəyişikliklərin yazılacağı əlavə cədvəl yaradır. Nəticədə aktual məlumat əldə edirik və, sənəddə verilmiş nümunə tələblərlə, spesifik virtual maşının həyat dövrünü və ya müəyyən bir vaxtda bütün VМ-lərin vəziyyətini görə bilərik.

Performans baxımından – əsas cədvələ yazma əməliyyatı, temporal cədvələ yazma əməliyyatı tamamlanmadan başa çatmayacaq. Yəni yazma əməliyyatlarının çox olduğu cədvəllərdə bu funksionallığı ehtiyatla tətbiq etmək lazımdır, amma bizim halımızda bu, doğrudan da çox maraqlı bir şeydir.

Mexanizm düzgün işləməsi üçün R dilində yeni cədvəli bütün VMs-in məlumatları ilə müqayisə edən və yalnız dəyişdirilmiş sətirləri verilənlər bazasına yazan kiçik bir kod parçasası yazmaq lazım gəldi. Kod çox çətin deyil, compareDF kitabxanasından istifadə edir, amma onu da aşağıda verəcəyəm.

Verilənlərin Baza yanına R kodu

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

Skripti tətbiq etdikdən sonra bir neçə ayda nizam-intizam yaradıldı və saxlanılır. Bəzən səhv doldurulmuş VМ-lər yaranır, amma skript yaxşı bir xatırlatma rolunu oynayır və nadir VМ iki gün ardıcıl siyahıya düşür.

Eyni zamanda, tarix məlumatlarının analizi üçün də bir platforma yaradıldı.

Aydındır ki, bunun bir hissəsi ixtisaslaşmış proqram təminatı ilə həyata keçirilə bilər, amma vəzifə maraqlı və, demək olar ki, fakultativ idi.

R yenə də mükəmməl, çoxfunksiyalı bir dil olduğunu sübut etdi, yalnız statistik vəzifələri həll etməklə deyil, eyni zamanda digər məlumat mənbələri arasında mükəmməl "birləşdirici" rolunu oynayır.

R və PowerShell ilə virtual maşınların vəziyyəti haqqında gündəlik hesabatlar

Mənbə: habr.com

DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər 🔥 DDoS qoruması olan saytlara etibarlı hosting satın alın, VPS VDS serverlər | ProHoster