Skip to content
Merged
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
9 changes: 7 additions & 2 deletions rbd/diff_iterate.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,8 +53,13 @@ type DiffIterateCallback func(uint64, uint64, int, interface{}) int
// Callback, Offset, and Length should always be specified when passed to
// DiffIterate. The other values are optional.
type DiffIterateConfig struct {
SnapName string
Offset uint64
SnapName string
// Offset is the starting byte position in the image from which to
// begin scanning for changed extents.
Offset uint64
// Length is the number of bytes to scan starting from Offset.
// The scanned range is [Offset, Offset+Length). Length must not
// exceed (imageSize - Offset).
Length uint64
IncludeParent DiffIncludeParent
WholeObject DiffWholeObject
Expand Down
9 changes: 7 additions & 2 deletions rbd/diff_iterate_by_id.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,13 @@ var (
// Callback, Offset, and Length should always be specified when passed to
// DiffIterateByID. The other values are optional.
type DiffIterateByIDConfig struct {
FromSnapID uint64
Offset uint64
FromSnapID uint64
// Offset is the starting byte position in the image from which to
// begin scanning for changed extents.
Offset uint64
// Length is the number of bytes to scan starting from Offset.
// The scanned range is [Offset, Offset+Length). Length must not
// exceed (imageSize - Offset).
Length uint64
IncludeParent DiffIncludeParent
WholeObject DiffWholeObject
Expand Down
97 changes: 97 additions & 0 deletions rbd/diff_iterate_by_id_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ func TestDiffIterateByID(t *testing.T) {
t.Run("callbackData", func(t *testing.T) {
testDiffIterateByIDCallbackData(t, ioctx)
})
t.Run("nonZeroOffset", func(t *testing.T) {
testDiffIterateByIDNonZeroOffset(t, ioctx)
})
t.Run("badImage", func(t *testing.T) {
var gotCalled int
img := GetImage(ioctx, "bob")
Expand Down Expand Up @@ -462,6 +465,100 @@ func testDiffIterateByIDSnapshot(t *testing.T, ioctx *rados.IOContext) {
}
}

// testDiffIterateByIDNonZeroOffset verifies that DiffIterateByID works
// correctly with a non-zero Offset. This exercises the scenario where a caller
// wants to skip a portion of the image (e.g. a LUKS header) and only scan
// the remainder. In this scenario, Length is set to (imageSize - Offset) so
// that the diff covers only the remaining range rather than the full imageSize.
func testDiffIterateByIDNonZeroOffset(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
}

// Write data at three locations:
// - offset 0 (before the skip region)
// - offset 2MiB (after the skip region)
// - offset 5MiB (well after the skip region)
_, err = img.WriteAt([]byte("header-data"), 0)
assert.NoError(t, err)
_, err = img.WriteAt([]byte("real-data-1"), 2*1024*1024) // 2MiB
assert.NoError(t, err)
_, err = img.WriteAt([]byte("real-data-2"), 5*1024*1024) // 5MiB
assert.NoError(t, err)

// Scan with Offset=0, Length=isize to see all 3 extents
calls := []diResult{}
err = img.DiffIterateByID(
DiffIterateByIDConfig{
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, 3, "expected 3 extents with Offset=0")

// Scan with Offset=1MiB, Length=isize-1MiB (correct usage)
// Should skip the first extent at offset 0 and return only 2 extents
skipBytes := uint64(1 << 20) // 1MiB
calls = []diResult{}
err = img.DiffIterateByID(
DiffIterateByIDConfig{
Offset: skipBytes,
Length: isize - skipBytes,
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, "expected 2 extents with Offset=1MiB") {
assert.EqualValues(t, 2*1024*1024, calls[0].offset)
assert.EqualValues(t, 11, calls[0].length)
assert.EqualValues(t, 5*1024*1024, calls[1].offset)
assert.EqualValues(t, 11, calls[1].length)
}

// Scan with Offset=3MiB, Length=isize-3MiB
// Should return only the extent at 5MiB
skipBytes = uint64(3 << 20) // 3MiB
calls = []diResult{}
err = img.DiffIterateByID(
DiffIterateByIDConfig{
Offset: skipBytes,
Length: isize - skipBytes,
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, "expected 1 extent with Offset=3MiB") {
assert.EqualValues(t, 5*1024*1024, calls[0].offset)
assert.EqualValues(t, 11, calls[0].length)
}
}

func testDiffIterateByIDCallbackData(t *testing.T, ioctx *rados.IOContext) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
Expand Down
97 changes: 97 additions & 0 deletions rbd/diff_iterate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ func TestDiffIterate(t *testing.T) {
t.Run("callbackData", func(t *testing.T) {
testDiffIterateCallbackData(t, ioctx)
})
t.Run("nonZeroOffset", func(t *testing.T) {
testDiffIterateNonZeroOffset(t, ioctx)
})
t.Run("badImage", func(t *testing.T) {
var gotCalled int
img := GetImage(ioctx, "bob")
Expand Down Expand Up @@ -450,6 +453,100 @@ func testDiffIterateSnapshot(t *testing.T, ioctx *rados.IOContext) {
}
}

// testDiffIterateNonZeroOffset verifies that DiffIterate works correctly with
// a non-zero Offset. This exercises the scenario where a caller wants to skip
// a portion of the image (e.g. a LUKS header) and only scan the remainder.
// In this scenario, Length is set to (imageSize - Offset) so that the diff
// covers only the remaining range rather than the full imageSize.
func testDiffIterateNonZeroOffset(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
}

// Write data at three locations:
// - offset 0 (before the skip region)
// - offset 2MiB (after the skip region)
// - offset 5MiB (well after the skip region)
_, err = img.WriteAt([]byte("header-data"), 0)
assert.NoError(t, err)
_, err = img.WriteAt([]byte("real-data-1"), 2*1024*1024) // 2MiB
assert.NoError(t, err)
_, err = img.WriteAt([]byte("real-data-2"), 5*1024*1024) // 5MiB
assert.NoError(t, err)

// Scan with Offset=0, Length=isize to see all 3 extents
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)
assert.Len(t, calls, 3, "expected 3 extents with Offset=0")

// Scan with Offset=1MiB, Length=isize-1MiB (correct usage)
// Should skip the first extent at offset 0 and return only 2 extents
skipBytes := uint64(1 << 20) // 1MiB
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: skipBytes,
Length: isize - skipBytes,
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, "expected 2 extents with Offset=1MiB") {
assert.EqualValues(t, 2*1024*1024, calls[0].offset)
assert.EqualValues(t, 11, calls[0].length)
assert.EqualValues(t, 5*1024*1024, calls[1].offset)
assert.EqualValues(t, 11, calls[1].length)
}

// Scan with Offset=3MiB, Length=isize-3MiB
// Should return only the extent at 5MiB
skipBytes = uint64(3 << 20) // 3MiB
calls = []diResult{}
err = img.DiffIterate(
DiffIterateConfig{
Offset: skipBytes,
Length: isize - skipBytes,
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, "expected 1 extent with Offset=3MiB") {
assert.EqualValues(t, 5*1024*1024, calls[0].offset)
assert.EqualValues(t, 11, calls[0].length)
}
}

func testDiffIterateCallbackData(t *testing.T, ioctx *rados.IOContext) {
name := GetUUID()
isize := uint64(1 << 23) // 8MiB
Expand Down