scripts/Drive.ps1

# Copyright (C) 2020 Huawei Technologies Co., Ltd. All rights reserved.
# This program is free software; you can redistribute it and/or modify
# it under the terms of the MIT License

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# MIT License for more detail

<# NOTE: iBMC Drive module Cmdlets #>

function Get-iBMCDrives {
<#
.SYNOPSIS
Query information about the drive resource collection of a server.

.DESCRIPTION
Query information about the drive resource collection of a server.
This cmdlet works only after BIOS boot is complete when the RAID controller card supports out-of-band management or after iBMA 2.0 has been installed and started.

.PARAMETER Session
iBMC redfish session object which is created by Connect-iBMC cmdlet.
A session object identifies an iBMC server to which this cmdlet will be executed.

.PARAMETER StorageId
Indicates the identifier of the storage which the drive belongs to.
If StorageId is not provided, will get all drives instead.
The Id properties of "Get-iBMCRAIDControllers" cmdlet's return value represents Storage ID.

.OUTPUTS
PSObject[][]
Returns an array of PSObject indicates all drive resources if cmdlet executes successfully.
In case of an error or warning, exception will be returned.

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> $Drives = Get-iBMCDrives -Session $Session
PS C:\> $Drives

Host : 10.1.1.2
Id : HDDPlaneDisk0
Name : Disk0
Model : MG04ACA400N
Revision : FJ3J
Status : @{State=Enabled; Health=OK}
CapacityBytes : 3999999721472
FailurePredicted : False
Protocol : SATA
MediaType : HDD
Manufacturer : TOSHIBA
SerialNumber : 38DGK77LF77D
CapableSpeedGbs : 6
NegotiatedSpeedGbs : 12
PredictedMediaLifeLeftPercent :
IndicatorLED : Off
HotspareType : None
StatusIndicator : OK
Location : {@{Info=Disk0; InfoFormat=DeviceName}}
DriveID : 0
FirmwareStatus : Online
HoursOfPoweredUp : 6056
PatrolState : DoneOrNotPatrolled
Position : HDDPlane
RebuildProgress :
RebuildState : DoneOrNotRebuilt
SASAddress : {500e004aaaaaaa00, 0000000000000000}
SASSmartInformation :
SATASmartInformation : @{AttributeRevision=; AttributeRevisionNumber=; AttributeItemList=System.Object[]}
SpareforLogicalDrives : {}
TemperatureCelsius : 33
Type : Disk

Host : 10.1.1.2
Id : HDDPlaneDisk1
Name : Disk1
Model : MG04ACA400N
Revision : FJ3J
Status : @{State=Enabled; Health=OK}
CapacityBytes : 3999999721472
FailurePredicted : False
Protocol : SATA
MediaType : HDD
Manufacturer : TOSHIBA
SerialNumber : 38DFK62PF77D
CapableSpeedGbs : 6
NegotiatedSpeedGbs : 12
PredictedMediaLifeLeftPercent :
IndicatorLED : Off
HotspareType : None
StatusIndicator : OK
Location : {@{Info=Disk1; InfoFormat=DeviceName}}
DriveID : 1
FirmwareStatus : UnconfiguredGood
HoursOfPoweredUp : 6058
PatrolState : DoneOrNotPatrolled
Position : HDDPlane
RebuildProgress :
RebuildState : DoneOrNotRebuilt
SASAddress : {500e004aaaaaaa01, 0000000000000000}
SASSmartInformation :
SATASmartInformation : @{AttributeRevision=; AttributeRevisionNumber=; AttributeItemList=System.Object[]}
SpareforLogicalDrives : {}
TemperatureCelsius : 33
Type : Disk

This example shows how to get all drives.

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> $Drives = Get-iBMCDrives -Session $Session -StorageId RAIDStorage0
PS C:\> $Drives

Host : 10.1.1.2
Id : HDDPlaneDisk0
Name : Disk0
Model : MG04ACA400N
Revision : FJ3J
Status : @{State=Enabled; Health=OK}
CapacityBytes : 3999999721472
FailurePredicted : False
Protocol : SATA
MediaType : HDD
Manufacturer : TOSHIBA
SerialNumber : 38DGK77LF77D
CapableSpeedGbs : 6
NegotiatedSpeedGbs : 12
PredictedMediaLifeLeftPercent :
IndicatorLED : Off
HotspareType : None
StatusIndicator : OK
Location : {@{Info=Disk0; InfoFormat=DeviceName}}
DriveID : 0
FirmwareStatus : Online
HoursOfPoweredUp : 6056
PatrolState : DoneOrNotPatrolled
Position : HDDPlane
RebuildProgress :
RebuildState : DoneOrNotRebuilt
SASAddress : {500e004aaaaaaa00, 0000000000000000}
SASSmartInformation :
SATASmartInformation : @{AttributeRevision=; AttributeRevisionNumber=; AttributeItemList=System.Object[]}
SpareforLogicalDrives : {}
TemperatureCelsius : 33
Type : Disk

This example shows how to get all drives belongs to Storage controller "RAIDStorage0".

.LINK
https://github.com/Huawei/Huawei-iBMC-Cmdlets

Get-iBMCDrivesHealth
Set-iBMCDrive
Connect-iBMC
Disconnect-iBMC
#>

  [CmdletBinding()]
  param (
    [RedfishSession[]]
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
    $Session,

    [String[]]
    [parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 1)]
    $StorageId
  )

  begin {
  }

  process {
    Assert-ArrayNotNull $Session 'Session'

    $StorageIdList = Get-OptionalMatchedSizeArray $Session $StorageId

    $Logger.info("Invoke Get iBMC drive resources function")

    $ScriptBlock = {
      param($RedfishSession, $StorageId)
      $Logger.info($(Trace-Session $RedfishSession "Invoke Get iBMC drive resources now"))

      $Drives = New-Object System.Collections.ArrayList
      if ($null -eq $StorageId) {
        $GetChassisPath = "/Chassis/$($RedfishSession.Id)"
        $Chassis = Invoke-RedfishRequest $RedfishSession $GetChassisPath | ConvertFrom-WebResponse
        $DriveLinkList = $Chassis.Links.Drives
      } else {
        # validate whether storage exists
        $GetStoragesPath = "/Systems/$($RedfishSession.Id)/Storages/$StorageId"
        $Response = Invoke-RedfishRequest $RedfishSession $GetStoragesPath -ContinueEvenFailed
        $StatusCode = $Response.StatusCode.value__
        if ($StatusCode -eq 404) {
          $ErrorDetail = [String]::Format($(Get-i18n ERROR_STORAGE_ID_NOT_EXISTS), $StorageId)
          throw "[$($RedfishSession.Address)] $ErrorDetail"
        }

        $StorageController = $Response | ConvertFrom-WebResponse
        $DriveLinkList = $StorageController.Drives
      }

      $DriveLinkList | ForEach-Object {
        $OdataId = $_."@odata.id"
        $Drive = Invoke-RedfishRequest $RedfishSession $OdataId | ConvertFrom-WebResponse
        $CleanUp = $Drive | Clear-OdataProperties | Merge-OemProperties
        [Void] $Drives.Add($(Update-SessionAddress $RedfishSession $CleanUp))
      }

      return ,$Drives.ToArray()
    }

    try {
      $tasks = New-Object System.Collections.ArrayList
      $pool = New-RunspacePool $Session.Count
      for ($idx = 0; $idx -lt $Session.Count; $idx++) {
        $RedfishSession = $Session[$idx]
        $_StorageId = $StorageIdList[$idx]
        $Logger.info($(Trace-Session $RedfishSession "Submit Get iBMC drive resources task"))
        [Void] $tasks.Add($(Start-ScriptBlockThread $pool $ScriptBlock @($RedfishSession, $_StorageId)))
      }
      $Results = Get-AsyncTaskResults $tasks
      return ,$Results
    }
    finally {
      Close-Pool $pool
    }
  }

  end {
  }
}

