Masking LUN paths using PowerCLI

I got a question on my previous post about the Get-EsxCli cmdlet, ESXCLI the PowerCLI way, from Alasdair Carnie who was having trouble using the Get-EsxCli cmdlet to mask luns at the ESXi host level. In this post I’ll show you how to accomplish LUN masking at the ESXi host level.

Note 1:
Before you read any further, please notice that the Get-EsxCli cmdlet is experimental. Also notice that in order to retrieve an esxcli instance you have to be connected directly to an ESX host.

Note 2:
When using the esxcli methods in PowerCLI, remember that you pay close attention to the definition of the specific method. You always have to provide a value for every parameter. If you don’t need to provide a value for a specific parameter, you have to specify the $null value.

Now that we are aware of these requirements, let’s create an additional claimrule to mask a LUN at the ESXi host level. Before we create a new claimrule, we’re going to have a look at the running claimrules on the ESXi host first.

Viewing the currently running claimrules

First we create an esxcli instance from PowerCLI

$esxcli = Get-EsxCli

To view the currently running claimrules, we need to call the list() method in the corestorage.claimrule branch.

$esxcli.corestorage.claimrule.list()

Note that I piped the output to ft, which is an alias of the Format-Table cmdlet, to forcibly show the output in table format. As you can see, there are two types of claimrule entries present. The “runtime” class is the rules currently running in the Pluggable Storage Architecture (PSA). The “file” class is a reference to the rules present in the /etc/vmware/esx.conf file. You’ll notice later on that after you create a claimrule it’s initially only listed in the “file” class until you explicitly load the new ruleset.

Adding a new claimrule

When you want to mask a LUN or path, you have to specify a claimrule for the MASK_PATH plugin. To view the available plugins run:

$esxcli.corestorage.plugin.list()

To add a new claimrule we need to call the add() method of the corestorage.claimrule branch. First let’s have a close look at the method’s definition.

$esxcli.corestorage.claimrule | gm

As you notice, the definition is hard to read, as it doesn’t fit in the screen. For this purpose you can better view the output in list format (Format-List or fl). To avoid showing a large list of all methods and properties, we narrow our output to include only the add() method using a where filter.

$esxcli.corestorage.claimrule | gm | where {$_.name -eq "add"} | fl

As you can notice there are a lot of parameters available for creating claimrules. Remember that you have to specify ALL of them. Parameters that aren’t required need to be specified as $null

For a detailed command description, see esxcli corestorage Command-Line Options.

For this purpose I’ve presented an iSCSI LUN to my ESXi host which is located at vmhba33:C0:T0:L0. To list the devices that are available to the ESXi host run the command:

$esxcli.corestorage.device.list()

As you can see, the last device listed is my iSCSI LUN.

To add a claimrule for the MASK_PATH plugin for this iSCSI device we issue:

$esxcli.corestorage.claimrule.add("vmhba33",$null,0,$null,$null,
    $null,$null,0,$null,"MASK_PATH",102,0,$null,"location",$null)

Note 1:
The previous command masks only one path to the iSCSI device (vmhba33:C0:T0:L0). If there are more paths available, you’ll need to add additional rules for each path to completely hide the device.

Note 2:
I specified 102 for the rule number as this is the next available number.

We can now verify the creation of our claimrule by listing the available claimrules again.

$esxcli.corestorage.claimrule.list()

Notice that the new claimrule is only available in the “file” class, but is not actively running. We need to first load the new ruleset into our system.

$esxcli.corestorage.claimrule.load()
$esxcli.corestorage.claimrule.list()

As you can see; our new claiming rule is now available in the “runtime” class and hence active.

Re-Claiming the LUN

We’re not quite there yet, as we need to perform one last step. Our iSCSI LUN is still claimed by the NMP plugin. We can verify this by listing the storage devices using:

$esxcli.corestorage.device.list()

Notice that the iSCSI LUN is still present. We need to unclaim the iSCSI LUN from the NMP plugin and run the claiming rules so that the iSCSI LUN can be claimed by our new masking rule.

