Skip to content
Merged
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
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ jobs:
uses: golangci/golangci-lint-action@v6
with:
version: latest
verify: false
- run: make build
- run: go test -cover ./... -coverprofile ./coverage.out
- name: Upload coverage reports to Codecov
Expand Down Expand Up @@ -83,15 +84,15 @@ jobs:
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
go-version-file: "go.mod"
check-latest: true

- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

- name: Install devbox
uses: jetify-com/devbox-install-action@v0.12.0

Expand Down
7 changes: 4 additions & 3 deletions internal/driver/controllerserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -409,7 +409,8 @@ func (cs *ControllerServer) ListVolumes(ctx context.Context, req *csi.ListVolume
NextToken: nextToken,
}

log.V(2).Info("Volumes listed", "response", resp)
log.V(2).Info("Volumes listed")
log.V(6).Info("Volumes listed", "response", resp)
return resp, nil
}

Expand Down Expand Up @@ -458,7 +459,7 @@ func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi
log.V(4).Info("Checking if volume exists", "volume_id", volumeID)
vol, err := cs.client.GetVolume(ctx, volumeID)
if err != nil {
return resp, errInternal("get volume: %v", err)
return resp, errNotFound("get volume: %v", err)
}

// Is the caller trying to resize the volume to be smaller than it currently is?
Expand All @@ -483,7 +484,7 @@ func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi
log.V(2).Info("Volume resized successfully", "volume_id", volumeID)
resp = &csi.ControllerExpandVolumeResponse{
CapacityBytes: size,
NodeExpansionRequired: false,
NodeExpansionRequired: true,
}
return resp, nil
}
Expand Down
23 changes: 21 additions & 2 deletions internal/driver/controllerserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -424,7 +424,7 @@ func TestControllerExpandVolume(t *testing.T) {
expectedError error
}{
{
name: "expandvolume",
name: "expandvolumeInUse",
req: &csi.ControllerExpandVolumeRequest{
VolumeId: "1003",
CapacityRange: &csi.CapacityRange{
Expand All @@ -438,7 +438,26 @@ func TestControllerExpandVolume(t *testing.T) {
expectLinodeClientCalls: func(m *mocks.MockLinodeClient) {
m.EXPECT().GetVolume(gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil)
m.EXPECT().ResizeVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
m.EXPECT().WaitForVolumeStatus(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, LinodeID: createLinodeID(1003), Size: 10, Status: linodego.VolumeActive}, nil)
m.EXPECT().WaitForVolumeStatus(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, Size: 10, Status: linodego.VolumeActive}, nil)
},
expectedError: nil,
},
{
name: "expandvolume",
req: &csi.ControllerExpandVolumeRequest{
VolumeId: "1003",
CapacityRange: &csi.CapacityRange{
LimitBytes: 20 << 30, // 20 GiB
},
},
resp: &csi.ControllerExpandVolumeResponse{
CapacityBytes: 10 << 30,
NodeExpansionRequired: false,
},
expectLinodeClientCalls: func(m *mocks.MockLinodeClient) {
m.EXPECT().GetVolume(gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, Size: 10, Status: linodego.VolumeActive}, nil)
m.EXPECT().ResizeVolume(gomock.Any(), gomock.Any(), gomock.Any()).Return(nil)
m.EXPECT().WaitForVolumeStatus(gomock.Any(), gomock.Any(), gomock.Any(), gomock.Any()).Return(&linodego.Volume{ID: 1001, Size: 10, Status: linodego.VolumeActive}, nil)
},
expectedError: nil,
},
Expand Down
26 changes: 26 additions & 0 deletions internal/driver/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,32 @@ func (ns *NodeServer) NodeExpandVolume(ctx context.Context, req *csi.NodeExpandV
return nil, err
}

// Check if size in API is different from actual size
// it means the volume has been resized offline
linodeVolumeID, err := linodevolumes.VolumeIdAsInt("NodeExpandVolume", req)
if err != nil {
observability.RecordMetrics(observability.NodeExpandTotal, observability.NodeExpandDuration, observability.Failed, functionStartTime)
return nil, errInternal("failed to get volume id: %v", err)
}

volume, err := ns.client.GetVolume(ctx, linodeVolumeID)
if err != nil {
observability.RecordMetrics(observability.NodeExpandTotal, observability.NodeExpandDuration, observability.Failed, functionStartTime)
return nil, errInternal("failed to get volume %d: %v", volume.ID, err)
}

diskSize, err := ns.getDeviceSize(devicePath)
if err != nil {
observability.RecordMetrics(observability.NodeExpandTotal, observability.NodeExpandDuration, observability.Failed, functionStartTime)
return nil, errInternal("failed to get device size: %v", err)
}

const GiB uint64 = 1 << 30
if uint64(volume.Size)*GiB != diskSize {
log.V(4).Info("Volume size is different from disk siz", "volumeID", volumeID, "volumeSize", volume.Size, "diskSize", diskSize)
return nil, status.Error(codes.FailedPrecondition, "volume has been resized, but volume has not been detach / attach")
}

// Resize the volume

resized, err := ns.resize(devicePath, volumePath)
Expand Down
16 changes: 16 additions & 0 deletions internal/driver/nodeserver_helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"

"github.com/container-storage-interface/spec/lib/go/csi"
Expand Down Expand Up @@ -415,3 +416,18 @@ func (ns *NodeServer) resize(devicePath, volumePath string) (bool, error) {

return needResize, nil
}

// getDeviceSize returns the size of the device at the given path.
// size is returned in bytes.
func (ns *NodeServer) getDeviceSize(devicePath string) (uint64, error) {
output, err := ns.mounter.Exec.Command("blockdev", "--getsize64", devicePath).CombinedOutput()
outStr := strings.TrimSpace(string(output))
if err != nil {
return 0, fmt.Errorf("failed to read size of device %s: %w: %s", devicePath, err, outStr)
}
size, err := strconv.ParseUint(outStr, 10, 64)
if err != nil {
return 0, fmt.Errorf("failed to parse size of device %s %s: %w", devicePath, outStr, err)
}
return size, nil
}
26 changes: 26 additions & 0 deletions internal/driver/nodeserver_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -478,6 +478,7 @@ func TestNodeExpandVolume(t *testing.T) {
expectLinodeClientCalls func(m *mocks.MockLinodeClient)
expectFormatCalls func(m *mocks.MockFormater)
expectResizeFsCall func(m *mocks.MockResizeFSer)
expectExecCalls func(m *mocks.MockExecutor, ctrl *gomock.Controller)
expectedError error
}{
{
Expand All @@ -496,6 +497,17 @@ func TestNodeExpandVolume(t *testing.T) {
m.EXPECT().Glob("/dev/sd*").Return([]string{"/dev/sda", "/dev/sdb"}, nil).AnyTimes()
m.EXPECT().Stat("/dev/disk/by-id/linode-volkey").Return(nil, nil)
},
expectLinodeClientCalls: func(m *mocks.MockLinodeClient) {
m.EXPECT().GetVolume(gomock.Any(), 1001).Return(&linodego.Volume{
ID: 1001,
Size: 10,
}, nil)
},
expectExecCalls: func(m *mocks.MockExecutor, ctrl *gomock.Controller) {
command := mocks.NewMockCommand(ctrl)
m.EXPECT().Command("blockdev", "--getsize64", "/dev/disk/by-id/linode-volkey").Return(command)
command.EXPECT().CombinedOutput().Return([]byte("10737418240"), nil)
},
expectResizeFsCall: func(m *mocks.MockResizeFSer) {
m.EXPECT().NeedResize("/dev/disk/by-id/linode-volkey", "/mnt/staging").Return(true, nil)
m.EXPECT().Resize("/dev/disk/by-id/linode-volkey", "/mnt/staging").Return(true, nil)
Expand Down Expand Up @@ -526,6 +538,17 @@ func TestNodeExpandVolume(t *testing.T) {
m.EXPECT().Glob("/dev/sd*").Return([]string{"/dev/sda", "/dev/sdb"}, nil).AnyTimes()
m.EXPECT().Stat("/dev/disk/by-id/linode-volkey").Return(nil, nil)
},
expectLinodeClientCalls: func(m *mocks.MockLinodeClient) {
m.EXPECT().GetVolume(gomock.Any(), 1001).Return(&linodego.Volume{
ID: 1001,
Size: 10,
}, nil)
},
expectExecCalls: func(m *mocks.MockExecutor, ctrl *gomock.Controller) {
command := mocks.NewMockCommand(ctrl)
m.EXPECT().Command("blockdev", "--getsize64", "/dev/disk/by-id/linode-volkey").Return(command)
command.EXPECT().CombinedOutput().Return([]byte("10737418240"), nil)
},
expectResizeFsCall: func(m *mocks.MockResizeFSer) {
m.EXPECT().NeedResize("/dev/disk/by-id/linode-volkey", "/mnt/staging").Return(true, nil)
m.EXPECT().Resize("/dev/disk/by-id/linode-volkey", "/mnt/staging").Return(true, nil)
Expand Down Expand Up @@ -631,6 +654,9 @@ func TestNodeExpandVolume(t *testing.T) {
if tt.expectResizeFsCall != nil {
tt.expectResizeFsCall(mockResizeFS)
}
if tt.expectExecCalls != nil {
tt.expectExecCalls(mockExec, ctrl)
}
ns := &NodeServer{
driver: &LinodeDriver{},
mounter: &mountmanager.SafeFormatAndMount{
Expand Down
15 changes: 14 additions & 1 deletion tests/e2e/test/pod-pvc-unexpected-reboot/chainsaw-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ metadata:
all:
basic:
spec:
description:
This test validates the behavior of a Pod with PVC when a node is unexpectedly rebooted.
1. Create a Pod with PVC.
2. Validate the Pod is running.
3. Reboot the node of the Pod.
4. Validate the Pod is running after the reboot.
skip: true
concurrent: false
bindings:
- name: nodes
Expand Down Expand Up @@ -129,7 +136,7 @@ spec:
- wait:
apiVersion: v1
kind: Node
timeout: 120s
timeout: 5m
name: ($nodeName)
for:
condition:
Expand Down Expand Up @@ -171,3 +178,9 @@ spec:
- describe:
apiVersion: v1
kind: Pod
name: e2e-pod
namespace: ($namespace)
- describe:
apiVersion: v1
kind: PersistentVolumeClaim
namespace: ($namespace)
44 changes: 29 additions & 15 deletions tests/e2e/test/sts-pvc-unexpected-reboot/chainsaw-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,14 @@ metadata:
all:
basic:
spec:
description: |
This test validates the behavior of a StatefulSet with PVCs when a node is unexpectedly rebooted.
1. Create a StatefulSet with PVCs.
2. Validate the StatefulSet is running.
3. Reboot the node of the StatefulSet.
4. Validate the StatefulSet is running after the reboot.
concurrent: false
skip: true
bindings:
- name: nodes
# number of nodes in cluster
Expand Down Expand Up @@ -74,6 +81,19 @@ spec:
value: ($namespace)
content: |
kubectl debug -n $NAMESPACE node/$NODE_NAME --profile=sysadmin --image=busybox -- chroot /host/ reboot --force
- assert:
timeout: 5m
resource:
apiVersion: v1
kind: Event
reason: NodeNotReady
source:
component: node-controller
involvedObject:
apiVersion: v1
kind: Pod
name: redis-test-0
namespace: ($namespace)
- patch:
# force sts on another node with nodeAffinity
resource:
Expand All @@ -93,22 +113,10 @@ spec:
operator: NotIn
values:
- ($nodeName)
- assert:
resource:
apiVersion: v1
kind: Event
reason: NodeNotReady
source:
component: node-controller
involvedObject:
apiVersion: v1
kind: Pod
name: redis-test-0
namespace: ($namespace)
- wait:
apiVersion: v1
kind: Node
timeout: 120s
timeout: 5m
name: ($nodeName)
for:
condition:
Expand Down Expand Up @@ -148,5 +156,11 @@ spec:
(contains($stdout, 'testfile')): true
catch:
- describe:
apiVersion: apps/v1
kind: StatefulSet
apiVersion: v1
kind: Pod
name: redis-test-0
namespace: ($namespace)
- describe:
apiVersion: v1
kind: PersistentVolumeClaim
namespace: ($namespace)
Loading