diff --git a/pkg/daemon/ceph/client/image.go b/pkg/daemon/ceph/client/image.go index 2b2340dbc..b6a1726eb 100644 --- a/pkg/daemon/ceph/client/image.go +++ b/pkg/daemon/ceph/client/image.go @@ -19,18 +19,9 @@ import ( "encoding/json" "fmt" "regexp" - "strconv" - "syscall" "github.com/pkg/errors" "github.com/rook/rook/pkg/clusterd" - "github.com/rook/rook/pkg/util/display" - "github.com/rook/rook/pkg/util/exec" -) - -const ( - MiB = uint64(1048576) // 1 MiB - ImageMinSize = MiB ) type CephBlockImage struct { @@ -140,61 +131,6 @@ func DeleteImageFromTrashInRadosNamespace(context *clusterd.Context, clusterInfo return nil } -// for a given size in bytes, round up to the nearest number of Mibibytes, -// i. e. return the smallest number of Mibibytes larger than or equal to size. -func roundupSizeMiB(size uint64) uint64 { - sizeMiB := (size + MiB - 1) / MiB - return sizeMiB -} - -// CreateImage creates a block storage image. -// If dataPoolName is not empty, the image will use poolName as the metadata pool and the dataPoolname for data. -// If size is zero an empty image will be created. Otherwise, an image will be -// created with a size rounded up to the nearest Mi. The adjusted image size is -// placed in return value CephBlockImage.Size. -func CreateImage(context *clusterd.Context, clusterInfo *ClusterInfo, name, poolName, dataPoolName string, size uint64) (*CephBlockImage, error) { - if size > 0 && size < ImageMinSize { - // rbd tool uses MB as the smallest unit for size input. 0 is OK but anything else smaller - // than 1 MB should just be rounded up to 1 MB. - logger.Warningf("requested image size %d is less than the minimum size of %d, using the minimum.", size, ImageMinSize) - size = ImageMinSize - } - - // Roundup the size of the volume image since we only create images on 1MB boundaries and we should never create an image - // size that's smaller than the requested one, e.g, requested 1048698 bytes should be 2MB while not be truncated to 1MB - sizeMB := roundupSizeMiB(size) - - imageSpec := getImageSpec(name, poolName) - - args := []string{"create", imageSpec, "--size", fmt.Sprintf("%d", sizeMB)} - - if dataPoolName != "" { - args = append(args, fmt.Sprintf("--data-pool=%s", dataPoolName)) - } - logger.Infof("creating rbd image %q with size %dMB in pool %q", imageSpec, sizeMB, dataPoolName) - - buf, err := NewRBDCommand(context, clusterInfo, args).Run() - if err != nil { - if code, ok := exec.ExitStatus(err); ok && code == int(syscall.EEXIST) { - // Image with the same name already exists in the given rbd pool. Continuing with the link to PV. - logger.Warningf("Requested image %s exists in pool %s. Continuing", name, poolName) - } else { - return nil, errors.Wrapf(err, "failed to create image %s in pool %s of size %d, output: %s", - name, poolName, size, string(buf)) - } - } - - // report the adjusted size which will always be >= to the requested size - var newSizeBytes uint64 - if sizeMB > 0 { - newSizeBytes = display.MbTob(uint64(sizeMB)) - } else { - newSizeBytes = 0 - } - - return &CephBlockImage{Name: name, Size: newSizeBytes}, nil -} - func DeleteImageInPool(context *clusterd.Context, clusterInfo *ClusterInfo, name, poolName string) error { return DeleteImageInRadosNamespace(context, clusterInfo, name, poolName, "") } @@ -214,70 +150,6 @@ func DeleteImageInRadosNamespace(context *clusterd.Context, clusterInfo *Cluster return nil } -func ExpandImage(context *clusterd.Context, clusterInfo *ClusterInfo, name, poolName, monitors, keyring string, size uint64) error { - logger.Infof("expanding rbd image %q in pool %q to size %dMB", name, poolName, display.BToMb(size)) - imageSpec := getImageSpec(name, poolName) - args := []string{ - "resize", - imageSpec, - fmt.Sprintf("--size=%s", strconv.FormatUint(size, 10)), - fmt.Sprintf("--cluster=%s", clusterInfo.Namespace), - fmt.Sprintf("--keyring=%s", keyring), - "-m", monitors, - } - output, err := ExecuteRBDCommandWithTimeout(context, args) - if err != nil { - return errors.Wrapf(err, "failed to resize image %s in pool %s, output: %s", name, poolName, string(output)) - } - return nil -} - -// MapImage maps an RBD image using admin cephfx and returns the device path -func MapImage(context *clusterd.Context, clusterInfo *ClusterInfo, imageName, poolName, id, keyring, monitors string) error { - imageSpec := getImageSpec(imageName, poolName) - args := []string{ - "map", - imageSpec, - fmt.Sprintf("--id=%s", id), - fmt.Sprintf("--cluster=%s", clusterInfo.Namespace), - fmt.Sprintf("--keyring=%s", keyring), - "-m", monitors, - "--conf=/dev/null", // no config file needed because we are passing all required config as arguments - } - - output, err := ExecuteRBDCommandWithTimeout(context, args) - if err != nil { - return errors.Wrapf(err, "failed to map image %s, output: %s", imageSpec, output) - } - - return nil -} - -// UnMapImage unmap an RBD image from the node -func UnMapImage(context *clusterd.Context, clusterInfo *ClusterInfo, imageName, poolName, id, keyring, monitors string, force bool) error { - deviceImage := getImageSpec(imageName, poolName) - args := []string{ - "unmap", - deviceImage, - fmt.Sprintf("--id=%s", id), - fmt.Sprintf("--cluster=%s", clusterInfo.Namespace), - fmt.Sprintf("--keyring=%s", keyring), - "-m", monitors, - "--conf=/dev/null", // no config file needed because we are passing all required config as arguments - } - - if force { - args = append(args, "-o", "force") - } - - output, err := ExecuteRBDCommandWithTimeout(context, args) - if err != nil { - return errors.Wrapf(err, "failed to unmap image %s, output: %s", deviceImage, output) - } - - return nil -} - func getImageSpec(name, poolName string) string { return fmt.Sprintf("%s/%s", poolName, name) } diff --git a/pkg/daemon/ceph/client/image_test.go b/pkg/daemon/ceph/client/image_test.go index fd5e52aad..4d13c272a 100644 --- a/pkg/daemon/ceph/client/image_test.go +++ b/pkg/daemon/ceph/client/image_test.go @@ -17,9 +17,7 @@ package client import ( "fmt" - "strings" "testing" - "time" "github.com/pkg/errors" "github.com/rook/rook/pkg/clusterd" @@ -27,149 +25,6 @@ import ( "github.com/stretchr/testify/assert" ) -const ( - sizeMB = MiB // 1 MB -) - -func TestRoundup_size_MiB(t *testing.T) { - assert.Equal(t, uint64(1), roundupSizeMiB(MiB)) - assert.Equal(t, uint64(2), roundupSizeMiB(2*MiB)) - assert.Equal(t, uint64(2), roundupSizeMiB(MiB+1)) - assert.Equal(t, uint64(1), roundupSizeMiB(MiB-1)) -} - -func TestCreateImage(t *testing.T) { - executor := &exectest.MockExecutor{} - context := &clusterd.Context{Executor: executor} - - // mock an error during the create image call. rbd tool returns error information to the output stream, - // separate from the error object, so verify that information also makes it back to us (because it is useful). - executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) { - switch { - case command == "rbd" && args[0] == "create": - return "mocked detailed ceph error output stream", errors.New("some mocked error") - } - return "", errors.Errorf("unexpected ceph command %q", args) - } - clusterInfo := AdminTestClusterInfo("mycluster") - _, err := CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB)) // 1MB - assert.NotNil(t, err) - assert.True(t, strings.Contains(err.Error(), "mocked detailed ceph error output stream")) - - // rbd tool interprets sizes as MB, so anything smaller than that should get rounded up to the minimum - // (except for 0, that's OK) - createCalled := false - expectedSizeArg := "" - executor.MockExecuteCommandWithOutput = func(command string, args ...string) (string, error) { - switch { - case command == "rbd" && args[0] == "create": - createCalled = true - assert.Equal(t, expectedSizeArg, args[3]) - return "", nil - case command == "rbd" && args[0] == "info": - assert.Equal(t, "pool1/image1", args[1]) - return `{"name":"image1","size":1048576,"objects":1,"order":20,"object_size":1048576,"block_name_prefix":"pool1_data.229226b8b4567",` + - `"format":2,"features":["layering"],"op_features":[],"flags":[],"create_timestamp":"Fri Oct 5 19:46:20 2018"}`, nil - } - return "", errors.Errorf("unexpected ceph command %q", args) - } - - // 0 byte --> 0 MB - expectedSizeArg = "0" - image, err := CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(0)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // 1 byte --> 1 MB - expectedSizeArg = "1" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(1)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // (1 MB - 1 byte) --> 1 MB - expectedSizeArg = "1" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB-1)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // 1 MB - expectedSizeArg = "1" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - assert.Equal(t, "image1", image.Name) - assert.Equal(t, uint64(sizeMB), image.Size) - createCalled = false - - // (1 MB + 1 byte) --> 2 MB - expectedSizeArg = "2" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB+1)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // (2 MB - 1 byte) --> 2 MB - expectedSizeArg = "2" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB*2-1)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // 2 MB - expectedSizeArg = "2" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB*2)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // (2 MB + 1 byte) --> 3MB - expectedSizeArg = "3" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "", uint64(sizeMB*2+1)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false - - // Pool with data pool - expectedSizeArg = "1" - image, err = CreateImage(context, clusterInfo, "image1", "pool1", "datapool1", uint64(sizeMB)) - assert.Nil(t, err) - assert.NotNil(t, image) - assert.True(t, createCalled) - createCalled = false -} - -func TestExpandImage(t *testing.T) { - executor := &exectest.MockExecutor{} - context := &clusterd.Context{Executor: executor} - executor.MockExecuteCommandWithTimeout = func(timeout time.Duration, command string, args ...string) (string, error) { - switch { - case args[1] != "kube/some-image": - return "", errors.Errorf("no image %s", args[1]) - - case command == "rbd" && args[0] == "resize": - return "everything is okay", nil - } - return "", errors.Errorf("unexpected ceph command %q", args) - } - clusterInfo := AdminTestClusterInfo("mycluster") - err := ExpandImage(context, clusterInfo, "error-name", "kube", "mon1,mon2,mon3", "/tmp/keyring", 1000000) - assert.Error(t, err) - - err = ExpandImage(context, clusterInfo, "some-image", "kube", "mon1,mon2,mon3", "/tmp/keyring", 1000000) - assert.NoError(t, err) -} - func TestListImageLogLevelInfo(t *testing.T) { executor := &exectest.MockExecutor{} context := &clusterd.Context{Executor: executor}