Scripts/Remove-Unreferenced-Replica-Files/Remove-UnreferencedReplicaFiles.ps1 (466 lines of code) (raw):

<# .SYNOPSIS Removes files corresponding to leaked replicas. .DESCRIPTION When a replica is force removed, files associated with it are not removed from the file system. This tool deletes those files. .EXAMPLE Deletes all leaked files. .\Remove-UnreferencedReplicaFiles.ps1 -NodeName <NodeName> Shows what would happen if the cmdlet runs. .\Remove-UnreferencedReplicaFiles.ps1 -NodeName <NodeName> -WhatIf Deletes and displays leaked files corresponding to force removed replicas. .\Remove-UnreferencedReplicaFiles.ps1 -NodeName <NodeName> -Verbose #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $NodeName, [Parameter(Mandatory=$false)] [switch] $WhatIf=$false ) function GetApplicationDeployedNode { <# .SYNOPSIS Returns a node on which the given application is deployed. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Fabric.Query.Application] $Application ) $services = Get-ServiceFabricService -ApplicationName $Application.ApplicationName foreach ($service in $services) { if ($service.ServiceKind.ToString() -eq "Stateful") { $partitions = Get-ServiceFabricPartition -ServiceName $service.ServiceName foreach ($partition in $partitions) { $replicas = Get-ServiceFabricReplica -PartitionId $partition.PartitionId foreach ($replica in $replicas) { $nameOfNode = $replica.NodeName return $nameOfNode } } } } return $null } function GetWorkDirectories { <# .SYNOPSIS Returns work directories. If the application is not deployed on the given node, it gets work directory path from another node on which this application is deployed. Assumed that the work directories will be the same (all the nodes the application runs on have the same configuration), and this script fails to cleanup the leaked files if the work directories are different. #> $workDirectories = New-Object System.Collections.ArrayList $applicationsInCluster = Get-ServiceFabricApplication foreach ($application in $applicationsInCluster) { $deployedApplication = Get-ServiceFabricDeployedApplication -NodeName $NodeName -ApplicationName $application.ApplicationName if ($null -eq $deployedApplication) { $nameOfNode = GetApplicationDeployedNode -Application $application if ($null -eq $nameOfNode) { continue } $deployedApplication = Get-ServiceFabricDeployedApplication -NodeName $nameOfNode -ApplicationName $application.ApplicationName } $workDirectory = $deployedApplication.WorkDirectory [void] $workDirectories.Add($workDirectory) } return $workDirectories } function GetWorkDirectoriesContent { <# .SYNOPSIS Returns content of work directories. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Collections.ArrayList] [AllowEmptyCollection()] $WorkDirectories ) $content = New-Object System.Collections.ArrayList foreach ($workDirectory in $WorkDirectories) { if (Test-Path $workDirectory) { $paths = Get-Childitem -Path $workDirectory $content.AddRange($paths); } } return $content } function GetActivePartitions { <# .SYNOPSIS Returns all active partitions. #> $partitionDescriptionMap = @{} $deployedApplications = Get-ServiceFabricDeployedApplication -NodeName $NodeName foreach ($application in $deployedApplications) { $applicationName = $application.ApplicationName $services = Get-ServiceFabricService -ApplicationName $applicationName foreach ($service in $services) { if ($service.ServiceKind.ToString() -eq "Stateful") { $serviceName = $service.ServiceName $partitions = Get-ServiceFabricPartition -ServiceName $serviceName foreach ($partition in $partitions) { $partitionId = $partition.PartitionId $applicationNameAndServiceName = $applicationName.ToString(), $serviceName.ToString() [void] $partitionDescriptionMap.Add($partitionId, $applicationNameAndServiceName) } } } } return $partitionDescriptionMap } function HasCheckpointFiles { <# .SYNOPSIS Given a directory, checks if it contains checkpoint files. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $Directory, [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $CheckpointExtensions ) $paths = Get-ChildItem -Path $Directory foreach ($path in $paths) { if ((-not ((Get-Item $path.FullName) -is [System.IO.DirectoryInfo])) -and $CheckpointExtensions.Contains($path.Extension)) { return $true } } return $false } function AddPartitionReplicaIdEntryToMap { <# .SYNOPSIS Given a map, adds a partitionid_replicaid entry to the map if it does not exist. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $PartitionReplicaIds, [Parameter(Mandatory=$true)] [System.Collections.Hashtable] [AllowEmptyCollection()] $ReplicaToFilesMap ) if (-not $ReplicaToFilesMap.ContainsKey($PartitionReplicaIds)) { $files = New-Object System.Collections.ArrayList [void] $ReplicaToFilesMap.Add($PartitionReplicaIds, $files) } } function GetForceRemovedReplicaToFilesMap { <# .SYNOPSIS Returns all force removed replicas and leaked files corresponding to those replicas. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $PartitonReplicaIdsSet, [Parameter(Mandatory=$true)] [System.Collections.ArrayList] [AllowEmptyCollection()] $WorkDirectoriesContent, [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $CheckpointFileExtensions ) $deletedReplicaToFilesMap = @{} # Go through paths in work directories and add leaked files to the map foreach ($path in $WorkDirectoriesContent) { $fileName = Split-Path $path.FullName -leaf $ids = $fileName.split('_') if (($ids.Count -gt 1) -and ($ids[0].Length -eq 32) -and ($ids[1].Length -eq 18)) { $partitionReplicaIds = $ids[0] + "_" + $ids[1] if ($PartitonReplicaIdsSet.Contains($partitionReplicaIds) -eq $false) { # Add the directory path if it either does not contain any files or if it contains checkpoint files, # but not if it contains only some random files if ((Get-Item $path.FullName) -is [System.IO.DirectoryInfo]) { $containsCheckpointFiles = HasCheckpointFiles -Directory $path.FullName -CheckpointExtensions $CheckpointFileExtensions if (((Get-ChildItem $path.FullName | Measure-Object).Count -eq 0) -or $containsCheckpointFiles) { AddPartitionReplicaIdEntryToMap -PartitionReplicaIds $partitionReplicaIds -ReplicaToFilesMap $deletedReplicaToFilesMap [void] $deletedReplicaToFilesMap[$partitionReplicaIds].Add($path.FullName) continue; } } else { AddPartitionReplicaIdEntryToMap -PartitionReplicaIds $partitionReplicaIds -ReplicaToFilesMap $deletedReplicaToFilesMap # Delete both log reference and log files if ([IO.Path]::GetExtension($path.FullName) -eq '.SFlogref') { $logFilePath = Get-Content -Path $path.FullName if (Test-Path $logFilePath) { [void] $deletedReplicaToFilesMap[$partitionReplicaIds].Add($logFilePath) } } [void] $deletedReplicaToFilesMap[$partitionReplicaIds].Add($path.FullName) } } } } return $deletedReplicaToFilesMap } function GetReplicaDescription { <# .SYNOPSIS Given a string in partitionid_replicaid format, returns replica info which includes application name, service name, partition id and replica id. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $PartitionReplicaIds, [Parameter(Mandatory=$true)] [System.Collections.Hashtable] [AllowEmptyCollection()] $PartitionDescriptionMap ) [GUID]$partitionId = $PartitionReplicaIds.split('_')[0] $replicaId = $PartitionReplicaIds.split('_')[1] if ($PartitionDescriptionMap.ContainsKey($partitionId)) { $applicationName = ($PartitionDescriptionMap[$partitionId])[0] $serviceName = ($PartitionDescriptionMap[$partitionId])[1] } else { $applicationName = "Unknown" $serviceName = "Unknown" } $replicaDescription = "Application Name: ""{0}"" Service Name: ""{1}"" Partition ID: ""{2}"" Replica ID: ""{3}""" -f ` $applicationName, $serviceName, $partitionId, $replicaId return $replicaDescription } function PrintLeakedFilesInDirectory { <# .SYNOPSIS Prints what would be deleted from the directory. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $Directory, [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $CheckpointExtensions ) $paths = Get-Childitem -Path $Directory foreach ($path in $paths) { if (-not ((Get-Item $path.FullName) -is [System.IO.DirectoryInfo]) -and $CheckpointExtensions.Contains($path.Extension)) { $fileRemovedInfo = "Removes {0}" -f $path.FullName Write-Verbose $fileRemovedInfo } } } function PrintLeakedContent { <# .SYNOPSIS Prints what would be deleted if the cmdlet runs. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [System.Collections.Hashtable] [AllowEmptyCollection()] $ForceRemovedReplicasToFilesMap, [Parameter(Mandatory=$true)] [System.Collections.Hashtable] [AllowEmptyCollection()] $PartitionDescriptions, [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $CheckpointFileExtensions ) if ($ForceRemovedReplicasToFilesMap.Count -gt 0) { Write-Output "Removes files and folders corresponding to:" foreach ($partitionReplicaIds in $ForceRemovedReplicasToFilesMap.Keys) { $replicaRemovedInfo = GetReplicaDescription -PartitionReplicaIds $partitionReplicaIds -PartitionDescriptionMap $PartitionDescriptions Write-Output $replicaRemovedInfo # If Verbose flag is provided, show all leaked files $paths = $ForceRemovedReplicasToFilesMap[$partitionReplicaIds] foreach ($path in $paths) { if ((Get-Item $path) -is [System.IO.DirectoryInfo]) { PrintLeakedFilesInDirectory -Directory $path -CheckpointExtensions $CheckpointFileExtensions } else { $fileRemovedInfo = "Removes {0}" -f $path Write-Verbose $fileRemovedInfo } } } } else { Write-Output "There are no leaked files to remove." } } function RemoveCheckpointFilesFromDirectory { <# .SYNOPSIS Given a directory, deletes checkpoint files. #> [CmdletBinding()] Param ( [Parameter(Mandatory=$true)] [string] $CheckpointFilesDirectory, [Parameter(Mandatory=$true)] [System.Collections.Generic.HashSet[string]] [AllowEmptyCollection()] $CheckpointFileExtensions ) $directoryContents = Get-Childitem -Path $CheckpointFilesDirectory foreach ($content in $directoryContents) { if (-not ((Get-Item $content.FullName) -is [System.IO.DirectoryInfo]) -and $CheckpointFileExtensions.Contains($content.Extension)) { $fileRemovedInfo = "Removing {0}" -f $content.FullName Write-Verbose $fileRemovedInfo Remove-Item $content.FullName } } } #****************************************************************************** # Script body # Execution begins here #****************************************************************************** $_workDirectories = GetWorkDirectories $_workDirectoriesContent = GetWorkDirectoriesContent -WorkDirectories $_workDirectories # Map between active partitions and application name, service name $_activePartitions = GetActivePartitions $_activeReplicas = New-Object System.Collections.Generic.HashSet[string] foreach ($partitionId in $_activePartitions.Keys) { $replicas = Get-ServiceFabricReplica -PartitionId $partitionId foreach ($replica in $replicas) { if ($replica.NodeName -eq $NodeName) { $replicaId = $replica.ReplicaId.ToString() [void] $_activeReplicas.Add($partitionId.ToString().Replace("-", "") + "_" + $replicaId) } } } $_checkpointFileExtensions = New-Object System.Collections.Generic.HashSet[string] [void] $_checkpointFileExtensions.Add(".sfk"); [void] $_checkpointFileExtensions.Add(".sfv"); [void] $_checkpointFileExtensions.Add(".sfm"); [void] $_checkpointFileExtensions.Add(".sfc"); $_forceRemovedReplicasToFilesMap = GetForceRemovedReplicaToFilesMap -PartitonReplicaIdsSet $_activeReplicas ` -WorkDirectoriesContent $_workDirectoriesContent -CheckpointFileExtensions $_checkpointFileExtensions $Verbose = $false if ($PSBoundParameters.ContainsKey('Verbose')) { $Verbose = $PsBoundParameters.Get_Item('Verbose') } PrintLeakedContent -ForceRemovedReplicasToFilesMap $_forceRemovedReplicasToFilesMap ` -PartitionDescriptions $_activePartitions -CheckpointFileExtensions $_checkpointFileExtensions # Return either if there are no leaked files to delete or if WhatIf flag is set if (($_forceRemovedReplicasToFilesMap.Count -eq 0) -or $WhatIf) { return } # Get confirmation before actually deleting $input = Read-Host "Are you sure you want to perform this action? `nYes[Y] No[N]" $lowerCaseInput = $input.ToLower() if ($lowerCaseInput -ne "y" -and $lowerCaseInput -ne "yes" -and $lowerCaseInput -ne "n" -and $lowerCaseInput -ne "no") { Write-Output "Incorrect Choice! Action Aborted." return } if ($lowerCaseInput -eq "n" -or $lowerCaseInput -eq "no") { return } # Delete all leaked files foreach ($partitionReplicaIds in $_forceRemovedReplicasToFilesMap.Keys) { $replicaRemovedInfo = GetReplicaDescription -PartitionReplicaIds $partitionReplicaIds -PartitionDescriptionMap $_activePartitions $leakedFilesAndFoldersInfo = "Removing files and folders corresponding to {0}" -f $replicaRemovedInfo Write-Output $leakedFilesAndFoldersInfo $paths = $_forceRemovedReplicasToFilesMap[$partitionReplicaIds] foreach ($path in $paths) { if ((Get-Item $path) -is [System.IO.DirectoryInfo]) { RemoveCheckpointFilesFromDirectory -CheckpointFilesDirectory $path -CheckpointFileExtensions $_checkpointFileExtensions if ((Get-ChildItem $path | Measure-Object).Count -eq 0) { Remove-Item $path } } else { $fileRemovedInfo = "Removing {0}" -f $path Write-Verbose $fileRemovedInfo Remove-Item $path } } }