تقارير يومية عن صحة الأجهزة الافتراضية باستخدام R وPowerShell

تقارير يومية عن صحة الأجهزة الافتراضية باستخدام R وPowerShell

دخول

مساء الخير. منذ نصف عام، نقوم بتشغيل برنامج نصي (أو بالأحرى مجموعة من البرامج النصية) يقوم بإنشاء تقارير عن حالة الأجهزة الافتراضية (وليس فقط). قررت مشاركة تجربتي في الإنشاء والرمز نفسه. أتمنى النقد وأن تكون هذه المادة مفيدة لشخص ما.

تشكيل الحاجة

لدينا الكثير من الأجهزة الافتراضية (حوالي 1500 جهاز افتراضي موزعة على 3 مراكز افتراضية). يتم إنشاء عناصر جديدة ويتم حذف العناصر القديمة في كثير من الأحيان. للحفاظ على النظام، تمت إضافة العديد من الحقول المخصصة إلى vCenter لتقسيم الأجهزة الافتراضية إلى أنظمة فرعية، والإشارة إلى ما إذا كانت أنظمة اختبارية، ومن قام بإنشائها ومتى. وأدى العامل البشري إلى ترك أكثر من نصف الآلات مع حقول فارغة، مما أدى إلى تعقيد العمل. مرة واحدة كل ستة أشهر، كان شخص ما يشعر بالفزع وبدأ العمل على تحديث هذه البيانات، لكن النتيجة لم تعد ذات صلة بعد أسبوع ونصف.
اسمحوا لي أن أوضح على الفور أن الجميع يفهم أنه يجب أن تكون هناك تطبيقات لإنشاء الآلات، وعملية إنشائها، وما إلى ذلك. وما إلى ذلك وهلم جرا. وفي الوقت نفسه، يتبع الجميع هذه العملية بدقة وكل شيء في النظام. للأسف ليس هذا هو الحال هنا، ولكن ليس هذا موضوع المقال :)

بشكل عام، تم اتخاذ القرار بأتمتة التحقق من صحة ملء الحقول.
قررنا أن إرسال رسالة يومية تحتوي على قائمة بالآلات المملوءة بشكل غير صحيح إلى جميع المهندسين المسؤولين ورؤسائهم سيكون بداية جيدة.

في هذه المرحلة، كان أحد زملائي قد نفذ بالفعل برنامجًا نصيًا في PowerShell، والذي يقوم كل يوم، وفقًا لجدول زمني، بجمع معلومات عن جميع الأجهزة في جميع مراكز vCenters وإنشاء 3 مستندات بتنسيق csv (كل منها لـ vCenter الخاص به)، والتي تم تحميلها إلى قرص مشترك. تقرر أن نأخذ هذا البرنامج النصي كأساس ونكمله بعمليات التحقق باستخدام لغة R، التي لدينا بعض الخبرة فيها.

في عملية وضع اللمسات النهائية، حصل الحل على معلومات عن طريق البريد، وقاعدة بيانات تحتوي على جدول رئيسي وتاريخي (المزيد حول هذا لاحقًا)، بالإضافة إلى تحليل سجلات vSphere للعثور على المنشئين الفعليين لـ vm ووقت إنشائهم.

تم استخدام IDE RStudio Desktop وPowerShell ISE للتطوير.

يتم تشغيل البرنامج النصي من جهاز ظاهري يعمل بنظام Windows.

وصف المنطق العام.

