Netapp Transition Migration with Powershell helpers


I do work with customers moving legacy NetApp 7-mode systems to current ONTAP. This is termed as transition In NetApp parlance and they provide extensive tools to evaluate, plan and execute a transition. In the ideal situation, their 7mTT tool is used to orchestrate all of the transition work. However, customer systems and customer requirements may not make that entirely possible. Customers may have old 32-bit aggregates and volumes, for example, and they may not want to negotiate an additional maintenance window to upgrade an old 7-mode filer to a new version to be able to update those aggregates to 64 bit. They may want to consolidate some data down on the ONTAP target systems in such a way that they can't utilize 7mTT and transition snapmirror.

There are of course many ways to migrate data. Netapp provides xcp freely for very efficient host-based copies but it's currently used today for NFS data. Robocopy on windows with multithreading can be reasonable as well. There are other 3rd party tool options. NetApp filers also provide ndmpcopy to duplicate volumes using the NDMP protocol for transport. In my case and the examples below I used ndmpcopy controlled in powershell to move data and used powershell to perform various cutover tasks that 7mTT typically performs. I had volumes containing ISCSI luns as well as SMB shares to migrate.

Ndmpcopy has been around a long time as has ndmp. It's not necessarily simple to use. It's not the most efficient way to move data. It can have challenges with user files that have problematic file names. But, despite those caveats, it was suitable for my use case detailed here.

Setup and planning notes

I added additional IP aliases to the 7-mode filers and I used those IPs as the source for communicating with the filers and for data copy activities for items that I was going to move via 7mTT as well as ndmpcopy. The reason is that the filer's source IP would eventually move to the destination SVM when SMB shares were migrated and I would still have need to communicate with the old filer and possibly still migrate ISCSI data after I moved that primary IP. I recommend doing this as part of a transition regardless. Also even if you can't use transition snapmirror at all, use 7mTT to assess the environment. I did use 7mTT for a substantial portion of the transition I reference in this post.

setup connections and variables

The first item you need to do is establish a credentials cache for all the filer sources and destinations. You really only need to do this once to populate a credentials cache file: populate_creds.ps1

  import-module DataOntap
  write-host "Get credentials for bwnetapp_cluster"
  Add-NcCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for nonetapp_cluster"
  Add-NcCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for bwnetapp1"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for bwnetapp2"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for bwnetapp3"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for bwnetapp4"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for nonetapp3"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for nonetapp4"
  Add-NaCredential -Controller -Credential(Get-Credential)
  write-host "Get credentials for nonetapp1"
  Add-NaCredential -Controller -Credential(Get-Credential)

Configure NDMP on the destination filers and 7-mode filers as well. I'm assuming this is already done. I used node-scoped NDMP to support these operations on cdot using the same ndmp creds for the nodes. file: netapp_variables.ps1

  $bwnetapp3 = ""
  $bwnetapp4 = ""
  $nonetapp3 = ""
  $nonetapp4 = ""
  $nonetapp1 = ""

  $ndmp_nonetapp1_pw = convertto-securestring "qBsGDbB2JFJGXDrL" -asplaintext -force
  $ndmp_bwnetapp1_pw = convertto-securestring "06gPCbA6NXXG3vbc" -asplaintext -force
  $ndmp_bwnetapp2_pw = convertto-securestring "JAqByt9HaZgPYGXl" -asplaintext -force
  $ndmp_bwnetapp_cluster_pw = convertto-securestring "aJHhbPCkJ5PklFZe" -asplaintext -force
  $ndmp_nonetapp_cluster_pw = convertto-securestring "iHp4CiXcorXxAVz4" -asplaintext -force
  $nonetapp1_ndmpcred = new-object ("scott",$ndmp_nonetapp1_pw)
  $bwnetapp1_ndmpcred = new-object ("scott",$ndmp_bwnetapp1_pw)
  $bwnetapp2_ndmpcred = new-object ("scott",$ndmp_bwnetapp2_pw)
  $bwnetapp_cluster_ndmpcred = new-object ("backup",$ndmp_bwnetapp_cluster_pw)
  $nonetapp_cluster_ndmpcred = new-object ("backup",$ndmp_nonetapp_cluster_pw)

