Skip to content

Commit 6afcddf

Browse files
authored
Handle nodeUnstage, empty hostSession map
Handle a specific corner case when nodeUnstageIscsiVolume is called and the hostSession map is empty
1 parent 7da73ec commit 6afcddf

File tree

11 files changed

+491
-1
lines changed

11 files changed

+491
-1
lines changed

frontend/csi/node_server.go

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1814,8 +1814,57 @@ func (p *Plugin) nodeUnstageISCSIVolume(
18141814
ctx context.Context, req *csi.NodeUnstageVolumeRequest, publishInfo *models.VolumePublishInfo, force bool,
18151815
) error {
18161816
hostSessionMap := iscsiUtils.GetISCSIHostSessionMapForTarget(ctx, publishInfo.IscsiTargetIQN)
1817+
// TODO: (jharrod) This is a temporary fix to handle the case where the hostSessionMap is empty. We need to
1818+
// refactor nodeUnstageISCSIVolume to handle this case more gracefully and not rely on the hostSessionMap.
18171819
if len(hostSessionMap) == 0 {
1818-
Logc(ctx).Debug("No host sessions found, nothing to do.")
1820+
// If we are in this block it likely means we have errored or had a pod restart after the iscsi logout and
1821+
// before the tracking file has been removed. We need to ensure the device was removed and remove the tracking
1822+
// file, without going through the rest of the unstage process.
1823+
if convert.ToBool(publishInfo.LUKSEncryption) {
1824+
var err error
1825+
var luksMapperPath string
1826+
fields := LogFields{"device": publishInfo.DevicePath}
1827+
// Set device path to dm device to correctly verify legacy volumes.
1828+
if luks.IsLegacyLUKSDevicePath(publishInfo.DevicePath) {
1829+
luksMapperPath = publishInfo.DevicePath
1830+
dmPath, err := luks.GetDmDevicePathFromLUKSLegacyPath(ctx, p.command,
1831+
publishInfo.DevicePath)
1832+
if err != nil {
1833+
Logc(ctx).WithFields(fields).WithError(err).Warn(
1834+
"Could not determine dm device path from legacy LUKS device path. " +
1835+
"Continuing with device removal.")
1836+
} else {
1837+
publishInfo.DevicePath = dmPath
1838+
}
1839+
} else {
1840+
// If not using luks legacy device path we need to find the LUKS mapper device
1841+
luksMapperPath, err = p.devices.GetLUKSDeviceForMultipathDevice(publishInfo.DevicePath)
1842+
if err != nil {
1843+
if !errors.IsNotFoundError(err) {
1844+
Logc(ctx).WithFields(fields).WithError(err).Warn(
1845+
"Could not determine LUKS device path from multipath device. " +
1846+
"Continuing with device removal.")
1847+
}
1848+
Logc(ctx).WithFields(fields).Info("No LUKS device path found from multipath device.")
1849+
}
1850+
}
1851+
err = p.devices.EnsureLUKSDeviceClosedWithMaxWaitLimit(ctx, luksMapperPath)
1852+
if err != nil {
1853+
Logc(ctx).WithError(err).Debug("Unable to remove LUKS device. Continuing with tracking file removal.")
1854+
}
1855+
}
1856+
if err := p.devices.RemoveMultipathDeviceMappingWithRetries(ctx, publishInfo.DevicePath,
1857+
removeMultipathDeviceMappingRetries, removeMultipathDeviceMappingRetryDelay); err != nil {
1858+
Logc(ctx).Warn("Unable to remove multipath device. Continuing with tracking file removal.")
1859+
}
1860+
// Ensure the tracking file is removed.
1861+
volumeId, _, err := p.getVolumeIdAndStagingPath(req)
1862+
if err != nil {
1863+
return err
1864+
}
1865+
if err := p.nodeHelper.DeleteTrackingInfo(ctx, volumeId); err != nil {
1866+
return status.Error(codes.Internal, err.Error())
1867+
}
18191868
return nil
18201869
}
18211870

frontend/csi/node_server_test.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2134,6 +2134,20 @@ func TestNodeUnstageISCSIVolume(t *testing.T) {
21342134
gomock.Any()).Return(map[int]int{})
21352135
return mockIscsiReconcileUtilsClient
21362136
},
2137+
getDeviceClient: func() devices.Devices {
2138+
mockDeviceClient := mock_devices.NewMockDevices(gomock.NewController(t))
2139+
mockDeviceClient.EXPECT().GetLUKSDeviceForMultipathDevice(gomock.Any()).Return(mockDevicePath, nil)
2140+
mockDeviceClient.EXPECT().EnsureLUKSDeviceClosedWithMaxWaitLimit(gomock.Any(), mockDevicePath).
2141+
Return(nil)
2142+
mockDeviceClient.EXPECT().RemoveMultipathDeviceMappingWithRetries(gomock.Any(), gomock.Any(),
2143+
gomock.Any(), gomock.Any()).Return(nil)
2144+
return mockDeviceClient
2145+
},
2146+
getNodeHelper: func() nodehelpers.NodeHelper {
2147+
mockNodeHelper := mockNodeHelpers.NewMockNodeHelper(gomock.NewController(t))
2148+
mockNodeHelper.EXPECT().DeleteTrackingInfo(gomock.Any(), gomock.Any()).Return(nil)
2149+
return mockNodeHelper
2150+
},
21372151
},
21382152
"SAN: iSCSI unstage: GetDeviceInfoForLUN no devices": {
21392153
assertError: assert.NoError,

utils/devices/luks/luks.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/netapp/trident/utils/devices"
1616
"github.com/netapp/trident/utils/errors"
1717
execCmd "github.com/netapp/trident/utils/exec"
18+
"github.com/netapp/trident/utils/lsblk"
1819
)
1920

