github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/integration/v3_stm_test.go (about) 1 // Copyright 2016 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package integration 16 17 import ( 18 "context" 19 "fmt" 20 "math/rand" 21 "strconv" 22 "testing" 23 24 "github.com/lfch/etcd-io/client/pkg/v3/testutil" 25 v3 "github.com/lfch/etcd-io/client/v3" 26 "github.com/lfch/etcd-io/client/v3/concurrency" 27 "github.com/lfch/etcd-io/tests/v3/framework/integration" 28 ) 29 30 // TestSTMConflict tests that conflicts are retried. 31 func TestSTMConflict(t *testing.T) { 32 integration.BeforeTest(t) 33 34 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 3}) 35 defer clus.Terminate(t) 36 37 etcdc := clus.RandClient() 38 keys := make([]string, 5) 39 for i := 0; i < len(keys); i++ { 40 keys[i] = fmt.Sprintf("foo-%d", i) 41 if _, err := etcdc.Put(context.TODO(), keys[i], "100"); err != nil { 42 t.Fatalf("could not make key (%v)", err) 43 } 44 } 45 46 errc := make(chan error) 47 for i := range keys { 48 curEtcdc := clus.RandClient() 49 srcKey := keys[i] 50 applyf := func(stm concurrency.STM) error { 51 src := stm.Get(srcKey) 52 // must be different key to avoid double-adding 53 dstKey := srcKey 54 for dstKey == srcKey { 55 dstKey = keys[rand.Intn(len(keys))] 56 } 57 dst := stm.Get(dstKey) 58 srcV, _ := strconv.ParseInt(src, 10, 64) 59 dstV, _ := strconv.ParseInt(dst, 10, 64) 60 if srcV == 0 { 61 // can't rand.Intn on 0, so skip this transaction 62 return nil 63 } 64 xfer := int64(rand.Intn(int(srcV)) / 2) 65 stm.Put(srcKey, fmt.Sprintf("%d", srcV-xfer)) 66 stm.Put(dstKey, fmt.Sprintf("%d", dstV+xfer)) 67 return nil 68 } 69 go func() { 70 iso := concurrency.WithIsolation(concurrency.RepeatableReads) 71 _, err := concurrency.NewSTM(curEtcdc, applyf, iso) 72 errc <- err 73 }() 74 } 75 76 // wait for txns 77 for range keys { 78 if err := <-errc; err != nil { 79 t.Fatalf("apply failed (%v)", err) 80 } 81 } 82 83 // ensure sum matches initial sum 84 sum := 0 85 for _, oldkey := range keys { 86 rk, err := etcdc.Get(context.TODO(), oldkey) 87 if err != nil { 88 t.Fatalf("couldn't fetch key %s (%v)", oldkey, err) 89 } 90 v, _ := strconv.ParseInt(string(rk.Kvs[0].Value), 10, 64) 91 sum += int(v) 92 } 93 if sum != len(keys)*100 { 94 t.Fatalf("bad sum. got %d, expected %d", sum, len(keys)*100) 95 } 96 } 97 98 // TestSTMPutNewKey confirms a STM put on a new key is visible after commit. 99 func TestSTMPutNewKey(t *testing.T) { 100 integration.BeforeTest(t) 101 102 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) 103 defer clus.Terminate(t) 104 105 etcdc := clus.RandClient() 106 applyf := func(stm concurrency.STM) error { 107 stm.Put("foo", "bar") 108 return nil 109 } 110 111 iso := concurrency.WithIsolation(concurrency.RepeatableReads) 112 if _, err := concurrency.NewSTM(etcdc, applyf, iso); err != nil { 113 t.Fatalf("error on stm txn (%v)", err) 114 } 115 116 resp, err := etcdc.Get(context.TODO(), "foo") 117 if err != nil { 118 t.Fatalf("error fetching key (%v)", err) 119 } 120 if string(resp.Kvs[0].Value) != "bar" { 121 t.Fatalf("bad value. got %+v, expected 'bar' value", resp) 122 } 123 } 124 125 // TestSTMAbort tests that an aborted txn does not modify any keys. 126 func TestSTMAbort(t *testing.T) { 127 integration.BeforeTest(t) 128 129 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) 130 defer clus.Terminate(t) 131 132 etcdc := clus.RandClient() 133 ctx, cancel := context.WithCancel(context.TODO()) 134 applyf := func(stm concurrency.STM) error { 135 stm.Put("foo", "baz") 136 cancel() 137 stm.Put("foo", "bap") 138 return nil 139 } 140 141 iso := concurrency.WithIsolation(concurrency.RepeatableReads) 142 sctx := concurrency.WithAbortContext(ctx) 143 if _, err := concurrency.NewSTM(etcdc, applyf, iso, sctx); err == nil { 144 t.Fatalf("no error on stm txn") 145 } 146 147 resp, err := etcdc.Get(context.TODO(), "foo") 148 if err != nil { 149 t.Fatalf("error fetching key (%v)", err) 150 } 151 if len(resp.Kvs) != 0 { 152 t.Fatalf("bad value. got %+v, expected nothing", resp) 153 } 154 } 155 156 // TestSTMSerialize tests that serialization is honored when serializable. 157 func TestSTMSerialize(t *testing.T) { 158 integration.BeforeTest(t) 159 160 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 3}) 161 defer clus.Terminate(t) 162 163 etcdc := clus.RandClient() 164 165 // set up initial keys 166 keys := make([]string, 5) 167 for i := 0; i < len(keys); i++ { 168 keys[i] = fmt.Sprintf("foo-%d", i) 169 } 170 171 // update keys in full batches 172 updatec := make(chan struct{}) 173 go func() { 174 defer close(updatec) 175 for i := 0; i < 5; i++ { 176 s := fmt.Sprintf("%d", i) 177 var ops []v3.Op 178 for _, k := range keys { 179 ops = append(ops, v3.OpPut(k, s)) 180 } 181 if _, err := etcdc.Txn(context.TODO()).Then(ops...).Commit(); err != nil { 182 t.Errorf("couldn't put keys (%v)", err) 183 } 184 updatec <- struct{}{} 185 } 186 }() 187 188 // read all keys in txn, make sure all values match 189 errc := make(chan error) 190 for range updatec { 191 curEtcdc := clus.RandClient() 192 applyf := func(stm concurrency.STM) error { 193 var vs []string 194 for i := range keys { 195 vs = append(vs, stm.Get(keys[i])) 196 } 197 for i := range vs { 198 if vs[0] != vs[i] { 199 return fmt.Errorf("got vs[%d] = %v, want %v", i, vs[i], vs[0]) 200 } 201 } 202 return nil 203 } 204 go func() { 205 iso := concurrency.WithIsolation(concurrency.Serializable) 206 _, err := concurrency.NewSTM(curEtcdc, applyf, iso) 207 errc <- err 208 }() 209 } 210 211 for i := 0; i < 5; i++ { 212 if err := <-errc; err != nil { 213 t.Error(err) 214 } 215 } 216 } 217 218 // TestSTMApplyOnConcurrentDeletion ensures that concurrent key deletion 219 // fails the first GET revision comparison within STM; trigger retry. 220 func TestSTMApplyOnConcurrentDeletion(t *testing.T) { 221 integration.BeforeTest(t) 222 223 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) 224 defer clus.Terminate(t) 225 226 etcdc := clus.RandClient() 227 if _, err := etcdc.Put(context.TODO(), "foo", "bar"); err != nil { 228 t.Fatal(err) 229 } 230 donec, readyc := make(chan struct{}), make(chan struct{}) 231 go func() { 232 <-readyc 233 if _, err := etcdc.Delete(context.TODO(), "foo"); err != nil { 234 t.Error(err) 235 } 236 close(donec) 237 }() 238 239 try := 0 240 applyf := func(stm concurrency.STM) error { 241 try++ 242 stm.Get("foo") 243 if try == 1 { 244 // trigger delete to make GET rev comparison outdated 245 close(readyc) 246 <-donec 247 } 248 stm.Put("foo2", "bar2") 249 return nil 250 } 251 252 iso := concurrency.WithIsolation(concurrency.RepeatableReads) 253 if _, err := concurrency.NewSTM(etcdc, applyf, iso); err != nil { 254 t.Fatalf("error on stm txn (%v)", err) 255 } 256 if try != 2 { 257 t.Fatalf("STM apply expected to run twice, got %d", try) 258 } 259 260 resp, err := etcdc.Get(context.TODO(), "foo2") 261 if err != nil { 262 t.Fatalf("error fetching key (%v)", err) 263 } 264 if string(resp.Kvs[0].Value) != "bar2" { 265 t.Fatalf("bad value. got %+v, expected 'bar2' value", resp) 266 } 267 } 268 269 func TestSTMSerializableSnapshotPut(t *testing.T) { 270 integration.BeforeTest(t) 271 272 clus := integration.NewCluster(t, &integration.ClusterConfig{Size: 1}) 273 defer clus.Terminate(t) 274 275 cli := clus.Client(0) 276 // key with lower create/mod revision than keys being updated 277 _, err := cli.Put(context.TODO(), "a", "0") 278 testutil.AssertNil(t, err) 279 280 tries := 0 281 applyf := func(stm concurrency.STM) error { 282 if tries > 2 { 283 return fmt.Errorf("too many retries") 284 } 285 tries++ 286 stm.Get("a") 287 stm.Put("b", "1") 288 return nil 289 } 290 291 iso := concurrency.WithIsolation(concurrency.SerializableSnapshot) 292 _, err = concurrency.NewSTM(cli, applyf, iso) 293 testutil.AssertNil(t, err) 294 _, err = concurrency.NewSTM(cli, applyf, iso) 295 testutil.AssertNil(t, err) 296 297 resp, err := cli.Get(context.TODO(), "b") 298 testutil.AssertNil(t, err) 299 if resp.Kvs[0].Version != 2 { 300 t.Fatalf("bad version. got %+v, expected version 2", resp) 301 } 302 }