diff --git a/integrationtests/iscsi_ps_scripts.go b/integrationtests/iscsi_ps_scripts.go index 89bec253..202e390b 100644 --- a/integrationtests/iscsi_ps_scripts.go +++ b/integrationtests/iscsi_ps_scripts.go @@ -42,14 +42,14 @@ $ProgressPreference = "SilentlyContinue" $targetName = "%s" # Get local IPv4 (e.g. 10.30.1.15, not 127.0.0.1) -$address = $(Get-NetIPAddress | Where-Object { $_.InterfaceAlias -eq "Ethernet" -and $_.AddressFamily -eq "IPv4" }).IPAddress +$address = $(Get-NetIPAddress | Where-Object { $_.InterfaceAlias -eq "%s" -and $_.AddressFamily -eq "IPv4" }).IPAddress # Create virtual disk in RAM -New-IscsiVirtualDisk -Path "ramdisk:scratch-${targetName}.vhdx" -Size 100MB | Out-Null +New-IscsiVirtualDisk -Path "ramdisk:scratch-${targetName}.vhdx" -Size 100MB -ComputerName $env:computername | Out-Null # Create a target that allows all initiator IQNs and map a disk to the new target -$target = New-IscsiServerTarget -TargetName $targetName -InitiatorIds @("Iqn:*") -Add-IscsiVirtualDiskTargetMapping -TargetName $targetName -DevicePath "ramdisk:scratch-${targetName}.vhdx" | Out-Null +$target = New-IscsiServerTarget -TargetName $targetName -InitiatorIds @("Iqn:*") -ComputerName $env:computername +Add-IscsiVirtualDiskTargetMapping -TargetName $targetName -DevicePath "ramdisk:scratch-${targetName}.vhdx" -ComputerName $env:computername | Out-Null $output = @{ "iqn" = "$($target.TargetIqn)" @@ -68,7 +68,7 @@ $username = "%s" $password = "%s" $securestring = ConvertTo-SecureString -String $password -AsPlainText -Force $chap = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $securestring) -Set-IscsiServerTarget -TargetName $targetName -EnableChap $true -Chap $chap +Set-IscsiServerTarget -TargetName $targetName -EnableChap $true -Chap $chap -ComputerName $env:computername ` func setChap(targetName string, username string, password string) error { @@ -92,7 +92,7 @@ $securestring = ConvertTo-SecureString -String $password -AsPlainText -Force # Windows initiator does not uses the username for mutual authentication $chap = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($username, $securestring) -Set-IscsiServerTarget -TargetName $targetName -EnableReverseChap $true -ReverseChap $chap +Set-IscsiServerTarget -TargetName $targetName -EnableReverseChap $true -ReverseChap $chap -ComputerName $env:computername ` func setReverseChap(targetName string, password string) error { @@ -131,8 +131,8 @@ Get-IscsiTarget | Disconnect-IscsiTarget -Confirm:$false Get-IscsiTargetPortal | Remove-IscsiTargetPortal -confirm:$false # Clean target -Get-IscsiServerTarget | Remove-IscsiServerTarget -Get-IscsiVirtualDisk | Remove-IscsiVirtualDisk +Get-IscsiServerTarget -ComputerName $env:computername | Remove-IscsiServerTarget +Get-IscsiVirtualDisk -ComputerName $env:computername | Remove-IscsiVirtualDisk # Stop iSCSI initiator Get-Service "MsiSCSI" | Stop-Service @@ -173,7 +173,12 @@ func runPowershellScript(script string) (string, error) { } func setupEnv(targetName string) (*IscsiSetupConfig, error) { - script := fmt.Sprintf(IscsiEnvironmentSetupScript, targetName) + ethernetName := "Ethernet" + if val, ok := os.LookupEnv("ETHERNET_NAME"); ok { + ethernetName = val + } + + script := fmt.Sprintf(IscsiEnvironmentSetupScript, targetName, ethernetName) out, err := runPowershellScript(script) if err != nil { return nil, fmt.Errorf("failed setting up environment. err=%v", err) diff --git a/pkg/cim/iscsi.go b/pkg/cim/iscsi.go new file mode 100644 index 00000000..9f1ac2a1 --- /dev/null +++ b/pkg/cim/iscsi.go @@ -0,0 +1,261 @@ +//go:build windows +// +build windows + +package cim + +import ( + "fmt" + "strconv" + + "github.com/microsoft/wmi/pkg/base/query" + "github.com/microsoft/wmi/server2019/root/microsoft/windows/storage" +) + +// ListISCSITargetPortals retrieves a list of iSCSI target portals. +// +// The equivalent WMI query is: +// +// SELECT [selectors] FROM MSFT_IscsiTargetPortal +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal +// for the WMI class definition. +func ListISCSITargetPortals(selectorList []string) ([]*storage.MSFT_iSCSITargetPortal, error) { + q := query.NewWmiQueryWithSelectList("MSFT_IscsiTargetPortal", selectorList) + instances, err := QueryInstances(WMINamespaceStorage, q) + if IgnoreNotFound(err) != nil { + return nil, err + } + + var targetPortals []*storage.MSFT_iSCSITargetPortal + for _, instance := range instances { + portal, err := storage.NewMSFT_iSCSITargetPortalEx1(instance) + if err != nil { + return nil, fmt.Errorf("failed to query iSCSI target portal %v. error: %v", instance, err) + } + + targetPortals = append(targetPortals, portal) + } + + return targetPortals, nil +} + +// QueryISCSITargetPortal retrieves information about a specific iSCSI target portal +// identified by its network address and port number. +// +// The equivalent WMI query is: +// +// SELECT [selectors] FROM MSFT_IscsiTargetPortal +// WHERE TargetPortalAddress = '
' +// AND TargetPortalPortNumber = '' +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal +// for the WMI class definition. +func QueryISCSITargetPortal(address string, port uint32, selectorList []string) (*storage.MSFT_iSCSITargetPortal, error) { + portalQuery := query.NewWmiQueryWithSelectList( + "MSFT_iSCSITargetPortal", selectorList, + "TargetPortalAddress", address, + "TargetPortalPortNumber", strconv.Itoa(int(port))) + instances, err := QueryInstances(WMINamespaceStorage, portalQuery) + if err != nil { + return nil, err + } + + targetPortal, err := storage.NewMSFT_iSCSITargetPortalEx1(instances[0]) + if err != nil { + return nil, fmt.Errorf("failed to query iSCSI target portal at (%s:%d). error: %v", address, port, err) + } + + return targetPortal, nil +} + +// NewISCSITargetPortal creates a new iSCSI target portal. +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitargetportal-new +// for the WMI method definition. +func NewISCSITargetPortal(targetPortalAddress string, + targetPortalPortNumber uint32, + initiatorInstanceName *string, + initiatorPortalAddress *string, + isHeaderDigest *bool, + isDataDigest *bool) (*storage.MSFT_iSCSITargetPortal, error) { + params := map[string]interface{}{ + "TargetPortalAddress": targetPortalAddress, + "TargetPortalPortNumber": targetPortalPortNumber, + } + if initiatorInstanceName != nil { + params["InitiatorInstanceName"] = *initiatorInstanceName + } + if initiatorPortalAddress != nil { + params["InitiatorPortalAddress"] = *initiatorPortalAddress + } + if isHeaderDigest != nil { + params["IsHeaderDigest"] = *isHeaderDigest + } + if isDataDigest != nil { + params["IsDataDigest"] = *isDataDigest + } + result, _, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_iSCSITargetPortal", "New", params) + if err != nil { + return nil, fmt.Errorf("failed to create iSCSI target portal with %v. result: %d, error: %v", params, result, err) + } + + return QueryISCSITargetPortal(targetPortalAddress, targetPortalPortNumber, nil) +} + +// ListISCSITargetsByTargetPortal retrieves all iSCSI targets from the specified iSCSI target portal +// using MSFT_iSCSITargetToiSCSITargetPortal association. +// +// WMI association MSFT_iSCSITargetToiSCSITargetPortal: +// +// iSCSITarget | iSCSITargetPortal +// ----------- | ----------------- +// MSFT_iSCSITarget (NodeAddress = "iqn.1991-05.com.microsoft:win-8e2evaq9q...) | MSFT_iSCSITargetPortal (TargetPortalAdd... +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget +// for the WMI class definition. +func ListISCSITargetsByTargetPortal(portals []*storage.MSFT_iSCSITargetPortal) ([]*storage.MSFT_iSCSITarget, error) { + var targets []*storage.MSFT_iSCSITarget + for _, portal := range portals { + collection, err := portal.GetAssociated("MSFT_iSCSITargetToiSCSITargetPortal", "MSFT_iSCSITarget", "iSCSITarget", "iSCSITargetPortal") + if err != nil { + return nil, fmt.Errorf("failed to query associated iSCSITarget for %v. error: %v", portal, err) + } + + for _, instance := range collection { + target, err := storage.NewMSFT_iSCSITargetEx1(instance) + if err != nil { + return nil, fmt.Errorf("failed to query iSCSI target %v. error: %v", instance, err) + } + + targets = append(targets, target) + } + } + + return targets, nil +} + +// QueryISCSITarget retrieves the iSCSI target from the specified portal address, portal and node address. +func QueryISCSITarget(address string, port uint32, nodeAddress string) (*storage.MSFT_iSCSITarget, error) { + portal, err := QueryISCSITargetPortal(address, port, nil) + if err != nil { + return nil, err + } + + targets, err := ListISCSITargetsByTargetPortal([]*storage.MSFT_iSCSITargetPortal{portal}) + if err != nil { + return nil, err + } + + for _, target := range targets { + targetNodeAddress, err := target.GetProperty("NodeAddress") + if err != nil { + return nil, fmt.Errorf("failed to query iSCSI target %v. error: %v", target, err) + } + + if targetNodeAddress == nodeAddress { + return target, nil + } + } + + return nil, nil +} + +// QueryISCSISessionByTarget retrieves the iSCSI session from the specified iSCSI target +// using MSFT_iSCSITargetToiSCSISession association. +// +// WMI association MSFT_iSCSITargetToiSCSISession: +// +// iSCSISession | iSCSITarget +// ------------ | ----------- +// MSFT_iSCSISession (SessionIdentifier = "ffffac0cacbff010-4000013700000016") | MSFT_iSCSITarget (NodeAddress = "iqn.199... +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsisession +// for the WMI class definition. +func QueryISCSISessionByTarget(target *storage.MSFT_iSCSITarget) (*storage.MSFT_iSCSISession, error) { + collection, err := target.GetAssociated("MSFT_iSCSITargetToiSCSISession", "MSFT_iSCSISession", "iSCSISession", "iSCSITarget") + if err != nil { + return nil, fmt.Errorf("failed to query associated iSCSISession for %v. error: %v", target, err) + } + + if len(collection) == 0 { + return nil, nil + } + + session, err := storage.NewMSFT_iSCSISessionEx1(collection[0]) + return session, err +} + +// ListDisksByTarget find all disks associated with an iSCSITarget. +// It finds out the iSCSIConnections from MSFT_iSCSITargetToiSCSIConnection association, +// then locate MSFT_Disk objects from MSFT_iSCSIConnectionToDisk association. +// +// WMI association MSFT_iSCSITargetToiSCSIConnection: +// +// iSCSIConnection | iSCSITarget +// --------------- | ----------- +// MSFT_iSCSIConnection (ConnectionIdentifier = "ffffac0cacbff010-15") | MSFT_iSCSITarget (NodeAddress = "iqn.1991-05.com... +// +// WMI association MSFT_iSCSIConnectionToDisk: +// +// Disk | iSCSIConnection +// ---- | --------------- +// MSFT_Disk (ObjectId = "{1}\\WIN-8E2EVAQ9QSB\root/Microsoft/Win...) | MSFT_iSCSIConnection (ConnectionIdentifier = "fff... +// +// Refer to https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsiconnection +// for the WMI class definition. +func ListDisksByTarget(target *storage.MSFT_iSCSITarget, selectorList []string) ([]*storage.MSFT_Disk, error) { + // list connections to the given iSCSI target + collection, err := target.GetAssociated("MSFT_iSCSITargetToiSCSIConnection", "MSFT_iSCSIConnection", "iSCSIConnection", "iSCSITarget") + if err != nil { + return nil, fmt.Errorf("failed to query associated iSCSISession for %v. error: %v", target, err) + } + + if len(collection) == 0 { + return nil, nil + } + + var result []*storage.MSFT_Disk + for _, conn := range collection { + instances, err := conn.GetAssociated("MSFT_iSCSIConnectionToDisk", "MSFT_Disk", "Disk", "iSCSIConnection") + if err != nil { + return nil, fmt.Errorf("failed to query associated disk for %v. error: %v", target, err) + } + + for _, instance := range instances { + disk, err := storage.NewMSFT_DiskEx1(instance) + if err != nil { + return nil, fmt.Errorf("failed to query associated disk %v. error: %v", instance, err) + } + + result = append(result, disk) + } + } + + return result, err +} + +// ConnectISCSITarget establishes a connection to an iSCSI target with optional CHAP authentication credential. +// +// Refer https://learn.microsoft.com/en-us/previous-versions/windows/desktop/iscsidisc/msft-iscsitarget-connect +// for the WMI method definition. +func ConnectISCSITarget(portalAddress string, portalPortNumber uint32, nodeAddress string, authType string, chapUsername *string, chapSecret *string) (int, map[string]interface{}, error) { + inParams := map[string]interface{}{ + "NodeAddress": nodeAddress, + "TargetPortalAddress": portalAddress, + "TargetPortalPortNumber": int(portalPortNumber), + "AuthenticationType": authType, + } + // InitiatorPortalAddress + // IsDataDigest + // IsHeaderDigest + // ReportToPnP + if chapUsername != nil { + inParams["ChapUsername"] = *chapUsername + } + if chapSecret != nil { + inParams["ChapSecret"] = *chapSecret + } + + result, outParams, err := InvokeCimMethod(WMINamespaceStorage, "MSFT_iSCSITarget", "Connect", inParams) + return result, outParams, err +} diff --git a/pkg/os/iscsi/api.go b/pkg/os/iscsi/api.go index 559ed3b5..05532641 100644 --- a/pkg/os/iscsi/api.go +++ b/pkg/os/iscsi/api.go @@ -1,10 +1,13 @@ package iscsi import ( - "encoding/json" "fmt" + "strconv" + "strings" - "github.com/kubernetes-csi/csi-proxy/pkg/utils" + "github.com/kubernetes-csi/csi-proxy/pkg/cim" + "github.com/microsoft/wmi/server2019/root/microsoft/windows/storage" + "k8s.io/klog/v2" ) // Implements the iSCSI OS API calls. All code here should be very simple @@ -18,70 +21,104 @@ func New() APIImplementor { return APIImplementor{} } +func parseTargetPortal(instance *storage.MSFT_iSCSITargetPortal) (string, uint32, error) { + portalAddress, err := instance.GetPropertyTargetPortalAddress() + if err != nil { + return "", 0, fmt.Errorf("failed parsing target portal address %v. err: %w", instance, err) + } + + portalPort, err := instance.GetProperty("TargetPortalPortNumber") + if err != nil { + return "", 0, fmt.Errorf("failed parsing target portal port number %v. err: %w", instance, err) + } + + return portalAddress, uint32(portalPort.(int32)), nil +} + func (APIImplementor) AddTargetPortal(portal *TargetPortal) error { - cmdLine := fmt.Sprintf( - `New-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} ` + - `-TargetPortalPortNumber ${Env:iscsi_tp_port}`) - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port)) + existing, err := cim.QueryISCSITargetPortal(portal.Address, portal.Port, nil) + if cim.IgnoreNotFound(err) != nil { + return err + } + + if existing != nil { + klog.V(2).Infof("target portal at (%s:%d) already exists", portal.Address, portal.Port) + return nil + } + + _, err = cim.NewISCSITargetPortal(portal.Address, portal.Port, nil, nil, nil, nil) if err != nil { - return fmt.Errorf("error adding target portal. cmd %s, output: %s, err: %v", cmdLine, string(out), err) + return fmt.Errorf("error adding target portal at (%s:%d). err: %v", portal.Address, portal.Port, err) } return nil } func (APIImplementor) DiscoverTargetPortal(portal *TargetPortal) ([]string, error) { - // ConvertTo-Json is not part of the pipeline because powershell converts an - // array with one element to a single element - cmdLine := fmt.Sprintf( - `ConvertTo-Json -InputObject @(Get-IscsiTargetPortal -TargetPortalAddress ` + - `${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} | ` + - `Get-IscsiTarget | Select-Object -ExpandProperty NodeAddress)`) - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port)) + instance, err := cim.QueryISCSITargetPortal(portal.Address, portal.Port, nil) if err != nil { - return nil, fmt.Errorf("error discovering target portal. cmd: %s, output: %s, err: %w", cmdLine, string(out), err) + return nil, err } - var iqns []string - err = json.Unmarshal(out, &iqns) + targets, err := cim.ListISCSITargetsByTargetPortal([]*storage.MSFT_iSCSITargetPortal{instance}) if err != nil { - return nil, fmt.Errorf("failed parsing iqn list. cmd: %s output: %s, err: %w", cmdLine, string(out), err) + return nil, err + } + + var iqns []string + for _, target := range targets { + iqn, err := target.GetProperty("NodeAddress") + if err != nil { + return nil, fmt.Errorf("failed parsing node address of target %v to target portal at (%s:%d). err: %w", target, portal.Address, portal.Port, err) + } + + iqns = append(iqns, iqn.(string)) } return iqns, nil } func (APIImplementor) ListTargetPortals() ([]TargetPortal, error) { - cmdLine := fmt.Sprintf( - `ConvertTo-Json -InputObject @(Get-IscsiTargetPortal | ` + - `Select-Object TargetPortalAddress, TargetPortalPortNumber)`) - - out, err := utils.RunPowershellCmd(cmdLine) + instances, err := cim.ListISCSITargetPortals([]string{"TargetPortalAddress", "TargetPortalPortNumber"}) if err != nil { - return nil, fmt.Errorf("error listing target portals. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + return nil, err } var portals []TargetPortal - err = json.Unmarshal(out, &portals) - if err != nil { - return nil, fmt.Errorf("failed parsing target portal list. cmd: %s output: %s, err: %w", cmdLine, string(out), err) + for _, instance := range instances { + address, port, err := parseTargetPortal(instance) + if err != nil { + return nil, fmt.Errorf("failed parsing target portal %v. err: %w", instance, err) + } + + portals = append(portals, TargetPortal{ + Address: address, + Port: port, + }) } return portals, nil } func (APIImplementor) RemoveTargetPortal(portal *TargetPortal) error { - cmdLine := fmt.Sprintf( - `Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} ` + - `-TargetPortalPortNumber ${Env:iscsi_tp_port} | Remove-IscsiTargetPortal ` + - `-Confirm:$false`) + instance, err := cim.QueryISCSITargetPortal(portal.Address, portal.Port, nil) + if err != nil { + return err + } - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port)) + address, port, err := parseTargetPortal(instance) if err != nil { - return fmt.Errorf("error removing target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + return fmt.Errorf("failed to parse target portal %v. error: %v", instance, err) + } + + result, err := instance.InvokeMethodWithReturn("Remove", + nil, + nil, + int(port), + address, + ) + if result != 0 || err != nil { + return fmt.Errorf("error removing target portal at (%s:%d). result: %d, err: %w", address, port, result, err) } return nil @@ -89,86 +126,116 @@ func (APIImplementor) RemoveTargetPortal(portal *TargetPortal) error { func (APIImplementor) ConnectTarget(portal *TargetPortal, iqn string, authType string, chapUser string, chapSecret string) error { - // Not using InputObject as Connect-IscsiTarget's InputObject does not work. - // This is due to being a static WMI method together with a bug in the - // powershell version of the API. - cmdLine := fmt.Sprintf( - `Connect-IscsiTarget -TargetPortalAddress ${Env:iscsi_tp_address}` + - ` -TargetPortalPortNumber ${Env:iscsi_tp_port} -NodeAddress ${Env:iscsi_target_iqn}` + - ` -AuthenticationType ${Env:iscsi_auth_type}`) + target, err := cim.QueryISCSITarget(portal.Address, portal.Port, iqn) + if err != nil { + return err + } - if chapUser != "" { - cmdLine += ` -ChapUsername ${Env:iscsi_chap_user}` + connected, err := target.GetPropertyIsConnected() + if err != nil { + return err } - if chapSecret != "" { - cmdLine += ` -ChapSecret ${Env:iscsi_chap_secret}` + if connected { + klog.V(2).Infof("target %s from target portal at (%s:%d) is connected.", iqn, portal.Address, portal.Port) + return nil } - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port), - fmt.Sprintf("iscsi_target_iqn=%s", iqn), - fmt.Sprintf("iscsi_auth_type=%s", authType), - fmt.Sprintf("iscsi_chap_user=%s", chapUser), - fmt.Sprintf("iscsi_chap_secret=%s", chapSecret)) + targetAuthType := strings.ToUpper(strings.ReplaceAll(authType, "_", "")) + + result, _, err := cim.ConnectISCSITarget(portal.Address, portal.Port, iqn, targetAuthType, &chapUser, &chapSecret) if err != nil { - return fmt.Errorf("error connecting to target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + return fmt.Errorf("error connecting to target portal. result: %d, err: %w", result, err) } return nil } func (APIImplementor) DisconnectTarget(portal *TargetPortal, iqn string) error { - // Using InputObject instead of pipe to verify input is not empty - cmdLine := fmt.Sprintf( - `Disconnect-IscsiTarget -InputObject (Get-IscsiTargetPortal ` + - `-TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port} ` + - ` | Get-IscsiTarget | Where-Object { $_.NodeAddress -eq ${Env:iscsi_target_iqn} }) ` + - `-Confirm:$false`) + target, err := cim.QueryISCSITarget(portal.Address, portal.Port, iqn) + if err != nil { + return err + } + + connected, err := target.GetPropertyIsConnected() + if err != nil { + return fmt.Errorf("error query connected of target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) + } + + if !connected { + klog.V(2).Infof("target %s from target portal at (%s:%d) is not connected.", iqn, portal.Address, portal.Port) + return nil + } - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port), - fmt.Sprintf("iscsi_target_iqn=%s", iqn)) + // get session + session, err := cim.QueryISCSISessionByTarget(target) if err != nil { - return fmt.Errorf("error disconnecting from target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + return fmt.Errorf("error query session of target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) + } + + sessionIdentifier, err := session.GetPropertySessionIdentifier() + if err != nil { + return fmt.Errorf("error query session identifier of target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) + } + + persistent, err := session.GetPropertyIsPersistent() + if err != nil { + return fmt.Errorf("error query session persistency of target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) + } + + if persistent { + result, err := session.InvokeMethodWithReturn("Unregister") + if err != nil { + return fmt.Errorf("error unregister session on target %s from target portal at (%s:%d). result: %d, err: %w", iqn, portal.Address, portal.Port, result, err) + } + } + + result, err := target.InvokeMethodWithReturn("Disconnect", sessionIdentifier) + if err != nil { + return fmt.Errorf("error disconnecting target %s from target portal at (%s:%d). result: %d, err: %w", iqn, portal.Address, portal.Port, result, err) } return nil } func (APIImplementor) GetTargetDisks(portal *TargetPortal, iqn string) ([]string, error) { - // Converting DiskNumber to string for compatibility with disk api group - // Not using pipeline in order to validate that items are non-empty - cmdLine := fmt.Sprintf( - `$ErrorActionPreference = "Stop"; ` + - `$tp = Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} -TargetPortalPortNumber ${Env:iscsi_tp_port}; ` + - `$t = $tp | Get-IscsiTarget | Where-Object { $_.NodeAddress -eq ${Env:iscsi_target_iqn} }; ` + - `$c = Get-IscsiConnection -IscsiTarget $t; ` + - `$ids = $c | Get-Disk | Select -ExpandProperty Number | Out-String -Stream; ` + - `ConvertTo-Json -InputObject @($ids)`) + target, err := cim.QueryISCSITarget(portal.Address, portal.Port, iqn) + if err != nil { + return nil, err + } - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), - fmt.Sprintf("iscsi_tp_port=%d", portal.Port), - fmt.Sprintf("iscsi_target_iqn=%s", iqn)) + connected, err := target.GetPropertyIsConnected() if err != nil { - return nil, fmt.Errorf("error getting target disks. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + return nil, fmt.Errorf("error query connected of target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) } - var ids []string - err = json.Unmarshal(out, &ids) + if !connected { + klog.V(2).Infof("target %s from target portal at (%s:%d) is not connected.", iqn, portal.Address, portal.Port) + return nil, nil + } + + disks, err := cim.ListDisksByTarget(target, []string{}) + if err != nil { - return nil, fmt.Errorf("error parsing iqn target disks. cmd: %s output: %s, err: %w", cmdLine, string(out), err) + return nil, fmt.Errorf("error getting target disks on target %s from target portal at (%s:%d). err: %w", iqn, portal.Address, portal.Port, err) } + var ids []string + for _, disk := range disks { + number, err := disk.GetProperty("Number") + if err != nil { + return nil, fmt.Errorf("error getting number of disk %v on target %s from target portal at (%s:%d). err: %w", disk, iqn, portal.Address, portal.Port, err) + } + + ids = append(ids, strconv.Itoa(int(number.(int32)))) + } return ids, nil } func (APIImplementor) SetMutualChapSecret(mutualChapSecret string) error { - cmdLine := `Set-IscsiChapSecret -ChapSecret ${Env:iscsi_mutual_chap_secret}` - out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_mutual_chap_secret=%s", mutualChapSecret)) + result, _, err := cim.InvokeCimMethod(cim.WMINamespaceStorage, "MSFT_iSCSISession", "SetCHAPSecret", map[string]interface{}{"ChapSecret": mutualChapSecret}) if err != nil { - return fmt.Errorf("error setting mutual chap secret. cmd %s,"+ - " output: %s, err: %v", cmdLine, string(out), err) + return fmt.Errorf("error setting mutual chap secret. result: %d, err: %v", result, err) } return nil