How to Convert 7-Mode NFS Exports into cDOT Export Policies and Rules

Introduction

If you want to manually convert your 7-Mode exports file into the equivalent Clustered Data ONTAP Export Polices and Export Policy Rules - as close to equivalent as reasonably possible anyway - the following PowerShell script might help you to do this.

It only handles -sec=sys exports (which is probably 99.9% of exports anyway).
It doesn’t handle -actual= which isn’t supported in cDOT (check out NetApp TR-4067.)

Because you can only export at the volume level or qtree level in cDOT (8.2.1+), the tool consolidates volume and sub-volume (without a Qtree in the path) exports into volume specific Export Policies, and qtree and sub-qtree exports into qtree specific Export Policies.

The r/o hosts and r/w hosts need to be split out into separate rules. The r/o rules are always placed after the r/w rules.

The script is easy to run. Name it as say 7CET.ps1, and run as:

.\7CET.ps1 -FilePath YOUR_7M_EXPORTS_FILE

N.B. 7CET = 7 to C Exports Transitioner

The Script (formatted for blogger)

############################################
## 7 to C Exports Transitioner (7CET.ps1) ##
############################################

Param(
  # 7-Mode Exports File
  [Parameter(Mandatory=$true)][String]$FilePath,
  # cDOT: Vserver Name
  [String]$VserverName = "SVM1",
  # Specify a list as - "vol1.folder1","vol2.folder2",...
  # ... where ??? in the path /vol/VOLNAME/??? is not a Qtree
  [System.Array]$OverrideQtrees
)