المنطق العام للنصوص هو كما يلي.

  • نقوم بجمع البيانات على الأجهزة الافتراضية باستخدام برنامج PowerShell النصي، والذي نستدعيه من خلال R، ونجمع النتيجة في ملف CSV واحد. ويتم التفاعل العكسي بين اللغات بالمثل. (كان من الممكن نقل البيانات مباشرة من R إلى PowerShell في شكل متغيرات، ولكن هذا أمر صعب، كما أن وجود ملفات CSV وسيطة يجعل من السهل تصحيح الأخطاء ومشاركة النتائج المتوسطة مع شخص ما).
  • باستخدام R، نقوم بتكوين معلمات صالحة للحقول التي نتحقق من قيمها. - نقوم بإنشاء مستند Word يحتوي على قيم هذه الحقول لإدراجها في خطاب المعلومات، والذي سيكون بمثابة إجابة لأسئلة الزملاء "لا، ولكن كيف يجب أن أقوم بملء هذا؟"
  • نقوم بتحميل البيانات لجميع الأجهزة الافتراضية من ملف CSV باستخدام R، وإنشاء إطار بيانات، وإزالة الحقول غير الضرورية وإنشاء مستند معلومات xlsx يحتوي على معلومات ملخصة لجميع الأجهزة الافتراضية، والتي نقوم بتحميلها إلى مورد مشترك.
  • نحن نطبق جميع عمليات التحقق من صحة ملء الحقول في إطار البيانات لجميع الأجهزة الافتراضية وننشئ جدولًا يحتوي فقط على الأجهزة الافتراضية التي تحتوي على حقول مملوءة بشكل غير صحيح (وهذه الحقول فقط).
  • نرسل القائمة الناتجة من الأجهزة الافتراضية إلى برنامج PowerShell النصي آخر، والذي سينظر في سجلات vCenter لأحداث إنشاء الأجهزة الافتراضية، مما سيسمح لنا بالإشارة إلى الوقت المقدر لإنشاء الجهاز الافتراضي والمنشئ المقصود. هذا هو الحال عندما لا يعترف أحد بسيارته. لا يعمل هذا البرنامج النصي بسرعة، خاصة إذا كان هناك الكثير من السجلات، لذلك ننظر فقط إلى الأسبوعين الأخيرين، ونستخدم أيضًا سير عمل يسمح لك بالبحث عن معلومات حول العديد من الأجهزة الافتراضية في نفس الوقت. يحتوي المثال النصي على تعليقات مفصلة حول هذه الآلية. نضيف النتيجة إلى ملف CSV، ونقوم بتحميله مرة أخرى إلى R.
  • نقوم بإنشاء مستند xlsx منسق بشكل جميل حيث سيتم تمييز الحقول المملوءة بشكل غير صحيح باللون الأحمر، وسيتم تطبيق المرشحات على بعض الأعمدة، وسيتم الإشارة إلى الأعمدة الإضافية التي تحتوي على المبدعين المقصودين ووقت إنشاء الجهاز الافتراضي.
  • نقوم بإنشاء بريد إلكتروني، حيث نرفق مستندًا يصف قيم الحقول الصالحة، بالإضافة إلى جدول يحتوي على حقول مملوءة بشكل غير صحيح. نشير في النص إلى العدد الإجمالي للأجهزة الافتراضية التي تم إنشاؤها بشكل غير صحيح، ورابط لمورد مشترك وصورة تحفيزية. إذا لم تكن هناك أجهزة افتراضية مملوءة بشكل غير صحيح، فإننا نرسل خطابًا آخر يحتوي على صورة تحفيزية أكثر سعادة.
  • نقوم بتسجيل البيانات لجميع الأجهزة الافتراضية في قاعدة بيانات SQL Server، مع الأخذ في الاعتبار الآلية المطبقة للجداول التاريخية (آلية مثيرة للاهتمام للغاية - سنتحدث عنها لاحقًا)

في الواقع البرامج النصية

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

البرنامج النصي للحصول على قائمة vm في 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 النصي الذي يستخرج من سجلات منشئي الأجهزة الافتراضية وتاريخ إنشائها

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

المكتبة تستحق اهتماما خاصا XLSX، مما جعل من الممكن جعل المرفق بالرسالة منسقًا بشكل واضح (كما تحب الإدارة)، وليس مجرد جدول CSV.

إنشاء مستند xlsx جميل يحتوي على قائمة بالآلات المملوءة بشكل غير صحيح

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

يبدو الإخراج مثل هذا:

تقارير يومية عن صحة الأجهزة الافتراضية باستخدام R وPowerShell

كان هناك أيضًا فارق بسيط مثير للاهتمام حول إعداد برنامج جدولة Windows. كان من المستحيل العثور على الحقوق والإعدادات الصحيحة حتى يبدأ كل شيء كما ينبغي. ونتيجة لذلك، تم العثور على مكتبة R، والتي تقوم في حد ذاتها بإنشاء مهمة لتشغيل البرنامج النصي R ولا تنسى حتى ملف السجل. ثم يمكنك تصحيح المهمة يدويًا.

