Skip to content

Commit

Permalink
disk: call sfdisk directly to expand partition
Browse files Browse the repository at this point in the history
growpart is a shell script, and we don't have shell now in our image.

Fixes: af7a885
  • Loading branch information
huww98 committed Nov 1, 2024
1 parent 8b5a1e6 commit 1191e24
Show file tree
Hide file tree
Showing 7 changed files with 145 additions and 17 deletions.
2 changes: 1 addition & 1 deletion build/multi/Dockerfile.multi
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ LABEL maintainers="Alibaba Cloud Authors" description="Alibaba Cloud CSI Plugin"

ARG TARGETARCH
RUN --mount=type=cache,target=/var/cache/dnf,sharing=locked,id=dnf-cache-$TARGETARCH \
dnf install -y ca-certificates file tzdata nfs-utils xfsprogs e4fsprogs pciutils iputils strace cloud-utils-growpart gdisk nc telnet tar cpio lsof && \
dnf install -y ca-certificates file tzdata nfs-utils xfsprogs e4fsprogs pciutils iputils strace util-linux nc telnet tar cpio lsof && \
ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

FROM base as build-util-linux
Expand Down
2 changes: 1 addition & 1 deletion build/multi/Dockerfile.multi.asi
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ RUN --mount=type=cache,target=/root/.cache/go-build \
FROM registry.eu-west-1.aliyuncs.com/acs/alinux:3-update as base
LABEL maintainers="Alibaba Cloud Authors" description="Alibaba Cloud CSI Plugin"

RUN yum install -y ca-certificates file tzdata nfs-utils xfsprogs e4fsprogs pciutils iputils strace cloud-utils-growpart gdisk nc telnet tar cpio lsof && \
RUN yum install -y ca-certificates file tzdata nfs-utils xfsprogs e4fsprogs pciutils iputils strace util-linux nc telnet tar cpio lsof && \
yum clean all
RUN ln -sf /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && echo 'Asia/Shanghai' >/etc/timezone

Expand Down
22 changes: 7 additions & 15 deletions pkg/disk/nodeserver.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,14 +17,12 @@ limitations under the License.
package disk