2021
const (
@@ -179,3 +180,21 @@ func GetLUKSPassphrasesFromSecretMap(secrets map[string]string) (string, string,
179180
func IsLegacyLUKSDevicePath(devicePath string) bool {
180181
return strings.Contains(devicePath, "luks")
181182
}
183+
184+
// GetDmDevicePathFromLUKSLegacyPath returns the device mapper path (e.g. /dev/dm-0) for a legacy LUKS device
185+
// path (e.g. /dev/mapper/luks-<UUID>).
186+
func GetDmDevicePathFromLUKSLegacyPath(
187+
ctx context.Context, command execCmd.Command, devicePath string,
188+
) (string, error) {
189+
if !IsLegacyLUKSDevicePath(devicePath) {
190+
return "", fmt.Errorf("device path is not a legacy LUKS device path")
191+
}
192+
lsblk := lsblk.NewLsblkUtilDetailed(command)
193+
dev, err := lsblk.GetParentDeviceKname(ctx, devicePath)
194+
if err != nil {
195+
Logc(ctx).WithFields(LogFields{
196+
"devicePath": devicePath,
197+
}).WithError(err).Error("Unable to determine luks parent device.")
198+
}
199+
return devices.DevPrefix + dev, nil
200+
}

utils/lsblk/lsblk.go

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package lsblk
2+
3+
import (
4+
"context"
5+
6+
"github.com/netapp/trident/utils/exec"
7+
)
8+
9+
type Lsblk interface {
10+
GetBlockDevices(ctx context.Context) ([]BlockDevice, error)
11+
FindParentDevice(ctx context.Context, deviceName string, devices []BlockDevice,
12+
parent *BlockDevice) (*BlockDevice, error)
13+
GetParentDeviceKname(ctx context.Context, deviceName string) (string, error)
14+
}
15+
16+
type LsblkResults struct {
17+
BlockDevices []BlockDevice `json:"blockdevices"`
18+
}
19+
20+
// BlockDevice represents a block device in the lsblk output
21+
type BlockDevice struct {
22+
Name string `json:"name"`
23+
KName string `json:"kname"`
24+
Type string `json:"type"`
25+
Size string `json:"size"`
26+
Mountpoint string `json:"mountpoint"`
27+
Children []BlockDevice `json:"children"`
28+
}
29+
30+
type LsblkUtil struct {
31+
command exec.Command
32+
}
33+
34+
func NewLsblkUtil() *LsblkUtil {
35+
return &LsblkUtil{
36+
command: exec.NewCommand(),
37+
}
38+
}
39+
40+
func NewLsblkUtilDetailed(command exec.Command) *LsblkUtil {
41+
return &LsblkUtil{
42+
command: command,
43+
}
44+
}

utils/lsblk/lsblk_darwin.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package lsblk
2+
3+
import (
4+
"context"
5+
6+
. "github.com/netapp/trident/logging"
7+
"github.com/netapp/trident/utils/errors"
8+
)
9+
10+
func (l *LsblkUtil) GetBlockDevices(ctx context.Context) ([]BlockDevice, error) {
11+
Logc(ctx).Debug(">>>> lsblk_darwin.GetBlockDevices")
12+
defer Logc(ctx).Debug("<<<< lsblk_darwin.GetBlockDevices")
13+
return nil, errors.UnsupportedError("GetBlockDevices is not supported for darwin")
14+
}
15+
16+
func (l *LsblkUtil) FindParentDevice(ctx context.Context, deviceName string, devices []BlockDevice,
17+
parent *BlockDevice) (*BlockDevice, error) {
18+
Logc(ctx).Debug(">>>> lsblk_darwin.FindParentDevice")
19+
defer Logc(ctx).Debug("<<<< lsblk_darwin.FindParentDevice")
20+
return nil, errors.UnsupportedError("FindParentDevice is not supported for darwin")
21+
}
22+
23+
func (l *LsblkUtil) GetParentDeviceKname(ctx context.Context, deviceName string) (string, error) {
24+
Logc(ctx).Debug(">>>> lsblk_darwin.GetParentDeviceKname")
25+
defer Logc(ctx).Debug("<<<< lsblk_darwin.GetParentDeviceKname")
26+
return "", errors.UnsupportedError("GetParentDeviceKname is not supported for darwin")
27+
}

utils/lsblk/lsblk_darwin_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package lsblk
2+
3+
import (
4+
"context"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
"go.uber.org/mock/gomock"
9+
10+
"github.com/netapp/trident/mocks/mock_utils/mock_exec"
11+
)
12+
13+
func TestGetBlockDevices(t *testing.T) {
14+
ctrl := gomock.NewController(t)
15+
lsblk := NewLsblkUtilDetailed(mock_exec.NewMockCommand(ctrl))
16+
devices, err := lsblk.GetBlockDevices(context.Background())
17+
assert.Nil(t, devices)
18+
assert.Error(t, err)
19+
assert.Contains(t, err.Error(), "not supported")
20+
}
21+
22+
func TestFindParentDevice(t *testing.T) {
23+
ctrl := gomock.NewController(t)
24+
lsblk := NewLsblkUtilDetailed(mock_exec.NewMockCommand(ctrl))
25+
devices, err := lsblk.FindParentDevice(context.Background(), "deviceName", nil, nil)
26+
assert.Nil(t, devices)
27+
assert.Error(t, err)
28+
assert.Contains(t, err.Error(), "not supported")
29+
30+
}
31+
func TestGetParentDeviceKname(t *testing.T) {
32+
ctrl := gomock.NewController(t)
33+
lsblk := NewLsblkUtilDetailed(mock_exec.NewMockCommand(ctrl))
34+
devices, err := lsblk.GetParentDeviceKname(context.Background(), "deviceName")
35+
assert.Empty(t, devices)
36+
assert.Error(t, err)
37+
assert.Contains(t, err.Error(), "not supported")
38+
}

utils/lsblk/lsblk_linux.go

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package lsblk
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"path/filepath"
8+
"time"
9+
)
10+
11+
// GetBlockDevices returns a list of block devices and their details
12+
func (l *LsblkUtil) GetBlockDevices(ctx context.Context) ([]BlockDevice, error) {
13+
// Run the lsblk command with --json option
14+
bytes, err := l.command.ExecuteWithTimeout(ctx, "lsblk", 10*time.Second, false, "-o",
15+
"NAME,KNAME,TYPE,SIZE,MOUNTPOINT", "--json")
16+
if err != nil {
17+
return nil, fmt.Errorf("error running lsblk: %v", err)
18+
}
19+
20+
// Parse the JSON output
21+
var results LsblkResults
22+
err = json.Unmarshal(bytes, &results)
23+
if err != nil {
24+
return nil, fmt.Errorf("error parsing lsblk json output: %v", err)
25+
}
26+
return results.BlockDevices, nil
27+
}
28+
29+
// FindParentDevice finds the parent block device for a given device name or path
30+
func (l *LsblkUtil) FindParentDevice(
31+
ctx context.Context, deviceName string, devices []BlockDevice, parent *BlockDevice,
32+
) (*BlockDevice, error) {
33+
// Recursive function to find a parent device
34+
var findParentDevice func(devices []BlockDevice, parent *BlockDevice) *BlockDevice
35+
findParentDevice = func(devices []BlockDevice, parent *BlockDevice) *BlockDevice {
36+
for _, device := range devices {
37+
if device.Name == deviceName {
38+
return parent
39+
}
40+
if len(device.Children) > 0 {
41+
found := findParentDevice(device.Children, &device)
42+
if found != nil {
43+
return found
44+
}
45+
}
46+
}
47+
return nil
48+
}
49+
50+
// Find the parent device
51+
parentDevice := findParentDevice(devices, nil)
52+
if parentDevice == nil {
53+
return nil, fmt.Errorf("parent device for '%s' not found", deviceName)
54+
} else {
55+
return parentDevice, nil
56+
}
57+
}
58+
59+
// GetParentDmDeviceKname returns the kernel name (e.g. "dm-0") of the parent device
60+
// (e.g. "/dev/mapper/luks-<UUID>", aka "dm-1") for a given device name
61+
func (l *LsblkUtil) GetParentDeviceKname(ctx context.Context, deviceName string) (string, error) {
62+
// Get just the name if a path was passed in
63+
deviceName = filepath.Base(deviceName)
64+
devices, err := l.GetBlockDevices(ctx)
65+
if err != nil {
66+
return "", err
67+
}
68+
69+
parentDevice, err := l.FindParentDevice(ctx, deviceName, devices, nil)
70+
if err != nil {
71+
return "", err
72+
}
73+
74+
return parentDevice.KName, nil
75+
}

0 commit comments

Comments
 (0)