$esxcli.corestorage.claiming.unclaim("vmhba33",0,$null,$null,
    $null,0,$null, $null,$null,0,"location",$null)
$esxcli.corestorage.claimrule.run()

Now we verify by listing the devices again.

$esxcli.corestorage.device.list()

As you can see, the iSCSI LUN is now masked and not available anymore.

Summarizing

Let’s summarize all the steps to add a claimrule for path vmhba33:C0:T0:L0

$esxcli.corestorage.claimrule.add("vmhba33",$null,0,$null,$null,
    $null,$null,0,$null,"MASK_PATH",102,0,$null,"location",$null)
$esxcli.corestorage.claimrule.load()
$esxcli.corestorage.claiming.unclaim("vmhba33",0,$null,$null,
    $null,0,$null,$null,$null,0,"location",$null)
$esxcli.corestorage.claimrule.run()

As you can see, it’s quite a hassle to mask a path or LUN at the ESXi host level and because of that is can be very error-prone. Also during a re-install this configuration is lost and you have to re-mask everything and can possibly run into issues. Masking LUNs at the ESXi host level has very specific use cases and should only be used as a last resort, when masking at the storage level isn’t a possible solution. That’s why I recommend against masking paths from the ESXi host level.

Best Practice
Perform LUN masking at the storage level

Unmasking the path

If you want to unmask the path, so that is will be available to the ESXi host again, use this code:

$esxcli.corestorage.claimrule.delete($null,102)
$esxcli.corestorage.claimrule.load()
$esxcli.corestorage.claiming.unclaim("vmhba33",0,$null,$null,
    $null,0,$null,$null,$null,0,"location",$null)
$esxcli.corestorage.claimrule.run()

Related posts:

  1. ESXCLI the PowerCLI way Tweet When trying to perform a Round Robin (RR) test on my vSphere 4.1 environment, I needed to know the number of paths available to the luns of the test...
  2. ESXi Tech Support Mode Tweet As a security recommendation you should always disable Tech Support Mode (TSM) on your ESXi servers, but sometimes it’s helpful if you’re able to connect to your ESXi server...
  3. Rescan VMware vSphere Hypervisor (ESXi) using PowerCLI simplified Tweet While preparing some disaster recovery (DR) tests, I had to add and remove a couple of LUNS from several ESX hosts in different clusters. Doing so,  I had to...
  4. VMware Storage Sudoku Tweet Last Friday I was brainstorming with Gabrie van Zanten about the optimal placement of the VMDKs across our LUNs. We tried to come up with an algorithm that could...
  5. Managing VMware DRS rules using PowerCLI Tweet One of the core features of VMware vSphere is the Distributed Resource Scheduler (DRS). VMware DRS is vSphere’s workload load balancer and relies on VMware vMotion technology to live-migrate...