Connect to filers and populate additional variables. file netapp_initiate.ps1

  import-module DataOnTap

  $bwnetapp1 = connect-nacontroller -name -http
  $bwnetapp2 = connect-nacontroller -name -http
  $nonetapp1 = connect-nacontroller -name -http
  $nonetapp_cluster = Connect-NcController -name
  $bwnetapp_cluster = Connect-NcController -name

These files can be source into the powershell session used to control subsequent operations.

Create volumes and snapmirror destinations

Need to create initial volumes and snapmirror destinations as well. One challenge here is mentioned above. If ndmp incrementalforever works well, your snapmirror updates and their base snapshots will be okay. If you have to redo a level 0 ndmp dump you also have to consider the impact on the snapmirror base snapshot and may have to re-baseline as well. You should also think about snapshot schedules and precisely when to enable them on your transitioning volumes. In other words, your snapshots for snapmirror or scheduled snap sizes could balloon temporarily on the destination cluster.

In this example, I get my my volume information from 7-mode and then apply it to the ONTAP destination. I could grab the volume size and supply it the the subsequent New-NcVol here rather than manually inputting into my $newsize variable.

  PS C:\Users\Scott\Scripts> get-navol -controller $bwnetapp1 branch_houdata

  Name                      State       TotalSize  Used  Available Dedupe  FilesUsed FilesTotal Aggregate
  ----                      -----       ---------  ----  --------- ------  --------- ---------- ---------
  branch_houdata            online         1.0 TB   56%   454.0 GB  True        192k        32M aggr1

  PS C:\Users\Scott\Scripts> $newvol = "branch_houdata"
  PS C:\Users\Scott\Scripts> $newsize = "1t"
  PS C:\Users\Scott\Scripts> New-NcVol -controller $bwnetapp_cluster -Name $newvol  -Aggregate bwnetapp3_data -VserverConext BW_CIFS_SVM -SecurityStyle ntfs -spaceguarantee none -Size $newsize -JunctionPath /$newvol -ExportPolicy default -SapshotReserve 2 -QosPolicyGroup default_qos_cifs -EfficiencyPolicy default

  Name                      State       TotalSize  Used  Available Dedupe Aggregate                 Vserver
  ----                      -----       ---------  ----  --------- ------ ---------                 -------
  branch_houdata            online         1.0 TB    2%  1003.5 GB  True  bwnetapp3_data            BW_CIFS_SVM

  PS C:\Users\Scott\Scripts> New-NcVol -controller $nonetapp_cluster -Name $newvol  -Aggregate nonnetapp3_data -VserverCotext NO_CIFS_SVM -SecurityStyle ntfs -spaceguarantee none -Size $newsize -ExportPolicy default -SnapshotReserve 2 -QosPlicyGroup default_qos_cifs -JunctionPath $null -type dp

  Name                      State       TotalSize  Used  Available Dedupe Aggregate                 Vserver
  ----                      -----       ---------  ----  --------- ------ ---------                 -------
  branch_houdata            restricted     1.0 TB                         nonnetapp3_data           NO_CIFS_SVM

  PS C:\Users\Scott\Scripts> New-NcSnapmirror -Controller $nonetapp_cluster -Source bwnetapp_cluster://BW_CIFS_SVM/$newvol -Destination nonetapp_cluster://NO_CIFS_SVM/$newvol -policy MirrorAllSnapshots -Type vault -Schedule 8hour

  SourceLocation                                DestinationLocation                           Status       MirrorState
  --------------                                -------------------                           ------       -----------
  BW_CIFS_SVM:branch_houdata                    NO_CIFS_SVM:branch_houdata                    idle

  PS C:\Users\Scott\Scripts> Invoke-NcSnapmirrorInitialize -Controller $nonetapp_cluster -source bwnetapp_cluster://BW_CIFS_SVM/$newvol -Destination nonetapp_cluster://NO_CIFS_SVM/$newvol

  NcController      :
  ResultOperationId : 92fa8814-577e-11e7-9098-00a098b3de4f
  ErrorCode         :
  ErrorMessage      :
  JobId             :
  JobVserver        :
  Status            : succeeded

