-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
2 changed files
with
165 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,123 @@ | ||
package ybdbid | ||
|
||
/** | ||
https://yugabyte-db.slack.com/archives/CG0KQF0GG/p1644257955047349?thread_ts=1643868640.696389&cid=CG0KQF0GG | ||
------------------------------------------------------------------------------------------------------------ | ||
Please interpret the resp.snapshot_id() as an array of 16 bytes. And nothing more. (Not md5/base64/etc.) | ||
16 bytes. Each byte value is in: 0x00 - 0xFF range. | ||
In C++ code the decoding only checks the string size and do memcpy: | ||
Uuid Uuid::TryFullyDecode(const Slice& slice) { | ||
if (slice.size() != boost::uuids::uuid::static_size()) { | ||
return Uuid::Nil(); | ||
} | ||
Uuid id; | ||
memcpy(id.data(), slice.data(), boost::uuids::uuid::static_size()); | ||
return id; | ||
} | ||
https://yugabyte-db.slack.com/archives/CG0KQF0GG/p1644258454859919?thread_ts=1643868640.696389&cid=CG0KQF0GG | ||
------------------------------------------------------------------------------------------------------------ | ||
Just because the Snapshot ID for non-transaction-aware-snapshot (old snapshot when | ||
"transaction_aware=false" - not used now) was passed through the same PB field. So, in the code | ||
"32-bytes string" = old non-transactional snapshot UUID as a string. "16 bytes" = new transactional snapshot | ||
id in binary form. So.. two-in-one.. it's the reason of the complexity. Sorry. | ||
As the old (non-transactional) snapshots are not used more, you can always expect the 16 bytes. | ||
The case is only for Snapshot ID. | ||
Namespace/Table/Tablet ID is a UUID in simple string form. No such complexities. | ||
**/ | ||
|
||
import ( | ||
"fmt" | ||
"strings" | ||
|
||
"github.com/google/uuid" | ||
) | ||
|
||
// SnapshotID represents a parsed YugabyteDB snapshot ID. | ||
type SnapshotID interface { | ||
Bytes() []byte | ||
String() string | ||
UUID() uuid.UUID | ||
} | ||
|
||
type defaultSnapshotID struct { | ||
bytes []byte | ||
str string | ||
uuuid uuid.UUID | ||
} | ||
|
||
func (id *defaultSnapshotID) Bytes() []byte { | ||
return id.bytes | ||
} | ||
|
||
func (id *defaultSnapshotID) String() string { | ||
return id.str | ||
} | ||
|
||
func (id *defaultSnapshotID) UUID() uuid.UUID { | ||
return id.uuuid | ||
} | ||
|
||
// TryParseSnapshotIDFromBytes attempts to parse input bytes received | ||
// from the protobuf API as a YugabyteDB snapshot ID. | ||
func TryParseSnapshotIDFromBytes(input []byte) (SnapshotID, error) { | ||
|
||
if len(input) != 16 { | ||
return nil, fmt.Errorf("snapshot ID: input must be 16 bytes long") | ||
} | ||
|
||
aUUID := uuid.New() | ||
if err := aUUID.UnmarshalBinary(input); err != nil { | ||
return nil, err | ||
} | ||
output := &defaultSnapshotID{ | ||
bytes: make([]byte, len(input)), | ||
str: aUUID.String(), | ||
uuuid: aUUID, | ||
} | ||
copy(output.bytes, input) | ||
return output, nil | ||
} | ||
|
||
// TryParseSnapshotIDFromString attempts to parse input string as a YugabyteDB | ||
// snapshot ID. Input string must be a UUIDv4 string. | ||
func TryParseSnapshotIDFromString(input string) (SnapshotID, error) { | ||
|
||
switch len(input) { | ||
// xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ||
case 36: | ||
// urn:uuid:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx | ||
case 36 + 9: | ||
if strings.ToLower(input[:9]) != "urn:uuid:" { | ||
return nil, fmt.Errorf("snapshot ID: invalid urn prefix: %q", input[:9]) | ||
} | ||
// {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} | ||
case 36 + 2: | ||
default: | ||
return nil, fmt.Errorf("snapshot ID: invalid snapshot ID input") | ||
} | ||
|
||
// if failed, it could be a UUID string, can we parse it as such? | ||
aUUID, err := uuid.Parse(input) | ||
if err != nil { | ||
// no, it's neither base64 encoded, nor looks like UUID: | ||
return nil, fmt.Errorf("snapshot ID: input '%s' is not a valid YugabyteDB snapshot ID input", input) | ||
} | ||
|
||
// it parsed as UUID, we need the bytes too: | ||
bys, err := aUUID.MarshalBinary() | ||
if err != nil { | ||
return nil, fmt.Errorf("snapshot ID: input '%s' is a UUID but could not be marshaled", input) | ||
} | ||
|
||
output := &defaultSnapshotID{ | ||
bytes: make([]byte, len(bys)), | ||
str: aUUID.String(), | ||
uuuid: aUUID, | ||
} | ||
copy(output.bytes, bys) | ||
return output, nil | ||
} | ||
diff --git a/utils/ybdbid/snapshot_id_test.go b/utils/ybdbid/snapshot_id_test.go |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
package ybdbid | ||
|
||
import ( | ||
"testing" | ||
|
||
"github.com/stretchr/testify/assert" | ||
) | ||
|
||
func TestSnapshotIDParsing(t *testing.T) { | ||
|
||
t.Run("it=parses UUID formatted input and back", func(tt *testing.T) { | ||
|
||
validYBDBID := "dfec75ee-290e-4f3b-b965-469a0246c133" | ||
parsed, err := TryParseSnapshotIDFromString(validYBDBID) | ||
assert.Nil(tt, err) | ||
assert.Equal(tt, len(parsed.Bytes()), 16) | ||
|
||
parsedBackViaBytes, err := TryParseSnapshotIDFromBytes(parsed.Bytes()) | ||
assert.Nil(tt, err) | ||
assert.Equal(tt, parsed.String(), parsedBackViaBytes.String()) | ||
|
||
parsedBackViaString, err := TryParseSnapshotIDFromString(parsed.String()) | ||
assert.Nil(tt, err) | ||
assert.Equal(tt, parsed.String(), parsedBackViaString.String()) | ||
|
||
}) | ||
|
||
t.Run("it=handles non-UUID input", func(tt *testing.T) { | ||
invalidYBDBID := "dfec75ee-290e-4f3b---b965-469a0246c133" | ||
parsed, err := TryParseSnapshotIDFromString(invalidYBDBID) | ||
assert.NotNil(tt, err) | ||
assert.Nil(tt, parsed) | ||
}) | ||
|
||
t.Run("it=handles null byte input", func(tt *testing.T) { | ||
parsed, err := TryParseFromBytes(nil) | ||
assert.NotNil(tt, err) | ||
assert.Nil(tt, parsed) | ||
}) | ||
|
||
} | ||
|