Public/New-DMQosPolicy.ps1

function New-DMQosPolicy {
    <#
    .SYNOPSIS
        Creates an OceanStor SmartQoS policy.
 
    .DESCRIPTION
        Creates a SmartQoS policy via the ioclass resource. At least one of MaxBandwidth,
        MaxIOPS, MinBandwidth, MinIOPS, Latency must be supplied, matching the API's own
        requirement. LunName/LunId and FileSystemName/FileSystemId are mutually exclusive,
        since LUNs/LUN snapshots and file systems cannot coexist in one SmartQoS policy.
 
    .PARAMETER WebSession
        Optional session returned by Connect-deviceManager. The module's cached $script:CurrentOceanstorSession session is used by default.
 
    .PARAMETER Name
        SmartQoS policy name, 1-31 characters (letters, digits, underscores, hyphens, periods).
 
    .PARAMETER Description
        Optional description, 0-255 characters.
 
    .PARAMETER IOType
        I/O property the policy applies to: ReadWrite (default) or Split (read I/O or write I/O counted separately).
 
    .PARAMETER MaxBandwidth
        Maximum bandwidth in MB/s (1-999,999,999).
 
    .PARAMETER MaxIOPS
        Maximum IOPS (100-999,999,999).
 
    .PARAMETER MinBandwidth
        Minimum bandwidth in MB/s (1-999,999,999).
 
    .PARAMETER MinIOPS
        Minimum IOPS (100-999,999,999).
 
    .PARAMETER Latency
        I/O latency target in microseconds: 500 or 1500.
 
    .PARAMETER BurstBandwidth
        Maximum burst bandwidth in MB/s. Requires BurstTime.
 
    .PARAMETER BurstIOPS
        Maximum burst IOPS. Requires BurstTime.
 
    .PARAMETER BurstTime
        Burst time in seconds (1-999,999,999). Mandatory when BurstBandwidth or BurstIOPS is specified.
 
    .PARAMETER Priority
        Normal (default) or High. When High, the policy's performance objective is ensured first.
 
    .PARAMETER SchedulePolicy
        Once (default), Daily, or Weekly.
 
    .PARAMETER ScheduleStartTime
        Date (UTC) the policy settings take effect. Only year/month/day are effective.
 
    .PARAMETER StartTime
        Time of day the policy settings take effect, in hh:mm format.
 
    .PARAMETER Duration
        Validity period of the policy settings, in seconds, less than 24 hours (86400).
 
    .PARAMETER CycleSet
        Cycle days (0-6, Sunday-Saturday) the policy applies on. Mandatory when SchedulePolicy is Weekly.
 
    .PARAMETER LunName
        LUN name(s) to attach to the policy. Mutually exclusive with FileSystemName/FileSystemId.
 
    .PARAMETER LunId
        LUN ID(s) to attach to the policy. Mutually exclusive with FileSystemName/FileSystemId.
 
    .PARAMETER FileSystemName
        File system name(s) to attach to the policy. Mutually exclusive with LunName/LunId.
 
    .PARAMETER FileSystemId
        File system ID(s) to attach to the policy. Mutually exclusive with LunName/LunId.
 
    .PARAMETER VstoreId
        Optional vStore ID. A value of 4294967295 lets the policy take effect on any vStore in the system.
 
    .INPUTS
        System.Management.Automation.PSCustomObject
 
    .OUTPUTS
        OceanstorQosPolicy
 
    .EXAMPLE
        PS> New-DMQosPolicy -Name 'qos01' -MaxIOPS 5000 -ScheduleStartTime (Get-Date).AddDays(1) -StartTime '00:00' -Duration 3600
 
        For a 'Once' schedule the effective window (ScheduleStartTime date at StartTime, for Duration
        seconds) must be in the future; a past window is rejected by the array as an overdue schedule.
 
    .EXAMPLE
        PS> New-DMQosPolicy -Name 'qos02' -MaxBandwidth 500 -LunName 'lun01', 'lun02' -ScheduleStartTime (Get-Date).AddDays(1) -StartTime '00:00' -Duration 3600
    #>

    [CmdletBinding(SupportsShouldProcess = $true, ConfirmImpact = 'Medium', DefaultParameterSetName = 'NoAssociation')]
    # String form: class type literals in attributes do not resolve inside module scope.
    [OutputType('OceanstorQosPolicy')]
    param(
        [Parameter(ValueFromPipelineByPropertyName = $true)]
        [pscustomobject]$WebSession,

        [Parameter(Mandatory = $true, Position = 0)]
        [ValidateLength(1, 31)]
        [ValidatePattern('^[A-Za-z0-9_.-]+$')]
        [string]$Name,

        [AllowEmptyString()]
        [ValidateLength(0, 255)]
        [string]$Description,

        [ValidateSet('ReadWrite', 'Split')]
        [string]$IOType = 'ReadWrite',

        [ValidateRange(1, 999999999)]
        [uint32]$MaxBandwidth,

        [ValidateRange(100, 999999999)]
        [uint32]$MaxIOPS,

        [ValidateRange(1, 999999999)]
        [uint32]$MinBandwidth,

        [ValidateRange(100, 999999999)]
        [uint32]$MinIOPS,

        [ValidateSet(500, 1500)]
        [uint32]$Latency,

        [ValidateRange(1, 999999999)]
        [uint32]$BurstBandwidth,

        [ValidateRange(100, 999999999)]
        [uint32]$BurstIOPS,

        [ValidateRange(1, 999999999)]
        [uint32]$BurstTime,

        [ValidateSet('Normal', 'High')]
        [string]$Priority = 'Normal',

        [ValidateSet('Once', 'Daily', 'Weekly')]
        [string]$SchedulePolicy = 'Once',

        [Parameter(Mandatory = $true)]
        [datetime]$ScheduleStartTime,

        [Parameter(Mandatory = $true)]
        [ValidatePattern('^([01]\d|2[0-3]):[0-5]\d$')]
        [string]$StartTime,

        [Parameter(Mandatory = $true)]
        [ValidateRange(1, 86399)]
        [uint32]$Duration,

        [ValidateScript({
                foreach ($day in $_) {
                    if ($day -lt 0 -or $day -gt 6) { throw 'CycleSet values must be between 0 (Sunday) and 6 (Saturday).' }
                }
                return $true
            })]
        [int[]]$CycleSet,

        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                $session = if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $fakeBoundParameters.WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                (Get-DMlun -WebSession $session).Name | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string[]]$LunName,

        [string[]]$LunId,

        [ArgumentCompleter({
                param($commandName, $parameterName, $wordToComplete, $commandAst, $fakeBoundParameters)
                $session = if ($fakeBoundParameters.ContainsKey('WebSession')) {
                    $fakeBoundParameters.WebSession
                }
                else {
                    $script:CurrentOceanstorSession
                }
                (Get-DMFileSystem -WebSession $session).Name | Sort-Object -Unique | Where-Object { $_ -like "$wordToComplete*" }
            })]
        [string[]]$FileSystemName,

        [string[]]$FileSystemId,

        [string]$VstoreId
    )

    begin {
        $hasLimit = $PSBoundParameters.ContainsKey('MaxBandwidth') -or
            $PSBoundParameters.ContainsKey('MaxIOPS') -or
            $PSBoundParameters.ContainsKey('MinBandwidth') -or
            $PSBoundParameters.ContainsKey('MinIOPS') -or
            $PSBoundParameters.ContainsKey('Latency')
        if (-not $hasLimit) {
            throw 'Specify at least one of MaxBandwidth, MaxIOPS, MinBandwidth, MinIOPS, Latency.'
        }
        if (($PSBoundParameters.ContainsKey('BurstBandwidth') -or $PSBoundParameters.ContainsKey('BurstIOPS')) -and -not $PSBoundParameters.ContainsKey('BurstTime')) {
            throw 'BurstTime is mandatory when BurstBandwidth or BurstIOPS is specified.'
        }
        if ($SchedulePolicy -eq 'Weekly' -and -not $PSBoundParameters.ContainsKey('CycleSet')) {
            throw 'CycleSet is mandatory when SchedulePolicy is Weekly.'
        }
        $lunParamsSpecified = $PSBoundParameters.ContainsKey('LunName') -or $PSBoundParameters.ContainsKey('LunId')
        $fsParamsSpecified = $PSBoundParameters.ContainsKey('FileSystemName') -or $PSBoundParameters.ContainsKey('FileSystemId')
        if ($PSBoundParameters.ContainsKey('LunName') -and $PSBoundParameters.ContainsKey('LunId')) {
            throw 'LunName and LunId are mutually exclusive.'
        }
        if ($PSBoundParameters.ContainsKey('FileSystemName') -and $PSBoundParameters.ContainsKey('FileSystemId')) {
            throw 'FileSystemName and FileSystemId are mutually exclusive.'
        }
        if ($lunParamsSpecified -and $fsParamsSpecified) {
            throw 'LunName/LunId and FileSystemName/FileSystemId are mutually exclusive.'
        }
    }

    process {
        try {
            $session = if ($WebSession) { $WebSession } else { $script:CurrentOceanstorSession }

            $body = @{
                NAME              = $Name
                IOTYPE            = switch ($IOType) { 'ReadWrite' { 2 }; 'Split' { 3 } }
                SCHEDULEPOLICY    = switch ($SchedulePolicy) { 'Once' { 0 }; 'Daily' { 1 }; 'Weekly' { 2 } }
                SCHEDULESTARTTIME = [System.DateTimeOffset]::new($ScheduleStartTime.ToUniversalTime()).ToUnixTimeSeconds()
                STARTTIME         = $StartTime
                DURATION          = $Duration
            }

            # PRIORITY is only sent when the caller explicitly asks for it. Sending PRIORITY on
            # an ioclass create is rejected by some firmware (e.g. V600R005C27 returns the generic
            # 50331651 "The entered parameter is incorrect" for any PRIORITY value on a normal
            # policy), so the default path must omit it entirely. On read-back the array reports
            # PRIORITY=0 as its own default, which is not evidence that a create body supplied it.
            if ($PSBoundParameters.ContainsKey('Priority')) { $body.PRIORITY = switch ($Priority) { 'Normal' { 0 }; 'High' { 1 } } }
            if ($Description) { $body.DESCRIPTION = $Description }
            if ($PSBoundParameters.ContainsKey('MaxBandwidth')) { $body.MAXBANDWIDTH = $MaxBandwidth }
            if ($PSBoundParameters.ContainsKey('MaxIOPS')) { $body.MAXIOPS = $MaxIOPS }
            if ($PSBoundParameters.ContainsKey('MinBandwidth')) { $body.MINBANDWIDTH = $MinBandwidth }
            if ($PSBoundParameters.ContainsKey('MinIOPS')) { $body.MINIOPS = $MinIOPS }
            if ($PSBoundParameters.ContainsKey('Latency')) { $body.LATENCY = $Latency }
            if ($PSBoundParameters.ContainsKey('BurstBandwidth')) { $body.BURSTBANDWIDTH = $BurstBandwidth }
            if ($PSBoundParameters.ContainsKey('BurstIOPS')) { $body.BURSTIOPS = $BurstIOPS }
            if ($PSBoundParameters.ContainsKey('BurstTime')) { $body.BURSTTIME = $BurstTime }
            if ($PSBoundParameters.ContainsKey('CycleSet')) { $body.CYCLESET = @($CycleSet) }
            if ($VstoreId) { $body.vstoreId = $VstoreId }

            if ($PSBoundParameters.ContainsKey('LunName')) {
                # Use a distinct loop variable: PowerShell variable names are
                # case-insensitive, so a loop over $name would rebind the
                # validated $Name parameter and re-trigger its ValidateLength/
                # ValidatePattern attributes on the LUN name (which may be
                # longer than the 31-char SmartQoS policy-name limit).
                $lunIds = foreach ($lunNameToResolve in $LunName) {
                    $lun = @(Get-DMlun -WebSession $session -Name $lunNameToResolve | Where-Object Name -EQ $lunNameToResolve)[0]
                    if ($null -eq $lun) { throw "Could not resolve LunName '$lunNameToResolve'." }
                    $lun.Id
                }
                $body.LUNLIST = @($lunIds)
            }
            elseif ($PSBoundParameters.ContainsKey('LunId')) {
                $body.LUNLIST = @($LunId)
            }
            elseif ($PSBoundParameters.ContainsKey('FileSystemName')) {
                # Distinct loop variable for the same reason as the LunName loop
                # above: $name would collide with the validated $Name parameter.
                $fsIds = foreach ($fsNameToResolve in $FileSystemName) {
                    $fileSystem = @(Get-DMFileSystem -WebSession $session | Where-Object Name -CEQ $fsNameToResolve)[0]
                    if ($null -eq $fileSystem) { throw "Could not resolve FileSystemName '$fsNameToResolve'." }
                    $fileSystem.Id
                }
                $body.FSLIST = @($fsIds)
            }
            elseif ($PSBoundParameters.ContainsKey('FileSystemId')) {
                $body.FSLIST = @($FileSystemId)
            }

            if (-not $PSCmdlet.ShouldProcess($Name, 'Create SmartQoS policy')) {
                return
            }

            $response = Invoke-DeviceManager -WebSession $session -Method 'POST' -Resource 'ioclass' -BodyData $body
            $response = $response | Assert-DMApiSuccess
            return [OceanstorQosPolicy]::new($response.data, $session)
        }
        catch {
            $PSCmdlet.WriteError($_)
        }
    }
}