go-ceph/rbd/diff_iterate_test.go

523 lines
13 KiB
Go
Raw Normal View History

package rbd
import (
"sync"
"testing"
"time"
"github.com/ceph/go-ceph/rados"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDiffIterate(t *testing.T) {
conn := radosConnect(t)
defer conn.Shutdown()
poolname := GetUUID()
err := conn.MakePool(poolname)
assert.NoError(t, err)
defer conn.DeletePool(poolname)
ioctx, err := conn.OpenIOContext(poolname)
require.NoError(t, err)
defer ioctx.Destroy()
t.Run("basic", func(t *testing.T) {
testDiffIterateBasic(t, ioctx)
})
t.Run("twoAtOnce", func(t *testing.T) {
testDiffIterateTwoAtOnce(t, ioctx)
})
t.Run("earlyExit", func(t *testing.T) {
testDiffIterateEarlyExit(t, ioctx)
})
t.Run("snapshot", func(t *testing.T) {
testDiffIterateSnapshot(t, ioctx)
})
t.Run("callbackData", func(t *testing.T) {
testDiffIterateCallbackData(t, ioctx)
})
t.Run("badImage", func(t *testing.T) {
var gotCalled int
img := GetImage(ioctx, "bob")
err := img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: uint64(1 << 22),
Callback: func(_, _ uint64, _ int, _ interface{}) int {
gotCalled++
return 0
},
})
assert.Error(t, err)
assert.EqualValues(t, 0, gotCalled)
})
t.Run("missingCallback", func(t *testing.T) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
err := CreateImage(ioctx, name, isize, options)
assert.NoError(t, err)
img, err := OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
assert.NoError(t, img.Remove())
}()
var gotCalled int
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: uint64(1 << 22),
})
assert.Error(t, err)
assert.EqualValues(t, 0, gotCalled)
})
}
func testDiffIterateBasic(t *testing.T, ioctx *rados.IOContext) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
err := CreateImage(ioctx, name, isize, options)
assert.NoError(t, err)
img, err := OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
assert.NoError(t, img.Remove())
}()
type diResult struct {
offset uint64
length uint64
}
calls := []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
// Image is new, empty. Callback will not be called
assert.Len(t, calls, 0)
_, err = img.WriteAt([]byte("sometimes you feel like a nut"), 0)
assert.NoError(t, err)
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 29, calls[0].length)
}
_, err = img.WriteAt([]byte("sometimes you don't"), 32)
assert.NoError(t, err)
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
if assert.NoError(t, err) {
assert.Len(t, calls, 1)
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 51, calls[0].length)
}
// dirty a 2nd chunk
newOffset := 3145728 // 3MiB
_, err = img.WriteAt([]byte("alright, alright, alright"), int64(newOffset))
assert.NoError(t, err)
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 2) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 51, calls[0].length)
assert.EqualValues(t, newOffset, calls[1].offset)
assert.EqualValues(t, 25, calls[1].length)
}
// dirty a 3rd chunk
newOffset2 := 5242880 + 1024 // 5MiB + 1KiB
_, err = img.WriteAt([]byte("zowie!"), int64(newOffset2))
assert.NoError(t, err)
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 3) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 51, calls[0].length)
assert.EqualValues(t, newOffset, calls[1].offset)
assert.EqualValues(t, 25, calls[1].length)
assert.EqualValues(t, newOffset2-1024, calls[2].offset)
assert.EqualValues(t, 6+1024, calls[2].length)
}
}
// testDiffIterateTwoAtOnce aims to verify that multiple DiffIterate
// callbacks can be executed at the same time without error.
func testDiffIterateTwoAtOnce(t *testing.T, ioctx *rados.IOContext) {
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
name1 := GetUUID()
err := CreateImage(ioctx, name1, isize, options)
assert.NoError(t, err)
img1, err := OpenImage(ioctx, name1, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img1.Close())
assert.NoError(t, img1.Remove())
}()
name2 := GetUUID()
err = CreateImage(ioctx, name2, isize, options)
assert.NoError(t, err)
img2, err := OpenImage(ioctx, name2, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img2.Close())
assert.NoError(t, img2.Remove())
}()
type diResult struct {
offset uint64
length uint64
}
diffTest := func(wg *sync.WaitGroup, inbuf []byte, img *Image) {
_, err = img.WriteAt(inbuf[0:3], 0)
assert.NoError(t, err)
_, err = img.WriteAt(inbuf[3:6], 3145728)
assert.NoError(t, err)
_, err = img.WriteAt(inbuf[6:9], 5242880)
assert.NoError(t, err)
calls := []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
time.Sleep(8 * time.Millisecond)
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 3) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 3, calls[0].length)
assert.EqualValues(t, 3145728, calls[1].offset)
assert.EqualValues(t, 3, calls[1].length)
assert.EqualValues(t, 5242880, calls[2].offset)
assert.EqualValues(t, 3, calls[2].length)
}
wg.Done()
}
wg := &sync.WaitGroup{}
wg.Add(1)
go diffTest(wg, []byte("foobarbaz"), img1)
wg.Add(1)
go diffTest(wg, []byte("abcdefghi"), img2)
wg.Wait()
}
// testDiffIterateEarlyExit checks that returning an error from the callback
// function triggers the DiffIterate call to stop.
func testDiffIterateEarlyExit(t *testing.T, ioctx *rados.IOContext) {
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
name := GetUUID()
err := CreateImage(ioctx, name, isize, options)
assert.NoError(t, err)
img, err := OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
assert.NoError(t, img.Remove())
}()
type diResult struct {
offset uint64
length uint64
}
// "damage" the image
inbuf := []byte("xxxyyyzzz")
_, err = img.WriteAt(inbuf[0:3], 0)
assert.NoError(t, err)
_, err = img.WriteAt(inbuf[3:6], 3145728)
assert.NoError(t, err)
_, err = img.WriteAt(inbuf[6:9], 5242880)
assert.NoError(t, err)
// if the offset is less than zero the callback will return an "error" and
// that will abort the DiffIterate call early and it will return the error
// code our callback used.
calls := []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
if o > 1 {
return -5
}
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.Error(t, err)
if errno, ok := err.(interface{ ErrorCode() int }); assert.True(t, ok) {
assert.EqualValues(t, -5, errno.ErrorCode())
}
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 3, calls[0].length)
}
}
func testDiffIterateSnapshot(t *testing.T, ioctx *rados.IOContext) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
err := CreateImage(ioctx, name, isize, options)
assert.NoError(t, err)
img, err := OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
assert.NoError(t, img.Remove())
}()
type diResult struct {
offset uint64
length uint64
}
calls := []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
// Image is new, empty. Callback will not be called
assert.Len(t, calls, 0)
_, err = img.WriteAt([]byte("sometimes you feel like a nut"), 0)
assert.NoError(t, err)
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 29, calls[0].length)
}
ss1, err := img.CreateSnapshot("ss1")
assert.NoError(t, err)
defer func() { assert.NoError(t, ss1.Remove()) }()
// there should be no differences between "now" and "ss1"
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
SnapName: "ss1",
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
assert.Len(t, calls, 0)
// this next check was shamelessly cribbed from the pybind
// tests for diff_iterate out of the ceph tree
// it discards the current image, makes a 2nd snap, and compares
// the diff between snapshots 1 & 2.
_, err = img.Discard(0, isize)
assert.NoError(t, err)
ss2, err := img.CreateSnapshot("ss2")
assert.NoError(t, err)
defer func() { assert.NoError(t, ss2.Remove()) }()
err = ss2.Set() // caution: this side-effects img!
assert.NoError(t, err)
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
SnapName: "ss1",
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, _ interface{}) int {
calls = append(calls, diResult{offset: o, length: l})
return 0
},
})
assert.NoError(t, err)
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 29, calls[0].length)
}
}
func testDiffIterateCallbackData(t *testing.T, ioctx *rados.IOContext) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
iorder := 20 // 1MiB
options := NewRbdImageOptions()
defer options.Destroy()
assert.NoError(t,
options.SetUint64(RbdImageOptionOrder, uint64(iorder)))
err := CreateImage(ioctx, name, isize, options)
assert.NoError(t, err)
img, err := OpenImage(ioctx, name, NoSnapshot)
assert.NoError(t, err)
defer func() {
assert.NoError(t, img.Close())
assert.NoError(t, img.Remove())
}()
type diResult struct {
offset uint64
length uint64
}
calls := []diResult{}
_, err = img.WriteAt([]byte("sometimes you feel like a nut"), 0)
assert.NoError(t, err)
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, x interface{}) int {
if v, ok := x.(int); ok {
assert.EqualValues(t, 77, v)
} else {
t.Fatalf("incorrect type")
}
calls = append(calls, diResult{offset: o, length: l})
return 0
},
Data: 77,
})
assert.NoError(t, err)
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 29, calls[0].length)
}
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: 0,
Length: isize,
Callback: func(o, l uint64, _ int, x interface{}) int {
if v, ok := x.(string); ok {
assert.EqualValues(t, "bob", v)
} else {
t.Fatalf("incorrect type")
}
calls = append(calls, diResult{offset: o, length: l})
return 0
},
Data: "bob",
})
assert.NoError(t, err)
if assert.Len(t, calls, 1) {
assert.EqualValues(t, 0, calls[0].offset)
assert.EqualValues(t, 29, calls[0].length)
}
}