diff --git a/pkg/registry/core/service/storage/transaction.go b/pkg/registry/core/service/storage/transaction.go new file mode 100644 index 00000000000..417e57323bd --- /dev/null +++ b/pkg/registry/core/service/storage/transaction.go @@ -0,0 +1,62 @@ +/* +Copyright 2020 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +// transaction represents something that may need to be finalized on success or +// failure of the larger transaction. +type transaction interface { + // Commit tells the transaction to finalize any changes it may have + // pending. This cannot fail, so errors must be handled internally. + Commit() + + // Revert tells the transaction to abandon or undo any changes it may have + // pending. This cannot fail, so errors must be handled internally. + Revert() +} + +// metaTransaction is a collection of transactions. +type metaTransaction []transaction + +func (mt metaTransaction) Commit() { + for _, t := range mt { + t.Commit() + } +} + +func (mt metaTransaction) Revert() { + for _, t := range mt { + t.Revert() + } +} + +// callbackTransaction is a transaction which calls arbitrary functions. +type callbackTransaction struct { + commit func() + revert func() +} + +func (cb callbackTransaction) Commit() { + if cb.commit != nil { + cb.commit() + } +} + +func (cb callbackTransaction) Revert() { + if cb.revert != nil { + cb.revert() + } +} diff --git a/pkg/registry/core/service/storage/transaction_test.go b/pkg/registry/core/service/storage/transaction_test.go new file mode 100644 index 00000000000..e4f4e3220f4 --- /dev/null +++ b/pkg/registry/core/service/storage/transaction_test.go @@ -0,0 +1,167 @@ +/* +Copyright 2021 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package storage + +import ( + "testing" +) + +func Test_metaTransaction(t *testing.T) { + const initial = 10 + var temp int + + tests := []struct { + name string + mt metaTransaction + start int + want int + }{{ + name: "commit and revert match", + mt: metaTransaction{ + callbackTransaction{ + commit: func() { + temp = temp + 1 + }, + revert: func() { + temp = temp - 1 + }, + }, + }, + want: 10, + }, { + name: "commit and revert match multiple times", + mt: metaTransaction{ + callbackTransaction{ + commit: func() { + temp = temp + 1 + }, + revert: func() { + temp = temp - 1 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 2 + }, + revert: func() { + temp = temp - 2 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 3 + }, + revert: func() { + temp = temp - 3 + }, + }, + }, + want: 10, + }, { + name: "missing revert", + mt: metaTransaction{ + callbackTransaction{ + commit: func() { + temp = temp + 1 + }, + revert: func() { + temp = temp - 1 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 2 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 3 + }, + revert: func() { + temp = temp - 3 + }, + }, + }, + want: 12, + }, { + name: "missing commit", + mt: metaTransaction{ + callbackTransaction{ + commit: func() { + temp = temp + 1 + }, + revert: func() { + temp = temp - 1 + }, + }, + callbackTransaction{ + revert: func() { + temp = temp - 2 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 3 + }, + revert: func() { + temp = temp - 3 + }, + }, + }, + want: 8, + }, { + name: "commit and revert match multiple but different order", + mt: metaTransaction{ + callbackTransaction{ + commit: func() { + temp = temp + 1 + }, + revert: func() { + temp = temp - 2 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 2 + }, + revert: func() { + temp = temp - 1 + }, + }, + callbackTransaction{ + commit: func() { + temp = temp + 3 + }, + revert: func() { + temp = temp - 3 + }, + }, + }, + want: 10, + }} + t.Parallel() + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + temp = initial + tt.mt.Commit() + tt.mt.Revert() + if temp != tt.want { + t.Fatalf("expected %d got %d", tt.want, temp) + } + }) + } +}