function Get-iBMCDrivesHealth {
<#
.SYNOPSIS
Query health information about the Drive resources of a server.

.DESCRIPTION
Query health information about the Drive resources of a server including summary health status and every Drive controller health status.

.PARAMETER Session
iBMC redfish session object which is created by Connect-iBMC cmdlet.
A session object identifies an iBMC server to which this cmdlet will be executed.

.OUTPUTS
PSObject[]
Returns PSObject indicates Drive health status of server if cmdlet executes successfully.
In case of an error or warning, exception will be returned.

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> $health = Get-iBMCDrivesHealth -Session $session
PS C:\> $health | fl

Host : 10.1.1.2
Summary : @{HealthRollup=OK}
ID#HDDPlaneDisk0 : @{Health=OK; State=; Name=Disk0}
ID#HDDPlaneDisk1 : @{Health=OK; State=; Name=Disk1}
ID#HDDPlaneDisk2 : @{Health=OK; State=; Name=Disk2}
ID#HDDPlaneDisk3 : @{Health=OK; State=; Name=Disk3}
ID#HDDPlaneDisk4 : @{Health=OK; State=; Name=Disk4}
ID#HDDPlaneDisk5 : @{Health=OK; State=; Name=Disk5}
ID#HDDPlaneDisk6 : @{Health=OK; State=; Name=Disk6}
ID#HDDPlaneDisk7 : @{Health=OK; State=; Name=Disk7}
ID#HDDPlaneDisk40 : @{Health=OK; State=; Name=Disk40}
ID#HDDPlaneDisk41 : @{Health=OK; State=; Name=Disk41}


.LINK
https://github.com/Huawei/Huawei-iBMC-Cmdlets

Get-iBMCDrives
Set-iBMCDrive
Connect-iBMC
Disconnect-iBMC
#>

  [CmdletBinding()]
  param (
    [RedfishSession[]]
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true, Position = 0)]
    $Session
  )

  begin {
  }

  process {
    Assert-ArrayNotNull $Session 'Session'

    $Logger.info("Invoke Get iBMC Drive health function")

    $ScriptBlock = {
      param($RedfishSession)
      $(Get-Logger).info($(Trace-Session $RedfishSession "Invoke Get iBMC Drive health now"))

      $GetChassisPath = "/Chassis/$($RedfishSession.Id)"
      $Chassis = Invoke-RedfishRequest $RedfishSession $GetChassisPath | ConvertFrom-WebResponse

      $Health = New-Object PSObject -Property @{
        Host    = $RedfishSession.Address;
        Summary = $Chassis.Oem.huawei.DriveSummary.Status;
      }

      $StatusPropertyOrder = @("Health", "State")
      $Chassis.Links.Drives | ForEach-Object {
        $OdataId = $_."@odata.id"
        $Drive = Invoke-RedfishRequest $RedfishSession $OdataId | ConvertFrom-WebResponse
        $Status = Copy-ObjectProperties $Drive.Status $StatusPropertyOrder
        $Status | Add-member Noteproperty "Name" $Drive.Name
        $Health | Add-Member Noteproperty "ID#$($Drive.ID)" $Status
      }

      return $Health
    }

    try {
      $tasks = New-Object System.Collections.ArrayList
      $pool = New-RunspacePool $Session.Count
      for ($idx = 0; $idx -lt $Session.Count; $idx++) {
        $RedfishSession = $Session[$idx]
        $Logger.info($(Trace-Session $RedfishSession "Submit Get iBMC Drive health task"))
        [Void] $tasks.Add($(Start-ScriptBlockThread $pool $ScriptBlock @($RedfishSession)))
      }
      $Results = Get-AsyncTaskResults $tasks
      return ,$Results
    }
    finally {
      Close-Pool $pool
    }
  }

  end {
  }
}


