1// Copyright 2015 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 15package backend 16 17import ( 18 "fmt" 19 "io/ioutil" 20 "os" 21 "reflect" 22 "testing" 23 "time" 24 25 bolt "github.com/coreos/bbolt" 26) 27 28func TestBackendClose(t *testing.T) { 29 b, tmpPath := NewTmpBackend(time.Hour, 10000) 30 defer os.Remove(tmpPath) 31 32 // check close could work 33 done := make(chan struct{}) 34 go func() { 35 err := b.Close() 36 if err != nil { 37 t.Errorf("close error = %v, want nil", err) 38 } 39 done <- struct{}{} 40 }() 41 select { 42 case <-done: 43 case <-time.After(10 * time.Second): 44 t.Errorf("failed to close database in 10s") 45 } 46} 47 48func TestBackendSnapshot(t *testing.T) { 49 b, tmpPath := NewTmpBackend(time.Hour, 10000) 50 defer cleanup(b, tmpPath) 51 52 tx := b.BatchTx() 53 tx.Lock() 54 tx.UnsafeCreateBucket([]byte("test")) 55 tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar")) 56 tx.Unlock() 57 b.ForceCommit() 58 59 // write snapshot to a new file 60 f, err := ioutil.TempFile(os.TempDir(), "etcd_backend_test") 61 if err != nil { 62 t.Fatal(err) 63 } 64 snap := b.Snapshot() 65 defer snap.Close() 66 if _, err := snap.WriteTo(f); err != nil { 67 t.Fatal(err) 68 } 69 f.Close() 70 71 // bootstrap new backend from the snapshot 72 bcfg := DefaultBackendConfig() 73 bcfg.Path, bcfg.BatchInterval, bcfg.BatchLimit = f.Name(), time.Hour, 10000 74 nb := New(bcfg) 75 defer cleanup(nb, f.Name()) 76 77 newTx := b.BatchTx() 78 newTx.Lock() 79 ks, _ := newTx.UnsafeRange([]byte("test"), []byte("foo"), []byte("goo"), 0) 80 if len(ks) != 1 { 81 t.Errorf("len(kvs) = %d, want 1", len(ks)) 82 } 83 newTx.Unlock() 84} 85 86func TestBackendBatchIntervalCommit(t *testing.T) { 87 // start backend with super short batch interval so 88 // we do not need to wait long before commit to happen. 89 b, tmpPath := NewTmpBackend(time.Nanosecond, 10000) 90 defer cleanup(b, tmpPath) 91 92 pc := b.Commits() 93 94 tx := b.BatchTx() 95 tx.Lock() 96 tx.UnsafeCreateBucket([]byte("test")) 97 tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar")) 98 tx.Unlock() 99 100 for i := 0; i < 10; i++ { 101 if b.Commits() >= pc+1 { 102 break 103 } 104 time.Sleep(time.Duration(i*100) * time.Millisecond) 105 } 106 107 // check whether put happens via db view 108 b.db.View(func(tx *bolt.Tx) error { 109 bucket := tx.Bucket([]byte("test")) 110 if bucket == nil { 111 t.Errorf("bucket test does not exit") 112 return nil 113 } 114 v := bucket.Get([]byte("foo")) 115 if v == nil { 116 t.Errorf("foo key failed to written in backend") 117 } 118 return nil 119 }) 120} 121 122func TestBackendDefrag(t *testing.T) { 123 b, tmpPath := NewDefaultTmpBackend() 124 defer cleanup(b, tmpPath) 125 126 tx := b.BatchTx() 127 tx.Lock() 128 tx.UnsafeCreateBucket([]byte("test")) 129 for i := 0; i < defragLimit+100; i++ { 130 tx.UnsafePut([]byte("test"), []byte(fmt.Sprintf("foo_%d", i)), []byte("bar")) 131 } 132 tx.Unlock() 133 b.ForceCommit() 134 135 // remove some keys to ensure the disk space will be reclaimed after defrag 136 tx = b.BatchTx() 137 tx.Lock() 138 for i := 0; i < 50; i++ { 139 tx.UnsafeDelete([]byte("test"), []byte(fmt.Sprintf("foo_%d", i))) 140 } 141 tx.Unlock() 142 b.ForceCommit() 143 144 size := b.Size() 145 146 // shrink and check hash 147 oh, err := b.Hash(nil) 148 if err != nil { 149 t.Fatal(err) 150 } 151 152 err = b.Defrag() 153 if err != nil { 154 t.Fatal(err) 155 } 156 157 nh, err := b.Hash(nil) 158 if err != nil { 159 t.Fatal(err) 160 } 161 if oh != nh { 162 t.Errorf("hash = %v, want %v", nh, oh) 163 } 164 165 nsize := b.Size() 166 if nsize >= size { 167 t.Errorf("new size = %v, want < %d", nsize, size) 168 } 169 170 // try put more keys after shrink. 171 tx = b.BatchTx() 172 tx.Lock() 173 tx.UnsafeCreateBucket([]byte("test")) 174 tx.UnsafePut([]byte("test"), []byte("more"), []byte("bar")) 175 tx.Unlock() 176 b.ForceCommit() 177} 178 179// TestBackendWriteback ensures writes are stored to the read txn on write txn unlock. 180func TestBackendWriteback(t *testing.T) { 181 b, tmpPath := NewDefaultTmpBackend() 182 defer cleanup(b, tmpPath) 183 184 tx := b.BatchTx() 185 tx.Lock() 186 tx.UnsafeCreateBucket([]byte("key")) 187 tx.UnsafePut([]byte("key"), []byte("abc"), []byte("bar")) 188 tx.UnsafePut([]byte("key"), []byte("def"), []byte("baz")) 189 tx.UnsafePut([]byte("key"), []byte("overwrite"), []byte("1")) 190 tx.Unlock() 191 192 // overwrites should be propagated too 193 tx.Lock() 194 tx.UnsafePut([]byte("key"), []byte("overwrite"), []byte("2")) 195 tx.Unlock() 196 197 keys := []struct { 198 key []byte 199 end []byte 200 limit int64 201 202 wkey [][]byte 203 wval [][]byte 204 }{ 205 { 206 key: []byte("abc"), 207 end: nil, 208 209 wkey: [][]byte{[]byte("abc")}, 210 wval: [][]byte{[]byte("bar")}, 211 }, 212 { 213 key: []byte("abc"), 214 end: []byte("def"), 215 216 wkey: [][]byte{[]byte("abc")}, 217 wval: [][]byte{[]byte("bar")}, 218 }, 219 { 220 key: []byte("abc"), 221 end: []byte("deg"), 222 223 wkey: [][]byte{[]byte("abc"), []byte("def")}, 224 wval: [][]byte{[]byte("bar"), []byte("baz")}, 225 }, 226 { 227 key: []byte("abc"), 228 end: []byte("\xff"), 229 limit: 1, 230 231 wkey: [][]byte{[]byte("abc")}, 232 wval: [][]byte{[]byte("bar")}, 233 }, 234 { 235 key: []byte("abc"), 236 end: []byte("\xff"), 237 238 wkey: [][]byte{[]byte("abc"), []byte("def"), []byte("overwrite")}, 239 wval: [][]byte{[]byte("bar"), []byte("baz"), []byte("2")}, 240 }, 241 } 242 rtx := b.ReadTx() 243 for i, tt := range keys { 244 rtx.Lock() 245 k, v := rtx.UnsafeRange([]byte("key"), tt.key, tt.end, tt.limit) 246 rtx.Unlock() 247 if !reflect.DeepEqual(tt.wkey, k) || !reflect.DeepEqual(tt.wval, v) { 248 t.Errorf("#%d: want k=%+v, v=%+v; got k=%+v, v=%+v", i, tt.wkey, tt.wval, k, v) 249 } 250 } 251} 252 253func cleanup(b Backend, path string) { 254 b.Close() 255 os.Remove(path) 256} 257