diff --git a/integrationtests/iscsi_test.go b/integrationtests/iscsi_test.go index 5664cba6..39024c80 100644 --- a/integrationtests/iscsi_test.go +++ b/integrationtests/iscsi_test.go @@ -6,12 +6,12 @@ import ( "strconv" "testing" - diskApi "github.com/kubernetes-csi/csi-proxy/client/api/disk/v1beta3" - iscsiApi "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha2" - systemApi "github.com/kubernetes-csi/csi-proxy/client/api/system/v1alpha1" - diskClient "github.com/kubernetes-csi/csi-proxy/client/groups/disk/v1beta3" - iscsiClient "github.com/kubernetes-csi/csi-proxy/client/groups/iscsi/v1alpha2" - systemClient "github.com/kubernetes-csi/csi-proxy/client/groups/system/v1alpha1" + disk "github.com/kubernetes-csi/csi-proxy/pkg/disk" + diskapi "github.com/kubernetes-csi/csi-proxy/pkg/disk/api" + iscsi "github.com/kubernetes-csi/csi-proxy/pkg/iscsi" + iscsiapi "github.com/kubernetes-csi/csi-proxy/pkg/iscsi/api" + system "github.com/kubernetes-csi/csi-proxy/pkg/system" + systemapi "github.com/kubernetes-csi/csi-proxy/pkg/system/api" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -20,7 +20,7 @@ import ( const defaultIscsiPort = 3260 const defaultProtoPort = 0 // default value when port is not set -func TestIscsiAPIGroup(t *testing.T) { +func TestIscsi(t *testing.T) { skipTestOnCondition(t, !shouldRunIscsiTests()) err := installIscsiTarget() @@ -58,48 +58,42 @@ func e2eTest(t *testing.T) { defer requireCleanup(t) - iscsi, err := iscsiClient.NewClient() + iscsiClient, err := iscsi.New(iscsiapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, iscsi.Close()) }() - - disk, err := diskClient.NewClient() + diskClient, err := disk.New(diskapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, disk.Close()) }() - - system, err := systemClient.NewClient() + systemClient, err := system.New(systemapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, system.Close()) }() - - startReq := &systemApi.StartServiceRequest{Name: "MSiSCSI"} - _, err = system.StartService(context.TODO(), startReq) + startReq := &system.StartServiceRequest{Name: "MSiSCSI"} + _, err = systemClient.StartService(context.TODO(), startReq) require.NoError(t, err) - tp := &iscsiApi.TargetPortal{ + tp := &iscsi.TargetPortal{ TargetAddress: config.Ip, TargetPort: defaultIscsiPort, } - addTpReq := &iscsiApi.AddTargetPortalRequest{ + addTpReq := &iscsi.AddTargetPortalRequest{ TargetPortal: tp, } - _, err = iscsi.AddTargetPortal(context.Background(), addTpReq) + _, err = iscsiClient.AddTargetPortal(context.Background(), addTpReq) assert.Nil(t, err) - discReq := &iscsiApi.DiscoverTargetPortalRequest{TargetPortal: tp} - discResp, err := iscsi.DiscoverTargetPortal(context.TODO(), discReq) + discReq := &iscsi.DiscoverTargetPortalRequest{TargetPortal: tp} + discResp, err := iscsiClient.DiscoverTargetPortal(context.TODO(), discReq) if assert.Nil(t, err) { assert.Contains(t, discResp.Iqns, config.Iqn) } - connectReq := &iscsiApi.ConnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} - _, err = iscsi.ConnectTarget(context.TODO(), connectReq) + connectReq := &iscsi.ConnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} + _, err = iscsiClient.ConnectTarget(context.TODO(), connectReq) assert.Nil(t, err) - tgtDisksReq := &iscsiApi.GetTargetDisksRequest{TargetPortal: tp, Iqn: config.Iqn} - tgtDisksResp, err := iscsi.GetTargetDisks(context.TODO(), tgtDisksReq) + tgtDisksReq := &iscsi.GetTargetDisksRequest{TargetPortal: tp, Iqn: config.Iqn} + tgtDisksResp, err := iscsiClient.GetTargetDisks(context.TODO(), tgtDisksReq) require.Nil(t, err) require.Len(t, tgtDisksResp.DiskIDs, 1) @@ -107,16 +101,16 @@ func e2eTest(t *testing.T) { diskNumber, err := strconv.ParseUint(diskId, 10, 64) require.NoError(t, err) - attachReq := &diskApi.SetDiskStateRequest{DiskNumber: uint32(diskNumber), IsOnline: true} - _, err = disk.SetDiskState(context.TODO(), attachReq) + attachReq := &disk.SetDiskStateRequest{DiskNumber: uint32(diskNumber), IsOnline: true} + _, err = diskClient.SetDiskState(context.TODO(), attachReq) require.Nil(t, err) - partReq := &diskApi.PartitionDiskRequest{DiskNumber: uint32(diskNumber)} - _, err = disk.PartitionDisk(context.TODO(), partReq) + partReq := &disk.PartitionDiskRequest{DiskNumber: uint32(diskNumber)} + _, err = diskClient.PartitionDisk(context.TODO(), partReq) assert.Nil(t, err) - detachReq := &diskApi.SetDiskStateRequest{DiskNumber: uint32(diskNumber), IsOnline: false} - _, err = disk.SetDiskState(context.TODO(), detachReq) + detachReq := &disk.SetDiskStateRequest{DiskNumber: uint32(diskNumber), IsOnline: false} + _, err = diskClient.SetDiskState(context.TODO(), detachReq) assert.Nil(t, err) } @@ -126,43 +120,39 @@ func targetTest(t *testing.T) { defer requireCleanup(t) - client, err := iscsiClient.NewClient() + iscsiClient, err := iscsi.New(iscsiapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, client.Close()) }() - - system, err := systemClient.NewClient() + systemClient, err := system.New(systemapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, system.Close()) }() - - startReq := &systemApi.StartServiceRequest{Name: "MSiSCSI"} - _, err = system.StartService(context.TODO(), startReq) + startReq := &system.StartServiceRequest{Name: "MSiSCSI"} + _, err = systemClient.StartService(context.TODO(), startReq) require.NoError(t, err) - tp := &iscsiApi.TargetPortal{ + tp := &iscsi.TargetPortal{ TargetAddress: config.Ip, TargetPort: defaultIscsiPort, } - addTpReq := &iscsiApi.AddTargetPortalRequest{ + addTpReq := &iscsi.AddTargetPortalRequest{ TargetPortal: tp, } - _, err = client.AddTargetPortal(context.Background(), addTpReq) + _, err = iscsiClient.AddTargetPortal(context.Background(), addTpReq) assert.Nil(t, err) - discReq := &iscsiApi.DiscoverTargetPortalRequest{TargetPortal: tp} - discResp, err := client.DiscoverTargetPortal(context.TODO(), discReq) + discReq := &iscsi.DiscoverTargetPortalRequest{TargetPortal: tp} + discResp, err := iscsiClient.DiscoverTargetPortal(context.TODO(), discReq) if assert.Nil(t, err) { assert.Contains(t, discResp.Iqns, config.Iqn) } - connectReq := &iscsiApi.ConnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} - _, err = client.ConnectTarget(context.TODO(), connectReq) + connectReq := &iscsi.ConnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} + _, err = iscsiClient.ConnectTarget(context.TODO(), connectReq) assert.Nil(t, err) - disconReq := &iscsiApi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} - _, err = client.DisconnectTarget(context.TODO(), disconReq) + disconReq := &iscsi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} + _, err = iscsiClient.DisconnectTarget(context.TODO(), disconReq) assert.Nil(t, err) } @@ -179,49 +169,45 @@ func targetChapTest(t *testing.T) { err = setChap(targetName, username, password) require.NoError(t, err) - client, err := iscsiClient.NewClient() + iscsiClient, err := iscsi.New(iscsiapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, client.Close()) }() - - system, err := systemClient.NewClient() + systemClient, err := system.New(systemapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, system.Close()) }() - - startReq := &systemApi.StartServiceRequest{Name: "MSiSCSI"} - _, err = system.StartService(context.TODO(), startReq) + startReq := &system.StartServiceRequest{Name: "MSiSCSI"} + _, err = systemClient.StartService(context.TODO(), startReq) require.NoError(t, err) - tp := &iscsiApi.TargetPortal{ + tp := &iscsi.TargetPortal{ TargetAddress: config.Ip, TargetPort: defaultIscsiPort, } - addTpReq := &iscsiApi.AddTargetPortalRequest{ + addTpReq := &iscsi.AddTargetPortalRequest{ TargetPortal: tp, } - _, err = client.AddTargetPortal(context.Background(), addTpReq) + _, err = iscsiClient.AddTargetPortal(context.Background(), addTpReq) assert.Nil(t, err) - discReq := &iscsiApi.DiscoverTargetPortalRequest{TargetPortal: tp} - discResp, err := client.DiscoverTargetPortal(context.TODO(), discReq) + discReq := &iscsi.DiscoverTargetPortalRequest{TargetPortal: tp} + discResp, err := iscsiClient.DiscoverTargetPortal(context.TODO(), discReq) if assert.Nil(t, err) { assert.Contains(t, discResp.Iqns, config.Iqn) } - connectReq := &iscsiApi.ConnectTargetRequest{ + connectReq := &iscsi.ConnectTargetRequest{ TargetPortal: tp, Iqn: config.Iqn, ChapUsername: username, ChapSecret: password, - AuthType: iscsiApi.AuthenticationType_ONE_WAY_CHAP, + AuthType: iscsi.ONE_WAY_CHAP, } - _, err = client.ConnectTarget(context.TODO(), connectReq) + _, err = iscsiClient.ConnectTarget(context.TODO(), connectReq) assert.Nil(t, err) - disconReq := &iscsiApi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} - _, err = client.DisconnectTarget(context.TODO(), disconReq) + disconReq := &iscsi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} + _, err = iscsiClient.DisconnectTarget(context.TODO(), disconReq) assert.Nil(t, err) } @@ -242,40 +228,36 @@ func targetMutualChapTest(t *testing.T) { err = setReverseChap(targetName, reverse_password) require.NoError(t, err) - client, err := iscsiClient.NewClient() + iscsiClient, err := iscsi.New(iscsiapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, client.Close()) }() - - system, err := systemClient.NewClient() + systemClient, err := system.New(systemapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, system.Close()) }() - { - req := &systemApi.StartServiceRequest{Name: "MSiSCSI"} - resp, err := system.StartService(context.TODO(), req) + req := &system.StartServiceRequest{Name: "MSiSCSI"} + resp, err := systemClient.StartService(context.TODO(), req) require.NoError(t, err) assert.NotNil(t, resp) } - tp := &iscsiApi.TargetPortal{ + tp := &iscsi.TargetPortal{ TargetAddress: config.Ip, TargetPort: defaultIscsiPort, } { - req := &iscsiApi.AddTargetPortalRequest{ + req := &iscsi.AddTargetPortalRequest{ TargetPortal: tp, } - resp, err := client.AddTargetPortal(context.Background(), req) + resp, err := iscsiClient.AddTargetPortal(context.Background(), req) assert.Nil(t, err) assert.NotNil(t, resp) } { - req := &iscsiApi.DiscoverTargetPortalRequest{TargetPortal: tp} - resp, err := client.DiscoverTargetPortal(context.TODO(), req) + req := &iscsi.DiscoverTargetPortalRequest{TargetPortal: tp} + resp, err := iscsiClient.DiscoverTargetPortal(context.TODO(), req) if assert.Nil(t, err) && assert.NotNil(t, resp) { assert.Contains(t, resp.Iqns, config.Iqn) } @@ -283,36 +265,36 @@ func targetMutualChapTest(t *testing.T) { { // Try using a wrong initiator password and expect error on connection - req := &iscsiApi.SetMutualChapSecretRequest{MutualChapSecret: "made-up-pass"} - resp, err := client.SetMutualChapSecret(context.TODO(), req) + req := &iscsi.SetMutualChapSecretRequest{MutualChapSecret: "made-up-pass"} + resp, err := iscsiClient.SetMutualChapSecret(context.TODO(), req) require.NoError(t, err) assert.NotNil(t, resp) } - connectReq := &iscsiApi.ConnectTargetRequest{ + connectReq := &iscsi.ConnectTargetRequest{ TargetPortal: tp, Iqn: config.Iqn, ChapUsername: username, ChapSecret: password, - AuthType: iscsiApi.AuthenticationType_MUTUAL_CHAP, + AuthType: iscsi.MUTUAL_CHAP, } - _, err = client.ConnectTarget(context.TODO(), connectReq) + _, err = iscsiClient.ConnectTarget(context.TODO(), connectReq) assert.NotNil(t, err) { - req := &iscsiApi.SetMutualChapSecretRequest{MutualChapSecret: reverse_password} - resp, err := client.SetMutualChapSecret(context.TODO(), req) + req := &iscsi.SetMutualChapSecretRequest{MutualChapSecret: reverse_password} + resp, err := iscsiClient.SetMutualChapSecret(context.TODO(), req) require.NoError(t, err) assert.NotNil(t, resp) } - _, err = client.ConnectTarget(context.TODO(), connectReq) + _, err = iscsiClient.ConnectTarget(context.TODO(), connectReq) assert.Nil(t, err) { - req := &iscsiApi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} - resp, err := client.DisconnectTarget(context.TODO(), req) + req := &iscsi.DisconnectTargetRequest{TargetPortal: tp, Iqn: config.Iqn} + resp, err := iscsiClient.DisconnectTarget(context.TODO(), req) assert.Nil(t, err) assert.NotNil(t, resp) } @@ -324,35 +306,31 @@ func targetPortalTest(t *testing.T, port uint32) { defer requireCleanup(t) - client, err := iscsiClient.NewClient() + iscsiClient, err := iscsi.New(iscsiapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, client.Close()) }() - - system, err := systemClient.NewClient() + systemClient, err := system.New(systemapi.New()) require.Nil(t, err) - defer func() { assert.NoError(t, system.Close()) }() - - startReq := &systemApi.StartServiceRequest{Name: "MSiSCSI"} - _, err = system.StartService(context.TODO(), startReq) + startReq := &system.StartServiceRequest{Name: "MSiSCSI"} + _, err = systemClient.StartService(context.TODO(), startReq) require.NoError(t, err) - tp := &iscsiApi.TargetPortal{ + tp := &iscsi.TargetPortal{ TargetAddress: config.Ip, TargetPort: port, } - listReq := &iscsiApi.ListTargetPortalsRequest{} + listReq := &iscsi.ListTargetPortalsRequest{} - listResp, err := client.ListTargetPortals(context.Background(), listReq) + listResp, err := iscsiClient.ListTargetPortals(context.Background(), listReq) if assert.Nil(t, err) { assert.Len(t, listResp.TargetPortals, 0, "Expect no registered target portals") } - addTpReq := &iscsiApi.AddTargetPortalRequest{TargetPortal: tp} - _, err = client.AddTargetPortal(context.Background(), addTpReq) + addTpReq := &iscsi.AddTargetPortalRequest{TargetPortal: tp} + _, err = iscsiClient.AddTargetPortal(context.Background(), addTpReq) assert.Nil(t, err) // Port 0 (unset) is handled as the default iSCSI port @@ -361,20 +339,20 @@ func targetPortalTest(t *testing.T, port uint32) { expectedPort = defaultIscsiPort } - gotListResp, err := client.ListTargetPortals(context.Background(), listReq) + gotListResp, err := iscsiClient.ListTargetPortals(context.Background(), listReq) if assert.Nil(t, err) { assert.Len(t, gotListResp.TargetPortals, 1) assert.Equal(t, gotListResp.TargetPortals[0].TargetPort, expectedPort) assert.Equal(t, gotListResp.TargetPortals[0].TargetAddress, tp.TargetAddress) } - remReq := &iscsiApi.RemoveTargetPortalRequest{ + remReq := &iscsi.RemoveTargetPortalRequest{ TargetPortal: tp, } - _, err = client.RemoveTargetPortal(context.Background(), remReq) + _, err = iscsiClient.RemoveTargetPortal(context.Background(), remReq) assert.Nil(t, err) - listResp, err = client.ListTargetPortals(context.Background(), listReq) + listResp, err = iscsiClient.ListTargetPortals(context.Background(), listReq) if assert.Nil(t, err) { assert.Len(t, listResp.TargetPortals, 0, "Expect no registered target portals after delete") diff --git a/pkg/iscsi/api/api.go b/pkg/iscsi/api/api.go new file mode 100644 index 00000000..626ff65f --- /dev/null +++ b/pkg/iscsi/api/api.go @@ -0,0 +1,190 @@ +package api + +import ( + "encoding/json" + "fmt" + + "github.com/kubernetes-csi/csi-proxy/pkg/utils" +) + +// Implements the iSCSI OS API calls. All code here should be very simple +// pass-through to the OS APIs. Any logic around the APIs should go in +// pkg/iscsi/iscsi.go so that logic can be easily unit-tested +// without requiring specific OS environments. + +type API interface { + AddTargetPortal(portal *TargetPortal) error + DiscoverTargetPortal(portal *TargetPortal) ([]string, error) + ListTargetPortals() ([]TargetPortal, error) + RemoveTargetPortal(portal *TargetPortal) error + ConnectTarget(portal *TargetPortal, iqn string, authType string, + chapUser string, chapSecret string) error + DisconnectTarget(portal *TargetPortal, iqn string) error + GetTargetDisks(portal *TargetPortal, iqn string) ([]string, error) + SetMutualChapSecret(mutualChapSecret string) error +} + +type iscsiAPI struct{} + +// check that iscsiAPI implements API +var _ API = &iscsiAPI{} + +func New() API { + return iscsiAPI{} +} + +func (iscsiAPI) 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)) + if err != nil { + return fmt.Errorf("error adding target portal. cmd %s, output: %s, err: %v", cmdLine, string(out), err) + } + + return nil +} + +func (iscsiAPI) 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)) + if err != nil { + return nil, fmt.Errorf("error discovering target portal. cmd: %s, output: %s, err: %w", cmdLine, string(out), err) + } + + var iqns []string + err = json.Unmarshal(out, &iqns) + if err != nil { + return nil, fmt.Errorf("failed parsing iqn list. cmd: %s output: %s, err: %w", cmdLine, string(out), err) + } + + return iqns, nil +} + +func (iscsiAPI) ListTargetPortals() ([]TargetPortal, error) { + cmdLine := fmt.Sprintf( + `ConvertTo-Json -InputObject @(Get-IscsiTargetPortal | ` + + `Select-Object TargetPortalAddress, TargetPortalPortNumber)`) + + out, err := utils.RunPowershellCmd(cmdLine) + if err != nil { + return nil, fmt.Errorf("error listing target portals. cmd %s, output: %s, err: %w", cmdLine, string(out), 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) + } + + return portals, nil +} + +func (iscsiAPI) RemoveTargetPortal(portal *TargetPortal) error { + cmdLine := fmt.Sprintf( + `Get-IscsiTargetPortal -TargetPortalAddress ${Env:iscsi_tp_address} ` + + `-TargetPortalPortNumber ${Env:iscsi_tp_port} | Remove-IscsiTargetPortal ` + + `-Confirm:$false`) + + out, err := utils.RunPowershellCmd(cmdLine, fmt.Sprintf("iscsi_tp_address=%s", portal.Address), + fmt.Sprintf("iscsi_tp_port=%d", portal.Port)) + if err != nil { + return fmt.Errorf("error removing target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + } + + return nil +} + +func (iscsiAPI) 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}`) + + if chapUser != "" { + cmdLine += ` -ChapUsername ${Env:iscsi_chap_user}` + } + + if chapSecret != "" { + cmdLine += ` -ChapSecret ${Env:iscsi_chap_secret}` + } + + 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)) + if err != nil { + return fmt.Errorf("error connecting to target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + } + + return nil +} + +func (iscsiAPI) 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`) + + 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)) + if err != nil { + return fmt.Errorf("error disconnecting from target portal. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + } + + return nil +} + +func (iscsiAPI) 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)`) + + 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)) + if err != nil { + return nil, fmt.Errorf("error getting target disks. cmd %s, output: %s, err: %w", cmdLine, string(out), err) + } + + var ids []string + err = json.Unmarshal(out, &ids) + if err != nil { + return nil, fmt.Errorf("error parsing iqn target disks. cmd: %s output: %s, err: %w", cmdLine, string(out), err) + } + + return ids, nil +} + +func (iscsiAPI) 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)) + if err != nil { + return fmt.Errorf("error setting mutual chap secret. cmd %s,"+ + " output: %s, err: %v", cmdLine, string(out), err) + } + + return nil +} diff --git a/pkg/iscsi/api/types.go b/pkg/iscsi/api/types.go new file mode 100644 index 00000000..e0a1d015 --- /dev/null +++ b/pkg/iscsi/api/types.go @@ -0,0 +1,9 @@ +package api + +// TargetPortal is an address and port pair for a specific iSCSI storage +// target. +// JSON field names are the WMI MSFT_iSCSITargetPortal field names. +type TargetPortal struct { + Address string `json:"TargetPortalAddress"` + Port uint32 `json:"TargetPortalPortNumber"` +} diff --git a/pkg/iscsi/iscsi.go b/pkg/iscsi/iscsi.go new file mode 100644 index 00000000..d31bd6e5 --- /dev/null +++ b/pkg/iscsi/iscsi.go @@ -0,0 +1,183 @@ +package iscsi + +import ( + "context" + "fmt" + + iscsiapi "github.com/kubernetes-csi/csi-proxy/pkg/iscsi/api" + "k8s.io/klog/v2" +) + +const defaultIscsiPort = 3260 + +type IsCSI struct { + hostAPI iscsiapi.API +} + +type Interface interface { + AddTargetPortal(context.Context, *AddTargetPortalRequest) (*AddTargetPortalResponse, error) + ConnectTarget(context.Context, *ConnectTargetRequest) (*ConnectTargetResponse, error) + DisconnectTarget(context.Context, *DisconnectTargetRequest) (*DisconnectTargetResponse, error) + DiscoverTargetPortal(context.Context, *DiscoverTargetPortalRequest) (*DiscoverTargetPortalResponse, error) + GetTargetDisks(context.Context, *GetTargetDisksRequest) (*GetTargetDisksResponse, error) + ListTargetPortals(context.Context, *ListTargetPortalsRequest) (*ListTargetPortalsResponse, error) + RemoveTargetPortal(context.Context, *RemoveTargetPortalRequest) (*RemoveTargetPortalResponse, error) + SetMutualChapSecret(context.Context, *SetMutualChapSecretRequest) (*SetMutualChapSecretResponse, error) +} + +var _ Interface = &IsCSI{} + +func New(hostAPI iscsiapi.API) (*IsCSI, error) { + return &IsCSI{ + hostAPI: hostAPI, + }, nil +} + +func (ic *IsCSI) requestTPtoAPITP(portal *TargetPortal) *iscsiapi.TargetPortal { + port := portal.TargetPort + if port == 0 { + port = defaultIscsiPort + } + return &iscsiapi.TargetPortal{Address: portal.TargetAddress, Port: port} +} + +func (ic *IsCSI) AddTargetPortal(context context.Context, request *AddTargetPortalRequest) (*AddTargetPortalResponse, error) { + klog.V(4).Infof("calling AddTargetPortal with portal %s:%d", request.TargetPortal.TargetAddress, request.TargetPortal.TargetPort) + response := &AddTargetPortalResponse{} + err := ic.hostAPI.AddTargetPortal(ic.requestTPtoAPITP(request.TargetPortal)) + if err != nil { + klog.Errorf("failed AddTargetPortal %v", err) + return response, err + } + + return response, nil +} + +func AuthTypeToString(authType AuthenticationType) (string, error) { + switch authType { + case NONE: + return "NONE", nil + case ONE_WAY_CHAP: + return "ONEWAYCHAP", nil + case MUTUAL_CHAP: + return "MUTUALCHAP", nil + default: + return "", fmt.Errorf("invalid authentication type authType=%v", authType) + } +} + +func (ic *IsCSI) ConnectTarget(context context.Context, req *ConnectTargetRequest) (*ConnectTargetResponse, error) { + klog.V(4).Infof("calling ConnectTarget with portal %s:%d and iqn %s"+ + " auth=%v chapuser=%v", req.TargetPortal.TargetAddress, + req.TargetPortal.TargetPort, req.Iqn, req.AuthType, req.ChapUsername) + + response := &ConnectTargetResponse{} + authType, err := AuthTypeToString(req.AuthType) + if err != nil { + klog.Errorf("Error parsing parameters: %v", err) + return response, err + } + + err = ic.hostAPI.ConnectTarget(ic.requestTPtoAPITP(req.TargetPortal), req.Iqn, + authType, req.ChapUsername, req.ChapSecret) + if err != nil { + klog.Errorf("failed ConnectTarget %v", err) + return response, err + } + + return response, nil +} + +func (ic *IsCSI) DisconnectTarget(context context.Context, request *DisconnectTargetRequest) (*DisconnectTargetResponse, error) { + klog.V(4).Infof("calling DisconnectTarget with portal %s:%d and iqn %s", + request.TargetPortal.TargetAddress, request.TargetPortal.TargetPort, request.Iqn) + + response := &DisconnectTargetResponse{} + err := ic.hostAPI.DisconnectTarget(ic.requestTPtoAPITP(request.TargetPortal), request.Iqn) + if err != nil { + klog.Errorf("failed DisconnectTarget %v", err) + return response, err + } + + return response, nil +} + +func (ic *IsCSI) DiscoverTargetPortal(context context.Context, request *DiscoverTargetPortalRequest) (*DiscoverTargetPortalResponse, error) { + klog.V(4).Infof("calling DiscoverTargetPortal with portal %s:%d", request.TargetPortal.TargetAddress, request.TargetPortal.TargetPort) + response := &DiscoverTargetPortalResponse{} + iqns, err := ic.hostAPI.DiscoverTargetPortal(ic.requestTPtoAPITP(request.TargetPortal)) + if err != nil { + klog.Errorf("failed DiscoverTargetPortal %v", err) + return response, err + } + + response.Iqns = iqns + return response, nil +} + +func (ic *IsCSI) GetTargetDisks(context context.Context, request *GetTargetDisksRequest) (*GetTargetDisksResponse, error) { + klog.V(4).Infof("calling GetTargetDisks with portal %s:%d and iqn %s", + request.TargetPortal.TargetAddress, request.TargetPortal.TargetPort, request.Iqn) + response := &GetTargetDisksResponse{} + disks, err := ic.hostAPI.GetTargetDisks(ic.requestTPtoAPITP(request.TargetPortal), request.Iqn) + if err != nil { + klog.Errorf("failed GetTargetDisks %v", err) + return response, err + } + + result := make([]string, 0, len(disks)) + for _, d := range disks { + result = append(result, d) + } + + response.DiskIDs = result + + return response, nil +} + +func (ic *IsCSI) ListTargetPortals(context context.Context, request *ListTargetPortalsRequest) (*ListTargetPortalsResponse, error) { + klog.V(4).Infof("calling ListTargetPortals") + response := &ListTargetPortalsResponse{} + portals, err := ic.hostAPI.ListTargetPortals() + if err != nil { + klog.Errorf("failed ListTargetPortals %v", err) + return response, err + } + + result := make([]*TargetPortal, 0, len(portals)) + for _, p := range portals { + result = append(result, &TargetPortal{ + TargetAddress: p.Address, + TargetPort: p.Port, + }) + } + + response.TargetPortals = result + + return response, nil +} + +func (ic *IsCSI) RemoveTargetPortal(context context.Context, request *RemoveTargetPortalRequest) (*RemoveTargetPortalResponse, error) { + klog.V(4).Infof("calling RemoveTargetPortal with portal %s:%d", request.TargetPortal.TargetAddress, request.TargetPortal.TargetPort) + response := &RemoveTargetPortalResponse{} + err := ic.hostAPI.RemoveTargetPortal(ic.requestTPtoAPITP(request.TargetPortal)) + if err != nil { + klog.Errorf("failed RemoveTargetPortal %v", err) + return response, err + } + + return response, nil +} + +func (ic *IsCSI) SetMutualChapSecret(context context.Context, request *SetMutualChapSecretRequest) (*SetMutualChapSecretResponse, error) { + klog.V(4).Info("calling SetMutualChapSecret") + + response := &SetMutualChapSecretResponse{} + err := ic.hostAPI.SetMutualChapSecret(request.MutualChapSecret) + if err != nil { + klog.Errorf("failed SetMutualChapSecret %v", err) + return response, err + } + + return response, nil +} diff --git a/pkg/iscsi/types.go b/pkg/iscsi/types.go new file mode 100644 index 00000000..da32c524 --- /dev/null +++ b/pkg/iscsi/types.go @@ -0,0 +1,122 @@ +package iscsi + +type AddTargetPortalRequest struct { + // iSCSI Target Portal to register in the initiator + TargetPortal *TargetPortal +} + +type AddTargetPortalResponse struct { + // Intentionally empty +} + +type AuthenticationType uint32 + +const ( + // No authentication is used + NONE = 0 + + // One way CHAP authentication. The target authenticates the initiator. + ONE_WAY_CHAP = 1 + + // Mutual CHAP authentication. The target and initiator authenticate each + // other. + MUTUAL_CHAP = 2 +) + +type ConnectTargetRequest struct { + // Target portal to which the initiator will connect. + TargetPortal *TargetPortal + + // IQN of the iSCSI Target + Iqn string + + // Connection authentication type, None by default + // + // One Way Chap uses the chap_username and chap_secret + // fields mentioned below to authenticate the initiator. + // + // Mutual Chap uses both the user/secret mentioned below + // and the Initiator Chap Secret to authenticate the target and initiator. + AuthType AuthenticationType + + // CHAP Username used to authenticate the initiator + ChapUsername string + + // CHAP password used to authenticate the initiator + ChapSecret string +} + +type ConnectTargetResponse struct { + // Intentionally empty +} + +type DisconnectTargetRequest struct { + // Target portal from which initiator will disconnect + TargetPortal *TargetPortal + // IQN of the iSCSI Target + Iqn string +} + +type DisconnectTargetResponse struct { + // Intentionally empty +} + +type DiscoverTargetPortalRequest struct { + // iSCSI Target Portal on which to initiate discovery + TargetPortal *TargetPortal +} + +type DiscoverTargetPortalResponse struct { + // List of discovered IQN addresses + // follows IQN format: iqn.yyyy-mm.naming-authority:unique-name + Iqns []string +} + +type GetTargetDisksRequest struct { + // Target portal whose disks will be queried + TargetPortal *TargetPortal + // IQN of the iSCSI Target + Iqn string +} + +type GetTargetDisksResponse struct { + // List composed of disk ids (numbers) that are associated with the + // iSCSI target + DiskIDs []string +} + +type ListTargetPortalsRequest struct { +} + +type ListTargetPortalsResponse struct { + // A list of Target Portals currently registered in the initiator + TargetPortals []*TargetPortal +} + +type RemoveTargetPortalRequest struct { + // iSCSI Target Portal + TargetPortal *TargetPortal +} + +type RemoveTargetPortalResponse struct { + // Intentionally empty +} + +type TargetPortal struct { + // iSCSI Target (server) address + TargetAddress string + // iSCSI Target port (default iSCSI port is 3260) + TargetPort uint32 +} + +type SetMutualChapSecretRequest struct { + // the default CHAP secret that all initiators on this machine (node) use to + // authenticate the target on mutual CHAP authentication. + // Must be at least 12 byte long for non-Ipsec connections, at least one + // byte long for Ipsec connections, and at most 16 bytes long. + MutualChapSecret string +} + +type SetMutualChapSecretResponse struct { + // Intentionally empty +} diff --git a/vendor/github.com/kubernetes-csi/csi-proxy/client/groups/iscsi/v1alpha2/client_generated.go b/vendor/github.com/kubernetes-csi/csi-proxy/client/groups/iscsi/v1alpha2/client_generated.go deleted file mode 100644 index 2d195a97..00000000 --- a/vendor/github.com/kubernetes-csi/csi-proxy/client/groups/iscsi/v1alpha2/client_generated.go +++ /dev/null @@ -1,98 +0,0 @@ -// Code generated by csi-proxy-api-gen. DO NOT EDIT. - -package v1alpha2 - -import ( - "context" - "net" - - "github.com/Microsoft/go-winio" - "github.com/kubernetes-csi/csi-proxy/client" - "github.com/kubernetes-csi/csi-proxy/client/api/iscsi/v1alpha2" - "github.com/kubernetes-csi/csi-proxy/client/apiversion" - "google.golang.org/grpc" -) - -// GroupName is the group name of this API. -const GroupName = "iscsi" - -// Version is the api version. -var Version = apiversion.NewVersionOrPanic("v1alpha2") - -type Client struct { - client v1alpha2.IscsiClient - connection *grpc.ClientConn -} - -// NewClient returns a client to make calls to the iscsi API group version v1alpha2. -// It's the caller's responsibility to Close the client when done. -func NewClient() (*Client, error) { - pipePath := client.PipePath(GroupName, Version) - return NewClientWithPipePath(pipePath) -} - -// NewClientWithPipePath returns a client to make calls to the named pipe located at "pipePath". -// It's the caller's responsibility to Close the client when done. -func NewClientWithPipePath(pipePath string) (*Client, error) { - - // verify that the pipe exists - _, err := winio.DialPipe(pipePath, nil) - if err != nil { - return nil, err - } - - connection, err := grpc.Dial(pipePath, - grpc.WithContextDialer(func(context context.Context, s string) (net.Conn, error) { - return winio.DialPipeContext(context, s) - }), - grpc.WithInsecure()) - if err != nil { - return nil, err - } - - client := v1alpha2.NewIscsiClient(connection) - return &Client{ - client: client, - connection: connection, - }, nil -} - -// Close closes the client. It must be called before the client gets GC-ed. -func (w *Client) Close() error { - return w.connection.Close() -} - -// ensures we implement all the required methods -var _ v1alpha2.IscsiClient = &Client{} - -func (w *Client) AddTargetPortal(context context.Context, request *v1alpha2.AddTargetPortalRequest, opts ...grpc.CallOption) (*v1alpha2.AddTargetPortalResponse, error) { - return w.client.AddTargetPortal(context, request, opts...) -} - -func (w *Client) ConnectTarget(context context.Context, request *v1alpha2.ConnectTargetRequest, opts ...grpc.CallOption) (*v1alpha2.ConnectTargetResponse, error) { - return w.client.ConnectTarget(context, request, opts...) -} - -func (w *Client) DisconnectTarget(context context.Context, request *v1alpha2.DisconnectTargetRequest, opts ...grpc.CallOption) (*v1alpha2.DisconnectTargetResponse, error) { - return w.client.DisconnectTarget(context, request, opts...) -} - -func (w *Client) DiscoverTargetPortal(context context.Context, request *v1alpha2.DiscoverTargetPortalRequest, opts ...grpc.CallOption) (*v1alpha2.DiscoverTargetPortalResponse, error) { - return w.client.DiscoverTargetPortal(context, request, opts...) -} - -func (w *Client) GetTargetDisks(context context.Context, request *v1alpha2.GetTargetDisksRequest, opts ...grpc.CallOption) (*v1alpha2.GetTargetDisksResponse, error) { - return w.client.GetTargetDisks(context, request, opts...) -} - -func (w *Client) ListTargetPortals(context context.Context, request *v1alpha2.ListTargetPortalsRequest, opts ...grpc.CallOption) (*v1alpha2.ListTargetPortalsResponse, error) { - return w.client.ListTargetPortals(context, request, opts...) -} - -func (w *Client) RemoveTargetPortal(context context.Context, request *v1alpha2.RemoveTargetPortalRequest, opts ...grpc.CallOption) (*v1alpha2.RemoveTargetPortalResponse, error) { - return w.client.RemoveTargetPortal(context, request, opts...) -} - -func (w *Client) SetMutualChapSecret(context context.Context, request *v1alpha2.SetMutualChapSecretRequest, opts ...grpc.CallOption) (*v1alpha2.SetMutualChapSecretResponse, error) { - return w.client.SetMutualChapSecret(context, request, opts...) -} diff --git a/vendor/modules.txt b/vendor/modules.txt index 65b3ccc9..a3037435 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -60,7 +60,6 @@ github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v1alpha1 github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v1beta1 github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v1beta2 github.com/kubernetes-csi/csi-proxy/client/groups/filesystem/v2alpha1 -github.com/kubernetes-csi/csi-proxy/client/groups/iscsi/v1alpha2 github.com/kubernetes-csi/csi-proxy/client/groups/smb/v1 github.com/kubernetes-csi/csi-proxy/client/groups/smb/v1alpha1 github.com/kubernetes-csi/csi-proxy/client/groups/smb/v1beta1