-
Notifications
You must be signed in to change notification settings - Fork 50
fix: implement FileAuditKV.MultiSave instead of returning "not implemented" #521
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,130 @@ | ||
| package kv | ||
|
|
||
| import ( | ||
| "bufio" | ||
| "context" | ||
| "os" | ||
| "testing" | ||
|
|
||
| "github.com/cockroachdb/errors" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| "go.etcd.io/etcd/api/v3/mvccpb" | ||
| ) | ||
|
|
||
| // fakeMetaKV is a minimal in-memory MetaKV used to verify FileAuditKV | ||
| // delegates to the wrapped client instead of swallowing calls. | ||
| type fakeMetaKV struct { | ||
| data map[string]string | ||
|
|
||
| multiSaveErr error | ||
| multiSaveCalls [][2][]string | ||
| } | ||
|
|
||
| func newFakeMetaKV() *fakeMetaKV { | ||
| return &fakeMetaKV{data: map[string]string{}} | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) Load(ctx context.Context, key string, opts ...LoadOption) (string, error) { | ||
| v, ok := f.data[key] | ||
| if !ok { | ||
| return "", errors.New("key not found") | ||
| } | ||
| return v, nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) LoadWithPrefix(ctx context.Context, key string, opts ...LoadOption) ([]string, []string, error) { | ||
| return nil, nil, nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) Save(ctx context.Context, key, value string) error { | ||
| f.data[key] = value | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) MultiSave(ctx context.Context, keys, values []string) error { | ||
| f.multiSaveCalls = append(f.multiSaveCalls, [2][]string{keys, values}) | ||
| if f.multiSaveErr != nil { | ||
| return f.multiSaveErr | ||
| } | ||
| for i, key := range keys { | ||
| f.data[key] = values[i] | ||
| } | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) Remove(ctx context.Context, key string) error { | ||
| delete(f.data, key) | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) RemoveWithPrefix(ctx context.Context, key string) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) removeWithPrevKV(ctx context.Context, key string) (*mvccpb.KeyValue, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) removeWithPrefixAndPrevKV(ctx context.Context, prefix string) ([]*mvccpb.KeyValue, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) GetAllRootPath(ctx context.Context) ([]string, error) { | ||
| return nil, nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) BackupKV(base, prefix string, w *bufio.Writer, ignoreRevision bool, batchSize int64) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) WalkWithPrefix(ctx context.Context, prefix string, paginationSize int, fn func([]byte, []byte) error) error { | ||
| return nil | ||
| } | ||
|
|
||
| func (f *fakeMetaKV) Close() {} | ||
|
|
||
| func newTestFileAuditKV(t *testing.T, cli MetaKV) *FileAuditKV { | ||
| t.Helper() | ||
| f, err := os.CreateTemp(t.TempDir(), "audit-*.log") | ||
| require.NoError(t, err) | ||
| t.Cleanup(func() { f.Close() }) | ||
| return NewFileAuditKV(cli, f) | ||
| } | ||
|
|
||
| func TestFileAuditKVMultiSave(t *testing.T) { | ||
| t.Run("delegates and persists values", func(t *testing.T) { | ||
| fake := newFakeMetaKV() | ||
| audit := newTestFileAuditKV(t, fake) | ||
|
|
||
| keys := []string{"k1", "k2"} | ||
| values := []string{"v1", "v2"} | ||
| err := audit.MultiSave(context.TODO(), keys, values) | ||
| require.NoError(t, err) | ||
|
|
||
| require.Len(t, fake.multiSaveCalls, 1) | ||
| assert.Equal(t, keys, fake.multiSaveCalls[0][0]) | ||
| assert.Equal(t, values, fake.multiSaveCalls[0][1]) | ||
| assert.Equal(t, "v1", fake.data["k1"]) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. states/kv/kv_audit_test.go line:108
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agreed, added in 731e2c8. Two new subtests in states/kv/kv_audit_test.go replay the raw file (length-prefixed records, decoded via proto.Unmarshal into AuditHeader / mvccpb.KeyValue) and assert on it directly:
That locks in the on-disk format so a regression in header ordering/counts or dropped records would now fail the test. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The issue is confirmed fixed. |
||
| assert.Equal(t, "v2", fake.data["k2"]) | ||
| }) | ||
|
|
||
| t.Run("propagates underlying error", func(t *testing.T) { | ||
| fake := newFakeMetaKV() | ||
| fake.multiSaveErr = errors.New("injected failure") | ||
| audit := newTestFileAuditKV(t, fake) | ||
|
|
||
| err := audit.MultiSave(context.TODO(), []string{"k1"}, []string{"v1"}) | ||
| assert.ErrorIs(t, err, fake.multiSaveErr) | ||
| assert.Empty(t, fake.data) | ||
| }) | ||
|
|
||
| t.Run("rejects mismatched keys and values", func(t *testing.T) { | ||
| fake := newFakeMetaKV() | ||
| audit := newTestFileAuditKV(t, fake) | ||
|
|
||
| err := audit.MultiSave(context.TODO(), []string{"k1", "k2"}, []string{"v1"}) | ||
| assert.Error(t, err) | ||
| assert.Empty(t, fake.multiSaveCalls) | ||
| }) | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
states/kv/kv_audit.go line:58
Medium ---- This delegation is correct and faithfully mirrors the Save() pattern, but the stated motivation (repair/restore broken by this stub) does not match the client wiring at head.
repair segment-storage-layoutruns onComponentRepair, which is constructed with the unwrapped client (states/instance.go:182 passescli, and states/backup_mock_connect.go:126 builds the client with plainkv.NewEtcdKV), so itsMultiSavecalls never reachFileAuditKV. On the TiKV connection the "not implemented" error actually comes fromtxnTiKV.MultiSave(states/kv/kv.go:476), which this PR does not touch, so that repair path stays broken;restore/load-backuplikewise writes through the raw etcd client. Could you confirm which path you reproduced the failure on, and whether fixingtxnTiKV.MultiSave(or wiring the components through the audit wrapper) belongs in this PR?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch, and you're right on both counts.
I reproduced this on the
restore/load-backuppath (restoreEtcdFromBackV2instates/etcd_restore.go:118, which writes viastate.client— the audit-wrappedkv), not on repair. On repair specifically, the actual bug was one step earlier:GetInstanceStateinstates/instance.goconstructedComponentRepair/ComponentRemove/ComponentShow/ComponentSetwith the rawcliinstead of the audit-wrappedkv, so those components' writes never reachedFileAuditKVat all — theMultiSavefix alone wouldn't have helped them.I've pushed a follow-up commit (731e2c8) that wires all four components through the audit-wrapped
kv, so repair segment-storage-layout now goes throughFileAuditKV.MultiSaveas intended.txnTiKV.MultiSave(states/kv/kv.go:476) is a separate, still-unimplemented method on a different backend — happy to file that as its own follow-up issue rather than scope-creep this PR, unless you'd prefer it bundled here too.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The issue is confirmed fixed.