Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 21 additions & 21 deletions internal/driver/node_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,14 @@ type nodeServiceTestEnv struct {
volumeResizeService *mock.VolumeResizeService
}

func newNodeServerTestEnv() nodeServiceTestEnv {
func newNodeServerTestEnv(t *testing.T) nodeServiceTestEnv {
var (
volumeMountService = &mock.VolumeMountService{}
volumeResizeService = &mock.VolumeResizeService{}
volumeStatsService = &mock.VolumeStatsService{}
)
return nodeServiceTestEnv{
ctx: context.Background(),
ctx: t.Context(),
service: NewNodeService(
slog.New(slog.DiscardHandler),
"1",
Expand All @@ -46,9 +46,9 @@ func newNodeServerTestEnv() nodeServiceTestEnv {
}

func TestNodeServiceNodePublishVolume(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.PublishFunc = func(ctx context.Context, targetPath string, devicePath string, opts volumes.MountOpts) error {
env.volumeMountService.PublishFunc = func(_ context.Context, targetPath string, devicePath string, opts volumes.MountOpts) error {
if targetPath != "target" {
t.Errorf("unexpected target path passed to volume service: %s", targetPath)
}
Expand Down Expand Up @@ -82,10 +82,10 @@ func TestNodeServiceNodePublishVolume(t *testing.T) {
}

func TestNodeServiceNodePublishBlockVolume(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.PublishFunc = func(
ctx context.Context, targetPath, devicePath string, opts volumes.MountOpts,
_ context.Context, targetPath, devicePath string, opts volumes.MountOpts,
) error {
if targetPath != "target" {
t.Errorf("unexpected target path: %s", targetPath)
Expand Down Expand Up @@ -115,9 +115,9 @@ func TestNodeServiceNodePublishBlockVolume(t *testing.T) {
}

func TestNodeServiceNodePublishPublishError(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.PublishFunc = func(ctx context.Context, targetPath string, devicePath string, opts volumes.MountOpts) error {
env.volumeMountService.PublishFunc = func(_ context.Context, targetPath string, devicePath string, opts volumes.MountOpts) error {
return io.EOF
}

Expand Down Expand Up @@ -145,7 +145,7 @@ func TestNodeServiceNodePublishPublishError(t *testing.T) {
}

func TestNodeServiceNodePublishVolumeInputErrors(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

testCases := []struct {
Name string
Expand Down Expand Up @@ -214,9 +214,9 @@ func TestNodeServiceNodePublishVolumeInputErrors(t *testing.T) {
}

func TestNodeServiceNodeUnpublishVolume(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.UnpublishFunc = func(ctx context.Context, targetPath string) error {
env.volumeMountService.UnpublishFunc = func(_ context.Context, targetPath string) error {
if targetPath != "target" {
t.Errorf("unexpected target path passed to volume service: %s", targetPath)
}
Expand All @@ -233,9 +233,9 @@ func TestNodeServiceNodeUnpublishVolume(t *testing.T) {
}

func TestNodeServiceNodeUnpublishUnpublishError(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.UnpublishFunc = func(ctx context.Context, targetPath string) error {
env.volumeMountService.UnpublishFunc = func(_ context.Context, targetPath string) error {
return io.EOF
}

Expand All @@ -249,7 +249,7 @@ func TestNodeServiceNodeUnpublishUnpublishError(t *testing.T) {
}

func TestNodeServiceNodeUnpublishVolumeInputErrors(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

testCases := []struct {
Name string
Expand Down Expand Up @@ -283,7 +283,7 @@ func TestNodeServiceNodeUnpublishVolumeInputErrors(t *testing.T) {
}

func TestNodeServiceNodeGetCapabilities(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

req := &proto.NodeGetCapabilitiesRequest{}
resp, err := env.service.NodeGetCapabilities(env.ctx, req)
Expand Down Expand Up @@ -312,7 +312,7 @@ func TestNodeServiceNodeGetCapabilities(t *testing.T) {
}

func TestNodeServiceNodeGetInfo(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

req := &proto.NodeGetInfoRequest{}
resp, err := env.service.NodeGetInfo(env.ctx, req)
Expand All @@ -328,15 +328,15 @@ func TestNodeServiceNodeGetInfo(t *testing.T) {
}

func TestNodeServiceNodeExpandVolume(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.PathExistsFunc = func(path string) (bool, error) {
if path != "volumePath" {
t.Errorf("unexpected volume path passed to volume mount service: %s", path)
}
return true, nil
}
env.volumeResizeService.ResizeFunc = func(ctx context.Context, volumePath string) error {
env.volumeResizeService.ResizeFunc = func(_ context.Context, volumePath string) error {
if volumePath != "volumePath" {
t.Errorf("unexpected volume path passed to volume service: %s", volumePath)
}
Expand All @@ -353,15 +353,15 @@ func TestNodeServiceNodeExpandVolume(t *testing.T) {
}

func TestNodeServiceNodeExpandBlockVolume(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

env.volumeMountService.PathExistsFunc = func(path string) (bool, error) {
if path != "volumePath" {
t.Errorf("unexpected volume path passed to volume mount service: %s", path)
}
return true, nil
}
env.volumeResizeService.ResizeFunc = func(ctx context.Context, volumePath string) error {
env.volumeResizeService.ResizeFunc = func(_ context.Context, volumePath string) error {
t.Errorf("This function should never be called.")
return nil
}
Expand All @@ -382,7 +382,7 @@ func TestNodeServiceNodeExpandBlockVolume(t *testing.T) {
}

func TestNodeServiceNodeExpandVolumeInputErrors(t *testing.T) {
env := newNodeServerTestEnv()
env := newNodeServerTestEnv(t)

testCases := []struct {
Name string
Expand Down
95 changes: 76 additions & 19 deletions internal/volumes/cryptsetup.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"strings"
)

const cryptsetupExecuable = "cryptsetup"
const cryptsetupExecutable = "cryptsetup"

type CryptSetup struct {
logger *slog.Logger
Expand All @@ -19,62 +19,85 @@ func NewCryptSetup(logger *slog.Logger) *CryptSetup {
return &CryptSetup{logger: logger}
}

func (cs *CryptSetup) IsActive(ctx context.Context, luksDeviceName string) (bool, error) {
output, code, err := command(ctx, cryptsetupExecuable, "status", luksDeviceName)
func (cs *CryptSetup) Status(ctx context.Context, device string) (LUKSDeviceStatus, error) {
output, code, err := cryptsetup(ctx, "status", device)
if err != nil {
if code == 4 {
return false, nil
return LUKSDeviceStatus{Active: false}, nil
}
return false, fmt.Errorf("unable to check LUKS device %s activity: %s", luksDeviceName, output)
return LUKSDeviceStatus{Active: false}, fmt.Errorf("unable to check LUKS device %s status: %w", device, err)
}
return true, nil

return NewLUKSDeviceStatus(output), nil
}

func (cs *CryptSetup) Format(ctx context.Context, devicePath string, passphrase string) error {
cs.logger.Info(
"formatting LUKS device",
"devicePath", devicePath,
)
output, _, err := commandWithStdin(ctx, passphrase, cryptsetupExecuable, "luksFormat", "--type", "luks1", devicePath)
output, _, err := cryptsetupWithStdin(ctx, passphrase, "luksFormat", "--type", "luks1", devicePath)
if err != nil {
return fmt.Errorf("unable to format device %s with LUKS: %s", devicePath, output)
}
return nil
}

func (cs *CryptSetup) Open(ctx context.Context, devicePath string, luksDeviceName string, passphrase string) error {
active, err := cs.IsActive(ctx, luksDeviceName)
status, err := cs.Status(ctx, luksDeviceName)
if err != nil {
return err
}
if active {
return nil
if status.Active {
if !status.IsZombie() {
cs.logger.Debug(
"luks device already active",
"devicePath", devicePath,
"luksDeviceName", luksDeviceName,
)
return nil
}

// If the volume is not correctly unpublished, a prior attach might leave a dm-crypt
// target whose backing block device disappeared. Reusing this mapper would mount
// on a dead device so we have to close it first.
cs.logger.Warn(
"detected zombie luks device; cleaning up",
"devicePath", devicePath,
"luksDeviceName", luksDeviceName,
"type", status.Type,
"device", status.Device,
)
if err := cs.Close(ctx, luksDeviceName); err != nil {
return fmt.Errorf("failed to close zombie LUKS device %s: %w", luksDeviceName, err)
}
}

cs.logger.Info(
"opening LUKS device",
"devicePath", devicePath,
"luksDeviceName", luksDeviceName,
)
output, _, err := commandWithStdin(ctx, passphrase, cryptsetupExecuable, "luksOpen", "--allow-discards", devicePath, luksDeviceName)
output, _, err := cryptsetupWithStdin(ctx, passphrase, "luksOpen", "--allow-discards", devicePath, luksDeviceName)
if err != nil {
return fmt.Errorf("unable to open LUKS device %s: %s", devicePath, output)
}
return nil
}

func (cs *CryptSetup) Close(ctx context.Context, luksDeviceName string) error {
active, err := cs.IsActive(ctx, luksDeviceName)
status, err := cs.Status(ctx, luksDeviceName)
if err != nil {
return err
}
if !active {
if !status.Active {
return nil
}
cs.logger.Info(
"closing LUKS device",
"luksDeviceName", luksDeviceName,
)
output, _, err := command(ctx, cryptsetupExecuable, "luksClose", luksDeviceName)
output, _, err := cryptsetup(ctx, "luksClose", luksDeviceName)
if err != nil {
return fmt.Errorf("unable to close LUKS device %s: %s", luksDeviceName, output)
}
Expand All @@ -86,7 +109,7 @@ func (cs *CryptSetup) Resize(ctx context.Context, luksDeviceName string) error {
"resizing LUKS device",
"luksDeviceName", luksDeviceName,
)
output, _, err := command(ctx, cryptsetupExecuable, "resize", luksDeviceName)
output, _, err := cryptsetup(ctx, "resize", luksDeviceName)
if err != nil {
return fmt.Errorf("unable to resize LUKS device %s: %s", luksDeviceName, output)
}
Expand All @@ -102,12 +125,12 @@ func GenerateLUKSDevicePath(luksDeviceName string) string {
return "/dev/mapper/" + luksDeviceName
}

func command(ctx context.Context, name string, args ...string) (string, int, error) {
return commandWithStdin(ctx, "", name, args...)
func cryptsetup(ctx context.Context, args ...string) (string, int, error) {
return cryptsetupWithStdin(ctx, "", args...)
}

func commandWithStdin(ctx context.Context, stdin string, name string, args ...string) (string, int, error) {
cmd := exec.CommandContext(ctx, name, args...)
func cryptsetupWithStdin(ctx context.Context, stdin string, args ...string) (string, int, error) {
cmd := exec.CommandContext(ctx, cryptsetupExecutable, args...)
if stdin != "" {
cmd.Stdin = strings.NewReader(stdin)
}
Expand All @@ -122,3 +145,37 @@ func commandWithStdin(ctx context.Context, stdin string, name string, args ...st
}
return output, 0, nil
}

type LUKSDeviceStatus struct {
Device string
Type string
Active bool
}

func NewLUKSDeviceStatus(output string) LUKSDeviceStatus {
// Assumes the device is active; a non-active device is
// reported through the exit code, not the output.
status := LUKSDeviceStatus{Active: true}
for line := range strings.SplitSeq(output, "\n") {
key, value, ok := strings.Cut(strings.TrimSpace(line), ":")
if !ok {
continue
}
value = strings.TrimSpace(value)
switch key {
case "type":
status.Type = value
case "device":
status.Device = value
}
}

return status
}

func (s LUKSDeviceStatus) IsZombie() bool {
if !s.Active {
return false
}
return s.Device == "" || s.Type == "" || s.Device == "(null)" || s.Type == "n/a"
}
Loading
Loading