Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

disk: call sfdisk directly to expand partition #1192

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
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)
}