# # Name: WFA Data Source for VMware vCenter # Vers: 1.1.0 # From: NetApp # Desc: Connects to a VMware vCenter 6.0 server and acquires data into the WFA database. # # Revision History # 1.0.0 = Initial Version # 1.1.0 = Added Logger for easy execution trace. # Handled Lun path when RuntimeName is null # Exclusively converted CSV files to UTF8 Eccoded without BOM # Provided check for CSV files being busy. # # Copyright (C) 2016 NetApp, Inc. All rights reserved. # ###### Create the Logger Configuration WFA ###### #Load the assembley from your dll location $PSScriptRoot = Split-Path $MyInvocation.MyCommand.Path -Parent $dllLocation = $PSScriptRoot.Substring(0, $($PSScriptRoot.Length - 24)) + "PoSH\Modules\DataONTAP\log4net.dll" [void][Reflection.Assembly]::LoadFrom($dllLocation) #Define your logging pattern. See more about it here: http://logging.apache.org/log4net/release/sdk/log4net.Layout.PatternLayout.html $pattern = "%d %w %-5p %c : %m%n" #Reset the log4net configuration [log4net.LogManager]::ResetConfiguration() #Create the Logging file for every single Datasource based on the Hostname. $DSHostName = Get-WfaRestParameter "host" $logFile = $PSScriptRoot.Substring(0, $($PSScriptRoot.Length - 8)) + "\log\" + $DSHostName + ".log" New-Item -Path $logFile -type file -Force #Create the log4net config Appender $Appender = new-object log4net.Appender.FileAppender $Appender.File = $logFile $Appender.Layout = new-object log4net.Layout.PatternLayout($pattern) $Appender.Threshold = [log4net.Core.Level]::All $Appender.ActivateOptions() [log4net.Config.BasicConfigurator]::Configure($Appender) #Create Logger for the DataSource Type Name. You can actually put anything $logg = [log4net.LogManager]::GetLogger("[VMware vCenter 6.0]") ###### Logger is ready ##### $logg.Info("====== Data Source acquisition begins now =======") $hostFile = "./Host.csv" $datastoreFile = "./Data_Store.csv" $lunFile = "./Lun.csv" $virtualDiskFile = "./Virtual_Disk.csv" $nasShareFile = "./Nas_Share.csv" $vmsFile = "./Virtual_Machine.csv" New-Item -Path $hostFile -type file -Force New-Item -Path $datastoreFile -type file -Force New-Item -Path $lunFile -type file -Force New-Item -Path $virtualDiskFile -type file -Force New-Item -Path $nasShareFile -type file -Force New-Item -Path $vmsFile -type file -Force # Ensure that dates are always returned in English [System.Threading.Thread]::CurrentThread.CurrentCulture = "en-US" Function loadVMware { try { if (-not (Get-PSSnapin | ? { $_.name -eq 'VMware.VimAutomation.Core' })) { $logg.Info("Trying to add PS Snapin for VMware") Add-PSSnapin VMware.VimAutomation.Core -ErrorAction Stop } } catch { $logg.Error("FAILED to load PS Snapin for VMware") Throw "Unable to load VMware Poweshell Snapin" } } # Get the credentials for the VirtualCenter set in the Datasource Function getConnectionInfo { $connectionInfo = @{ }; try { $logg.Info("Get credentials for the VMware Host") $connectionInfo["host"] = Get-WfaRestParameter "host" $connectionInfo["port"] = Get-WfaRestParameter "port" $connectionInfo["credentials"] = Get-WfaCredentials } catch { $logg.Error("Error getting data source credentials") throw "Error getting data source credentials. Please see the log file for more details" } $logg.Info("Host credentials found successfully") return $connectionInfo } # This function recieve an array of VMView objects and add the relevant information # to the Virtual_Machine.csv file Function addVmEntriesToVmFile { param ( [parameter(Mandatory = $true, HelpMessage = "Virtual Machine views to add to vm CSV file")] [array]$VirtualMachineViews ) $logg.Info("In function addVmEntriesToVmFile") foreach ($vmView in $VirtualMachineViews) { # Skipping vm if object has no UUID, if UUID already seen (Extreme but possible) or vm is orphaned or corrupted or unavailable if (!$vmView.Config.InstanceUuid -or $vmInstanceUuidsHash[$vmView.Config.InstanceUuid] -or $vmView.Runtime.ConnectionState -ne "connected") { continue } [string]$vmName = $vmView.Name [string]$vmDatastoreId = "\N" [string]$vmInstanceUuid = "\N" [string]$vmPathName = $vmView.Summary.Config.VmPathName [string]$vmHostId = $vmview.Summary.Runtime.Host.GetHashCode() # vmPathName is in the format of [datastoreName] /vmDir/dir/file.vmx $vmDatastore = $dcDatastores | ?{ $vmPathName.StartsWith("[" + $_.Name + "]") } if ($vmDatastore) { $vmDatastoreId = $vmDatastore.Id.GetHashCode() } $vmInstanceUuidsHash[$vmView.Config.InstanceUuid] = $true $vmInstanceUuid = $vmView.Config.InstanceUuid # the wfa uuid is the uuid hashed in order to recieve an integer (uuid has alphabetic chars) [int]$hashedVmInstanceUuid = $vmInstanceUuid.GetHashCode() [int]$vmNumCpu = $vmView.Summary.Config.NumCpu [int]$vmMemoryMb = $vmView.Summary.Config.MemorySizeMB $vmPowerState = $vmview.Summary.Runtime.PowerState $vmGuestState = "\N" [string]$vmGuestOs = $vmView.Guest.GuestFullName [int]$vmIsTemplate = [int]$vmView.Config.Template $vmGuestOs = $vmGuestOs -replace "\n", " " # guest depended information $vmDnsName = "\N" $ipAddress = "\N" $vmBootTime = "\N" if ($vmView.Summary.Runtime.BootTime) { # getting the boottime and save it in a format to fits mysql [DateTime]$bootDate = $vmView.Summary.Runtime.BootTime $vmBootTime = Get-Date -Date $bootDate -Format "yyyy-MM-dd HH:mm:ss" } # there is situations where the PowerState is poweredOff but the guestState is running. # Overriding it - whenever the PowerState is poweredOff GuestState will be null if ($vmPowerState -eq "PoweredOn") { $wfaVmPowerState = "On" $vmGuestState = $vmView.Guest.GuestState if ($vmView.Guest.GuestState -eq "running") { $vmDnsName = $vmView.Guest.HostName if ($vmView.Guest.IpAddress) { $ipAddress = $vmView.Guest.IpAddress } elseif ($vmView.Guest.Net) { $firstNic = $vmView.Guest.Net[0] if ($firstNic.IpConfig.IpAddress.Length -gt 0) { $ipAddress = $firstNic.IpConfig.ipaddress[0].IpAddress } elseif ($firstNic.IpAddress.Length -gt 0) { $ipAddress = $firstNic.ipAddress[0] } } } } elseif ($vmPowerState -eq "PoweredOff") { $wfaVmPowerState = "Off" } else { $wfaVmPowerState = "Suspended" } Add-Content $vmsFile ([Byte[]][Char[]] "$hashedVmInstanceUuid`t$vmDatastoreId`t$vmHostId`t$vmInstanceUuid`t$vmName`t$vmDnsName`t$ipAddress`t$wfaVmPowerState`t$vmBootTime`t$vmGuestState`t$vmGuestOs`t$vmNumCpu`t$vmMemoryMb`t$vmIsTemplate`n") -Encoding Byte [array]$vmVdisks = $vmView.Config.Hardware.Device | ?{ $_ -is [VMware.Vim.VirtualDisk] } if ($vmVdisks) { addDiskEntriesToDiskFile -Disks $vmVdisks -hashedVmInstanceUuid $hashedVmInstanceUuid -vmHostId $vmHostId } } } Function addDiskEntriesToDiskFile { param ( [parameter(Mandatory = $true, HelpMessage = "Target vm disks to add to the Virtual_Disk CSV file")] [array]$Disks, [parameter(Mandatory = $true, HelpMessage = "Hashed UUID of the target vm handled")] [int]$hashedVmInstanceUuid, [parameter(Mandatory = $true, HelpMessage = "The integer part of the vm's running esx/i host")] [int]$VMHostId ) $Disks | %{ $hd = $_ if (!$hd.Backing.Datastore) { # skipping if hd is not accessible continue } $datastoreId = $hd.Backing.Datastore.GetHashCode() # csv null $lunId = "\N" if ($hd.Backing.GetType().Name.Contains("RawDiskMapping")) { # imatating sdk scsi lun id value $lunId = ($VMHostId.ToString() + "/" + $hd.Backing.LunUuid).GetHashCode() } [string]$diskFileName = $hd.Backing.FileName $capacityMB = $hd.CapacityInKB / 1KB # getting all files associated with the curred disk with file from the LayoutEx.Disk array $vmLayoutExDisk = $vmView.LayoutEx.Disk | ?{ $_.Key -eq $hd.Key } # recording only files associated with the disk [array]$arrLayoutExDiskFileKeys = $vmLayoutExDisk.Chain | ?{ $_ -is [VMware.Vim.VirtualMachineFileLayoutExDiskUnit] } # calculating actual size of disk by measuring all disk file actual size and calculate sum $sizeOnDatastoreBytes = ($arrLayoutExDiskFileKeys | %{ $_.FileKey } | %{ $intFileKey = $_ # matching the file from the LayoutEx.File tree with matching key file and that represent # a file that is a diskExtent - part of the disk $vmView.LayoutEx.File | ?{ ($_.Key -eq $intFileKey) -and ($_.Type -eq "diskExtent") } } | Measure-Object -Sum Size).Sum $sizeOnDatastoreMB = [Math]::Round($sizeOnDatastoreBytes / 1MB, 1) Add-Content $virtualDiskFile ([Byte[]][Char[]] "\N`t$datastoreId`t$hashedVmInstanceUuid`t$lunId`t$diskFileName`t$capacityMB`t$sizeOnDatastoreMB`n") -Encoding Byte } } loadVMware $connectionInfo = getConnectionInfo $logg.Info("Trying to connect to VMware Host") try { Connect-VIServer $connectionInfo["host"] -Port $connectionInfo["port"] -Credential $connectionInfo["credentials"] -Protocol https $logg.Info("Connected Successfully") } catch { $logg.Error("Failed to Connect to VmWare Host") throw $_.Message } # Getting the connected virtualcenter server ip. the GetHostAddresses will return an ip in either case of the supplied host (ip/hostname) $virtualCenterIp = ([System.Net.Dns]::GetHostAddresses($connectionInfo["host"]) | Select -First 1).IPAddressToString $datacenters = Get-Datacenter if (!$datacenters) { Disconnect-VIServer -Confirm:$false exit 0 } $globalVmhosts = @() # hash tables $extentsToVmfsDsHash = @{ } $vmInstanceUuidsHash = @{ } foreach ($datacenter in $datacenters) { $logg.Info("For Datacenter $datacenter") $datacenterName = $datacenter.Name $dcDatastores = @() # putting datastore collection ahead of hosts collection as # host collection includes luns connection which uses the exntestovmfsdshash populated in # the datastore section Foreach ($datastore in ($datacenter | Get-Datastore | ?{ $_.Accessible })) { # gathering dc datastores for later us in vm section $dcDatastores += $datastore $datastoreName = $datastore.Name $capacityMB = $datastore.CapacityMB $freeSpaceMB = $datastore.FreeSpaceMB $datastoreId = $datastore.Id.GetHashCode() $type = $datastore.Type if ($type -eq "VMFS") { $datastore.ExtensionData.Info.Vmfs.Extent | %{ $extentsToVmfsDsHash[$_.DiskName] = $datastoreId } } Add-Content $datastoreFile ([byte[]][Char[]] "$datastoreId`t$datastoreName`t$capacityMB`t$freespaceMB`t$type`t$datacenterName`n") -Encoding Byte if ($type -eq "NFS") { $nfsHost = $datastore.RemoteHost $nfsPath = $datastore.RemotePath $datastore.ExtensionData.Host | %{ $mountingHostId = $_.Key.GetHashCode() Add-Content $nasShareFile ([byte[]][Char[]] "\N`t$mountingHostId`t$datastoreId`t$nfsHost`t$nfsPath`t$capacityMB`n") -Encoding Byte } } } # collecting only operational hosts $vmhosts = $datacenter | Get-VMHost -State Connected # collecting all hosts objects for later use. doing that to reduce queries from vc $globalVmhosts += $vmhosts $vmhosts | %{ $name = $_.Name # Getting integer part of the VMHost's moid $hostId = $_.Id.GetHashCode() # ESX and ESXi are different in the way the API stores the management ip address # in their objects as concept changed in ESXi. if ($_.ExtensionData.Summary.Config.Product.Name.Contains("ESXi")) { $ip = ($_.NetworkInfo.VirtualNic | ?{ $_.ManagementTrafficEnabled -eq $true } | Select -First 1).IP } else { $ip = ($_.NetworkInfo.ConsoleNic | Select -First 1).ip } $os_version = $_.ExtensionData.Summary.Config.Product.FullName # the csv null $cluster = "\N" # see if the parent of the host is a cluster if ($_.Parent.GetType().Name -eq "ClusterImpl") { $cluster = $_.Parent.Name } Add-Content $hostFile ([byte[]][char[]] "$hostId`t$name`t$ip`t$os_version`t$virtualCenterIp`t$cluster`t$datacenterName`n") -Encoding Byte } #working with views so we can have size on disk for thin provisioned disks $dcVmViews = Get-View -ViewType VirtualMachine -SearchRoot $datacenter.ExtensionData.MoRef # if dcVmview is null ie no virtual machines exists on the datacenter, the foreach loop is still entered once. avoiding that if ($dcVmViews) { # addVmEntriesToVmFile function also adds the virtual disks entries addVmEntriesToVmFile -VirtualMachineViews $dcVmViews } } $scsiLunUuidToHbaTypeHash = @{ } $scsiLuns = @() # The Get-VMHostHBA and the Get-ScsiLuns are the top time consumers of the datasource # as these return results really slow if ($globalVmhosts) { $hbas = Get-VMHostHba -VMHost $globalVmhosts -ErrorAction SilentlyContinue $hbas | %{ $hba = $_ # collecting only SCSI luns which their primary opertional state is 'ok' $hbaDiskLuns = Get-ScsiLun -Hba $hba -LunType disk -ErrorAction SilentlyContinue | ?{ ($_.ExtensionData.OperationalState[0] -eq "ok") -or ($_.ExtensionData.OperationalState[0] -eq "quiesced") } # setting type for each lun in hash # if the current hbas is not seeing any online disk scsi luns and $hbaDiskLuns is $null - it loops single time even though # it is null casuing null entry in $scsiLuns collection -> adding validation before if ($hbaDiskLuns) { $hbaDiskLuns | %{ $scsiLunUuidToHbaTypeHash[$_.ExtensionData.Uuid] = $hba.Type } # adding to all disk luns array for later use $scsiLuns += $hbaDiskLuns } } } if ($scsiLuns) { $seenScsiLunsHash = @{ } foreach ($scsi_lun in $scsiLuns) { $lunUuid = $scsi_lun.ExtensionData.Uuid # index 2 because this is a full moref in format of HostSystem-host-xxxx and not moref value $hostId = $scsi_lun.VMHostId.GetHashCode() $lunCanonicalName = $scsi_lun.CanonicalName # same lun will have the same uuid on its different instances from the different esx # so creating a unique id based on the host and the lun uuids $lunIdentifier = ($hostId.ToString() + "/" + $lunUuid).GetHashCode() #eliminate duplications of luns casued by multiple LUN paths on the vmhost if (!$seenScsiLunsHash[$lunIdentifier]) { $seenScsiLunsHash[$lunIdentifier] = $true; $lunDatastoreId = $extentsToVmfsDsHash[$lunCanonicalName] if (!$lunDatastoreId) { # csv null $lunDatastoreId = "\N" } $lunTransport = "\N" #Try to get the arrRuntime for each lun. If not present try to get the Lun Path try { # run time in the format vmhbaXX:CX:TX:LXXX $arrRuntime = $scsi_lun.RuntimeName.Split(":") $vmhostHbaName = $arrRuntime[0] $targetId = [int]$arrRuntime[2].SubString(1) # the LUN number $lunNumber = [int]$arrRuntime[3].subString(1) $sizeMB = $scsi_lun.CapacityMB $logg.Info("Successfully obtained LUN RuntimeName for lun : $lunCanonicalName") } catch { # Get the first ACTIVE LUN path in format vmhbaXX:CX:TX:LXXX $logg.Info("Failed to get LUN data from RuntimeName for Lun $lunCanonicalName. Using cmdlet Get-ScsiLunPath") $paths = Get-ScsiLunPath -ScsiLun $scsi_lun try { foreach ($path in $paths) { if ($path.State -eq "Active") { $arrRuntime = $path.Name.Split(":") break } } $vmhostHbaName = $arrRuntime[0] $targetId = [int]$arrRuntime[2].subString(1) $lunNumber = [int]$arrRuntime[3].subString(1) $sizeMB = $scsi_lun.CapacityMB $logg.Info("Successfully obtained LUN data for lun : $lunCanonicalName") } catch { #Some Luns(perhaps from unknown non NetApp vendors) do not return ACTIVE paths in format of vmhbaXX:CX:TX:LXXX. Such Luns will be ignored. $logg.Error("FAILED to get Lun Info for the path ACTIVE path $path. Ignoring this lun path: $path") continue } } switch ($scsiLunUuidToHbaTypeHash[$scsi_lun.ExtensionData.Uuid]) { "Block" { $lunTransport = "Local"; break } "ParallelSCSI" { $lunTransport = "Local"; break } "FibreChannel" { $lunTransport = "FibreChannel"; break } "iSCSI" { $lunTransport = "iSCSI"; break } } Add-Content $lunFile ([Byte[]][Char[]] "$lunIdentifier`t$hostId`t$lunDatastoreId`t$vmhostHbaName`t$lunNumber`t$targetId`t$sizeMB`t$lunCanonicalName`t$lunTransport`n") -Encoding Byte } } } $logg.Info("Disconneting from VMware Host") Disconnect-VIServer -Confirm:$false $logg.Info("Disconneted Successfully") #The CSV files may not be of UTF8 encoded. Convert the encoding of the all CSV files to UTF8 $logg.Info("Changing File Encoding to UTF8 without BOM") try{ $Hostcontent = get-content $hostFile $dscontent = get-content $datastoreFile $luncontent = get-content $lunFile $vdcontent = get-content $virtualDiskFile $nscontent = get-content $nasShareFile $vmcontent = get-content $vmsFile sleep 5 $dscontent | Set-Content -Path $datastoreFile -Encoding ascii $Hostcontent | Set-Content -Path $hostFile -Encoding ascii $luncontent | Set-Content -Path $lunFile -Encoding ascii $vdcontent | Set-Content -Path $virtualDiskFile -Encoding ascii $nscontent | Set-Content -Path $nasShareFile -Encoding ascii $vmcontent | Set-Content -Path $vmsFile -Encoding ascii } catch { $logg.Warn("Unable to convert the content to ascii") throw 'Unable to convert the content to ascii' }