rbd: repair thick-provisioned images on CreateVolume restart

Signed-off-by: Niels de Vos <ndevos@redhat.com>
(cherry picked from commit 7cbad9305f)
This commit is contained in:
Niels de Vos 2021-05-26 10:29:32 +02:00 committed by mergify[bot]
parent 58d606ab8d
commit a0ca713d79
3 changed files with 146 additions and 9 deletions

View File

@ -19,7 +19,6 @@ package rbd
import ( import (
"context" "context"
"errors" "errors"
"fmt"
"strconv" "strconv"
csicommon "github.com/ceph/ceph-csi/internal/csi-common" csicommon "github.com/ceph/ceph-csi/internal/csi-common"
@ -118,13 +117,7 @@ func (cs *ControllerServer) parseVolCreateRequest(ctx context.Context, req *csi.
return nil, status.Error(codes.InvalidArgument, err.Error()) return nil, status.Error(codes.InvalidArgument, err.Error())
} }
tp := "thickProvision" rbdVol.ThickProvision = cs.isThickProvisionRequest(req)
thick := req.GetParameters()[tp]
if thick != "" {
if rbdVol.ThickProvision, err = strconv.ParseBool(thick); err != nil {
return nil, fmt.Errorf("failed to parse %q: %w", tp, err)
}
}
rbdVol.RequestName = req.GetName() rbdVol.RequestName = req.GetName()
@ -322,7 +315,23 @@ func flattenParentImage(ctx context.Context, rbdVol *rbdVolume, cr *util.Credent
// when the process of creating a volume was interrupted. // when the process of creating a volume was interrupted.
func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.CreateVolumeRequest, func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.CreateVolumeRequest,
cr *util.Credentials, rbdVol *rbdVolume, rbdSnap *rbdSnapshot) (*csi.CreateVolumeResponse, error) { cr *util.Credentials, rbdVol *rbdVolume, rbdSnap *rbdSnapshot) (*csi.CreateVolumeResponse, error) {
if rbdSnap != nil { vcs := req.GetVolumeContentSource()
switch {
// normal CreateVolume without VolumeContentSource
case vcs == nil:
// continue/restart allocating the volume in case it
// should be thick-provisioned
if cs.isThickProvisionRequest(req) {
err := rbdVol.RepairThickProvision()
if err != nil {
return nil, status.Error(codes.Internal, err.Error())
}
}
// rbdVol is a restore from snapshot, rbdSnap is passed
case vcs.GetSnapshot() != nil:
// restore from snapshot imploes rbdSnap != nil
// check if image depth is reached limit and requires flatten // check if image depth is reached limit and requires flatten
err := checkFlatten(ctx, rbdVol, cr) err := checkFlatten(ctx, rbdVol, cr)
if err != nil { if err != nil {
@ -333,6 +342,10 @@ func (cs *ControllerServer) repairExistingVolume(ctx context.Context, req *csi.C
if err != nil { if err != nil {
return nil, err return nil, err
} }
// rbdVol is a clone from an other volume
case vcs.GetVolume() != nil:
// TODO: is there really nothing to repair on image clone?
} }
return buildCreateVolumeResponse(req, rbdVol), nil return buildCreateVolumeResponse(req, rbdVol), nil
@ -1129,3 +1142,21 @@ func (cs *ControllerServer) ControllerExpandVolume(ctx context.Context, req *csi
NodeExpansionRequired: nodeExpansion, NodeExpansionRequired: nodeExpansion,
}, nil }, nil
} }
// isThickProvisionRequest returns true in case the request contains the
// `thickProvision` option set to `true`.
func (cs *ControllerServer) isThickProvisionRequest(req *csi.CreateVolumeRequest) bool {
tp := "thickProvision"
thick, ok := req.GetParameters()[tp]
if !ok || thick == "" {
return false
}
thickBool, err := strconv.ParseBool(thick)
if err != nil {
return false
}
return thickBool
}

View File

@ -0,0 +1,69 @@
/*
Copyright 2021 The Ceph-CSI Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package rbd
import (
"testing"
"github.com/container-storage-interface/spec/lib/go/csi"
)
func TestIsThickProvisionRequest(t *testing.T) {
cs := &ControllerServer{}
req := &csi.CreateVolumeRequest{
Name: "fake",
Parameters: map[string]string{
"unkownOption": "not-set",
},
}
// pass disabled/invalid values for "thickProvision" option
if cs.isThickProvisionRequest(req) {
t.Error("request is not for thick-provisioning")
}
req.Parameters["thickProvision"] = ""
if cs.isThickProvisionRequest(req) {
t.Errorf("request is not for thick-provisioning: %s", req.Parameters["thickProvision"])
}
req.Parameters["thickProvision"] = "false"
if cs.isThickProvisionRequest(req) {
t.Errorf("request is not for thick-provisioning: %s", req.Parameters["thickProvision"])
}
req.Parameters["thickProvision"] = "off"
if cs.isThickProvisionRequest(req) {
t.Errorf("request is not for thick-provisioning: %s", req.Parameters["thickProvision"])
}
req.Parameters["thickProvision"] = "no"
if cs.isThickProvisionRequest(req) {
t.Errorf("request is not for thick-provisioning: %s", req.Parameters["thickProvision"])
}
req.Parameters["thickProvision"] = "**true**"
if cs.isThickProvisionRequest(req) {
t.Errorf("request is not for thick-provisioning: %s", req.Parameters["thickProvision"])
}
// only "true" should enable thick provisioning
req.Parameters["thickProvision"] = "true"
if !cs.isThickProvisionRequest(req) {
t.Errorf("request should be for thick-provisioning: %s", req.Parameters["thickProvision"])
}
}

View File

@ -1441,6 +1441,43 @@ func (rv *rbdVolume) isThickProvisioned() (bool, error) {
return thick, nil return thick, nil
} }
// RepairThickProvision writes zero bytes to the volume so that it will be
// completely allocated. In case the volume is already marked as
// thick-provisioned, nothing will be done.
func (rv *rbdVolume) RepairThickProvision() error {
// if the image has the thick-provisioned metadata, it has been fully
// allocated
done, err := rv.isThickProvisioned()
if err != nil {
return fmt.Errorf("failed to repair thick-provisioning of %q: %w", rv, err)
} else if done {
return nil
}
// in case there are watchers, assume allocating is still happening in
// the background (by an other process?)
background, err := rv.isInUse()
if err != nil {
return fmt.Errorf("failed to get users of %q: %w", rv, err)
} else if background {
return fmt.Errorf("not going to restart thick-provisioning of in-use image %q", rv)
}
// TODO: can this be improved by starting at the offset where
// allocating was aborted/restarted?
err = rv.allocate(0)
if err != nil {
return fmt.Errorf("failed to continue thick-provisioning of %q: %w", rv, err)
}
err = rv.setThickProvisioned()
if err != nil {
return fmt.Errorf("failed to continue thick-provisioning of %q: %w", rv, err)
}
return nil
}
func (rv *rbdVolume) listSnapshots() ([]librbd.SnapInfo, error) { func (rv *rbdVolume) listSnapshots() ([]librbd.SnapInfo, error) {
image, err := rv.open() image, err := rv.open()
if err != nil { if err != nil {