جزء من كود R مع مثالين يقوم بإنشاء مهمة في برنامج جدولة Windows

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

بشكل منفصل عن قاعدة البيانات

بعد إعداد البرنامج النصي، بدأت مشاكل أخرى في الظهور. على سبيل المثال، كنت أرغب في العثور على التاريخ الذي تم فيه حذف الجهاز الافتراضي، ولكن السجلات الموجودة في vCenter كانت مهترئة بالفعل. نظرًا لأن البرنامج النصي يضع الملفات في مجلد كل يوم ولا ينظفها (نقوم بتنظيفها بأيدينا عندما نتذكرها)، يمكنك البحث في الملفات القديمة والعثور على الملف الأول الذي لا يوجد فيه هذا الجهاز الافتراضي. ولكن هذا ليس رائعا.

كنت أرغب في إنشاء قاعدة بيانات تاريخية.

جاءت وظيفة MS SQL SERVER - الجدول الزمني لإصدار النظام - للإنقاذ. وعادة ما يتم ترجمتها كجداول مؤقتة (وليست مؤقتة).

يمكنك القراءة بالتفصيل على وثائق مايكروسوفت الرسمية.

باختصار، نقوم بإنشاء جدول، لنفترض أننا سنحصل عليه مع الإصدار، ويقوم SQL Server بإنشاء عمودين إضافيين للتاريخ والوقت في هذا الجدول (تاريخ إنشاء السجل وتاريخ انتهاء صلاحية السجل) وجدول إضافي يتم فيه التغييرات وسوف تكون مكتوبة. ونتيجة لذلك، نتلقى معلومات محدثة، ومن خلال الاستعلامات البسيطة، التي يتم تقديم أمثلة عليها في الوثائق، يمكننا أن نرى إما دورة حياة جهاز افتراضي معين، أو حالة جميع الأجهزة الافتراضية في نقطة معينة في الوقت المناسب.

من منظور الأداء، لن تكتمل معاملة الكتابة إلى الجدول الرئيسي حتى تكتمل معاملة الكتابة إلى الجدول المؤقت. أولئك. على الجداول التي تحتوي على عدد كبير من عمليات الكتابة، يجب تنفيذ هذه الوظيفة بحذر، لكنها في حالتنا شيء رائع حقًا.

لكي تعمل الآلية بشكل صحيح، كان علي إضافة جزء صغير من التعليمات البرمجية في لغة R من شأنه أن يقارن الجدول الجديد ببيانات جميع الأجهزة الافتراضية مع تلك المخزنة في قاعدة البيانات ويكتب فيها الصفوف التي تم تغييرها فقط. الكود ليس ذكيًا بشكل خاص، فهو يستخدم مكتبة CompareDF، لكنني سأقدمه أيضًا أدناه.

رمز R لكتابة البيانات إلى قاعدة البيانات

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

في المجموع

نتيجة لتنفيذ البرنامج النصي، تمت استعادة النظام والحفاظ عليه في غضون بضعة أشهر. في بعض الأحيان تظهر أجهزة افتراضية مملوءة بشكل غير صحيح، ولكن البرنامج النصي يعمل بمثابة تذكير جيد ويتم إدراج جهاز افتراضي نادر في القائمة لمدة يومين متتاليين.

كما تم وضع الأساس لتحليل البيانات التاريخية.

من الواضح أن الكثير من هذا لا يمكن تنفيذه على الركبة، ولكن باستخدام برامج متخصصة، لكن المهمة كانت مثيرة للاهتمام ويمكن القول أنها اختيارية.

لقد أثبتت لغة R مرة أخرى أنها لغة عالمية ممتازة، وهي مثالية ليس فقط لحل المشكلات الإحصائية، ولكنها تعمل أيضًا بمثابة "طبقة" ممتازة بين مصادر البيانات الأخرى.

تقارير يومية عن صحة الأجهزة الافتراضية باستخدام R وPowerShell

المصدر: www.habr.com

إضافة تعليق