Skip to content

Commit 2c41b01

Browse files
rados: Add support for rados_checksum API
Add support for in-OSD calculations of object checksums using one of the supported algorithms: * XXHash32 * XXHash64 * CRC32 Signed-off-by: Max Naylor <maxnaylor09@gmail.com>
1 parent 392c49d commit 2c41b01

6 files changed

Lines changed: 294 additions & 2 deletions

File tree

docs/api-status.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1266,7 +1266,14 @@
12661266
"became_stable_version": "v0.33.0"
12671267
}
12681268
],
1269-
"preview_api": []
1269+
"preview_api": [
1270+
{
1271+
"name": "IOContext.Checksum",
1272+
"comment": "Checksum calculates the checksum of the given object data, using one of the supported checksum algorithms.\n\nImplements:\n\n\tint rados_checksum(rados_ioctx_t io,\n\t const char *oid,\n\t rados_checksum_type_t type,\n\t const char *init_value,\n\t size_t init_value_len,\n\t size_t len,\n\t uint64_t off,\n\t size_t chunk_size,\n\t char *pchecksum,\n\t size_t checksum_len);\n",
1273+
"added_in_version": "$NEXT_RELEASE",
1274+
"expected_stable_version": "$NEXT_RELEASE_STABLE"
1275+
}
1276+
]
12701277
},
12711278
"rbd": {
12721279
"deprecated_api": [

docs/api-status.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,11 @@ FSAdmin.SubVolumeGroupInfo | $NEXT_RELEASE | $NEXT_RELEASE_STABLE |
2323

2424
## Package: rados
2525

26-
No Preview/Deprecated APIs found. All APIs are considered stable.
26+
### Preview APIs
27+
28+
Name | Added in Version | Expected Stable Version |
29+
---- | ---------------- | ----------------------- |
30+
IOContext.Checksum | $NEXT_RELEASE | $NEXT_RELEASE_STABLE |
2731

2832
## Package: rbd
2933

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/aws/aws-sdk-go-v2/service/s3 v1.98.0
1010
github.com/aws/smithy-go v1.24.2
1111
github.com/gofrs/uuid/v5 v5.4.0
12+
github.com/pierrec/xxHash v0.1.5
1213
github.com/stretchr/testify v1.11.1
1314
golang.org/x/sys v0.42.0
1415
)

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
4040
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
4141
github.com/gofrs/uuid/v5 v5.4.0 h1:EfbpCTjqMuGyq5ZJwxqzn3Cbr2d0rUZU7v5ycAk/e/0=
4242
github.com/gofrs/uuid/v5 v5.4.0/go.mod h1:CDOjlDMVAtN56jqyRUZh58JT31Tiw7/oQyEXZV+9bD8=
43+
github.com/pierrec/xxHash v0.1.5 h1:n/jBpwTHiER4xYvK3/CdPVnLDPchj8eTJFFLUb4QHBo=
44+
github.com/pierrec/xxHash v0.1.5/go.mod h1:w2waW5Zoa/Wc4Yqe0wgrIYAGKqRMf7czn2HNKXmuL+I=
4345
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4446
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
4547
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=

rados/ioctx_checksum.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
//go:build ceph_preview
2+
3+
package rados
4+
5+
/*
6+
#cgo LDFLAGS: -lrados
7+
#include <stdlib.h>
8+
#include <rados/librados.h>
9+
*/
10+
import "C"
11+
12+
import "unsafe"
13+
14+
// Checksum calculates the checksum of the given object data, using one of the supported checksum algorithms.
15+
//
16+
// Implements:
17+
//
18+
// int rados_checksum(rados_ioctx_t io,
19+
// const char *oid,
20+
// rados_checksum_type_t type,
21+
// const char *init_value,
22+
// size_t init_value_len,
23+
// size_t len,
24+
// uint64_t off,
25+
// size_t chunk_size,
26+
// char *pchecksum,
27+
// size_t checksum_len);
28+
func (ioctx *IOContext) Checksum(oid string, checksumType ChecksumType, dst []byte, opts *ChecksumOptions) error {
29+
// apply defaults
30+
if opts == nil {
31+
opts = &ChecksumOptions{}
32+
}
33+
if opts.InitValue == nil {
34+
initLen := 4
35+
if checksumType == ChecksumTypeXXHash64 {
36+
initLen = 8
37+
}
38+
opts.InitValue = make([]byte, initLen)
39+
}
40+
41+
// call library
42+
coid := C.CString(oid)
43+
defer C.free(unsafe.Pointer(coid))
44+
45+
return getError(C.rados_checksum(
46+
ioctx.ioctx,
47+
coid,
48+
C.rados_checksum_type_t(checksumType),
49+
(*C.char)(unsafe.Pointer(&opts.InitValue[0])),
50+
C.size_t(len(opts.InitValue)),
51+
C.size_t(opts.Len),
52+
C.uint64_t(opts.Off),
53+
C.size_t(opts.ChunkSize),
54+
(*C.char)(unsafe.Pointer(&dst[0])),
55+
C.size_t(len(dst)),
56+
))
57+
}
58+
59+
// ChecksumType indicates checksum algorithm types supported by the IOContext.Checksum method.
60+
// Equivalent to the rados_checksum_type_t enum.
61+
type ChecksumType uint32
62+
63+
const (
64+
// ChecksumTypeXXHash32 produces an encoded le32 checksum of the given object.
65+
ChecksumTypeXXHash32 = ChecksumType(C.LIBRADOS_CHECKSUM_TYPE_XXHASH32)
66+
// ChecksumTypeXXHash64 produces an encoded le64 checksum of the given object.
67+
ChecksumTypeXXHash64 = ChecksumType(C.LIBRADOS_CHECKSUM_TYPE_XXHASH64)
68+
// ChecksumTypeCRC32C produces an encoded le32 checksum of the given object.
69+
ChecksumTypeCRC32C = ChecksumType(C.LIBRADOS_CHECKSUM_TYPE_CRC32C)
70+
)
71+
72+
// ChecksumOptions exposes non-required parameters for the Checksum method.
73+
type ChecksumOptions struct {
74+
// Off sets the object offset to start checksumming in the object.
75+
// By default, the entire object will be checksummed.
76+
Off uint64
77+
// Len sets the the number of bytes to checksum in the object.
78+
// By default, the entire object will be checksummed.
79+
Len uint64
80+
// ChunkSize sets the length-aligned chunk size for the checksum calculation.
81+
// By default, the entire object will be checksummed as a single chunk.
82+
ChunkSize uint64
83+
// InitValue sets the initial value for the checksum calculation.
84+
// By default, the initial value will be a zeroed-out byte slice.
85+
InitValue []byte
86+
}

rados/ioctx_checksum_test.go

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
package rados
2+
3+
import (
4+
"encoding/binary"
5+
"hash/crc32"
6+
7+
xxhash32 "github.com/pierrec/xxHash/xxHash32"
8+
xxhash64 "github.com/pierrec/xxHash/xxHash64"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func (suite *RadosTestSuite) TestChecksum() {
13+
suite.SetupConnection()
14+
15+
var (
16+
oid = suite.GenObjectName()
17+
contents = suite.RandomBytes(3 * 1024)
18+
)
19+
20+
// write some random data to the object
21+
assert.NoError(suite.T(), suite.ioctx.Write(oid, contents, 0))
22+
23+
// test each checksum type
24+
suite.Run("CRC32", func() {
25+
init := []byte{0xff, 0xff, 0xff, 0xff}
26+
27+
dst := make([]byte, 4+4)
28+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeCRC32C, dst, &ChecksumOptions{InitValue: init}))
29+
30+
chunks := binary.LittleEndian.Uint32(dst[:4])
31+
assert.Equal(suite.T(), uint32(1), chunks)
32+
33+
// Note: the CRC32 Go standard library produces checksum results with the final XOR already applied,
34+
// while rados_checksum returns raw results; so here we do additional processing to assert correctness.
35+
sum := binary.LittleEndian.Uint32(dst[4:]) ^ 0xffffffff
36+
want := crc32.Checksum(contents, crc32.MakeTable(crc32.Castagnoli))
37+
assert.Equal(suite.T(), want, sum)
38+
})
39+
40+
suite.Run("XXHash32", func() {
41+
init := []byte{0x0, 0x0, 0x0, 0x0}
42+
seed := binary.LittleEndian.Uint32(init)
43+
44+
hash := xxhash32.New(seed)
45+
_, err := hash.Write(contents)
46+
assert.NoError(suite.T(), err)
47+
want := binary.LittleEndian.Uint32(hash.Sum(nil))
48+
49+
dst := make([]byte, 4+4)
50+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeXXHash32, dst, nil))
51+
52+
chunks := binary.LittleEndian.Uint32(dst[:4])
53+
assert.Equal(suite.T(), uint32(1), chunks)
54+
55+
sum := binary.LittleEndian.Uint32(dst[4:])
56+
assert.Equal(suite.T(), want, sum)
57+
})
58+
59+
suite.Run("XXHash64", func() {
60+
init := []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}
61+
seed := binary.LittleEndian.Uint64(init)
62+
63+
xxh := xxhash64.New(seed)
64+
_, err := xxh.Write(contents)
65+
assert.NoError(suite.T(), err)
66+
want := binary.LittleEndian.Uint64(xxh.Sum(nil))
67+
68+
dst := make([]byte, 4+8)
69+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeXXHash64, dst, nil))
70+
71+
chunks := binary.LittleEndian.Uint32(dst[:4])
72+
assert.Equal(suite.T(), uint32(1), chunks)
73+
74+
sum := binary.LittleEndian.Uint64(dst[4:])
75+
assert.Equal(suite.T(), want, sum)
76+
})
77+
}
78+
func (suite *RadosTestSuite) TestChecksumWithOpts() {
79+
suite.SetupConnection()
80+
81+
var (
82+
off uint64 = 256
83+
chunkSize uint64 = 1024
84+
chunkCount uint64 = 2
85+
86+
oid = suite.GenObjectName()
87+
contents = suite.RandomBytes(int(off + chunkSize*(chunkCount+1))) // write one extra chunk, to be ignored
88+
)
89+
90+
// write some random data to the object
91+
assert.NoError(suite.T(), suite.ioctx.Write(oid, contents, 0))
92+
93+
// test each checksum type with optional parameters
94+
suite.Run("CRC32", func() {
95+
init := []byte{0xff, 0xff, 0xff, 0xff}
96+
97+
table := crc32.MakeTable(crc32.Castagnoli)
98+
want := []uint32{
99+
crc32.Checksum(contents[off:off+chunkSize], table),
100+
crc32.Checksum(contents[off+chunkSize:off+chunkSize*2], table),
101+
}
102+
103+
dst := make([]byte, 4+4*chunkCount) // allocate space for multiple uint32 checksum values
104+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeCRC32C, dst, &ChecksumOptions{
105+
InitValue: init,
106+
ChunkSize: chunkSize,
107+
Off: off,
108+
Len: chunkSize * chunkCount,
109+
}))
110+
111+
chunks := binary.LittleEndian.Uint32(dst[:4])
112+
assert.Equal(suite.T(), uint32(chunkCount), chunks)
113+
114+
got := []uint32{
115+
binary.LittleEndian.Uint32(dst[4:8]) ^ 0xffffffff,
116+
binary.LittleEndian.Uint32(dst[8:12]) ^ 0xffffffff,
117+
}
118+
for i := range want {
119+
assert.Equal(suite.T(), want[i], got[i])
120+
}
121+
})
122+
123+
suite.Run("XXHash32", func() {
124+
init := []byte{0xff, 0xff, 0xff, 0xff}
125+
seed := binary.LittleEndian.Uint32(init)
126+
127+
var want [2]uint32
128+
xxh := xxhash32.New(seed)
129+
130+
_, err := xxh.Write(contents[off : off+chunkSize])
131+
assert.NoError(suite.T(), err)
132+
want[0] = binary.LittleEndian.Uint32(xxh.Sum(nil))
133+
xxh.Reset()
134+
_, err = xxh.Write(contents[off+chunkSize : off+chunkSize*2])
135+
assert.NoError(suite.T(), err)
136+
want[1] = binary.LittleEndian.Uint32(xxh.Sum(nil))
137+
138+
dst := make([]byte, 4+4*chunkCount) // allocate space for multiple uint32 checksum values
139+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeXXHash32, dst, &ChecksumOptions{
140+
InitValue: init,
141+
ChunkSize: chunkSize,
142+
Off: off,
143+
Len: chunkSize * chunkCount,
144+
}))
145+
146+
chunks := binary.LittleEndian.Uint32(dst[:4])
147+
assert.Equal(suite.T(), uint32(chunkCount), chunks)
148+
149+
got := []uint32{
150+
binary.LittleEndian.Uint32(dst[4:8]),
151+
binary.LittleEndian.Uint32(dst[8:12]),
152+
}
153+
for i := range want {
154+
assert.Equal(suite.T(), want[i], got[i])
155+
}
156+
})
157+
158+
suite.Run("XXHash64", func() {
159+
init := []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
160+
seed := binary.LittleEndian.Uint64(init)
161+
162+
var want [2]uint64
163+
xxh := xxhash64.New(seed)
164+
165+
_, err := xxh.Write(contents[off : off+chunkSize])
166+
assert.NoError(suite.T(), err)
167+
want[0] = binary.LittleEndian.Uint64(xxh.Sum(nil))
168+
xxh.Reset()
169+
_, err = xxh.Write(contents[off+chunkSize : off+chunkSize*2])
170+
assert.NoError(suite.T(), err)
171+
want[1] = binary.LittleEndian.Uint64(xxh.Sum(nil))
172+
173+
dst := make([]byte, 4+8*chunkCount) // allocate space for multiple uint64 checksum values
174+
assert.NoError(suite.T(), suite.ioctx.Checksum(oid, ChecksumTypeXXHash64, dst, &ChecksumOptions{
175+
InitValue: init,
176+
ChunkSize: chunkSize,
177+
Off: off,
178+
Len: chunkSize * chunkCount,
179+
}))
180+
181+
chunks := binary.LittleEndian.Uint32(dst[:4])
182+
assert.Equal(suite.T(), uint32(chunkCount), chunks)
183+
184+
got := []uint64{
185+
binary.LittleEndian.Uint64(dst[4:12]),
186+
binary.LittleEndian.Uint64(dst[12:20]),
187+
}
188+
for i := range want {
189+
assert.Equal(suite.T(), want[i], got[i])
190+
}
191+
})
192+
}

0 commit comments

Comments
 (0)