diff --git a/csi/deploy/009-storage-class.yaml b/csi/deploy/009-storage-class.yaml index 77749766..0f1db325 100644 --- a/csi/deploy/009-storage-class.yaml +++ b/csi/deploy/009-storage-class.yaml @@ -17,3 +17,4 @@ parameters: # multiple etcdUrls may be specified, delimited by comma #etcdUrl: "http://192.168.7.2:2379" #etcdPrefix: "/vitastor" +allowVolumeExpansion: true diff --git a/csi/src/controllerserver.go b/csi/src/controllerserver.go index cff2082d..cfff358a 100644 --- a/csi/src/controllerserver.go +++ b/csi/src/controllerserver.go @@ -361,6 +361,7 @@ func (cs *ControllerServer) ControllerGetCapabilities(ctx context.Context, req * for _, capability := range []csi.ControllerServiceCapability_RPC_Type{ csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME, csi.ControllerServiceCapability_RPC_LIST_VOLUMES, + csi.ControllerServiceCapability_RPC_EXPAND_VOLUME, csi.ControllerServiceCapability_RPC_CREATE_DELETE_SNAPSHOT, csi.ControllerServiceCapability_RPC_LIST_SNAPSHOTS, // TODO: csi.ControllerServiceCapability_RPC_CLONE_VOLUME, @@ -534,10 +535,53 @@ func (cs *ControllerServer) ListSnapshots(ctx context.Context, req *csi.ListSnap return resp, nil } -// ControllerExpandVolume resizes a volume +// ControllerExpandVolume increases the size of a volume func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) { - return nil, status.Error(codes.Unimplemented, "") + klog.Infof("received controller expand volume request %+v", protosanitizer.StripSecrets(req)) + if (req == nil) + { + return nil, status.Error(codes.InvalidArgument, "request cannot be empty") + } + if (req.VolumeId == "" || req.CapacityRange == nil || req.CapacityRange.RequiredBytes == 0) + { + return nil, status.Error(codes.InvalidArgument, "VolumeId, CapacityRange and RequiredBytes are required fields") + } + + volVars := make(map[string]string) + err := json.Unmarshal([]byte(req.VolumeId), &volVars) + if (err != nil) + { + return nil, status.Error(codes.Internal, "volume ID not in JSON format") + } + volName := volVars["name"] + ctxVars, _, _ := GetConnectionParams(volVars) + + inodeCfg, err := invokeList(ctxVars, volName, true) + if (err != nil) + { + return nil, err + } + + if (req.CapacityRange.RequiredBytes > 0 && inodeCfg[0].Size < uint64(req.CapacityRange.RequiredBytes)) + { + sz := ((req.CapacityRange.RequiredBytes+4095)/4096)*4096 + _, err := invokeCLI(ctxVars, []string{ "modify", "--inc_size", "1", "--resize", fmt.Sprintf("%d", sz), volName }) + if (err != nil) + { + return nil, err + } + inodeCfg, err = invokeList(ctxVars, volName, true) + if (err != nil) + { + return nil, err + } + } + + return &csi.ControllerExpandVolumeResponse{ + CapacityBytes: int64(inodeCfg[0].Size), + NodeExpansionRequired: false, + }, nil } // ControllerGetVolume get volume info diff --git a/csi/src/identityserver.go b/csi/src/identityserver.go index b1050411..dbe2bc6a 100644 --- a/csi/src/identityserver.go +++ b/csi/src/identityserver.go @@ -49,6 +49,13 @@ func (is *IdentityServer) GetPluginCapabilities(ctx context.Context, req *csi.Ge }, }, }, + { + Type: &csi.PluginCapability_VolumeExpansion_{ + VolumeExpansion: &csi.PluginCapability_VolumeExpansion{ + Type: csi.PluginCapability_VolumeExpansion_OFFLINE, + }, + }, + }, }, }, nil } diff --git a/csi/src/nodeserver.go b/csi/src/nodeserver.go index 083a4e55..57d523c4 100644 --- a/csi/src/nodeserver.go +++ b/csi/src/nodeserver.go @@ -70,10 +70,10 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis isBlock := req.GetVolumeCapability().GetBlock() != nil // Check that it's not already mounted - _, error := mount.IsNotMountPoint(ns.mounter, targetPath) - if (error != nil) + _, err := mount.IsNotMountPoint(ns.mounter, targetPath) + if (err != nil) { - if (os.IsNotExist(error)) + if (os.IsNotExist(err)) { if (isBlock) { @@ -102,12 +102,12 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis } else { - return nil, status.Error(codes.Internal, error.Error()) + return nil, status.Error(codes.Internal, err.Error()) } } ctxVars := make(map[string]string) - err := json.Unmarshal([]byte(req.VolumeId), &ctxVars) + err = json.Unmarshal([]byte(req.VolumeId), &ctxVars) if (err != nil) { return nil, status.Error(codes.Internal, "volume ID not in JSON format") @@ -147,70 +147,74 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis } devicePath := strings.TrimSpace(stdoutStr) - // Check existing format diskMounter := &mount.SafeFormatAndMount{Interface: ns.mounter, Exec: utilexec.New()} - existingFormat, err := diskMounter.GetDiskFormat(devicePath) - if (err != nil) - { - klog.Errorf("failed to get disk format for path %s, error: %v", err) - // unmap NBD device - unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() - if (unmapErr != nil) - { - klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) - } - return nil, err - } - - // Format the device (ext4 or xfs) - fsType := req.GetVolumeCapability().GetMount().GetFsType() - opt := req.GetVolumeCapability().GetMount().GetMountFlags() - opt = append(opt, "_netdev") - if ((req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY || - req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY) && - !Contains(opt, "ro")) - { - opt = append(opt, "ro") - } - if (fsType == "xfs") - { - opt = append(opt, "nouuid") - } - readOnly := Contains(opt, "ro") - if (existingFormat == "" && !readOnly) - { - args := []string{} - switch fsType - { - case "ext4": - args = []string{"-m0", "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1", devicePath} - case "xfs": - args = []string{"-K", devicePath} - } - if (len(args) > 0) - { - cmdOut, cmdErr := diskMounter.Exec.Command("mkfs."+fsType, args...).CombinedOutput() - if (cmdErr != nil) - { - klog.Errorf("failed to run mkfs error: %v, output: %v", cmdErr, string(cmdOut)) - // unmap NBD device - unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() - if (unmapErr != nil) - { - klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) - } - return nil, status.Error(codes.Internal, cmdErr.Error()) - } - } - } if (isBlock) { - opt = append(opt, "bind") - err = diskMounter.Mount(devicePath, targetPath, fsType, opt) + err = diskMounter.Mount(devicePath, targetPath, "", []string{"bind"}) } else { + // Check existing format + existingFormat, err := diskMounter.GetDiskFormat(devicePath) + if (err != nil) + { + klog.Errorf("failed to get disk format for path %s, error: %v", err) + goto unmap + } + + // Format the device (ext4 or xfs) + fsType := req.GetVolumeCapability().GetMount().GetFsType() + opt := req.GetVolumeCapability().GetMount().GetMountFlags() + opt = append(opt, "_netdev") + if ((req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_MULTI_NODE_READER_ONLY || + req.VolumeCapability.AccessMode.Mode == csi.VolumeCapability_AccessMode_SINGLE_NODE_READER_ONLY) && + !Contains(opt, "ro")) + { + opt = append(opt, "ro") + } + if (fsType == "xfs") + { + opt = append(opt, "nouuid") + } + readOnly := Contains(opt, "ro") + if (existingFormat == "" && !readOnly) + { + var cmdOut []byte + switch fsType + { + case "ext4": + args := []string{"-m0", "-Enodiscard,lazy_itable_init=1,lazy_journal_init=1", devicePath} + cmdOut, err = diskMounter.Exec.Command("mkfs.ext4", args...).CombinedOutput() + case "xfs": + cmdOut, err = diskMounter.Exec.Command("mkfs.xfs", "-K", devicePath).CombinedOutput() + } + if (err != nil) + { + klog.Errorf("failed to run mkfs error: %v, output: %v", err, string(cmdOut)) + goto unmap + } + } + err = diskMounter.FormatAndMount(devicePath, targetPath, fsType, opt) + + // Try to run online resize on mount. + // FIXME: Implement online resize. It requires online resize support in vitastor-nbd. + if (err == nil && existingFormat != "" && !readOnly) + { + var cmdOut []byte + switch (fsType) + { + case "ext4": + cmdOut, err = diskMounter.Exec.Command("resize2fs", devicePath).CombinedOutput() + case "xfs": + cmdOut, err = diskMounter.Exec.Command("xfs_growfs", devicePath).CombinedOutput() + } + if (err != nil) + { + klog.Errorf("failed to run resizefs error: %v, output: %v", err, string(cmdOut)) + goto unmap + } + } } if (err != nil) { @@ -218,15 +222,18 @@ func (ns *NodeServer) NodePublishVolume(ctx context.Context, req *csi.NodePublis "failed to mount device path (%s) to path (%s) for volume (%s) error: %s", devicePath, targetPath, volName, err, ) - // unmap NBD device - unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() - if (unmapErr != nil) - { - klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) - } - return nil, status.Error(codes.Internal, err.Error()) + goto unmap } return &csi.NodePublishVolumeResponse{}, nil + +unmap: + // unmap NBD device + unmapOut, unmapErr := exec.Command("/usr/bin/vitastor-nbd", "unmap", devicePath).CombinedOutput() + if (unmapErr != nil) + { + klog.Errorf("failed to unmap NBD device %s: %s, error: %v", devicePath, unmapOut, unmapErr) + } + return nil, status.Error(codes.Internal, err.Error()) } // NodeUnpublishVolume unmounts the volume from the target path