Just to show where we're at right now... The only part not working is the threshold PATCH at the end of the group loop (same disclaimer as always; neither I nor Beyond Impact 2.0, LLC warranty this software. It's aggressive, use with caution):
Import-Module CredentialManager
function Send-Request {
param (
$cred ,
$URL ,
$accessid = $null,
$accesskey = $null,
$data = $null,
$version = '2' ,
$httpVerb = "GET"
)
if ( $accessId -eq $null) {
$accessId = $cred.UserName
$accessKey = $cred.GetNetworkCredential().Password
}
<# Use TLS 1.2 #>
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
<# Get current time in milliseconds #>
$epoch = [Math]::Round(
( New-TimeSpan `
-start (Get-Date -Date "1/1/1970") `
-end (Get-Date).ToUniversalTime()).TotalMilliseconds
)
<# Concatenate Request Details #>
$requestVars = $httpVerb + $epoch + $data + $resourcePath
<# Construct Signature #>
$hmac = New-Object System.Security.Cryptography.HMACSHA256
$hmac.Key = [Text.Encoding]::UTF8.GetBytes( $accessKey )
$signatureBytes = $hmac.ComputeHash( [Text.Encoding]::UTF8.GetBytes( $requestVars ) )
$signatureHex = [System.BitConverter]::ToString( $signatureBytes ) -replace '-'
$signature = [System.Convert]::ToBase64String( [System.Text.Encoding]::UTF8.GetBytes( $signatureHex.ToLower() ) )
<# Construct Headers #>
$auth = 'LMv1 ' + $accessId + ':' + $signature + ':' + $epoch
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add( "Authorization", $auth )
$headers.Add( "Content-Type" , 'application/json' )
# uses version 2 of the API
$headers.Add( "X-version" , $version )
<# Make Request #>
$response = Invoke-RestMethod `
-Uri $URL `
-Method $httpVerb `
-Body $data `
-Header $headers
Return $response
}
function Get-LMRestAPIObjectListing {
param (
$URLBase ,
$resourcePathRoot , # "/device/devices"
$size = 1000 ,
$accessKey ,
$accessId
)
$output = @()
$looping = $true
$counter = 0
# write-host "Gathering data from $resourcePathRoot"
while ($looping) {
#re-calc offset based on iteration
$offset = $counter * $size
$resourcePath = $resourcePathRoot
$queryParam = "?size=$size&offset=$offset"
$url = $URLBase + $resourcePath + $queryParam
# Make Request
$response = Send-Request `
-accesskey $accessKey `
-accessid $accessId `
-URL $url
if ( $response.items.count -eq $size ) {
# Return set is full, more items to retrieve
$output += $response.items
$counter++
} elseif ( $response.items.count -gt 0 ) {
# Return set is not full, store date, end loop
$output += $response.items
$looping = $false
} else {
# Return set is empty, no data to store, end loop
$looping = $false
}
}
write-output $output
}
function Calculate-Threshold {
param (
$collectorCount = 2,
$collectorMemory = 4,
$InstanceCount
)
# Effective Threshold Calculation:
# (Target_Collector_Mem/Medium_Mem)^1/2 * Medium_Threshold
# Assumes all collectors are configured the same size
$mediumMemory = 4
# Calculate the threshold to enter to achieve the correct "effective threshold"
$effectiveThreshold = [int](( $InstanceCount/$collectorCount ) / ( [math]::pow(($collectorMemory / $mediumMemory),.5) ))
# output - I'm rounding the result up to the nearest *50 or *00 to allow for a bit of wiggle room
$lastDigits = [int]$(-join ($effectiveThreshold.ToString()[-2..-1]))
if ( $lastDigits -gt 50 ) {
$addend = 100 - $lastDigits
} else {
$addend = 50 - $lastDigits
}
write-output ([int]$effectiveThreshold + [int]$addend)
}
$company = "YourCompanyNameHere"
$URLBase = "https://$company.logicmonitor.com/santaba/rest"
# This will resolve to proper values if it's being run from inside LM
$accessID = "##Logicmonitor.AccessID.key##"
$accessKey = "##Logicmonitor.AccessKey.key##"
if ( $accessID -like "##*" ) {
# Not being run from inside LM - populate manually for testing
Import-Module CredentialManager
$Cred = Get-StoredCredential -Target LogicMonitor
$accessID = $cred.UserName
$accessKey = $Cred.GetNetworkCredential().Password
}
#region Get Groups
$resourcePath = "/setting/collector/groups"
$collectorGroups = Get-LMRestAPIObjectListing `
-resourcePathRoot $resourcePath `
-accessKey $accessKey `
-accessId $accessID `
-URLBase $URLBase
#endregion
#region Get collectors
$resourcePath = "/setting/collector/collectors"
$collectors = Get-LMRestAPIObjectListing `
-resourcePathRoot $resourcePath `
-accessKey $accessKey `
-accessId $accessID `
-URLBase $URLBase
#endregion
#region Get Devices
$resourcePath = "/device/devices"
$devices = Get-LMRestAPIObjectListing `
-resourcePathRoot $resourcePath `
-accessKey $accessKey `
-accessId $accessID `
-URLBase $URLBase
#endregion
# Get list of Resources
$members = @{}
foreach ( $group in ( $collectors.collectorgroupname | sort -unique ) ) {
write-host $group
# Find Collectors
$groupCollectors = $collectors | ? collectorGroupName -eq $group
$collectorCount = $groupCollectors.count
$collectorGroupID = ($collectorGroups | ? name -like "*$group*").id
# foreach member
$deviceCollection = $()
foreach ( $member in ( $devices | ? preferredCollectorGroupName -eq $group ) ) {
$instances = 0
# # foreach devicedatasource
$resourcePath = "/device/devices/$($member.id)/devicedatasources"
$deviceDSs = Get-LMRestAPIObjectListing `
-resourcePathRoot $resourcePath `
-accessKey $accessKey `
-accessId $accessID `
-URLBase $URLBase
# # # count instances?
foreach ( $DS in $deviceDSs ) {
$instances += $DS.instanceNumber
}
$deviceCollection += @(@{
'deviceID' = $member.id
'instances' = $instances
'collectorgroup' = $group
})
# write-host "$group - $($member.id) - $instances"
}
$members[$group] += $deviceCollection
# Sort by Instance count
$flipFlop = $false
$currentCollector = 0
$groupInstances = 0
$members[$group].instances `
| sort -Unique `
| foreach { $members[$group] | ? instances -eq $_ } `
| foreach {
$collectorID = $groupCollectors[$currentCollector].id
# Set the preferred ABCG to 0 and
# Move every other to the next Collector in the group
# write-host "`t$($_.deviceID)`t$($_.instances)`t$collectorID" -ForegroundColor White
$resourcePath = "/device/devices/$($_.deviceID)"
$data = @"
{
`"autoBalancedCollectorGroupId`": `"0`",
`"preferredCollectorId`" : `"$collectorID`"
}
"@
$url = $URLBase + $resourcePath
$properties = Send-Request `
-accessKey $accessKey `
-accessId $accessID `
-data $data `
-URL $URL `
-httpVerb "PATCH"
$groupInstances += $_.instances
$nextCollector = $currentCollector + 1
if ( ($collectorCount -gt 1) -and ($nextCollector -lt $collectorCount) ) {
$currentCollector = $nextCollector
} else {
$currentCollector = 0
}
}
# Set the preferred ABCG back to the original Setting
$members[$group].instances `
| sort -Unique `
| foreach { $members[$group] | ? instances -eq $_ } `
| foreach {
$resourcePath = "/device/devices/$($_.deviceID)"
$data = @"
{
`"autoBalancedCollectorGroupId`": `"$collectorGroupID`"
}
"@
$url = $URLBase + $resourcePath
$properties = Send-Request `
-accessKey $accessKey `
-accessId $accessID `
-data $data `
-URL $URL `
-httpVerb "PATCH"
}
# Set the threshold for the ABCG based on number of instances and the size of the collectors
<#
$threshold = Calculate-Threshold `
-InstanceCount $groupInstances `
-collectorCount $collectorCount `
-collectorMemory 8
$resourcePath = "/setting/collector/groups/$collectorGroupID"
$data = "{`"autoBalanceInstanceCountThreshold`":`"$threshold`"}"
$url = $URLBase + $resourcePath
$properties = Send-Request `
-accessKey $accessKey `
-accessId $accessID `
-data $data `
-URL $URL `
-httpVerb "PATCH"
#>
}