function Set-iBMCDrive {
<#
.SYNOPSIS
Modify properties of the specified drive of a server.

.DESCRIPTION
Modify properties of the specified drive of a server.

.PARAMETER Session
iBMC redfish session object which is created by Connect-iBMC cmdlet.
A session object identifies an iBMC server to which this cmdlet will be executed.

.PARAMETER DriveId
Indicates the identifier of the drive to modify.
The Id properties of "Get-iBMCDrives" cmdlet's return value represents Drive ID.

.PARAMETER State
Indicates the state of a drive.
NOTE: Before setting the drive status to JBOD, need to enable JBOD for the RAID controller first.
Drive state can be alternated between the following status:
- Online and Offline
- UnconfiguredGood and JBOD
- UnconfigureBad and UnconfiguredGood

.PARAMETER LEDState
Indicates the location indicator state of a drive.
Support value set: Off, Blinking.

.PARAMETER HotSpareType
Indicates the hot spare state of a drive.
Support value set: None, Global, Dedicated.

.PARAMETER VolumeId
Indicates the ID of the associated volume if the drive is a dedicated hot spare drive.
The Id properties of "Get-iBMCLogicDrive" cmdlet's return value represents volume ID, example: LogicalDrive.
NOTE:
- When HotSpareType is None or Global, VolumeId is redundant.
- When HotSpareType is Dedicated, VolumeId is mandatory.

.OUTPUTS
Null
Returns null if cmdlet executes successfully.
In case of an error or warning, exception will be returned.

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> Set-iBMCDrive -Session $session -DriveId HDDPlaneDisk0 -State JBOD

This example shows how to set drive's state to "JBOD"

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> Set-iBMCDrive -Session $session -DriveId HDDPlaneDisk0 -LEDState Blinking

This example shows how to blinking drive's led


.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> Set-iBMCDrive -Session $session -DriveId HDDPlaneDisk0 -HotSpareType Dedicated -VolumeId LogicalDrive0

This example shows how to set drive's hot-spare type to "Dedicated" to volume "LogicalDrive0"

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> Set-iBMCDrive -Session $session -DriveId HDDPlaneDisk0 -HotSpareType Global

This example shows how to set drive's hot-spare type to "Global"

.EXAMPLE

PS C:\> $credential = Get-Credential
PS C:\> $Session = Connect-iBMC -Address 10.1.1.2 -Credential $credential -TrustCert
PS C:\> Set-iBMCDrive -Session $session -DriveId HDDPlaneDisk0 -HotSpareType None

This example shows how to set drive's hot-spare type to "None"


.LINK
https://github.com/Huawei/Huawei-iBMC-Cmdlets

Get-iBMCDrives
Get-iBMCDrivesHealth
Connect-iBMC
Disconnect-iBMC
#>

  [CmdletBinding()]
  param (
    [RedfishSession[]]
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $Session,

    [String[]]
    [parameter(Mandatory = $true, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $DriveId,

    [DriveState[]]
    [parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $State,

    [DriveLEDState[]]
    [parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $LEDState,

    [HotSpareType[]]
    [parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $HotSpareType,

    [String[]]
    [parameter(Mandatory = $false, ValueFromPipeline = $true, ValueFromPipelineByPropertyName = $true)]
    $VolumeId
  )

  begin {
  }

  process {
    Assert-ArrayNotNull $Session 'Session'
    Assert-ArrayNotNull $DriveId 'DriveId'
    $DriveIdList = Get-MatchedSizeArray $Session $DriveId

    $StateList = Get-OptionalMatchedSizeArray $Session $State
    $LEDStateList = Get-OptionalMatchedSizeArray $Session $LEDState
    $HotSpareTypeList = Get-OptionalMatchedSizeArray $Session $HotSpareType
    $VolumeIdList = Get-OptionalMatchedSizeArray $Session $VolumeId

    $Logger.info("Invoke Set iBMC drive resources function")

    $ScriptBlock = {
      param($RedfishSession, $DriveId, $Payload)

      $Logger.info($(Trace-Session $RedfishSession "Invoke Set iBMC drive now"))
      $Path = "/Chassis/$($RedfishSession.Id)/Drives/$DriveId"

      # fetch logical dirves
      if ($null -ne $Payload.Oem -and $null -ne $Payload.Oem.Huawei.SpareforLogicalDrives) {
        $VolumeOdataId = Get-VolumeOdataId $RedfishSession $Payload.Oem.Huawei.SpareforLogicalDrives
        if ($null -ne $VolumeOdataId) {
          $Payload.Oem.Huawei.SpareforLogicalDrives = @(
            @{
              "@odata.id" = $VolumeOdataId;
            }
          )
        }
      }

      $Logger.info($(Trace-Session $RedfishSession "Sending payload: $($Payload | ConvertTo-Json -Depth 5)"))
      $Response = Invoke-RedfishRequest $RedfishSession $Path 'PATCH' $Payload
      Resolve-RedfishPartialSuccessResponse $RedfishSession $Response | Out-Null
      return $null
    }

    try {
      $ParametersList = New-Object System.Collections.ArrayList
      for ($idx = 0; $idx -lt $Session.Count; $idx++) {
        $RedfishSession = $Session[$idx]
        $_HotSpareType = $HotspareTypeList[$idx]
        $_VolumeId = $VolumeIdList[$idx]
        $_State = $StateList[$idx]
        $_LEDState = $LEDStateList[$idx]

        if ($_HotSpareType -eq [HotSpareType]::Dedicated -and $null -eq $_VolumeId) {
          throw $(Get-i18n ERROR_VOLUMEID_MANDATORY)
        }

        $Oem = @{
          "FirmwareStatus" = $_State;
          "SpareforLogicalDrives" = $_VolumeId;
        } | Remove-EmptyValues | Resolve-EnumValues

        $Payload = @{
          "IndicatorLED" = $_LEDState;
          "HotspareType" = $_HotSpareType;
        } | Remove-EmptyValues | Resolve-EnumValues

        if ($Oem.Count -gt 0) {
          $Payload.Oem = @{
            "Huawei" = $Oem;
          }
        }

        if ($Payload.Count -eq 0) {
          throw $(Get-i18n ERROR_NO_UPDATE_PAYLOAD)
        }

        $Parameters = @($RedfishSession, $DriveIdList[$idx], $Payload)
        [Void] $ParametersList.Add($Parameters)
      }

      $pool = New-RunspacePool $Session.Count
      $tasks = New-Object System.Collections.ArrayList
      for ($idx = 0; $idx -lt $Session.Count; $idx++) {
        $Logger.info($(Trace-Session $RedfishSession "Submit Set iBMC drive task"))
        [Void] $tasks.Add($(Start-ScriptBlockThread $pool $ScriptBlock $ParametersList[$idx]))
      }

      $Results = Get-AsyncTaskResults $tasks
      return ,$Results
    }
    finally {
      Close-Pool $pool
    }
  }

  end {
  }
}