Files
ModernAdminStudio/Modules/NetAppService.ps1
T

982 lines
40 KiB
PowerShell

# Modules\NetAppService.ps1
# NetApp ONTAP REST API Service Module (Live & Fallback)
function Resolve-NetAppParameters {
param(
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[string]$SvmName = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[object]$Config = $null,
[hashtable]$BoundParameters = @{}
)
$resolvedEndpoint = $ClusterEndpoint
$resolvedUser = $Username
$resolvedPass = $Password
$resolvedSvm = $SvmName
$resolvedIgnoreSsl = $IgnoreSsl
$resolvedLive = $UseLive
if ($Config -and $Config.NetApp) {
if (-not $resolvedEndpoint) { $resolvedEndpoint = $Config.NetApp.ClusterEndpoint }
if (-not $resolvedUser) { $resolvedUser = $Config.NetApp.Username }
if (-not $resolvedPass) { $resolvedPass = $Config.NetApp.Password }
if (-not $resolvedSvm) { $resolvedSvm = $Config.NetApp.DefaultSvm }
if ($null -ne $Config.NetApp.IgnoreSslErrors) { $resolvedIgnoreSsl = [bool]$Config.NetApp.IgnoreSslErrors }
if ($null -ne $Config.NetApp.UseLive) { $resolvedLive = [bool]$Config.NetApp.UseLive }
}
# 사용자가 파라미터로 엔드포인트를 직접 지정한 경우, UseLive가 명시적으로 false로 주어지지 않았다면 실제 연결로 판단
if (-not [string]::IsNullOrWhiteSpace($resolvedEndpoint)) {
if ($BoundParameters.ContainsKey('UseLive')) {
$resolvedLive = $UseLive
} elseif ($Config -and $Config.NetApp -and ($null -ne $Config.NetApp.UseLive)) {
$resolvedLive = [bool]$Config.NetApp.UseLive
} else {
$resolvedLive = $true
}
}
return @{
Endpoint = $resolvedEndpoint
Username = $resolvedUser
Password = $resolvedPass
SvmName = $resolvedSvm
IgnoreSsl = $resolvedIgnoreSsl
UseLive = $resolvedLive
}
}
function Invoke-NetAppRestRequest {
param(
[string]$ClusterEndpoint,
[string]$UriPath,
[string]$Method = "GET",
[object]$Body = $null,
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[int]$TimeoutSec = 15
)
if ([string]::IsNullOrWhiteSpace($ClusterEndpoint)) {
throw "NetApp 클러스터 엔드포인트 URL이 지정되지 않았습니다."
}
$base = $ClusterEndpoint.TrimEnd('/')
if (-not ($base -like "http*")) {
$base = "https://$base"
}
$fullUri = "$base$UriPath"
# SSL 우회 설정 (.NET Framework 4.8 / Windows PowerShell 5.1 호환)
if ($IgnoreSsl) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
try {
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13
} catch {
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12
}
}
$headers = @{
"Accept" = "application/json"
}
if (-not [string]::IsNullOrWhiteSpace($Username)) {
$authBytes = [System.Text.Encoding]::ASCII.GetBytes("$($Username):$($Password)")
$authBase64 = [System.Convert]::ToBase64String($authBytes)
$headers["Authorization"] = "Basic $authBase64"
}
$params = @{
Uri = $fullUri
Method = $Method
Headers = $headers
TimeoutSec = $TimeoutSec
}
# PowerShell 7 (Core) SkipCertificateCheck 지원 (ServicePointManager 우회 무시 문제 해결)
if ($IgnoreSsl -and (Get-Command Invoke-RestMethod).Parameters.ContainsKey('SkipCertificateCheck')) {
$params["SkipCertificateCheck"] = $true
}
if ($Body) {
$params["Body"] = ($Body | ConvertTo-Json -Depth 5)
$params["ContentType"] = "application/json; charset=utf-8"
}
return Invoke-RestMethod @params
}
function Test-NetAppConnection {
param(
[string]$ClusterEndpoint,
[string]$Username,
[string]$Password,
[bool]$IgnoreSsl = $true,
[int]$TimeoutSec = 15
)
try {
$res = Invoke-NetAppRestRequest -ClusterEndpoint $ClusterEndpoint -UriPath "/api/cluster" -Method "GET" -Username $Username -Password $Password -IgnoreSsl $IgnoreSsl -TimeoutSec $TimeoutSec
$clusterName = if ($res.name) { $res.name } else { "ONTAP Cluster" }
$version = if ($res.version -and $res.version.full) { $res.version.full } else { "ONTAP 9.x" }
return [PSCustomObject]@{
Success = $true
Message = "NetApp ONTAP ($clusterName) 연결 성공 - 버전: $version"
Version = $version
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp 스토리지 연결 실패: $($_.Exception.Message)"
}
}
}
function Get-NetAppActiveDc {
param(
[string]$ClusterEndpoint = "",
[string]$SvmName = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 15,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -SvmName $SvmName -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$svm = $resolved.SvmName
$ignoreSslVal = $resolved.IgnoreSsl
$liveMode = $resolved.UseLive
$discoveredList = [System.Collections.Generic.List[PSCustomObject]]::new()
$attemptErrors = [System.Collections.Generic.List[string]]::new()
if ($liveMode -and (-not [string]::IsNullOrWhiteSpace($endpoint))) {
# 1. NetApp ONTAP REST API 질의 시도
$queryPaths = [System.Collections.Generic.List[string]]::new()
if (-not [string]::IsNullOrWhiteSpace($svm)) {
$queryPaths.Add("/api/protocols/cifs/domains?svm.name=$svm&fields=discovered_servers,svm.name")
$queryPaths.Add("/api/protocols/cifs/domains?svm.name=$svm&fields=*")
}
$queryPaths.Add("/api/protocols/cifs/domains?fields=discovered_servers,svm.name")
$queryPaths.Add("/api/protocols/cifs/domains?fields=*")
$queryPaths.Add("/api/protocols/cifs/domains")
$records = @()
foreach ($qPath in $queryPaths) {
try {
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath $qPath -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSslVal -TimeoutSec $TimeoutSec
if ($res -and $res.records -and $res.records.Count -gt 0) {
$records = @($res.records)
break
} elseif ($res -and $res.discovered_servers) {
$records = @($res)
break
}
} catch {
$attemptErrors.Add("[$qPath] $($_.Exception.Message)")
}
}
if ($records.Count -gt 0) {
# SVM 필터링 (일치하는 SVM 우선, 없으면 전체 records)
$targetRecs = if (-not [string]::IsNullOrWhiteSpace($svm)) {
$matched = @($records | Where-Object { $_.svm -and ($_.svm.name -eq $svm -or $_.svm.uuid -eq $svm) })
if ($matched.Count -gt 0) { $matched } else { $records }
} else {
$records
}
foreach ($rec in $targetRecs) {
$curSvm = if ($rec.svm -and $rec.svm.name) { $rec.svm.name } elseif ($svm) { $svm } else { "default_svm" }
# 만약 discovered_servers가 컬렉션에서 펼쳐지지 않았고 self 링크가 있으면 단건 조회
$dsList = $rec.discovered_servers
if (-not $dsList -and $rec._links -and $rec._links.self -and $rec._links.self.href) {
try {
$selfRes = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "$($rec._links.self.href)?fields=discovered_servers,svm.name" -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSslVal -TimeoutSec $TimeoutSec
if ($selfRes -and $selfRes.discovered_servers) {
$dsList = $selfRes.discovered_servers
}
} catch {
$attemptErrors.Add("[SelfHref $($rec._links.self.href)] $($_.Exception.Message)")
}
}
if ($dsList) {
foreach ($ds in $dsList) {
# ONTAP REST API JSON 스키마 필드 추출 (server_name, server_ip, server_type, state, node.name 등)
$srvName = if ($ds.server_name) { $ds.server_name }
elseif ($ds.name) { $ds.name }
elseif ($ds.server) { $ds.server }
elseif ($ds.server_ip) { $ds.server_ip }
else { "" }
$srvIp = if ($ds.server_ip) { $ds.server_ip } else { "" }
$srvType = if ($ds.server_type) { $ds.server_type } elseif ($ds.type) { $ds.type } else { "dc" }
$srvPref = if ($ds.preference) { $ds.preference.ToString().ToLower() } else { "regular" }
$srvState = if ($ds.state) { $ds.state } elseif ($ds.status) { $ds.status } else { "unknown" }
$srvNode = if ($ds.node -and $ds.node.name) { $ds.node.name }
elseif ($ds.node) { $ds.node.ToString() } else { "node-01" }
$srvDomain = if ($ds.domain) { $ds.domain } else { "" }
$discoveredList.Add([PSCustomObject]@{
ServerName = $srvName
ServerIp = $srvIp
ServerType = $srvType
Preference = $srvPref
State = $srvState
Node = $srvNode
Domain = $srvDomain
SvmName = $curSvm
})
}
}
}
# OK 상태인 DC 후보군 추출
$okDcList = @($discoveredList | Where-Object {
($_.State -like "*ok*" -or $_.State -like "*up*" -or $_.State -like "*active*") -and
($_.ServerType -like "*dc*" -or $_.ServerType -like "*primary*")
})
# OK 상태인 고유 DC FQDN 목록
$allOkDcNames = @($okDcList | Select-Object -ExpandProperty ServerName -Unique)
# 1순위: OK 상태 중 Preference 가중치 정렬 (preferred: 1, favored: 2, regular: 3)
$sortedOkDcs = @($okDcList | Sort-Object -Property @{
Expression = {
if ($_.Preference -eq "preferred") { 1 }
elseif ($_.Preference -eq "favored") { 2 }
else { 3 }
}
})
$bestMatch = if ($sortedOkDcs.Count -gt 0) { $sortedOkDcs[0] } else { $null }
# 2순위: 상태 무관 DC 타입 서버
if (-not $bestMatch) {
$bestMatch = $discoveredList | Where-Object {
$_.ServerType -like "*dc*"
} | Select-Object -First 1
}
# 3순위: 목록의 첫 번째 유효 서버
if (-not $bestMatch -and $discoveredList.Count -gt 0) {
$bestMatch = $discoveredList[0]
}
if ($bestMatch -and -not [string]::IsNullOrWhiteSpace($bestMatch.ServerName)) {
return [PSCustomObject]@{
ActiveDC = $bestMatch.ServerName
ActiveDcIp = $bestMatch.ServerIp
Preference = $bestMatch.Preference
Node = $bestMatch.Node
SvmName = $bestMatch.SvmName
Status = "OK (Active Connected)"
State = $bestMatch.State
ServerType = $bestMatch.ServerType
IsLive = $true
DiscoveryMethod = "ONTAP REST API (/api/protocols/cifs/domains)"
DiscoveredServerCount= $discoveredList.Count
OkCount = $allOkDcNames.Count
AllOkDcs = $allOkDcNames
AllDiscoveredServers = $discoveredList.ToArray()
}
}
}
# 2. NetApp DataONTAP PowerShell Toolkit 모듈 폴백
try {
if (Get-Command Get-NcCifsDomainDiscoveredServer -ErrorAction SilentlyContinue) {
$ncServers = if ($svm) {
Get-NcCifsDomainDiscoveredServer -Vserver $svm -ErrorAction Stop
} else {
Get-NcCifsDomainDiscoveredServer -ErrorAction Stop
}
if ($ncServers) {
$activeNc = $ncServers | Where-Object { ($_.ServerType -like "*dc*" -or $_.ServerType -like "*domain-controller*") -and ($_.ServerStatus -like "*ok*" -or $_.ServerStatus -like "*active*") } | Select-Object -First 1
if (-not $activeNc) { $activeNc = $ncServers | Select-Object -First 1 }
if ($activeNc) {
return [PSCustomObject]@{
ActiveDC = if ($activeNc.ServerName) { $activeNc.ServerName } else { $activeNc.ServerAddress }
ActiveDcIp = $activeNc.ServerAddress
Node = if ($activeNc.Node) { $activeNc.Node } else { "node-01" }
SvmName = if ($activeNc.Vserver) { $activeNc.Vserver } else { $svm }
Status = "OK (Active Binding)"
State = $activeNc.ServerStatus
ServerType = $activeNc.ServerType
IsLive = $true
DiscoveryMethod = "NetApp DataONTAP Toolkit (Get-NcCifsDomainDiscoveredServer)"
DiscoveredServerCount= @($ncServers).Count
AllDiscoveredServers = @($ncServers)
}
}
}
}
} catch {
$attemptErrors.Add("[DataONTAP Toolkit] $($_.Exception.Message)")
}
if ($attemptErrors.Count -gt 0) {
Write-Warning "NetApp 실제 Active DC 조회 실패 사유:`n$($attemptErrors -join "`n")`n(데모 시뮬레이션 모드로 전환합니다)"
}
}
# Fallback / Demo Mock
return [PSCustomObject]@{
ActiveDC = "dc04.corp.local"
ActiveDcIp = "10.20.1.14"
Preference = "preferred"
Node = "ontap-node-02"
SvmName = if ($svm) { $svm } else { "svm_cifs_prod01" }
Status = "OK (Simulated Active Binding)"
State = "ok"
ServerType = "ms_dc"
IsLive = $false
DiscoveryMethod = "ONTAP Domain Discovery (Simulated)"
DiscoveredServerCount= 2
OkCount = 2
AllOkDcs = @("dc04.corp.local", "dc01.corp.local")
FallbackReason = if ($attemptErrors.Count -gt 0) { ($attemptErrors -join "; ") } else { "Live mode not configured" }
AllDiscoveredServers = @(
[PSCustomObject]@{ ServerName = "dc04.corp.local"; ServerIp = "10.20.1.14"; ServerType = "ms_dc"; Preference = "preferred"; State = "ok"; Node = "ontap-node-02"; SvmName = "svm_cifs_prod01" },
[PSCustomObject]@{ ServerName = "dc01.corp.local"; ServerIp = "10.10.1.11"; ServerType = "ms_dc"; Preference = "favored"; State = "ok"; Node = "ontap-node-01"; SvmName = "svm_cifs_prod01" }
)
}
}
function Get-NetAppQtrees {
param(
[string]$Filter = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 15,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$ignoreSsl = $resolved.IgnoreSsl
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/qtrees?fields=name,volume.name,security_style,oplocks,status" -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
if ($res.records) {
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($item in $res.records) {
$vol = if ($item.volume) { $item.volume.name } else { "" }
$name = $item.name
$sec = if ($item.security_style) { $item.security_style } else { "ntfs" }
$op = if ($item.oplocks) { $item.oplocks } else { "enabled" }
$st = if ($item.status) { $item.status } else { "normal" }
$path = "/vol/$vol/$name"
if ([string]::IsNullOrWhiteSpace($Filter) -or $vol -like "*$Filter*" -or $name -like "*$Filter*") {
$list.Add([PSCustomObject]@{
Volume = $vol
QtreeName = $name
SecurityStyle = $sec
Oplocks = $op
Status = $st
FullPath = $path
IsLive = $true
})
}
}
return $list.ToArray()
}
} catch {
Write-Warning "실제 NetApp Qtree 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
}
}
return Get-NetAppSampleQtrees -Filter $Filter
}
function Get-NetAppQuotas {
param(
[string]$Filter = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 15,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$ignoreSsl = $resolved.IgnoreSsl
# ONTAP REST quota rules
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/quota/rules?fields=type,users.name,group.name,qtree.name,volume.name,space.hard_limit,space.soft_limit" -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
if ($res.records) {
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($item in $res.records) {
$type = $item.type
$vol = if ($item.volume) { $item.volume.name } else { "" }
$target = if ($item.users -and $item.users.Count -gt 0) { $item.users[0].name }
elseif ($item.group -and $item.group.name) { $item.group.name }
elseif ($item.qtree -and $item.qtree.name) { $item.qtree.name }
else { "-" }
$hardBytes = if ($item.space -and $item.space.hard_limit) { [int64]$item.space.hard_limit } else { 0 }
$softBytes = if ($item.space -and $item.space.soft_limit) { [int64]$item.space.soft_limit } else { 0 }
$hardStr = if ($hardBytes -gt 0) { "$([Math]::Round($hardBytes / 1GB, 1)) GB" } else { "무제한" }
$softStr = if ($softBytes -gt 0) { "$([Math]::Round($softBytes / 1GB, 1)) GB" } else { "무제한" }
$list.Add([PSCustomObject]@{
QuotaType = $type
Target = $target
VolumeQtree = $vol
UsedSpace = "조회중"
SoftLimit = $softStr
HardLimit = $hardStr
UsagePercent = "-"
QuotaStatus = "규칙 적용됨"
IsLive = $true
})
}
return $list.ToArray()
}
} catch {
Write-Warning "실제 NetApp Quota 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
}
}
return Get-NetAppSampleQuotas -Filter $Filter
}
function Get-NetAppShares {
param(
[string]$Filter = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 15,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$ignoreSsl = $resolved.IgnoreSsl
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares?fields=name,path,comment,svm.name" -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
if ($res.records) {
$list = [System.Collections.Generic.List[PSCustomObject]]::new()
foreach ($item in $res.records) {
$sName = $item.name
$sPath = $item.path
$sComment = if ($item.comment) { $item.comment } else { "-" }
if ([string]::IsNullOrWhiteSpace($Filter) -or $sName -like "*$Filter*" -or $sPath -like "*$Filter*") {
$list.Add([PSCustomObject]@{
Protocol = "CIFS (SMB)"
ShareName = $sName
Path = $sPath
Comment = $sComment
AclSummary = "ONTAP ACL 관리"
ActiveSessions = "-"
IsLive = $true
})
}
}
return $list.ToArray()
}
} catch {
Write-Warning "실제 NetApp Share 조회 실패: $($_.Exception.Message) (Mock 데이터로 전환합니다)"
}
}
return Get-NetAppSampleShares -Filter $Filter
}
function New-NetAppQtreeAction {
param(
[string]$Volume,
[string]$QtreeName,
[string]$SecurityStyle = "ntfs",
[string]$SvmName = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 20,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -SvmName $SvmName -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$svm = $resolved.SvmName
$ignoreSsl = $resolved.IgnoreSsl
$body = @{
name = $QtreeName
volume = @{ name = $Volume }
security_style = $SecurityStyle
}
if (-not [string]::IsNullOrWhiteSpace($svm)) {
$body["svm"] = @{ name = $svm }
}
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/qtrees" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
Message = "실제 NetApp 볼륨 [$Volume]에 Qtree [$QtreeName]가 성공적으로 생성되었습니다."
IsLive = $true
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp Qtree 생성 실패: $($_.Exception.Message)"
IsLive = $true
}
}
}
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] 볼륨 [$Volume]에 Qtree [$QtreeName]가 가상 생성되었습니다."
IsLive = $false
}
}
function New-NetAppShareAction {
param(
[string]$ShareName,
[string]$Path,
[string]$Comment = "",
[string]$SvmName = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 20,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -SvmName $SvmName -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$svm = $resolved.SvmName
$ignoreSsl = $resolved.IgnoreSsl
$body = @{
name = $ShareName
path = $Path
comment = $Comment
}
if (-not [string]::IsNullOrWhiteSpace($svm)) {
$body["svm"] = @{ name = $svm }
}
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
Message = "실제 NetApp SVM [$svm]에 CIFS 공유 [$ShareName] ($Path)가 생성되었습니다."
IsLive = $true
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp CIFS 공유 생성 실패: $($_.Exception.Message)"
IsLive = $true
}
}
}
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] CIFS 공유 [$ShareName] ($Path)가 가상 생성되었습니다."
IsLive = $false
}
}
function Set-NetAppShareAcl {
param(
[string]$ShareName,
[string]$UserOrGroup,
[string]$Permission = "change", # "full_control", "change", "read", "no_access"
[string]$ActionType = "grant", # "grant", "revoke"
[int]$MaxRetries = 3,
[int]$RetryDelayMs = 1000,
[string]$SvmName = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 20,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -SvmName $SvmName -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$svm = if ($resolved.SvmName) { $resolved.SvmName } else { "default_svm" }
$ignoreSsl = $resolved.IgnoreSsl
$attempt = 0
$lastError = ""
while ($attempt -lt $MaxRetries) {
$attempt++
try {
if ($ActionType -eq "grant") {
$body = @{
user_or_group = $UserOrGroup
permission = $Permission
type = "allow"
}
$path = "/api/protocols/cifs/shares/$svm/$ShareName/acls"
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath $path -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
} else {
$path = "/api/protocols/cifs/shares/$svm/$ShareName/acls/$UserOrGroup"
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath $path -Method "DELETE" -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
}
return [PSCustomObject]@{
Success = $true
Message = "NetApp Share [$ShareName]에 [$UserOrGroup] ($Permission) 권한이 반영되었습니다 (시도 횟수: $attempt)."
Attempts = $attempt
IsLive = $true
}
} catch {
$lastError = $_.Exception.Message
# AD DC 동기화 지연(객체를 찾을 수 없음) 또는 네트워크 지연인 경우 재시도
if ($attempt -lt $MaxRetries) {
Start-Sleep -Milliseconds $RetryDelayMs
}
}
}
return [PSCustomObject]@{
Success = $false
Message = "NetApp Share ACL 반영 실패 ($($MaxRetries)회 재시도 초과): $lastError"
Attempts = $attempt
IsLive = $true
}
}
# Mock / Fallback
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] NetApp Share [$ShareName]에 [$UserOrGroup] 권한 ($Permission) 등록 완료 (즉시 바인딩 성공)"
Attempts = 1
IsLive = $false
}
}
function Set-NetAppQuotaRule {
param(
[string]$Volume,
[string]$Qtree = "",
[string]$Target = "",
[string]$QuotaType = "user",
[int64]$SoftLimitGb = 450,
[int64]$HardLimitGb = 500,
[string]$SvmName = "",
[string]$ClusterEndpoint = "",
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true,
[bool]$UseLive = $false,
[int]$TimeoutSec = 20,
[object]$Config = $null
)
$resolved = Resolve-NetAppParameters -ClusterEndpoint $ClusterEndpoint -Username $Username -Password $Password -SvmName $SvmName -IgnoreSsl $IgnoreSsl -UseLive $UseLive -Config $Config -BoundParameters $PSBoundParameters
$useLive = $resolved.UseLive
if ($useLive -and (-not [string]::IsNullOrWhiteSpace($resolved.Endpoint))) {
try {
$endpoint = $resolved.Endpoint
$user = $resolved.Username
$pass = $resolved.Password
$svm = $resolved.SvmName
$ignoreSsl = $resolved.IgnoreSsl
$body = @{
volume = @{ name = $Volume }
type = $QuotaType
space = @{
hard_limit = $HardLimitGb * 1GB
soft_limit = $SoftLimitGb * 1GB
}
}
if (-not [string]::IsNullOrWhiteSpace($svm)) {
$body["svm"] = @{ name = $svm }
}
if ($Qtree) { $body["qtree"] = @{ name = $Qtree } }
if ($Target) {
if ($QuotaType -eq "user") { $body["users"] = @(@{ name = $Target }) }
elseif ($QuotaType -eq "group") { $body["group"] = @{ name = $Target } }
}
$null = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/quota/rules" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl -TimeoutSec $TimeoutSec
return [PSCustomObject]@{
Success = $true
Message = "NetApp 볼륨 [$Volume] Quota 규칙 (Soft: ${SoftLimitGb}GB, Hard: ${HardLimitGb}GB) 생성/적용 완료"
IsLive = $true
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp Quota 설정 실패: $($_.Exception.Message)"
IsLive = $true
}
}
}
# Mock / Fallback
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] 볼륨 [$Volume] (Qtree: $Qtree) Quota 정책 (Soft: ${SoftLimitGb}GB / Hard: ${HardLimitGb}GB) 반영 완료"
IsLive = $false
}
}
function Get-NetAppSampleQtrees {
param([string]$Filter = "")
$qtrees = @(
[PSCustomObject]@{
Volume = "vol_cifs_share01"
QtreeName = "qtree_finance_docs"
SecurityStyle = "ntfs"
Oplocks = "enabled"
Status = "normal"
FullPath = "/vol/vol_cifs_share01/qtree_finance_docs"
IsLive = $false
},
[PSCustomObject]@{
Volume = "vol_cifs_share01"
QtreeName = "qtree_hr_confidential"
SecurityStyle = "ntfs"
Oplocks = "enabled"
Status = "normal"
FullPath = "/vol/vol_cifs_share01/qtree_hr_confidential"
IsLive = $false
},
[PSCustomObject]@{
Volume = "vol_nfs_appdata"
QtreeName = "qtree_docker_registry"
SecurityStyle = "unix"
Oplocks = "disabled"
Status = "normal"
FullPath = "/vol/vol_nfs_appdata/qtree_docker_registry"
IsLive = $false
},
[PSCustomObject]@{
Volume = "vol_nfs_appdata"
QtreeName = "qtree_backup_targets"
SecurityStyle = "unix"
Oplocks = "disabled"
Status = "normal"
FullPath = "/vol/vol_nfs_appdata/qtree_backup_targets"
IsLive = $false
},
[PSCustomObject]@{
Volume = "vol_user_homes"
QtreeName = "qtree_dev_workspace"
SecurityStyle = "mixed"
Oplocks = "enabled"
Status = "normal"
FullPath = "/vol/vol_user_homes/qtree_dev_workspace"
IsLive = $false
}
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $qtrees
} else {
return @($qtrees | Where-Object {
$_.Volume -like "*$Filter*" -or
$_.QtreeName -like "*$Filter*"
})
}
}
function Get-NetAppSampleQuotas {
param([string]$Filter = "")
$quotas = @(
[PSCustomObject]@{
QuotaType = "tree"
Target = "qtree_finance_docs"
VolumeQtree = "vol_cifs_share01"
UsedSpace = "420 GB"
SoftLimit = "450 GB"
HardLimit = "500 GB"
UsagePercent = "84.0%"
QuotaStatus = "정상 (OK)"
IsLive = $false
},
[PSCustomObject]@{
QuotaType = "tree"
Target = "qtree_hr_confidential"
VolumeQtree = "vol_cifs_share01"
UsedSpace = "192 GB"
SoftLimit = "200 GB"
HardLimit = "220 GB"
UsagePercent = "87.2%"
QuotaStatus = "주의 (Warning)"
IsLive = $false
},
[PSCustomObject]@{
QuotaType = "user"
Target = "CORP\gdhong"
VolumeQtree = "vol_user_homes"
UsedSpace = "94.5 GB"
SoftLimit = "90 GB"
HardLimit = "100 GB"
UsagePercent = "94.5%"
QuotaStatus = "경고 (Soft 초과)"
IsLive = $false
},
[PSCustomObject]@{
QuotaType = "group"
Target = "CORP\Domain Devs"
VolumeQtree = "vol_user_homes"
UsedSpace = "1.42 TB"
SoftLimit = "1.8 TB"
HardLimit = "2.0 TB"
UsagePercent = "71.0%"
QuotaStatus = "정상 (OK)"
IsLive = $false
},
[PSCustomObject]@{
QuotaType = "tree"
Target = "qtree_docker_registry"
VolumeQtree = "vol_nfs_appdata"
UsedSpace = "890 GB"
SoftLimit = "900 GB"
HardLimit = "1.0 TB"
UsagePercent = "89.0%"
QuotaStatus = "주의 (Warning)"
IsLive = $false
}
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $quotas
} else {
return @($quotas | Where-Object {
$_.Target -like "*$Filter*" -or
$_.VolumeQtree -like "*$Filter*"
})
}
}
function Get-NetAppSampleShares {
param([string]$Filter = "")
$shares = @(
[PSCustomObject]@{
Protocol = "CIFS (SMB)"
ShareName = "Finance$"
Path = "/vol/vol_cifs_share01/qtree_finance_docs"
Comment = "재무기획팀 전용 기밀 문서 공유"
AclSummary = "재무팀 (Full), 감사팀 (Read)"
ActiveSessions = 14
IsLive = $false
},
[PSCustomObject]@{
Protocol = "CIFS (SMB)"
ShareName = "HR_Confidential"
Path = "/vol/vol_cifs_share01/qtree_hr_confidential"
Comment = "인사팀 보안 데이터 공유"
AclSummary = "인사팀 (Full), Admin (Read)"
ActiveSessions = 8
IsLive = $false
},
[PSCustomObject]@{
Protocol = "CIFS (SMB)"
ShareName = "PublicProjects"
Path = "/vol/vol_user_homes/qtree_dev_workspace"
Comment = "전사 프로젝트 공용 작업 디렉토리"
AclSummary = "Everyone (Change)"
ActiveSessions = 42
IsLive = $false
},
[PSCustomObject]@{
Protocol = "NFS v4.1"
ShareName = "/nfs/docker_registry"
Path = "/vol/vol_nfs_appdata/qtree_docker_registry"
Comment = "Kubernetes 클러스터 이미지 저장소 마운트"
AclSummary = "10.200.10.0/24 (rw, root_squash)"
ActiveSessions = 26
IsLive = $false
}
)
if ([string]::IsNullOrWhiteSpace($Filter)) {
return $shares
} else {
return @($shares | Where-Object {
$_.ShareName -like "*$Filter*" -or
$_.Path -like "*$Filter*"
})
}
}