ورود
عصر بخیر. نیم سال است که ما یک اسکریپت (یا بهتر بگوییم مجموعه ای از اسکریپت ها) را اجرا می کنیم که گزارش هایی در مورد وضعیت ماشین های مجازی (و نه تنها) تولید می کند. تصمیم گرفتم تجربه ایجاد و خود کد را به اشتراک بگذارم. امیدوارم انتقاد شود و این مطالب برای کسی مفید باشد.
شکل گیری نیاز
ما ماشین های مجازی زیادی داریم (حدود 1500 ماشین مجازی که در 3 مرکز مجازی توزیع شده اند). موارد جدید ایجاد می شوند و موارد قدیمی اغلب حذف می شوند. برای حفظ نظم، چندین فیلد سفارشی به vCenter اضافه شد تا ماشینهای مجازی را به زیرسیستمها تقسیم کنند، نشان دهند که آیا آزمایشی هستند و توسط چه کسی و چه زمانی ایجاد شدهاند. عامل انسانی منجر به این واقعیت شد که بیش از نیمی از ماشین ها با فیلدهای خالی باقی ماندند که کار را پیچیده کرد. هر شش ماه یک بار، شخصی ترسید و شروع به کار روی بهروزرسانی این دادهها کرد، اما نتیجه پس از یک هفته و نیم دیگر مرتبط نبود.
اجازه دهید فوراً توضیح دهم که همه می دانند که باید برنامه هایی برای ایجاد ماشین آلات، فرآیندی برای ایجاد آنها و غیره وجود داشته باشد. و غیره و در عین حال همه به شدت این روند را دنبال می کنند و همه چیز مرتب است. متأسفانه اینجا اینطور نیست، اما موضوع مقاله این نیست :)
به طور کلی، تصمیم به بررسی خودکار صحت پر کردن فیلدها گرفته شد.
ما تصمیم گرفتیم که یک نامه روزانه با لیستی از ماشین آلات پر شده اشتباه به همه مهندسان مسئول و روسای آنها شروع خوبی باشد.
در این مرحله، یکی از همکاران من قبلاً یک اسکریپت را در PowerShell پیاده سازی کرده بود که هر روز، طبق یک برنامه، اطلاعات مربوط به تمام ماشین های تمام vCenter ها را جمع آوری می کرد و 3 سند csv (هر کدام برای vCenter خود) تولید می کرد که در آن آپلود می شد. یک دیسک مشترک تصمیم بر این شد که این اسکریپت را مبنا قرار دهیم و با چک هایی با زبان R که تجربه ای با آن داشتیم تکمیل شود.
در فرآیند نهاییسازی، راهحل اطلاعاتی را از طریق پست، پایگاهدادهای با جدول اصلی و تاریخی (در ادامه در این مورد بیشتر توضیح میدهد)، و همچنین تجزیه و تحلیل گزارشهای vSphere برای یافتن سازندگان واقعی vm و زمان ایجاد آنها به دست آورد.
IDE RStudio Desktop و PowerShell ISE برای توسعه استفاده شد.
این اسکریپت از یک ماشین مجازی ویندوز معمولی راه اندازی می شود.
شرح منطق کلی
منطق کلی فیلمنامه ها به شرح زیر است.
- ما با استفاده از یک اسکریپت PowerShell که از طریق R فراخوانی می کنیم، داده ها را روی ماشین های مجازی جمع آوری می کنیم و نتیجه را در یک csv ترکیب می کنیم. تعامل معکوس بین زبان ها به همین ترتیب انجام می شود. (این امکان وجود داشت که داده ها را مستقیماً از R به PowerShell در قالب متغیرها هدایت کنید، اما این کار دشواری است و داشتن csv های متوسط باعث می شود اشکال زدایی و به اشتراک گذاری نتایج میانی با شخصی آسان تر شود).
- با استفاده از R، پارامترهای معتبری را برای فیلدهایی که مقادیر آنها را بررسی می کنیم، تشکیل می دهیم. - ما در حال ایجاد یک سند word هستیم که حاوی مقادیر این فیلدها برای درج در نامه اطلاعاتی است که پاسخ سوالات همکاران "نه، اما چگونه باید این را پر کنم؟"
- ما دادهها را برای تمام ماشینهای مجازی از csv با استفاده از R بارگیری میکنیم، یک دیتافریم ایجاد میکنیم، فیلدهای غیرضروری را حذف میکنیم و یک سند xlsx اطلاعاتی ایجاد میکنیم که حاوی اطلاعات خلاصهای برای همه ماشینهای مجازی است که آنها را در یک منبع مشترک آپلود میکنیم.
- ما تمام بررسیها را برای صحت پر کردن فیلدها روی dataframe برای همه ماشینهای مجازی اعمال میکنیم و جدولی ایجاد میکنیم که فقط ماشینهای مجازی با فیلدهای نادرست پر شده (و فقط این فیلدها) باشد.
- ما لیست حاصل از ماشین های مجازی را به اسکریپت PowerShell دیگری می فرستیم، که به گزارش های vCenter برای رویدادهای ایجاد VM نگاه می کند، که به ما امکان می دهد زمان تخمینی ایجاد VM و سازنده مورد نظر را نشان دهیم. این برای زمانی است که هیچ کس اعتراف نمی کند که ماشین کی است. این اسکریپت به سرعت کار نمی کند، به خصوص اگر لاگ های زیادی وجود داشته باشد، بنابراین ما فقط به 2 هفته گذشته نگاه می کنیم، و همچنین از یک گردش کاری استفاده می کنیم که به شما امکان می دهد همزمان اطلاعات چندین ماشین مجازی را جستجو کنید. اسکریپت مثال حاوی نظرات دقیق در مورد این مکانیسم است. نتیجه را به یک csv اضافه می کنیم که دوباره در R بارگذاری می کنیم.
- ما یک سند xlsx با قالب بندی زیبا ایجاد می کنیم که در آن فیلدهای پر شده به اشتباه با رنگ قرمز برجسته می شوند، فیلترها روی برخی از ستون ها اعمال می شوند و ستون های اضافی حاوی سازندگان مورد نظر و زمان ایجاد VM نشان داده می شود.
- ما یک ایمیل ایجاد می کنیم که در آن سندی را ضمیمه می کنیم که مقادیر فیلدهای معتبر را توصیف می کند، و همچنین جدولی با فیلدهای نادرست پر شده است. در متن، تعداد کل ماشینهای مجازی اشتباه ایجاد شده، پیوندی به یک منبع مشترک و تصویر انگیزشی را نشان میدهیم. اگر ماشین مجازی پر شده اشتباهی وجود نداشته باشد، نامه دیگری با تصویر انگیزشی شادتر ارسال می کنیم.
- ما داده ها را برای تمام ماشین های مجازی در پایگاه داده 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 زیبا با لیستی از ماشین هایی که به اشتباه پر شده اند
# Создаём новую книгу
# Возможные значения : "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 پیدا شد، که خود وظیفه ای برای راه اندازی یک اسکریپت R ایجاد می کند و حتی فایل log را فراموش نمی کند. سپس می توانید کار را به صورت دستی اصلاح کنید.
یک تکه کد R با دو مثال که یک کار در Windows Scheduler ایجاد می کند
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)
به طور جداگانه در مورد پایگاه داده
پس از تنظیم اسکریپت، مشکلات دیگری ظاهر شد. به عنوان مثال، من می خواستم تاریخ حذف VM را پیدا کنم، اما لاگ های vCenter قبلا فرسوده شده بودند. از آنجایی که اسکریپت هر روز فایلها را در یک پوشه قرار میدهد و آن را تمیز نمیکند (وقتی یادمان میآید آن را با دست خود تمیز میکنیم)، میتوانید فایلهای قدیمی را جستجو کنید و اولین فایلی را پیدا کنید که این ماشین مجازی در آن وجود ندارد. اما این جالب نیست.
من می خواستم یک پایگاه داده تاریخی ایجاد کنم.
عملکرد MS SQL SERVER - جدول زمانی نسخه سیستمی - به کمک آمد. معمولاً به عنوان جداول موقت (نه موقت) ترجمه می شود.
شما می توانید به طور مفصل بخوانید در
به طور خلاصه، ما یک جدول ایجاد می کنیم، می گوییم که آن را با نسخه سازی خواهیم داشت، و SQL Server 2 ستون تاریخ اضافی در این جدول ایجاد می کند (تاریخ ایجاد رکورد و تاریخ انقضای رکورد) و یک جدول اضافی که در آن تغییر می کند. نوشته خواهد شد. در نتیجه، ما اطلاعات بهروز دریافت میکنیم و از طریق پرسوجوهای ساده، که نمونههایی از آن در مستندات آمده است، میتوانیم چرخه عمر یک ماشین مجازی خاص یا وضعیت همه ماشینهای مجازی را در یک نقطه مشخص ببینیم. به موقع.
از منظر عملکرد، تراکنش نوشتن در جدول اصلی تا زمانی که تراکنش نوشتن در جدول موقت کامل نشود، کامل نخواهد شد. آن ها در جداول با تعداد زیادی عملیات نوشتن، این قابلیت باید با احتیاط اجرا شود، اما در مورد ما این یک چیز واقعا جالب است.
برای اینکه مکانیزم به درستی کار کند، باید یک کد کوچک در 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)
در کل
در نتیجه اجرای فیلمنامه، ظرف چند ماه نظم برقرار شد و حفظ شد. گاهی اوقات ماشین های مجازی پر نادرست ظاهر می شوند، اما اسکریپت به عنوان یک یادآوری خوب عمل می کند و یک ماشین مجازی نادر برای 2 روز متوالی وارد لیست می شود.
همچنین برای تجزیه و تحلیل داده های تاریخی مقدماتی ایجاد شد.
واضح است که بسیاری از این موارد را می توان نه روی زانو، بلکه با نرم افزارهای تخصصی پیاده سازی کرد، اما کار جالب و شاید بتوان گفت اختیاری بود.
R بار دیگر خود را به عنوان یک زبان جهانی عالی نشان داده است که نه تنها برای حل مسائل آماری عالی است، بلکه به عنوان یک "لایه" عالی بین سایر منابع داده عمل می کند.
منبع: www.habr.com