# We use the FileName in some outputs
[String]$FileName = $FilePath.Split("\")[0]

# Records lines of Screen Output (flushed to SOut on Wr)
[String]$Global:LOut = ""

# Used to record the ScreenOutput
[System.Array]$Global:SOut = @()

Function Wr  {      
  Param([String]$DisplayThis,[String]$ForegroundColor)
  If(!$DisplayThis){
    Write-Host
    $Global:SOut += $Global:LOut
    $Global:LOut = ""
    RETURN
  } elseif(!$ForegroundColor) {
    Write-Host $DisplayThis -ForegroundColor White -NoNewLine
  } else {
    Write-Host $DisplayThis -ForegroundColor $ForegroundColor -NoNewLine
  }
  $Global:LOut += $DisplayThis
}; Wr

Wr "<<<<< NFS EXPORTS 7 to C v1.0 >>>>>" MAGENTA; Wr; Wr

######################################
## GET AND PROCESS THE EXPORTS FILE ##
######################################

[System.Array]$ExportsFile = Get-Content $FilePath
Wr "Loaded file from: $FilePath" GREEN; Wr

Function Process-Array {
  Param([System.Array]$ArrayToProcess)
  [System.Array]$ProcessedArray = @()
  Foreach ($Line in $ArrayToProcess){          
    # REMOVE BLANK LINES "" AND LINES BEGINNING WITH "#"
    If( ($Line -ne "") -and ($Line.substring(0,1) -ne "#") ){
      $ProcessedArray += $Line
    }
  }
  # RETURN THE PROCESSED ARRAY
  , $ProcessedArray
}

$ExportsFile = Process-Array $ExportsFile; Wr

######################################################
## STEP 1: PROCESS 7-MODE EXPORTS FILE LINE BY LINE ##
######################################################

Wr ">>> STEP 1: PLEASE VERIFY THAT THE EXPORTS FILE HAS BEEN READ CORRECTLY! <<<" MAGENTA; Wr; Wr

Function Process-ExportsLine {
 
  Param([String]$Line)
      
  ##### SPLIT ON -actual= OR -sec= TO GET PATH & OPTIONS BITS #####
 
  # Expect (1)PATH_BIT[0]/(2)OPTIONS_BIT[1]
  [System.Array]$SplitOnDashSec    = $Line.Replace("-sec=","#").Split("#")
  [System.Array]$SplitOnDashActual = $Line.Replace("-actual=","#").Split("#")
 
  If( !( ($SplitOnDashSec.Count -eq 2) -or ($SplitOnDashActual.Count -eq 2) ) ){
    # NOT A VALID LINE ( .LineIsValid remains FALSE)
    RETURN
  }
 
  If( $SplitOnDashSec.Count -eq 2 ){
    ## HAS -sec=
    [String]$PathBit    = $SplitOnDashSec[0].Trim(" ","`t")
    [String]$OptionsBit = $SplitOnDashSec[1].Trim(" ","`t")
  } else {
    ## HAS -actual=
    $Exports.$Index.ActualSpecified = $TRUE
    [String]$PathBit    = $SplitOnDashActual[0].Trim(" ","`t")
    [String]$OptionsBit = $SplitOnDashActual[1].Trim(" ","`t")
  }
 
  # Expect (1)[0]/(2)vol[1]/(3)VOLNAME[2]/(4){QTREE}[3]/{SUBQTREE_BIT}
  $SplitOnSlash = $PathBit.Split("/")
 
  If($SplitOnSlash.Count -lt 3){
    # NOT A VALID LINE ( .LineIsValid remains FALSE)
    RETURN
  }
 
  [String]$VolNameFromExport = $SplitOnSlash[2]
  $Exports.$Index.VolName    = $VolNameFromExport
 
  If($SplitOnSlash.Count -ge 4){
    $PossibleQtree = $SplitOnSlash[3].Replace("\"," ")
    # N.B. Qtrees names can have " " in (which is \ in exports)
    If( !($OverrideQtrees -contains ($VolNameFromExport + "." + $PossibleQtree)) ){
      $Exports.$Index.QtreeName = $PossibleQtree
    }
  }
  If($SplitOnSlash.Count -ge 5){
    [String]$PathUpToQtree = ("/vol/" + $Exports.$Index.VolName + "/" + $Exports.$Index.QtreeName + "/")
    $Exports.$Index.SubQtreeBit = $PathBit.Substring($PathUpToQtree.Length).Replace("\"," ")
    # N.B. Folder names can have " " in (which is \ in exports)
  }
 
  ##### HANDLE THE OPTIONS #####
 
  ## -actual= COMES 1ST ##
  If($Exports.$Index.ActualSpecified){
    $Exports.$Index.ActualEquals = $OptionsBit.Replace(",sec=","#").Split("#")[0]
    $OptionsBit                  = $OptionsBit.Replace(",sec=","#").Split("#")[1]
  }
 
  ## -sec= COMES 2ND ##
  $Exports.$Index.SecEquals = $OptionsBit.Split(",")[0]
  $OptionsBit = $OptionsBit.TrimStart($Exports.$Index.SecEquals)
 
  ## THESE REST (all separated by ,) ##
  $OptionsSplitComma = $OptionsBit.Split(",")
  # N.B. We left a comma after the sec= bit
 
  For( [Int]$i=1; $i -lt ($OptionsSplitComma.Count); $i++ ){
   
    [String]$CheckThis = [String]($OptionsSplitComma[$i])
    $CheckThis         = $CheckThis.Trim(" ","`t","`n")
   
    If($CheckThis.StartsWith("anon=")){
      $Exports.$Index.AnonSpecified = $TRUE
      $Exports.$Index.AnonEquals = $CheckThis.Substring(5)
    }
    If($CheckThis -eq "nosuid"){
      $Exports.$Index.NoSuidSpecified = $TRUE
    }
    If($CheckThis -eq "ro"){
      $Exports.$Index.RoSpecified = $TRUE
    }
    If($CheckThis.StartsWith("ro=")){
      $Exports.$Index.RoEquals = $CheckThis.Substring(3)
    }
    If($CheckThis -eq "rw"){
      $Exports.$Index.RwSpecified = $TRUE
    }
    If($CheckThis.StartsWith("rw=")){
      $Exports.$Index.RwEquals = $CheckThis.Substring(3)
    }
    If($CheckThis.StartsWith("root=")){
      $Exports.$Index.RootEquals = $CheckThis.Substring(5)
    }
  }
 
  $Exports.$Index.LineIsValid = $TRUE
  # N.B. Could do with adding some validity tests!

}

[Int]$Index             = 0   # INITIALIZE THE INDEX AS 0
[System.Object]$Exports = @{} # INITIALIZE THE $Exports OBJECT

Foreach($Line in $ExportsFile){
 
  $Index++
 
  ## INITIALIZE THE OBJECT TO HOLD THE 7-MODE EXPORT LINE'S DATA ##
 
  # DATA STRUCTURE TO CONTAIN 7-MODE EXPORTS
  [System.Object]$Exports.$Index = @{}
 
  # /vol/VOLNAME   
  [String]$Exports.$Index.VolName = ""
 
  # /vol/VOLNAME/QTREENAME
  [String]$Exports.$Index.QtreeName = ""
 
  # /vol/VOLNAME/QTREENAME/SUBQTREEBIT
  [String]$Exports.$Index.SubQtreeBit = ""
 
  # actual= is specified
  [Boolean]$Exports.$Index.ActualSpecified = $FALSE
 
  # actual={path}
  [String]$Exports.$Index.ActualEquals = ""
 
  # anon= is specified
  [Boolean]$Exports.$Index.AnonSpecified = $FALSE
 
  # anon={uid|name}
  [String]$Exports.$Index.AnonEquals = ""
 
  # nosuid is specified
  [Boolean]$Exports.$Index.NoSuidSpecified = $FALSE
 
  # ro is specified (with no equals)
  [Boolean]$Exports.$Index.RoSpecified = $FALSE
 
  # ro={clientid[:clientid...]}
  [String]$Exports.$Index.RoEquals = ""
 
  # rw is specified (with no equals)
  [Boolean]$Exports.$Index.RwSpecified = $FALSE
 
  # rw={clientid[:clientid...]}
  [String]$Exports.$Index.RwEquals = ""
 
  # root={clientid[:clientid...]}
  [String]$Exports.$Index.RootEquals = ""
 
  # sec={sectype[:sectype...]}
  [String]$Exports.$Index.SecEquals = ""
 
  # (... check exports line is valid ...)
  [Boolean]$Exports.$Index.LineIsValid = $FALSE
 
  # (... record the line sent for processing ...)
  [String]$Exports.$Index.OriginalLine = ""
 
  # See: https://library.netapp.com/ecmdocs/ECMP1196979/html/man5/na_exports.5.html
 
  ## GET RID OF ANY COMMENTS AT THE END OF THE LINE AND TRIM SPACES AND TABS ##
  $Exports.$Index.OriginalLine = $Line.Split("#")[0].Trim(" ","`t")
 
  ## INVOKE THE PROCESS-EXPORTS FUNCTION TO PROCESS THE EXPORTS LINE ##
  Process-ExportsLine ($Exports.$Index.OriginalLine)
 
  ## DISPLAY THE PROCESSED LINE (TO MAKE SURE WE'VE READ IT RIGHT!) ##
  Wr "ORIGINAL LINE: "; Wr $Exports.$Index.OriginalLine GREEN; Wr
 
  If(!$Exports.$Index.LineIsValid){
    Wr "NOT A VALID LINE!" RED; Wr
  } else {
 
    Wr "Volume         : "
    Wr $Exports.$Index.VolName CYAN; Wr
      
    If($Exports.$Index.QtreeName){
      If($OverrideQtrees){
        Wr "Qtree          : "
        Wr $Exports.$Index.QtreeName CYAN; Wr
      } else {
        Wr "Qtree "
        Wr "(Assumed)" YELLOW; Wr ": "
        Wr $Exports.$Index.QtreeName CYAN; Wr
      }
    }
   
    If($Exports.$Index.SubQtreeBit){
      Wr "SUBQTREE BIT   : ";
      Wr $Exports.$Index.SubQtreeBit CYAN; Wr
    }
   
    If($Exports.$Index.ActualSpecified){
      Wr "actual=        : ";
      Wr $Exports.$Index.ActualEquals CYAN; Wr
    }
   
    Wr "sec=           : ";
    Wr $Exports.$Index.SecEquals CYAN; Wr
   
    If($Exports.$Index.RoSpecified){
      Wr "RO             : "
      Wr "Specified as ro!" YELLOW; Wr
    }
   
    If($Exports.$Index.RoEquals){
      Wr "RO HOSTS=      : "
      Wr $Exports.$Index.RoEquals CYAN; Wr
    }
   
    If($Exports.$Index.RwSpecified){
      Wr "RW             : "
      Wr "Specified as rw!" YELLOW; Wr
    }
   
    If($Exports.$Index.RwEquals){
      Wr "RW HOSTS=      : "
      Wr $Exports.$Index.RwEquals CYAN; Wr
    }
   
    If($Exports.$Index.RootEquals){
      Wr "ROOT HOSTS=    : "
      Wr $Exports.$Index.RootEquals CYAN; Wr
    }
   
    If($Exports.$Index.AnonSpecified){
      Wr "ANON =         : "
      Wr $Exports.$Index.AnonEquals YELLOW; Wr
    }
   
    If($Exports.$Index.NoSuidSpecified){
      Wr "NOSUID         : "
      Wr "Nosuid is specified." GREEN; Wr
    }
  }
  Wr
}

#########################################################################
## STEP 2: PROCESSING THE 7-MODE EXPORTS TO CDOT CLUSTERSHELL COMMANDS ##
#########################################################################

Wr ">>> STEP 2: PROCESSING THE 7-MODE EXPORTS TO CDOT CLUSTERSHELL COMMANDS <<<" MAGENTA; Wr; Wr

## CHECK FOR LINES WE CAN HANDLE ##
## ============================= ##

# Array for volume exports only - /vol/VOLNAME
[System.Array]$VolExportsOnly = @()

# Array for sub-volume exports only - /vol/VOLNAME/SUB-FOLDER
[System.Array]$SubVolExportsOnly = @()

# Array for qtree exports only - /vol/VOLNAME/QTREENAME
[System.Array]$QtreeExportsOnly = @()

# Array for sub-qtree exports only - /vol/VOLNAME/QTREENAME/SUB-QTREE-FOLDER
[System.Array]$SubQtreeExportsOnly = @()

# Initialize cExports object
[System.Object]$cExports = @{}

# Create an array for the ExportedVolumeList
[System.Array]$cExports.ExportedVolList = @()

# Create an object for volumes
[System.Object]$cExports.Volume = @{}

## CYCLE THROUGH THE 7-MODE EXPORTS ##
For($i=1; $i -le $Index; $i++){
 
  ## CHECK FOR VALID EXPORTS LINE ##
  If($Exports.$Index.LineIsValid){
    
    ## THINGS THIS PROGRAM DOESN'T HANDLE (YET) ##    
    If( $Exports.$i.ActualSpecified ){
      Wr "-actual= IS NOT SUPPORTED IN cDOT (see TR-4067)!" RED; Wr
      Wr "WON'T HANDLE: " RED; Wr $Exports.$i.OriginalLine YELLOW; Wr; Wr
    } elseif( $Exports.$i.SecEquals -ne "sys" ){
      Wr "ONLY HANDLING SEC=SYS EXPORTS!" RED; Wr
      Wr "WON'T HANDLE: " RED; Wr $Exports.$i.OriginalLine YELLOW; Wr; Wr
    } else {
      $VolName   = $Exports.$i.VolName
      $QtreeName = $Exports.$i.QtreeName
     
      ## THINGS WE CAN PROCESS ##
      If(!$QtreeName -and !$Exports.$i.SubQtreeBit){
        # 7M Exports that have NO Qtree NOR a Sub-Qtree bit
        $VolExportsOnly += $i
      } elseif (!$QtreeName -and  $Exports.$i.SubQtreeBit){
        # 7M Exports that have NO Qtree BUT a Sub-Qtree (Sub-Volume) bit
        $SubVolExportsOnly += $i
      }
      elseif ($QtreeName -and !$Exports.$i.SubQtreeBit){
        # 7M Exports that have a Qtree and NO Sub-Qtree bit
        $QtreeExportsOnly += $i
      }
      elseif ($QtreeName -and  $Exports.$i.SubQtreeBit){
        # 7M Exports that have a Qtree and a Sub-Qtree bit
        $SubQtreeExportsOnly += $i
      }
     
      ## WARN ABOUT SUB QTREE/SUB VOLUME EXPORTS ##
      If( $Exports.$i.SubQtreeBit ){
        Wr "WARNING! THE FOLLOWING LINE IS A SUB QTREE/SUB VOLUME (without a QTREE) EXPORT. " YELLOW
        If( $QtreeName ){
          Wr "WILL DEFINE RULES IN AN EXPORT POLICY ON THE QTREE." CYAN; Wr
        } else {
          Wr "WILL DEFINE RULES IN AN EXPORT POLICY ON THE VOLUME." CYAN; Wr
        }
        Wr "LINE: " CYAN; Wr $Exports.$i.OriginalLine GREEN; Wr; Wr
      }
     
      ## POPULATE EXPORTED VOLS LIST ##
      If( !($cExports.ExportedVolList -Contains $VolName) ){
       
        # Accumulate the Exported Vols List
        $cExports.ExportedVolList += $VolName
       
        # Create an object for this volume
        [System.Object]$cExports.Volume.$VolName = @{}
       
        # Initialize as FALSE for has Qtree Exports
        [Boolean]$cExports.Volume.$VolName.HasQtreeExports = $FALSE
       
        # Create an array for exported qtrees for $VolName
        [System.Array]$cExports.Volume.$VolName.ExportedQtreeList = @()
      }
     
      ## POPULATE EXPORTED QTREES LIST ##
      If($QtreeName){
        If( !($cExports.Volume.$VolName.ExportedQtreeList -Contains $QtreeName) ){
          $cExports.Volume.$VolName.ExportedQtreeList += $QtreeName
          $cExports.Volume.$VolName.HasQtreeExports    = $TRUE
        }
      }                   
    }
  }
}

## INITIALIZE CLUSTERSHELL OUTPUT ##
## ============================== ##

[System.Array]$Output = @()

$Output += ("### 7 TO C EXPORTS TRANSLATIONS for $FileName ###")
$Output += (""),("## VOLUME MOUNT FOR EXPORTED VOLUMES ##"),("")

# Mount Volumes
$cExports.ExportedVolList | Foreach {
  $Output += ("volume mount -vserver $VserverName -junction-path /vol/$_")
}

$Output += (""),("## CREATE NO ACCESS AND READONLY EXPORT POLICIES (8.2+) ##"),("")

# No Access Export Policy
$Output += ("vserver export-policy create -vserver $VserverName -policyname expol_noaccess")

# Read Only Export Policy
$Output += ("vserver export-policy create -vserver $VserverName -policyname expol_readonly")
$Output += ("vserver export-policy rule create -vserver $VserverName -policyname expol_readonly -ruleindex 1 -protocol any -clientmatch 0.0.0.0/0 -rorule any -rwrule never -anon 65534 -superuser none -allow-suid true -allow-dev true")

$Output += (""),("## INITIALLY APPLY READ ONLY TO ALL EXPORTED VOLUMES ##"),("")

# Initially apply readonly to all volumes
$cExports.ExportedVolList | Foreach {
  $Output += "volume modify -vserver $VserverName -volume $_ -policy expol_readonly"
}

$Output += (""),("## INITIALLY APPLY NO ACCESS TO QTREES ON EXPORTED VOLUMES ##"),("")

# Initially apply no access to all qtrees
$cExports.ExportedVolList | Foreach {
  $Output += ("qtree modify -vserver $VserverName -volume $_ -qtree !" + '"' + '"' + " -export-policy expol_noaccess")
}

$Output += ("")

## FUNCTION TO PROCESS ROOT HOSTS, RW HOSTS, AND RO HOSTS ##
## ====================================================== ##

# Initialize array for Read/Write Rules (must come before ro rules)
[System.Array]$Global:RwRules   = @()

# Initialize array for Read/Only Rules
[System.Array]$Global:RoRules   = @()

# Initialize array for RW Clients (used to prevent duplicates)
[System.Array]$Global:RwClients = @()

# Initialize array for RO Clients (used to prevent duplicates)
[System.Array]$Global:RoClients = @()

Function Process-AllHosts {
  Param([System.Object]$7mExport)
  [System.Array]$RootHosts = ($7mExport.RootEquals).Split(":")
  [System.Array]$RwHosts   = ($7mExport.RwEquals).Split(":")
  [System.Array]$RoHosts   = ($7mExport.RoEquals).Split(":")                            
  If($RwHosts){
    Foreach ($RwHost in $RwHosts){
      If( !($Global:RwClients -Contains $RwHost) ){
        $Global:RwClients += $RwHost
        If($RootHosts -Contains $RwHost){
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RwHost -rorule sys -rwrule sys $Anon -superuser sys $Suid $DefaultDev")
        } else {
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RwHost -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
        }
      }
    }
  }
  If($RoHosts){
    Foreach ($RoHost in $RoHosts){
      If( !($Global:RoClients -Contains $RoHost) ){
        $Global:RoClients += $RoHost
        If($RootHosts -Contains $RoHost){
          $Global:RoRules += ("$DefaultProtocol -clientmatch $RoHost -rorule sys -rwrule never $Anon -superuser sys $Suid $DefaultDev")
        } else {
          $Global:RoRules += ("$DefaultProtocol -clientmatch $RoHost -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
        }
      }
    }
  }
  If($RootHosts){
    Foreach ($RootHost in $RootHosts){
      If( !($Global:RwClients -Contains $RootHost) ){
        If( !($RwHosts -contains $RootHost) -and !($RoHosts -contains $RootHost) ){
          $Global:RwClients += $RootHost
          $Global:RwRules += ("$DefaultProtocol -clientmatch $RootHost -rorule sys -rwrule sys $Anon -superuser sys $Suid $DefaultDev")
        }
      }
    }
  }
  Return $TRUE
}

## CREATE AND APPLY PER VOLUME EXPORT POLICIES (INCLUDES SUB-VOLUME EXPORTS) ##
## ========================================================================= ##

# Not specified in 7-Mode
[String]$DefaultProtocol = "-protocol any"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultAnon = "-anon 65534"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultSuid = "-allow-suid true"

# Same default in 7-Mode (so, if not specified is this)
[String]$DefaultDev = "-allow-dev true"

$Output += ("## PER VOLUME EXPORT POLICIES (INCLUDES SUB-VOLUME EXPORTS ##"),("")

### CYCLE THROUGH THE EXPORTED VOLUMES LIST ##
Foreach($VolName in $cExports.ExportedVolList){

  [String]$ExPolicy = ("expol_v_" + $VolName)
 
  # N.B. We add rule indexes later
  [String]$ExPolRuleStart = "vserver export-policy rule create -vserver $VserverName -policyname $ExPolicy -ruleindex "
 
  # Initialize "Has a Vol or Sub Vol Export (for $VolName) as FALSE
  [Boolean]$HasAVolOrSubVolExport = $FALSE
 
  # Reset R/W Rules
  $Global:RwRules = @()
 
  # Reset R/O Rules
  $Global:RoRules = @()
 
  # Initialize as NULL general Read/Write rule
  [String]$RwRule = ""
 
  # Initialize as NULL general Read/Only rule
  [String]$RoRule = ""
 
  # Reset R/W Clients
  $Global:RwClients = @()
 
  # Reset R/O Clients
  $Global:RoClients = @()

  ## VOLUME EXPORTS ##
  $VolExportsOnly | Foreach {
    If($Exports.$_.AnonSpecified){
      [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
    } else {
      [String]$Anon = $DefaultAnon
    }
    If($Exports.$_.NoSuidSpecified){
      [String]$Suid = ("-allow-suid false")
    } else {
      [String]$Suid = $DefaultSuid
    } 
    If($Exports.$_.VolName -eq $VolName){
      # $VolName has a volume export (Process-AllHosts always returns TRUE)
      $HasAVolOrSubVolExport = Process-AllHosts $Exports.$_
      If($Exports.$_.RwSpecified){
        $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
      }
      If($Exports.$_.RoSpecified){
        $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
      }
    }
  }

  ## SUB VOLUME EXPORTS ##
  $SubVolExportsOnly | Foreach {
    If($Exports.$_.AnonSpecified){
      [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
    } else {
      [String]$Anon = $DefaultAnon
    }
    If($Exports.$_.NoSuidSpecified){
      [String]$Suid = ("-allow-suid false")
    } else {
      [String]$Suid = $DefaultSuid
    }
    If($Exports.$_.VolName -eq $VolName){
      # $VolName has a sub-volume export (Process-AllHosts always returns TRUE)
      $HasAVolOrSubVolExport = Process-AllHosts $Exports.$_
      If($Exports.$_.RwSpecified -and !$RwRule){
        $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
      }
      If($Exports.$_.RoSpecified -and !$RoRule){
        $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
      }
    }
  }
      
  ## IF THE VOLUME HAS AN EXPORTED QTREE, ADD THE STANDARD READONLY RULE ##
  If( $cExports.Volume.$VolName.HasQtreeExports -and !$RwRule -and !$RoRule){
    $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $DefaultAnon -superuser none $DefaultSuid $DefaultDev")
  }

  ## IF WE HAVE A VOLUME OR SUB VOLUME EXPORT, OUTPUT IT ##
  If($HasAVolOrSubVolExport){
    $RuleIndex = 1
    $Output += ("vserver export-policy create -vserver $VserverName -policyname $ExPolicy"      )
    $Global:RwRules | Foreach {
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
      $RuleIndex++
    }
    If($RwRule){
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RwRule)
      $RuleIndex++
    }
    If(!$RwRule){
      $Global:RoRules | Foreach {
        [Boolean]$NotRwClient = $TRUE
        Foreach ($RwClient in $Global:RwClients){
          If($_ -match $RwClient){
            $NotRwClient = $FALSE
          }
        }
        If($NotRwClient){
          $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
          $RuleIndex++
        }
      }
    }
    If(!$RwRule -and $RoRule){
      $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RoRule)
      $RuleIndex++
    }
    $Output += ("volume modify -vserver $VserverName -volume $VolName -policy $ExPolicy"),("")
  }
}

## CREATE AND APPLY PER QTREE EXPORT POLICIES (INCLUDES SUB-QTREE EXPORTS) ##
## ======================================================================= ##

$Output += ("## PER VOLUME QTREE POLICIES (INCLUDES SUB-QTREE EXPORTS ##"),("")

## CYCLE THROUGH THE EXPORTED VOLUMES LIST ##
Foreach($VolName in $cExports.ExportedVolList){
 
  ## CYCLE THROUGH THE EXPORTED QTREES FOR $VOLNAME ##
  Foreach($QtreeName in $cExports.Volume.$VolName.ExportedQtreeList){
   
    # Qtree Names can have spaces in!
    [String]$ExPolicy = ("expol_q_" + $VolName + "_" + $QtreeName.Replace(" ","_"))
   
    # Add rule indexes later
    [String]$ExPolRuleStart = "vserver export-policy rule create -vserver $VserverName -policyname $ExPolicy -ruleindex "
    
    # Initialize "Has a Qtree or Sub Qtree Export (for $VolName.$QtreeName) as FALSE
    [Boolean]$HasAQtreeOrSubQtreeExport = $FALSE
   
    # Reset R/W Rules
    $Global:RwRules = @()
   
    # Reset R/O Rules
    $Global:RoRules = @()
   
    # Initialize as NULL general Read/Write rule
    [String]$RwRule = ""
   
    # Initialize as NULL general Read/Only rule
    [String]$RoRule = ""
   
    # Reset R/W Clients
    $Global:RwClients = @()
   
    # Reset R/O Clients
    $Global:RoClients = @()
   
    ## QTREE EXPORTS ##
    $QtreeExportsOnly | Foreach {
      If($Exports.$_.AnonSpecified){
        [String]$Anon = ("-anon " + $Exports.$_.AnonEquals)
      } else {
        [String]$Anon = $DefaultAnon
      }
      If($Exports.$_.NoSuidSpecified){
        [String]$Suid = ("-allow-suid false")
      } else {
        [String]$Suid = $DefaultSuid
      }
      If($Exports.$_.VolName -eq $VolName){
        If($Exports.$_.QtreeName -eq $QtreeName){
          # $QtreeName has a Qtree export (Process-AllHosts always returns TRUE)
          $HasAQtreeOrSubQtreeExport = Process-AllHosts $Exports.$_
          If($Exports.$_.RwSpecified){
            $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
          }
          If($Exports.$_.RoSpecified){
            $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
          }
        }
      }
    }
             
    ## SUB QTREE EXPORTS ##
    $SubQtreeExportsOnly | Foreach {           
      If($Exports.$_.AnonSpecified){
        [String]$Anon = ("-anon " + $Exports.$_.AnonEquals) } else { [String]$Anon = $DefaultAnon
      }
      If($Exports.$_.NoSuidSpecified){
        [String]$Suid = ("-allow-suid false")
      } else {
        [String]$Suid = $DefaultSuid
      }
      If($Exports.$_.VolName -eq $VolName){
        If($Exports.$_.QtreeName -eq $QtreeName){                   
          # $QtreeName has a Sub-Qtree export (Process-AllHosts always returns TRUE)
          $HasAQtreeOrSubQtreeExport = Process-AllHosts $Exports.$_
          If($Exports.$_.RwSpecified -and !$RwRule){
            $RwRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule sys $Anon -superuser none $Suid $DefaultDev")
          }
          If($Exports.$_.RoSpecified -and !$RoRule){
            $RoRule = ("$DefaultProtocol -clientmatch 0.0.0.0/0 -rorule sys -rwrule never $Anon -superuser none $Suid $DefaultDev")
          }
        }
      }                   
    }
      
    ## IF WE HAVE A QTREE OR SUB QTREE EXPORT, OUTPUT IT ##
    If($HasAQtreeOrSubQtreeExport){
      $RuleIndex = 1
      $Output += ("vserver export-policy create -vserver $VserverName -policyname $ExPolicy"    )
      $Global:RwRules | Foreach {
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
        $RuleIndex++
      }
      If($RwRule){
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RwRule)
        $RuleIndex++
      }
      If(!$RwRule){
        $Global:RoRules | Foreach {
          $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $_)
          $RuleIndex++
        }
      }
      If(!$RwRule -and $RoRule){
        $Output += ($ExPolRuleStart + " " + [String]$RuleIndex + " " + $RoRule)
        $RuleIndex++
      }
      $Output += ("qtree modify -vserver $VserverName -volume $VolName -qtree " + '"' + $QtreeName + '"' + " -export-policy $ExPolicy"),("")
    }
  }
}

##################################
## CLUSTERSHELL COMMANDS OUTPUT ##
##################################

Wr ">>> STEP 3: CLUSTERSHELL COMMANDS OUTPUT <<<" MAGENTA; Wr; Wr
Wr ("For Clustershell commands see text file: " + ($FileName + "-CONVERTED_TO_CSHELL.txt")); Wr
Wr ("For log of screen output see: " + ($FileName + "-7toC_SCREEN_OUTPUT")); Wr; Wr
$Output | Out-File ($FileName + "-CONVERTED_TO_CSHELL.txt")
$Global:SOut | Out-File ($FileName + "-7toC_SCREEN_OUTPUT.txt")

NotePad ($FileName + "-CONVERTED_TO_CSHELL.txt")

Comments

  1. Great script, tiny bug:
    vserver export-policy rule create -vservername
    needs to be replaced by
    vserver export-policy rule create -vserver

    ReplyDelete
    Replies
    1. Hi Unknown, thank you for pointing out the error, I've updated the post to replace -vservername with -vserver. I do have an updated version v1.1, I'm pretty sure the only change was a fix for discovering incorrect subnets in the 7-Mode exports file. Let me know if you want me to post the update. Cheers VC

      Delete

Post a Comment