Both the source and DP snapmirror volumes are created and snapmirror is initialized which takes moments since the source volume doesn't have content yet.

Intial ndmpcopy

I used ndmpcopy with incrementalfover after the first level 0 dump to copy data. ndmpcopy with levels uses only up to level 2 so if you do an initial level 0 and 2 subsequent updates, you would have to do another level 0 which could be lengthy. The incrementalforever works okay with some data sets but I did find some situations which it errored out and I was forced to use level 0s. These were smaller volumes at least so the impact was not signficant. Again, NDMP may not be the way to go and you may need to choose an alternate method to move the data.

file: do_ndmcopy_cifs_bwnetapp1_group1.ps1

. ".\netapp_initiate.ps1"

$dstsvm = "BW_CIFS_SVM"
$level = 0
$volumes = @("branch_houdata", "branch_mobdata")
$srccontroller = $bwnetapp1.address.ipaddresstostring

foreach ($volume in $volumes) {
     write-host "start-nandmpcopy -SrcController $srccontroller -srcpath /vol/$volume -dstcontroller $bwnetapp3 -dstpath /$dstsvm/$volume -level $level -srccredential $bwnetapp1_ndmpcred -dstcredential $bwnetapp_cluster_ndmpcred -srcauthtype md5 -dstauthtype md5"
     start-nandmpcopy -SrcController $srccontroller -srcpath /vol/$volume -dstcontroller $bwnetapp3 -dstpath /$dstsvm/$volume -level $level -srccredential $bwnetapp1_ndmpcred -dstcredential $bwnetapp_cluster_ndmpcred -srcauthtype md5 -dstauthtype md5

The script calls netapp_initiate.ps1 and then launches level 0 ndmpcopy jobs. I grouped them as needed into filename groupings based on our intended eventual cutover windows.

launching the first job:

  PS C:\Users\Scott\Scripts> .\do_ndmpcopy_cifs_bwnetapp1_group1.ps1
  start-nandmpcopy -SrcController -srcpath /vol/branch_houdata -dstcontroller -dstpath /NO_CIFS_SVM/houdat
  a -incrementalforever -srccredential System.Management.Automation.PSCredential -dstcredential System.Management.Automati
  on.PSCredential -srcauthtype md5 -dstauthtype md5
  WARNING: PowerShell session must remain open until the NDMP copy has completed or the operation will fail.

  Id   State          SrcPath                    DstPath                        BackupBytesProcessed    BackupBytesRemain
  --   -----          -------                    -------                        --------------------    -----------------
  9   RUNNING        /vol/branch_houdata        /BW_CIFS_SVM/branch_houdata/                     0                    0
  PS C:\Users\Scott\Scripts> $houdata = get-nandmpcopy -id 9;$houdata.logmessages;$houdata 

The last command string I can repeat and get a status update as well as log entries for the particular job. Those logs are also stored on the filers but it's handy to be able to grab them here.

update ndmpcopy runs

I edit the file to use incrementalforever on subsequent runs like so:

  . ".\netapp_initiate.ps1"

  $dstsvm = "BW_CIFS_SVM"
  $level = 0
  $volumes = @("branch_houdata", "branch_mobdata")
  $srccontroller = $bwnetapp1.address.ipaddresstostring

  foreach ($volume in $volumes) {
       write-host "start-nandmpcopy -SrcController $srccontroller -srcpath /vol/$volume -dstcontroller $bwnetapp3 -dstpath /$dstsvm/$volume -incrementalforever -srccredential $bwnetapp1_ndmpcred -dstcredential $bwnetapp_cluster_ndmpcred -srcauthtype md5 -dstauthtype md5"
       start-nandmpcopy -SrcController $srccontroller -srcpath /vol/$volume -dstcontroller $bwnetapp3 -dstpath /$dstsvm/$volume -incrementalforever -srccredential $bwnetapp1_ndmpcred -dstcredential $bwnetapp_cluster_ndmpcred -srcauthtype md5 -dstauthtype md5

I will have a directory of various ndmpcopy groups that I can use a simple loop to run mutliple updates get-childitem .\do_ndmpcopy_cifs* | foreach-object { & $_.fullname}~ I can check their status using the same technique shown above for an individual job id or just ~Get-NaNdmpCopy for all of them.

Cutover for iSCSI

The steps below detail the additional steps needed to cutover volumes containing iSCSI lun data

Prepare for cutover "apply configuration"

Just as with 7mTT we want to create our igroups and mappings based off of the 7-mode source filer. So first we grab an igroup. Here I use where-object to match on wildcard string since the I happen to know the igroup will contain the relevant server name.

  PS C:\Users\Scott\Scripts> $myigroups = get-naigroup -controller $bwnetapp1 | where-object {($_.Name -like '*lawb*')}
  PS C:\Users\Scott\Scripts> $myigroups

  Name            :
  Type            : windows
  Protocol        : iscsi
  PortSet         :
  ALUA            : False
  ThrottleBorrow  : False
  ThrottleReserve : 0
  Partner         :
  VSA             : False
  Initiators      : {}
  PS C:\Users\Scott\Scripts> foreach ($initiator in $myigroups.initiators) {
  >> Get-NaLunMapByInitiator -controller $bwnetapp1 -Initiator $initiator.initiatorName
  >> }

  InitiatorGroup                                                            LunId Path
  --------------                                                            ----- ----                                       1 /vol/lawbase_sql_backup/lawbase_sql_...                                       0 /vol/lawbase_sql_db/lawbase_sql_db

Once I've grabbed that I can create my igroup and add the initiator(s) to it.

  PS C:\Users\Scott\Scripts> New-NcIgroup -Controller $bwnetapp_cluster -Name $myigroups.Name -Protocol iscsi -Type $myigroups.Type -vserver BW_ISCSI_SVM

  Name            :
  Type            : windows
  Protocol        : iscsi
  Portset         :
  ALUA            : True
  ThrottleBorrow  : False
  ThrottleReserve : 0
  Partner         : True
  VSA             : False
  Initiators      :
  Vserver         : BW_ISCSI_SVM

  PS C:\Users\Scott\Scripts> $myigroups.Initiators | foreach-object {
  >> Add-NcIgroupInitiator -controller $bwnetapp_cluster -name $myigroups.Name -vservercontext BW_ISCSI_SVM -IQN $_.initiatorName }

  Name            :
  Type            : windows
  Protocol        : iscsi
  Portset         :
  ALUA            : True
  ThrottleBorrow  : False
  ThrottleReserve : 0
  Partner         : True
  VSA             : False
  Initiators      : {}
  Vserver         : BW_ISCSI_SVM

And map them. Note that this step you should probably complete during the final cutover when the server using the LUNs is shut down. You can optionally map them prior to cutover but this could get confusing fast.

  PS C:\Users\Scott\Scripts> $mymappedluns | foreach-object { Add-NcLunMap -controller $bwnetapp_cluster -VserverContext BW_ISCSI_SVM -Path $_.Path -id $_.lunid -InitiatorGroup $_.initiatorgroup }

  Path                                           Size   SizeUsed Protocol     Online Mapped  Thin  Vserver
  ----                                           ----   -------- --------     ------ ------  ----  -------
  /vol/brekronos_backup/brekronos_backup     100.0 GB     4.4 GB windows_2008  True   True  False  BW_ISCSI_SVM
  /vol/brekronos_db/brekronos_db              75.0 GB     7.5 GB windows_2008  True   True  False  BW_ISCSI_SVM
  /vol/brekronos_logs/brekronos_logs          75.0 GB    91.9 MB windows_2008  True   True  False  BW_ISCSI_SVM

Cutover for iSCSI

Cutover for volumes containing iSCSI luns is similar to what 7mTT does. We shut down the machine containing the luns and perform a final ndmpcopy run to get a good sync

Once we map the luns (shown above) we need to clean up the 7-mode side prior to bringing up the server. The steps below show unmapping of the luns from the 7-mode system, offlining those luns, renaming the volumes containing them and then finally offlining the volumes on the 7-mode system.

  PS C:\Users\Scott\Scripts> $mymappedluns | foreach-object { Remove-NaLunMap -controller $bwnetapp1 -Path $_.Path -InitiatorGroup $_.initiatorgroup }

  Path                                      TotalSize   SizeUsed Protocol     Online Mapped  Thin  Comment
  ----                                      ---------   -------- --------     ------ ------  ----  -------
  /vol/brekronos_backup/brekronos_backup     100.0 GB     4.5 GB windows_2008 True  False  False
  /vol/brekronos_db/brekronos_db              75.0 GB     7.6 GB windows_2008 True  False  False
  /vol/brekronos_logs/brekronos_logs          75.0 GB   166.5 MB windows_2008 True  False  False
  PS C:\Users\Scott\Scripts> $mymappedluns | foreach-object { Set-NaLun -controller $bwnetapp1 -Path $_.Path -Offline }

  Path                                      TotalSize   SizeUsed Protocol     Online Mapped  Thin  Comment
  ----                                      ---------   -------- --------     ------ ------  ----  -------
  /vol/brekronos_backup/brekronos_backup     100.0 GB     4.5 GB windows_2008 False  False  False
  /vol/brekronos_db/brekronos_db              75.0 GB     7.6 GB windows_2008 False  False  False
  /vol/brekronos_logs/brekronos_logs          75.0 GB   166.5 MB windows_2008 False  False  False
  PS C:\Users\Scott\Scripts> $mymappedluns | foreach-object { $tempvol = $_.Path.Split("{/}"); $fooname = $tempvol[2]; Rename-NaVol -controller $bwnetapp1 -Name $fooname -NewName "Off_$fooname" }

  Name                      State       TotalSize  Used  Available Dedupe  FilesUsed FilesTotal Aggregate
  ----                      -----       ---------  ----  --------- ------  --------- ---------- ---------
  Off_brekronos_backup      online       200.0 GB   51%    98.9 GB  True         103         9M aggr2
  Off_brekronos_db          online       150.0 GB   51%    73.9 GB  True         103         6M aggr2
  Off_brekronos_logs        online       150.0 GB   51%    74.1 GB  True         103         6M aggr2

  PS C:\Users\Scott\Scripts> $mymappedluns | foreach-object { $tempvol = $_.Path.Split("{/}"); $fooname = $tempvol[2]; Set-NaVol -controller $bwnetapp1 -Name "Off_$fooname" -Offline }

  Name                      State       TotalSize  Used  Available Dedupe  FilesUsed FilesTotal Aggregate
  ----                      -----       ---------  ----  --------- ------  --------- ---------- ---------
  Off_brekronos_backup      offline             0    0%          0 False           0          0 aggr2
  Off_brekronos_db          offline             0    0%          0 False           0          0 aggr2
  Off_brekronos_logs        offline             0    0%          0 False           0          0 aggr2

The final step I took is renaming the NDMcopy script I used to indicate that my work is complete.

CIFS cutover

CIFS(SMB) cutover is a bit different. Here I elected to use the CLI on 7mode and clustered ONTAP to complete the work. Yes I could have done these same steps using the PSTK but it made more sense to me to complete using the CLI.

    The following things are true for this scenario:
  • the destination SVM is not taking on the active directory identity of the
  • original 7-mode system
  • It is, however, eventually taking on the IP address of the old system
  • The DNS record for the 7 mode system therefore remains, but the AD computer
  • account will be deleted by the AD admin during the cutover window. This is important for authentication/authorization.
  • the 7-mode system will continue to use the additional IP we added until it
  • is decommissioned. It will be renamed as well. CIFS services will remain shut down.
  • on the 7-mode system VLAN interfaces were created on top of LACP interface
  • groups. The same approach is applied on clustered data ontap to create interface "ports" and the final LIF with the layer 3 IP is applied to the SVM. You really need to understand the networking differences between legacy 7-mode and current clustered ONTAP going forward.

copying CIFS shares and CIFS share ACLs "apply configuration"

While it should be possible to use a similar technique to copy CIFS shares and their ACLs from 7-mode to clustered ONTAP, I chose to use Cosonok's script. His approach is to grab the data from the 7 mode files and translate them into clustered ONTAP CLI commands suitable for cut and paste. Yes, this is a bit more primitive perhaps, but it's tried and true. The script is also mature and covers a lot of details that I, frankly, didn't have time to research, test and debug.

The script worked as expected creating the shares and ACLs and providing some checks along the way to ensure they were created correctly

cutover for CIFS

This is relatively similar to iSCSI. However, in this scenario we control the final shutdown of CIFS services. Below I first grab a view of what is currently connected and then terminate CIFS.

  ~/Downloads on  master ⌚ 19:01:42
  $ ssh -c 3des-cbc scott@bwnetapp1 cifs sessions -t
    scott@bwnetapp1's password:
    Using domain authentication. Domain type is Windows 2000.
    Root volume language is not set. Use vol lang.
    Number of WINS servers: 1
    Total CIFS sessions: 29
    CIFS open shares: 28
    CIFS open files: 14
    CIFS locks: 146
    CIFS credentials: 64
    IPv4 CIFS sessions: 29
    IPv6 CIFS sessions: 0
    Cumulative IPv4 CIFS sessions: 1436846
    Cumulative IPv6 CIFS sessions: 0
    CIFS sessions using security signatures: 0

    bwnetapp1> cifs terminate
    Total number of connected CIFS users: 4
         Total number of open CIFS files: 3
    Warning: Terminating CIFS service while files are open may cause data loss!!
    Enter the number of minutes to wait before disconnecting [5]: 0

    CIFS local server is shutting down...

    CIFS local server has shut down...

Now I can complete the final ndmpcopy update. Following that I need to clean up the 7-mode system by removing the old IP, updating the relevant startup files /etc/rc and /etc/hosts. I used rdfile to grab the files and I use wrfile to write them out and another rdfile to verify.

new /etc/rc

hostname nonetapp1-old
ifgrp create lacp trunk -b mac e0a e6a e0b e6c
vlan create trunk 172 27 127 300
ifconfig trunk-172 netmask up
ifconfig trunk-300 `hostname`-trunk-300 netmask mtusize 1500 trusted -wins up
#ifconfig trunk-172 alias
#ifconfig trunk-300 alias netmask
route add default 1
routed on
options dns.domainname
options dns.enable on
options nis.enable off

new /etc/hosts       localhost    nonetapp1-old       nonetapp1-trunk-172  nonetapp1-trunk-300
#  nonetapp1-trunk-172    mailhost  noesx2  noesx1  noesx6  noesx5  noesx4  noesx3     noesx1     bwesx5     bwesx4     bwesx3     bwesx2  bwesx1     bwnetapp1     bwnetapp2

Those files will simply be pasted in once the final copy is complete. Then I do the following to replace the primary IP to match my new startup files.

ifconfig trunk-172 -alias
ifconfig trunk-172 netmask up
hostname nonetapp1-old

With all that do I can place the relevant IP on my clustered ontap svm with net int create -vserver BW_CIFS_SVM -address -netmask -home-node bwnetapp3 -home-port a0a-172 -admin-status up

Once that LIF is created on clustered ONTAP, the active directory computer record for the 7-mode system should be deleted while the DNS A record remains in place. Browsing the the system in Windows should pull up the list of visible shares assuming you are logged in via an account with appropriate credentials of course.


With any transition there's some clean up such as with snapshot policies. This is your opportunity to ensure snapshot intervals and snapmirror policies are rationalized for your environment. Cleaning up snapshots that transitioned with 7mTT may need to be done as well but that's a subject for a followup post.

All of the above is very specific to NetApp systems and transitions. But the techniques and overall approach is not. The reality is we could use similar methods to move data between varying systems. The tooling and APIs for the source or destination systems may differ but you should be able to find a way to automate as much of the process as possible. Wherever you can, if you're grabbing known good values from a source system (eg iSCSI IQNs) you should be able to programatically grab those values and send them to a destination host. This is preferable to typing in these manually. It's also preferable to cutting and pasting though in some cases that may be a viable approach as well as long as you are very careful.

comments powered by Disqus