import (
"bytes"
"context"
"crypto/sha256"
"errors"
"fmt"
"io/fs"
"os"
"os/exec"
"path/filepath"
"regexp"
"strconv"
Expand All @@ -34,6 +32,7 @@ import (
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/cloud/metadata"
"github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/common"
"github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/disk/sfdisk"
"github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/features"
"github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/utils"
utilsio "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/utils/io"
Expand Down Expand Up @@ -974,29 +973,22 @@ func localExpandVolume(ctx context.Context, req *csi.NodeExpandVolumeRequest) (*
}
return nil, status.Errorf(codes.Internal, "NodeExpandVolume: VolumeId: %s, get device name error: %s", req.VolumeId, err.Error())
}
logger := klog.FromContext(ctx).WithValues("device", devicePath)
ctx = klog.NewContext(ctx, logger)

klog.Infof("NodeExpandVolume:: volumeId: %s, devicePath: %s, volumePath: %s", diskID, devicePath, volumePath)
rootPath, index, err := DefaultDeviceManager.GetDeviceRootAndPartitionIndex(devicePath)
if err != nil {
return nil, status.Errorf(codes.Internal, "GetDeviceRootAndIndex(%s) failed: %v", diskID, err)
}
if index != "" {
output, err := exec.Command("growpart", rootPath, index).CombinedOutput()
err := sfdisk.ExpandPartition(ctx, rootPath, index)
if err != nil {
if bytes.Contains(output, []byte("NOCHANGE")) {
if bytes.Contains(output, []byte("it cannot be grown")) || bytes.Contains(output, []byte("could only be grown by")) {
deviceCapacity := getBlockDeviceCapacity(devicePath)
rootCapacity := getBlockDeviceCapacity(rootPath)
klog.Infof("NodeExpandVolume: Volume %s with Device Partition %s no need to grown, with request: %v, root: %v, partition: %v",
diskID, devicePath, DiskSize{requestBytes}, DiskSize{rootCapacity}, DiskSize{deviceCapacity})
return &csi.NodeExpandVolumeResponse{}, nil
}
}
return nil, status.Errorf(codes.InvalidArgument, "NodeExpandVolume: expand volume %s at %s %s failed: %s, with output %s", diskID, rootPath, index, err.Error(), string(output))
return nil, status.Error(codes.Internal, err.Error())
}
klog.Infof("NodeExpandVolume: Successful expand partition for volume: %s device: %s partition: %s", diskID, rootPath, index)
logger.V(2).Info("Successful expand partition", "root", rootPath, "partition", index)
}

klog.V(2).Info("Expand filesystem start", "volumePath", volumePath)
// use resizer to expand volume filesystem
r := k8smount.NewResizeFs(utilexec.New())
ok, err := r.Resize(devicePath, volumePath)
Expand Down
53 changes: 53 additions & 0 deletions pkg/disk/sfdisk/expand.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package sfdisk

import (
"bytes"
"context"
"fmt"
"os/exec"

utilsos "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/utils/os"

"golang.org/x/sys/unix"
"k8s.io/klog/v2"
)

func ExpandPartition(ctx context.Context, disk, partition string) error {
logger := klog.FromContext(ctx)
fd, err := unix.Open(disk, unix.O_RDONLY, 0)
if err != nil {
return err
}
defer func() {
if err := unix.Close(fd); err != nil {
logger.Error(err, "failed to close", "fd", fd)
}
}()

err = unix.Flock(fd, unix.LOCK_EX) // as suggested in the man sfdisk(8)
if err != nil {
return fmt.Errorf("failed to lock %s exclusively: %v", disk, err)
}
defer func() {
if err := unix.Flock(fd, unix.LOCK_UN); err != nil {
logger.Error(err, "failed to unlock", "fd", fd)
}
}()

dump, err := exec.CommandContext(ctx, "sfdisk", "--dump", disk).Output()
if err != nil {
return fmt.Errorf("failed to dump current partition table of %s: %v", disk, utilsos.ErrWithStderr(err))
}
dumpStr := string(dump)
logger.V(4).Info("sfdisk dump before expansion", "dump", dumpStr)

// Don't cancel this, we don't want to corrupt the partition table
cmd := exec.Command("sfdisk", disk, "-N", partition)
cmd.Stdin = bytes.NewReader([]byte(",+")) // enlarge the partition as much as possible
result, err := cmd.Output()
if err != nil {
return fmt.Errorf("failed to expand partition %s on %s: %v\noriginal table looked like:\n%s", partition, disk, utilsos.ErrWithStderr(err), dumpStr)
}
logger.V(3).Info("sfdisk success", "output", string(result))
return nil
}
41 changes: 41 additions & 0 deletions pkg/disk/sfdisk/expand_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package sfdisk

import (
"bytes"
"context"
"errors"
"os"
"os/exec"
"strings"
"testing"

utilsos "github.com/kubernetes-sigs/alibaba-cloud-csi-driver/pkg/utils/os"
"github.com/stretchr/testify/assert"
)

func TestExpandPartition(t *testing.T) {
path, err := exec.LookPath("sfdisk")
if errors.Is(err, exec.ErrNotFound) {
t.Skip("sfdisk not found")
}
assert.NoError(t, err)
t.Logf("sfdisk found at: %s", path)

testImage := t.TempDir() + "/test.img"
_, err = os.Create(testImage)
assert.NoError(t, err)
assert.NoError(t, os.Truncate(testImage, 1<<23)) // 8MB

cmd := exec.Command("sfdisk", testImage)
cmd.Stdin = bytes.NewReader([]byte("label: gpt\n,\n")) // create a single partition
result, err := cmd.Output()
assert.NoError(t, utilsos.ErrWithStderr(err))
t.Logf("create partition success: %s", string(result))

assert.NoError(t, os.Truncate(testImage, 1<<24)) // expand to 16MB
assert.NoError(t, ExpandPartition(context.Background(), testImage, "1"))

dump, err := exec.Command("sfdisk", "--dump", testImage).Output()
assert.NoError(t, utilsos.ErrWithStderr(err))
assert.Contains(t, strings.ReplaceAll(string(dump), " ", ""), "test.img1:start=2048,size=30687")
}
23 changes: 23 additions & 0 deletions pkg/utils/os/exec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package os

import (
"bytes"
"errors"
"os/exec"
)

type ExitErrorWithStderr struct {
*exec.ExitError
}

func (err ExitErrorWithStderr) Error() string {
return err.ExitError.Error() + ", with stderr: " + string(bytes.TrimSpace(err.Stderr))
}

func ErrWithStderr(err error) error {
var exitErr *exec.ExitError
if errors.As(err, &exitErr) && len(exitErr.Stderr) > 0 {
return ExitErrorWithStderr{exitErr}
}
return err
}
19 changes: 19 additions & 0 deletions pkg/utils/os/exec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package os

import (
"os/exec"
"testing"

"github.com/stretchr/testify/assert"
)

func TestErrWithStderr(t *testing.T) {
_, err := exec.Command("sh", "-c", "echo 'test error' 1>&2; exit 2").Output()
assert.Error(t, err)
assert.EqualError(t, ErrWithStderr(err), "exit status 2, with stderr: test error")
}

func TestErrWithStderrNoChange(t *testing.T) {
err := exec.ErrNotFound
assert.Equal(t, ErrWithStderr(err), err)
}

0 comments on commit 1191e24

Please sign in to comment.