7 Comments on “Masking LUN paths using PowerCLI”

  1. #1 Alasdair Carnie
    on Jul 27th, 2011 at 1:17 pm

    Hi Arnim,

    Thanks for your help on this. I didn’t realise I needed to specify so many $null entries.

    I wouldn’t normally want to mask at host level, but I have some scripting limitation on a couple of my teaching SANS, so it’s easier to do some basic masking at SP level and then implement more specific masking per host. As the config gets blown away every week, I needed to be able to script the changes as part of my build process.

    I’ll work this into a foreach loop for multiple luns and hosts.

    Thanks again for your help, it’s much appreciated.

    Alasdair……..

  2. #2 Jeff Couch
    on Jul 28th, 2011 at 2:54 am

    WOW. Great stuff here Arnim. I just did this by hand on 38 hosts to remove some luns. I am a powercli junki and didn’t even think about using powercli for such a painful task. Greatness. Bookmarked!

  3. #3 Darren Phillips
    on Jan 13th, 2012 at 11:39 am

    Hi Arnim

    I would just like to say thanks for your work on this it has helped me out alot. I have developed a powercli script which will enumerate a cluster and then mask out a list of luns based on the ID. I have tested on esx4.0 and esxi4.1. Assumes all host in the cluster have same root password.
    The code may be a little rudimentary but it works a treat.
    Thanks
    Darren

    disconnect-VIServer * -Confirm:$false -Force:$true
    $date = Get-Date
    “`n” + $myinvocation.mycommand.path +”`t”+ $date
    Add-PSsnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue | Out-Null

    $vcServer = “enter the name of your vcenter server here”
    $Luns = 122,123 #list lun ids here comma delimited
    $cluster = “enter the name of the cluster here”

    $title = “Mask Luns”
    $message = “Do you want to mask luns with runtime path ” + $Luns + “`nOn cluster ” + $cluster

    $yes = New-Object System.Management.Automation.Host.ChoiceDescription “&Yes”,”Mask out path.”
    $no = New-Object System.Management.Automation.Host.ChoiceDescription “&No”,”Exit.”
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    $result = $host.ui.PromptForChoice($title, $message, $options, 0)

    switch ($result)
    {
    0 {“Enter the root credentials for ESXi hosts”}
    1 {“You chose not to proceed.”;exit}
    }

    Try{#Get credentials for ESXi hosts
    $esxcred = Get-Credential root

    }
    catch{
    Write-Warning “$($Error[0])”
    “Credentials required”
    Break
    }
    $report = @{}

    #Connect to vCenter
    Connect-VIServer $vcServer -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null

    #Connect to ESX hosts in cluster
    foreach ($esx in Get-Cluster $cluster -ErrorAction Stop | Get-VMHost -State Connected )
    {
    $esx.Name
    $esxversion = $esx.Version
    if (-not(($esxversion -eq “4.0.0″) -or ($esxversion -eq “4.1.0″)))
    {“script not tested for esx version ” + $esxversion
    exit}
    Connect-VIServer $esx -Credential $esxCred -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null
    $esxcli = $null
    $esxcli = Get-EsxCli -ErrorAction Stop

    #Get maximum mask_path rule ID
    try{
    $claimrulelist = $null
    $claimrulelist = $esxcli.corestorage.claimrule.list()
    }
    catch{
    $esx.Name + ” $($Error[0])”
    Break
    }

    $claimrulelistf = $claimrulelist | Where-Object {$_.Plugin -eq ‘MASK_PATH’ } | Select-Object -expand Rule
    $intA = ($claimrulelistf | Measure-Object -Maximum).maximum
    #Get paths associcated with LUN
    Foreach ($Lun in $Luns)
    {
    $filterRTN = “*L” + $Lun
    $pathlist = $esxcli.nmp.path.list() | Where {($_.runtimename -like $filterRTN) -and ($_.path -like “fc*”)} | Select-Object RunTimeName
    if ($pathlist -eq $null)
    {
    $rtext = “No paths found to lun ” + $Lun
    $report.add($esx.Name, $rtext)
    continue
    }
    else {
    foreach ($path in $pathlist)
    {
    “`t” + $path.RunTimeName
    $intA = $intA + 1
    $RuntimeName = $path.RunTimeName.Split(“:”)

    $vmhba = $RuntimeName[0]
    $C = $RuntimeName[1].TrimStart(“C”)
    $T = $RuntimeName[2].TrimStart(“T”)
    $L = $RuntimeName[3].TrimStart(“L”)

    $claimrulelist = $esxcli.corestorage.claimrule.list() | ? {$_.Rule -eq $intA } | Select-Object -expand Rule
    if ($claimrulelist -ne $null)
    {
    $intR = $claimrulelist.count
    “`t`tRule ” + $inta + ” already exists”
    break
    }
    Try{ #Add claim rule
    if ($esx.Version -eq “4.0.0″)
    {$claimruleadd = $esxcli.corestorage.claimrule.add($vmhba,$C,$null,$null,$L,$null,”MASK_PATH”,$intA,$T,$null,”location”,$null) #4.0
    }
    elseif ($esx.Version -eq “4.1.0″)
    {$claimruleadd = $esxcli.corestorage.claimrule.add($vmhba,$null,$C,$null,$null,$null,$null,$L,$null,”MASK_PATH”,$intA,$T,$null,”location”,$null) #4.1 15 version
    }
    }
    catch{
    Write-Warning “$($Error[0])”
    Break
    }

    $esxcli.corestorage.claimrule.load()

    if ($esx.Version -eq “4.0.0″)
    {$esxcli.corestorage.claiming.unclaim($vmhba,$C,$null,$null,$L,$null,$null,$T,”location”)#4.0
    }
    elseif ($esx.Version -eq “4.1.0″)
    {$esxcli.corestorage.claiming.unclaim($vmhba,$C,$null,$null,$null,$L,$null,$null,$null,$T,”location”,$null)#4.1
    }

    $esxcli.corestorage.claimrule.run()
    }
    $pathlist = $esxcli.nmp.path.list() | Where {($_.runtimename -like $filterRTN) -and ($_.path -like “fc*”)} | Select-Object RunTimeName
    if ($pathlist -eq $null){
    $rtext = “Paths to lun ” + $Lun + ” have been masked successfully”
    }
    Else{
    $rtext = “Warning masking failed, paths found to lun ” + $Lun
    }
    $report[$esx.Name] = $rtext

    }
    }#End of Luns for loop
    try{
    disconnect-VIServer -Server $esx.Name -Force:$true -Confirm:$false | Out-Null
    }
    catch{
    “$($Error[0])”
    }

    }#End of ESX for loop

    $report

  4. #4 Arnim van Lieshout
    on Jan 30th, 2012 at 11:03 pm

    Thanks for sharing your script Darren. Great to hear my post was helpful.

  5. #5 SKA
    on Feb 9th, 2012 at 2:26 pm

    Hi,
    I am using PowerCLI 5.0 and ESXi 4.1. When I try the esxcli commands I get this error message:

    PowerCLI C:\> $esxcli=get-esxcli
    PowerCLI C:\> $esxcli.corestorage.claimrule.list()
    Object reference not set to an instance of an object.
    At line:1 char:35
    + $esxcli.corestorage.claimrule.list <<<< ()
    + CategoryInfo : InvalidOperation: (:) [], RuntimeException
    + FullyQualifiedErrorId : MethodInvocationException

    What should be between ()? What am I doing wrong?

    Thanks in advance..
    Koray

  6. #6 SKA
    on Feb 10th, 2012 at 3:20 pm

    My previous comment is still waiting for moderation but let me tell your how I solved: It was because of the version of powershell.exe
    32bit version (the one under windows\wow64 etc.) solved the case.

    My previous comment may be removed with this one..

  7. #7 vBryan
    on Dec 9th, 2012 at 3:24 pm

    I took Darren’s script above and changed it up a bit for performing masking by device id (i.e. naa.600***), powercli script below:

    disconnect-VIServer * -Confirm:$false -Force:$true
    $date = Get-Date
    “`n” + $myinvocation.mycommand.path +”`t”+ $date
    Add-PSsnapin VMware.VimAutomation.Core -ErrorAction SilentlyContinue | Out-Null

    $vcServer = “yourvirtualcenterserver”
    #list lun device names here comma delimited
    $Luns = “naa.6006001″,”naa.6006002″,”naa.6006003″
    $cluster = “yourclusterofhosts”

    $title = “Mask Luns”
    $message = “Do you want to mask luns with device name ” + $Luns + “`nOn cluster ” + $cluster

    $yes = New-Object System.Management.Automation.Host.ChoiceDescription “&Yes”,”Mask out path.”
    $no = New-Object System.Management.Automation.Host.ChoiceDescription “&No”,”Exit.”
    $options = [System.Management.Automation.Host.ChoiceDescription[]]($yes, $no)

    $result = $host.ui.PromptForChoice($title, $message, $options, 0)

    switch ($result)
    {
    0 {“Enter the root credentials for ESXi hosts”}
    1 {“You chose not to proceed.”;exit}
    }

    Try{#Get credentials for ESXi hosts
    $esxcred = Get-Credential root

    }
    catch{
    Write-Warning “$($Error[0])”
    “Credentials required”
    Break
    }
    $report = @{}

    #Connect to vCenter
    Connect-VIServer $vcServer -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null

    #Connect to ESX hosts in cluster
    foreach ($esx in Get-Cluster $cluster -ErrorAction Stop | Get-VMHost -State Connected )
    {
    $esx.Name
    $esxversion = $esx.Version
    if (-not(($esxversion -eq “4.0.0″) -or ($esxversion -eq “4.1.0″)))
    {“script not tested for esx version” + $esxversion
    exit}
    Connect-VIServer $esx -Credential $esxCred -WarningAction SilentlyContinue -ErrorAction Stop | Out-Null
    $esxcli = $null
    $esxcli = Get-EsxCli -ErrorAction Stop

    #Get maximum mask_path rule ID
    try{
    $claimrulelist = $null
    $claimrulelist = $esxcli.corestorage.claimrule.list()
    }
    catch{
    $esx.Name + ” $($Error[0])”
    Break
    }

    $claimrulelistf = $claimrulelist | Where-Object {$_.Plugin -eq ‘MASK_PATH’ } | Select-Object -expand Rule
    $intA = ($claimrulelistf | Measure-Object -Maximum).maximum
    #Get paths associcated with LUN
    Foreach ($Lun in $Luns)
    {
    $pathlist = $esxcli.nmp.path.list() | Where {($_.Device -like $Lun) -and ($_.path -like “fc*”)} | Select-Object RunTimeName
    if ($pathlist -eq $null)
    {
    $rtext = “No paths found to lun ” + $Lun
    $report.add($esx.Name, $rtext)
    continue
    }
    else {
    foreach ($path in $pathlist)
    {
    “`t” + $path.RunTimeName
    $intA = $intA + 1
    $RuntimeName = $path.RunTimeName.Split(“:”)

    $vmhba = $RuntimeName[0]
    $C = $RuntimeName[1].TrimStart(“C”)
    $T = $RuntimeName[2].TrimStart(“T”)
    $L = $RuntimeName[3].TrimStart(“L”)

    $claimrulelist = $esxcli.corestorage.claimrule.list() | ? {$_.Rule -eq $intA } | Select-Object -expand Rule
    if ($claimrulelist -ne $null)
    {
    $intR = $claimrulelist.count
    “`t`tRule ” + $inta + ” already exists”
    break
    }
    Try{ #Add claim rule
    if ($esx.Version -eq “4.0.0″)
    {$claimruleadd = $esxcli.corestorage.claimrule.add($vmhba,$C,$null,$null,$L,$null,”MASK_PATH”,$intA,$T,$null,”location”,$null) #4.0
    }
    elseif ($esx.Version -eq “4.1.0″)
    {$claimruleadd = $esxcli.corestorage.claimrule.add($vmhba,$null,$C,$null,$null,$null,$null,$L,$null,”MASK_PATH”,$intA,$T,$null,”location”,$null) #4.1 15 version
    }
    }
    catch{
    Write-Warning “$($Error[0])”
    Break
    }

    $esxcli.corestorage.claimrule.load()

    if ($esx.Version -eq “4.0.0″)
    {$esxcli.corestorage.claiming.unclaim($vmhba,$C,$null,$null,$L,$null,$null,$T,”location”)#4.0
    }
    elseif ($esx.Version -eq “4.1.0″)
    {$esxcli.corestorage.claiming.unclaim($vmhba,$C,$null,$null,$null,$L,$null,$null,$null,$T,”location”,$null)#4.1
    }

    $esxcli.corestorage.claimrule.run()
    }
    $pathlist = $esxcli.nmp.path.list() | Where {($_.Device -like $Lun) -and ($_.path -like “fc*”)} | Select-Object RunTimeName
    if ($pathlist -eq $null){
    $rtext = “Paths to lun ” + $Luns + ” have been masked successfully”
    }
    Else{
    $rtext = “Warning masking failed, paths found to lun ” + $Lun
    }
    $report[$esx.Name] = $rtext

    }
    }#End of Luns for loop
    try{
    disconnect-VIServer -Server $esx.Name -Force:$true -Confirm:$false | Out-Null
    }
    catch{
    “$($Error[0])”
    }

    }#End of ESX for loop

    $report

Leave a Comment