SnapmirrorConvert.psm1
############################################################################################# # HELPER FUNCTIONS for # - logging log4net # - pretty console output # - load modules # - connect to cdot cluster ############################################################################################# function init_log4net{ write-verbose "Loading log4net from path $PSScriptRoot\log4net.dll" $log4NetLocation = "$PSScriptRoot\log4net.dll" [Reflection.Assembly]::LoadFrom($log4NetLocation) | Out-Null [log4net.LogManager]::ResetConfiguration(); } function exit_log4net{ [log4net.LogManager]::Shutdown(); [log4net.LogManager]::ResetConfiguration(); } function get_log4net_level($level){ switch($level){ "Debug" {return [log4net.Core.Level]::Debug} "Info" {return [log4net.Core.Level]::Info} "Warn" {return [log4net.Core.Level]::Warn} "Error" {return [log4net.Core.Level]::Error} "Off" {return [log4net.Core.Level]::Off} "Fatal" {return [log4net.Core.Level]::Fatal} "All" {return [log4net.Core.Level]::All} } } function set_log_level{ param( $loggerInstance, [ValidateSet("All","Debug","Info","Warn","Error","Fatal","Off")] $level ) $wrapper = [log4net.LogManager]::GetLogger($loggerinstance) $l = $wrapper.Logger $l.Level = get_log4net_level -level $level $l.Repository.Configured=$true } function add_appender($loggerinstance,$appender){ if( $null -ne $appender ){ $wrapper = [log4net.LogManager]::GetLogger($loggerinstance) $l = $wrapper.Logger $l.AddAppender($appender) } } function create_rolling_log_appender($name,$file,$Threshold="Info"){ $rlf = new-object log4net.Appender.RollingFileAppender $rlf.MaximumFileSize = "50MB" $rlf.Name = $name $rlf.File = $file $rlf.RollingStyle = "Size" $rlf.StaticLogFileName = $true $rlf.MaxSizeRollBackups = "20" $rlf.Layout = new-object log4net.Layout.PatternLayout("[%date{yyyy-MM-dd HH:mm:ss.fff}] [%level] %message%n") $rlf.Threshold = (get_log4net_level -level $Threshold) $rlf.ActivateOptions() return $rlf } function get_logger($name){ return [log4net.LogManager]::GetLogger($name) } Function clean_and_exit () { Write-Log "[ EXIT ] Finished" exit_log4net } Function handle_error([object]$object){ $ErrorMessage = $object.Exception.Message $FailedItem = $object.Exception.ItemName $Type = $object.Exception.GetType().FullName $CategoryInfo = $object.CategoryInfo $ErrorDetails = $object.ErrorDetails $Exception = $_.Exception $FullyQualifiedErrorId = $object.FullyQualifiedErrorId $InvocationInfoLine = $object.InvocationInfo.Line $InvocationInfoLineNumber = $object.InvocationInfo.ScriptLineNumber $PipelineIterationInfo = $object.PipelineIterationInfo $ScriptStackTrace = $object.ScriptStackTrace $TargetObject = $object.TargetObject Write-LogError "Trap Error: [$ErrorMessage]" Write-LogDebug "Trap Item: [$FailedItem]" Write-LogDebug "Trap Type: [$Type]" Write-LogDebug "Trap CategoryInfo: [$CategoryInfo]" Write-LogDebug "Trap ErrorDetails: [$ErrorDetails]" Write-LogDebug "Trap Exception: [$Exception]" Write-LogDebug "Trap FullyQualifiedErrorId: [$FullyQualifiedErrorId]" Write-LogDebug "Trap InvocationInfo: [$InvocationInfoLineNumber] [$InvocationInfoLine]" Write-LogDebug "Trap PipelineIterationInfo: [$PipelineIterationInfo]" Write-LogDebug "Trap ScriptStackTrace: [$ScriptStackTrace]" Write-LogDebug "Trap TargetObject: [$TargetObject]" } function format-colorbrackets { Param( [Parameter(Position=0, Mandatory=$true)] [string] $Format, [switch] $FirstIsSpecial, [switch] $NoNewLine, [string] $ForceColor="" ) $result = Select-String -Pattern '\[([^\]]*)\]' -InputObject $Format -AllMatches $i = 0 $first = $true foreach($match in $result.Matches) { $group = $match.Groups[1] WriteHost $Format.Substring($i, $group.Index - $i) -NoNewline if(($first -and $FirstIsSpecial)){ $color="green" if($ForceColor.length -gt 1){$color=$ForceColor} $first=$false }else{ $color="cyan" } WriteHost $group.Value -NoNewline -ForegroundColor $color $i = $group.Index + $group.Length } if($i -lt $Format.Length){WriteHost $Format.Substring($i, $Format.Length - $i) -NoNewline } WriteHost "" -NoNewline:$NoNewLine } function import_module($name){ if (-not(Get-Module -Name $name)) { if (Get-Module -ListAvailable | Where-Object { $_.name -eq $name }) { Import-Module $name return $true } else { return $false } } else{ return $true } } function load_module($name){ try { if (import_module $name) { Write-LogSuccess "Loaded module $name" } else { Write-LogError "Failed to load module $name" Write-Log "Did you install the module ? `n`r" Write-Log "[ Tip ] Install-Module $name `n`r" -forceColor Magenta -firstIsSpecial clean_and_exit } } catch { Write-LogError "Failed to load module $name" Write-Log "Did you install the module ? `n`r" Write-Log "[ Tip ] Install-Module $name `n`r" -forceColor Magenta -firstIsSpecial clean_and_exit handle_error -object $_ clean_and_exit } } function connect_controller($name){ if(-not $connections[$name]){ $credentials = Get-Credential -Message "Provide credentials for $name" try{ $connections[$name] = Connect-NcController r2d2 -Credential $credentials -HTTPS Write-LogSuccess "Connected to cluster $name" }catch{ Write-LogError "Failed to connect to cluster $name" handle_error $_ clean_and_exit } } return $connections[$name] } function WriteHost{ param( [Parameter(Position=0, Mandatory=$true)] $Object, $ForegroundColor="white", [switch]$NoNewLine ) Write-Host $Object -ForegroundColor $ForegroundColor -NoNewline:$NoNewLine } function Write-Brackets ([string]$mess,$status,$foregroundcolor) { if($mess){ WriteHost "[" -NoNewLine WriteHost $status -ForegroundColor $foregroundcolor -NoNewLine WriteHost "] " -NoNewLine format-colorbrackets "$mess" -FirstIsSpecial -ForceColor yellow } } function Write-LogDebug ([string]$mess) { if($LogLevel -eq "Debug"){ Write-Brackets -mess $mess -status " INFO " -foregroundcolor cyan } $Global:MasterLog.Debug($mess) } function Write-LogSuccess ([string]$mess) { Write-Brackets -mess $mess -status " OK " -foregroundcolor green $Global:MasterLog.Info($mess) } function Write-LogError ([string]$mess) { Write-Brackets -mess $mess -status " ERR " -foregroundcolor red $Global:MasterLog.Error($mess) } function Write-LogWarn ([string]$mess) { Write-Brackets -mess $mess -status " WARN " -foregroundcolor yellow $Global:MasterLog.Warning($mess) } function Write-Log ([string]$mess,[switch]$firstIsSpecial,[switch]$noNewLine,$forceColor) { format-colorbrackets "$mess" -FirstIsSpecial:$firstIsSpecial -NoNewLine:$noNewLine -ForceColor $forceColor $Global:MasterLog.Info($mess) } function Write-LogTitle ([string]$mess) { if($mess){ WriteHost ("-" * $mess.Length) WriteHost $mess -ForegroundColor Magenta WriteHost ("-" * $mess.Length) $Global:MasterLog.Info($mess) } } ############################################################################################# # LOGIC STARTS HERE ############################################################################################# <# .Synopsis Convert snapmirror policy .DESCRIPTION SRM and NetApp(c) SRA (currently) have no capability to properly run planned migrations or test migrations when the underlying SnapMirror(c) relationsships are Synchronous. The SRA was built for Asynchronous relations and certain underlying actions such as snapmirror update fail. Also, after migrations, the SRA create async relations. The cmdlet converts all snapmirrors, found in the volumes matching the config regular expressions, to the target snapmirror policy in the config file. So now you can run this cmdlet to convert sync to async (before srm testing). And to convert async to sync (after finishing testing). The final state, either broken-off or snapmirrored, will remain. TIPS : Use -WhatIf and -Confirm. DEPENDENCIES : Requires DataONTAP and Powershell-yaml (use install-module) CONFIG FILE : What convertion and in what direction on which snapmirrors is defined in the config yaml file you need to pass. .EXAMPLE Invoke-SrmScript -configfile .\srmsync\prod-dr-prep.yaml -LogLevel Debug -Confirm:$false ---------------- Preparing script ---------------- [ OK ] Loaded module dataOntap [ OK ] Loaded module powershell-yaml [ OK ] Loaded configfile [ INFO ] source_cluster : r2d2 [ INFO ] destination_cluster : c3po [ INFO ] snapmirror_volume_regex : srm_sync.* [ INFO ] snapmirror_vserver_regex : srm_vserver.* [ INFO ] target_snapmirror_policy : MirrorAndVault [ OK ] Connected to cluster c3po [ OK ] Lookup volumes [ OK ] Lookup snapmirrors -------------------------------- Found following matching volumes -------------------------------- [broken-off][Sync][sync_mirror][srm_sync_volume] -> [srm_sync_volume_dest] ---------------------------- Converting to MirrorAndVault ---------------------------- [ INFO ] [already broken-off][srm_sync_volume] -> [srm_sync_volume_dest] [ OK ] [removed ][srm_sync_volume] -> [srm_sync_volume_dest] [ OK ] [released ][srm_sync_volume] -> [srm_sync_volume_dest] [ OK ] [recreated ][srm_sync_volume] -> [srm_sync_volume_dest] [ INFO ] [converted to async][srm_sync_volume] -> [srm_sync_volume_dest] [ EXIT ] Finished #> function Convert-SnapmirrorPolicy{ [CmdletBinding(SupportsShouldProcess=$true,ConfirmImpact="High")] param( # The path of the yaml file that contains the configuration # - determines the snapmirror direction (source, dest) # - determines the volume selection (volume & vserver regex) # - determines the target snapmirror policy # # Example : # source_cluster : "r2d2" # destination_cluster : "c3po" # snapmirror_volume_regex : "srm_sync.*" # snapmirror_vserver_regex : "srm_vserver.*" # target_snapmirror_policy : "MirrorAndVault" # [Parameter(Mandatory = $true)] [string]$ConfigFile, [ValidateSet("Debug", "Info", "Warn", "Error", "Fatal", "Off")] [string]$LogLevel = "Debug", [Parameter(Mandatory = $false)] [string]$LogFile = ".\SnapmirrorConvert.log", # Forces the resync in case the snapmirror is broken [switch]$ForceResync ) $ErrorActionPreference = "stop" $connections = @{} $config = @{} $found_snapmirrors = @() ########### # init log ########### try{ init_log4net $masterLogFileAppender = create_rolling_log_appender -name "srmsync.log" -file $LogFile -threshold $LogLevel add_appender -loggerinstance "srmsynclog" $masterLogFileAppender set_log_level -loggerinstance "srmsynclog" -level "All" $Global:MasterLog = get_logger -name "srmsynclog" }catch{ Write-Error "Failed to initialize log4net" } ########### # PREP ########### Write-LogTitle "Preparing script" # import modules load_module dataOntap load_module powershell-yaml # load config file try{ $config = Get-Content $ConfigFile | ConvertFrom-Yaml }catch{ Write-LogError "Failed to load ConfigFile $ConfigFile" handle_error $_ clean_and_exit } Write-LogSuccess "Loaded ConfigFile" Write-LogDebug ("source_cluster : {0}" -f $config.source_cluster) Write-LogDebug ("destination_cluster : {0}" -f $config.destination_cluster) Write-LogDebug ("snapmirror_volume_regex : {0}" -f $config.snapmirror_volume_regex) Write-LogDebug ("snapmirror_vserver_regex : {0}" -f $config.snapmirror_vserver_regex) Write-LogDebug ("target_snapmirror_policy : {0}" -f $config.target_snapmirror_policy) ################## # connect to both clusters ################## $source_controller = connect_controller $config.source_cluster $destination_controller = connect_controller $config.source_cluster ################### # policy check ################### $targetSnapmirrorPolicy = $config.target_snapmirror_policy $checkpolicy = Get-NcSnapmirrorPolicy -Name $targetSnapmirrorPolicy if(-not $checkpolicy){ Write-LogError "$targetSnapmirrorPolicy does not exist on $destination_controller" clean_and_exit }else{ Write-LogSuccess "$targetSnapmirrorPolicy is a valid snapmirror policy" } ################## # get information ################## try{ $source_volumes = Get-NcVol -Controller $source_controller Write-LogSuccess "Lookup volumes" $destination_snapmirrors = Get-NcSnapmirror -Controller $destination_controller -DestinationCluster $destination_controller.Name -SourceCluster $source_controller.Name Write-LogSuccess "Lookup snapmirrors" }catch{ handle_error $_ clean_and_exit } # search maching volumes based on regex's foreach($v in $source_volumes){ if($v.name -match $config.snapmirror_volume_regex -and $v.Vserver -match $config.snapmirror_vserver_regex){ $found_snapmirrors +=[PSCustomObject]@{ volume = $v.name mirror = "" policy = "" type = "" } } } # find mirrors on found volumes and complete info with snapmirror policy foreach($s in $found_snapmirrors){ foreach($sm in $destination_snapmirrors){ try{ $policy = Get-NcSnapmirrorPolicy -Name $sm.policy }catch{ Write-LogError "Failed to get snapmirror policy for $s" handle_error clean_and_exit } if($s.volume -eq $sm.sourceVolume -and $source_controller.name -eq $sm.sourcecluster){ $s.mirror = $sm $s.policy = $policy.Name $s.type = $policy.Type } } } ################## # output to console ################## Write-LogTitle "Found following matching volumes" foreach($s in $found_snapmirrors){ if($s.mirror){ if($s.type -eq "snapmirrored"){ $color = "green" }else{ $color = "yellow" } Write-Log ("[{0}][{4}][{3}][{1}] -> [{2}]" -f $s.mirror.mirrorstate,$s.volume,$s.mirror.destinationvolume,$s.type,$s.policy) -firstIsSpecial -forceColor $color }else{ Write-Log ("[missing][{0}] -> []" -f $s.volume) -firstIsSpecial -forceColor red } } ################## # start conversion ################## Write-LogTitle "Converting to $targetSnapmirrorPolicy" foreach($s in ($found_snapmirrors | ?{$_.mirror})){ try{ # implement whatif and confirm if($pscmdlet.ShouldProcess(("[{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume), "Convert Snapmirror to $targetSnapmirrorPolicy")){ # if not broken, break if($s.mirror.MirrorState -ne "broken-off"){ # quiesce before break try{ Invoke-NcSnapmirrorQuiesce -Controller $destination_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -Confirm:$false Write-LogSuccess ("[queisced ][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ Write-LogError ("Failed to quiesce relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } # break try{ Invoke-NcSnapmirrorBreak -Controller $destination_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -Confirm:$false Write-LogSuccess ("[broken-off][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ Write-LogError ("Failed to break relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } }elseif($s.mirror.MirrorState -eq "broken-off"){ Write-LogDebug ("[already broken-off][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) } # remove try{ Remove-NcSnapmirror -Controller $source_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -Confirm:$false Write-LogSuccess ("[removed ][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ Write-LogError ("Failed to remove relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } # release on source try{ Invoke-NcSnapmirrorRelease -Controller $source_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -RelationshipInfoOnly ` -Confirm:$false Write-LogSuccess ("[released ][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ if($_.Exception.Message -match "not found"){ Write-LogDebug ("[no relation to release][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }else{ Write-LogError ("Failed to release relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } } # recreate with new policy try{ $out = New-NcSnapmirror -Controller $source_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -Policy $targetSnapmirrorPolicy Write-LogSuccess ("[recreated ][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ Write-LogError ("Failed to recreate relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } if($s.mirror.MirrorState -ne "broken-off" -or $ForceResync){ if($s.mirror.MirrorState -eq "broken-off" -and $ForceResync){ Write-LogDebug ("[force resync][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) } try{ $out = Invoke-NcSnapmirrorResync -Controller $source_controller ` -DestinationCluster $s.mirror.DestinationCluster ` -DestinationVserver $s.mirror.DestinationVserver ` -DestinationVolume $s.mirror.DestinationVolume ` -SourceCluster $s.mirror.sourceCluster ` -SourceVserver $s.mirror.sourceVserver ` -SourceVolume $s.mirror.sourceVolume ` -Confirm:$false Write-LogSuccess ("[resynced ][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) }catch{ Write-LogError ("Failed to resync relation [{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) handle_error $_ clean_and_exit } } Write-LogDebug ("[converted to $targetSnapmirrorPolicy][{0}] -> [{1}]" -f $s.volume,$s.mirror.destinationvolume) } }catch{ handle_error $_ clean_and_exit }finally{ } } clean_and_exit } |