Files
ModernAdminStudio/Modules/NetAppService.ps1
T

499 lines
18 KiB
PowerShell

# Modules\NetAppService.ps1
# NetApp ONTAP REST API Service Module (Live & Fallback)
function Invoke-NetAppRestRequest {
param(
[string]$ClusterEndpoint,
[string]$UriPath,
[string]$Method = "GET",
[object]$Body = $null,
[string]$Username = "",
[string]$Password = "",
[bool]$IgnoreSsl = $true
)
# SSL 우회 설정 (.NET 기반)
if ($IgnoreSsl) {
[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { $true }
[System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12 -bor [System.Net.SecurityProtocolType]::Tls13
}
$base = $ClusterEndpoint.TrimEnd('/')
$fullUri = "$base$UriPath"
$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 = 3
}
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
)
try {
$res = Invoke-NetAppRestRequest -ClusterEndpoint $ClusterEndpoint -UriPath "/api/cluster" -Method "GET" -Username $Username -Password $Password -IgnoreSsl $IgnoreSsl
$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-NetAppQtrees {
param(
[string]$Filter = "",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.NetApp) { [bool]$Config.NetApp.UseLive } else { $false }
if ($useLive) {
try {
$endpoint = $Config.NetApp.ClusterEndpoint
$user = $Config.NetApp.Username
$pass = $Config.NetApp.Password
$ignoreSsl = [bool]$Config.NetApp.IgnoreSslErrors
$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
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 = "",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.NetApp) { [bool]$Config.NetApp.UseLive } else { $false }
if ($useLive) {
try {
$endpoint = $Config.NetApp.ClusterEndpoint
$user = $Config.NetApp.Username
$pass = $Config.NetApp.Password
$ignoreSsl = [bool]$Config.NetApp.IgnoreSslErrors
# 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
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 = "",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.NetApp) { [bool]$Config.NetApp.UseLive } else { $false }
if ($useLive) {
try {
$endpoint = $Config.NetApp.ClusterEndpoint
$user = $Config.NetApp.Username
$pass = $Config.NetApp.Password
$ignoreSsl = [bool]$Config.NetApp.IgnoreSslErrors
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares?fields=name,path,comment,svm.name" -Method "GET" -Username $user -Password $pass -IgnoreSsl $ignoreSsl
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",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.NetApp) { [bool]$Config.NetApp.UseLive } else { $false }
if ($useLive) {
try {
$endpoint = $Config.NetApp.ClusterEndpoint
$user = $Config.NetApp.Username
$pass = $Config.NetApp.Password
$svm = $Config.NetApp.DefaultSvm
$ignoreSsl = [bool]$Config.NetApp.IgnoreSslErrors
$body = @{
name = $QtreeName
volume = @{ name = $Volume }
svm = @{ name = $svm }
security_style = $SecurityStyle
}
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/storage/qtrees" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl
return [PSCustomObject]@{
Success = $true
Message = "실제 NetApp 볼륨 [$Volume]에 Qtree [$QtreeName]가 성공적으로 생성되었습니다."
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp Qtree 생성 실패: $($_.Exception.Message)"
}
}
}
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] 볼륨 [$Volume]에 Qtree [$QtreeName]가 가상 생성되었습니다."
}
}
function New-NetAppShareAction {
param(
[string]$ShareName,
[string]$Path,
[string]$Comment = "",
[object]$Config = $null
)
$useLive = if ($Config -and $Config.NetApp) { [bool]$Config.NetApp.UseLive } else { $false }
if ($useLive) {
try {
$endpoint = $Config.NetApp.ClusterEndpoint
$user = $Config.NetApp.Username
$pass = $Config.NetApp.Password
$svm = $Config.NetApp.DefaultSvm
$ignoreSsl = [bool]$Config.NetApp.IgnoreSslErrors
$body = @{
name = $ShareName
path = $Path
comment = $Comment
svm = @{ name = $svm }
}
$res = Invoke-NetAppRestRequest -ClusterEndpoint $endpoint -UriPath "/api/protocols/cifs/shares" -Method "POST" -Body $body -Username $user -Password $pass -IgnoreSsl $ignoreSsl
return [PSCustomObject]@{
Success = $true
Message = "실제 NetApp SVM [$svm]에 CIFS 공유 [$ShareName] ($Path)가 생성되었습니다."
}
} catch {
return [PSCustomObject]@{
Success = $false
Message = "NetApp CIFS 공유 생성 실패: $($_.Exception.Message)"
}
}
}
return [PSCustomObject]@{
Success = $true
Message = "[데모 모드] CIFS 공유 [$ShareName] ($Path)가 가상 생성되었습니다."
}
}
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*"
})
}
}