mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-06 17:57:51 +00:00
Add vendor folder to git
This commit is contained in:
parent
66cf5eaafb
commit
183585f56f
6916 changed files with 2629581 additions and 1 deletions
352
vendor/github.com/coreos/etcd/mvcc/backend/backend.go
generated
vendored
Normal file
352
vendor/github.com/coreos/etcd/mvcc/backend/backend.go
generated
vendored
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultBatchLimit = 10000
|
||||
defaultBatchInterval = 100 * time.Millisecond
|
||||
|
||||
defragLimit = 10000
|
||||
|
||||
// InitialMmapSize is the initial size of the mmapped region. Setting this larger than
|
||||
// the potential max db size can prevent writer from blocking reader.
|
||||
// This only works for linux.
|
||||
InitialMmapSize = int64(10 * 1024 * 1024 * 1024)
|
||||
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "mvcc/backend")
|
||||
)
|
||||
|
||||
const (
|
||||
// DefaultQuotaBytes is the number of bytes the backend Size may
|
||||
// consume before exceeding the space quota.
|
||||
DefaultQuotaBytes = int64(2 * 1024 * 1024 * 1024) // 2GB
|
||||
// MaxQuotaBytes is the maximum number of bytes suggested for a backend
|
||||
// quota. A larger quota may lead to degraded performance.
|
||||
MaxQuotaBytes = int64(8 * 1024 * 1024 * 1024) // 8GB
|
||||
)
|
||||
|
||||
type Backend interface {
|
||||
BatchTx() BatchTx
|
||||
Snapshot() Snapshot
|
||||
Hash(ignores map[IgnoreKey]struct{}) (uint32, error)
|
||||
// Size returns the current size of the backend.
|
||||
Size() int64
|
||||
Defrag() error
|
||||
ForceCommit()
|
||||
Close() error
|
||||
}
|
||||
|
||||
type Snapshot interface {
|
||||
// Size gets the size of the snapshot.
|
||||
Size() int64
|
||||
// WriteTo writes the snapshot into the given writer.
|
||||
WriteTo(w io.Writer) (n int64, err error)
|
||||
// Close closes the snapshot.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type backend struct {
|
||||
// size and commits are used with atomic operations so they must be
|
||||
// 64-bit aligned, otherwise 32-bit tests will crash
|
||||
|
||||
// size is the number of bytes in the backend
|
||||
size int64
|
||||
// commits counts number of commits since start
|
||||
commits int64
|
||||
|
||||
mu sync.RWMutex
|
||||
db *bolt.DB
|
||||
|
||||
batchInterval time.Duration
|
||||
batchLimit int
|
||||
batchTx *batchTx
|
||||
|
||||
stopc chan struct{}
|
||||
donec chan struct{}
|
||||
}
|
||||
|
||||
func New(path string, d time.Duration, limit int) Backend {
|
||||
return newBackend(path, d, limit)
|
||||
}
|
||||
|
||||
func NewDefaultBackend(path string) Backend {
|
||||
return newBackend(path, defaultBatchInterval, defaultBatchLimit)
|
||||
}
|
||||
|
||||
func newBackend(path string, d time.Duration, limit int) *backend {
|
||||
db, err := bolt.Open(path, 0600, boltOpenOptions)
|
||||
if err != nil {
|
||||
plog.Panicf("cannot open database at %s (%v)", path, err)
|
||||
}
|
||||
|
||||
b := &backend{
|
||||
db: db,
|
||||
|
||||
batchInterval: d,
|
||||
batchLimit: limit,
|
||||
|
||||
stopc: make(chan struct{}),
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
b.batchTx = newBatchTx(b)
|
||||
go b.run()
|
||||
return b
|
||||
}
|
||||
|
||||
// BatchTx returns the current batch tx in coalescer. The tx can be used for read and
|
||||
// write operations. The write result can be retrieved within the same tx immediately.
|
||||
// The write result is isolated with other txs until the current one get committed.
|
||||
func (b *backend) BatchTx() BatchTx {
|
||||
return b.batchTx
|
||||
}
|
||||
|
||||
// ForceCommit forces the current batching tx to commit.
|
||||
func (b *backend) ForceCommit() {
|
||||
b.batchTx.Commit()
|
||||
}
|
||||
|
||||
func (b *backend) Snapshot() Snapshot {
|
||||
b.batchTx.Commit()
|
||||
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
tx, err := b.db.Begin(false)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot begin tx (%s)", err)
|
||||
}
|
||||
return &snapshot{tx}
|
||||
}
|
||||
|
||||
type IgnoreKey struct {
|
||||
Bucket string
|
||||
Key string
|
||||
}
|
||||
|
||||
func (b *backend) Hash(ignores map[IgnoreKey]struct{}) (uint32, error) {
|
||||
h := crc32.New(crc32.MakeTable(crc32.Castagnoli))
|
||||
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
err := b.db.View(func(tx *bolt.Tx) error {
|
||||
c := tx.Cursor()
|
||||
for next, _ := c.First(); next != nil; next, _ = c.Next() {
|
||||
b := tx.Bucket(next)
|
||||
if b == nil {
|
||||
return fmt.Errorf("cannot get hash of bucket %s", string(next))
|
||||
}
|
||||
h.Write(next)
|
||||
b.ForEach(func(k, v []byte) error {
|
||||
bk := IgnoreKey{Bucket: string(next), Key: string(k)}
|
||||
if _, ok := ignores[bk]; !ok {
|
||||
h.Write(k)
|
||||
h.Write(v)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return h.Sum32(), nil
|
||||
}
|
||||
|
||||
func (b *backend) Size() int64 {
|
||||
return atomic.LoadInt64(&b.size)
|
||||
}
|
||||
|
||||
func (b *backend) run() {
|
||||
defer close(b.donec)
|
||||
t := time.NewTimer(b.batchInterval)
|
||||
defer t.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.C:
|
||||
case <-b.stopc:
|
||||
b.batchTx.CommitAndStop()
|
||||
return
|
||||
}
|
||||
b.batchTx.Commit()
|
||||
t.Reset(b.batchInterval)
|
||||
}
|
||||
}
|
||||
|
||||
func (b *backend) Close() error {
|
||||
close(b.stopc)
|
||||
<-b.donec
|
||||
return b.db.Close()
|
||||
}
|
||||
|
||||
// Commits returns total number of commits since start
|
||||
func (b *backend) Commits() int64 {
|
||||
return atomic.LoadInt64(&b.commits)
|
||||
}
|
||||
|
||||
func (b *backend) Defrag() error {
|
||||
err := b.defrag()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// commit to update metadata like db.size
|
||||
b.batchTx.Commit()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *backend) defrag() error {
|
||||
// TODO: make this non-blocking?
|
||||
// lock batchTx to ensure nobody is using previous tx, and then
|
||||
// close previous ongoing tx.
|
||||
b.batchTx.Lock()
|
||||
defer b.batchTx.Unlock()
|
||||
|
||||
// lock database after lock tx to avoid deadlock.
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
|
||||
b.batchTx.commit(true)
|
||||
b.batchTx.tx = nil
|
||||
|
||||
tmpdb, err := bolt.Open(b.db.Path()+".tmp", 0600, boltOpenOptions)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = defragdb(b.db, tmpdb, defragLimit)
|
||||
|
||||
if err != nil {
|
||||
tmpdb.Close()
|
||||
os.RemoveAll(tmpdb.Path())
|
||||
return err
|
||||
}
|
||||
|
||||
dbp := b.db.Path()
|
||||
tdbp := tmpdb.Path()
|
||||
|
||||
err = b.db.Close()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot close database (%s)", err)
|
||||
}
|
||||
err = tmpdb.Close()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot close database (%s)", err)
|
||||
}
|
||||
err = os.Rename(tdbp, dbp)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot rename database (%s)", err)
|
||||
}
|
||||
|
||||
b.db, err = bolt.Open(dbp, 0600, boltOpenOptions)
|
||||
if err != nil {
|
||||
plog.Panicf("cannot open database at %s (%v)", dbp, err)
|
||||
}
|
||||
b.batchTx.tx, err = b.db.Begin(true)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot begin tx (%s)", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func defragdb(odb, tmpdb *bolt.DB, limit int) error {
|
||||
// open a tx on tmpdb for writes
|
||||
tmptx, err := tmpdb.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// open a tx on old db for read
|
||||
tx, err := odb.Begin(false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
|
||||
c := tx.Cursor()
|
||||
|
||||
count := 0
|
||||
for next, _ := c.First(); next != nil; next, _ = c.Next() {
|
||||
b := tx.Bucket(next)
|
||||
if b == nil {
|
||||
return fmt.Errorf("backend: cannot defrag bucket %s", string(next))
|
||||
}
|
||||
|
||||
tmpb, berr := tmptx.CreateBucketIfNotExists(next)
|
||||
tmpb.FillPercent = 0.9 // for seq write in for each
|
||||
if berr != nil {
|
||||
return berr
|
||||
}
|
||||
|
||||
b.ForEach(func(k, v []byte) error {
|
||||
count++
|
||||
if count > limit {
|
||||
err = tmptx.Commit()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmptx, err = tmpdb.Begin(true)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tmpb = tmptx.Bucket(next)
|
||||
tmpb.FillPercent = 0.9 // for seq write in for each
|
||||
|
||||
count = 0
|
||||
}
|
||||
return tmpb.Put(k, v)
|
||||
})
|
||||
}
|
||||
|
||||
return tmptx.Commit()
|
||||
}
|
||||
|
||||
// NewTmpBackend creates a backend implementation for testing.
|
||||
func NewTmpBackend(batchInterval time.Duration, batchLimit int) (*backend, string) {
|
||||
dir, err := ioutil.TempDir(os.TempDir(), "etcd_backend_test")
|
||||
if err != nil {
|
||||
plog.Fatal(err)
|
||||
}
|
||||
tmpPath := filepath.Join(dir, "database")
|
||||
return newBackend(tmpPath, batchInterval, batchLimit), tmpPath
|
||||
}
|
||||
|
||||
func NewDefaultTmpBackend() (*backend, string) {
|
||||
return NewTmpBackend(defaultBatchInterval, defaultBatchLimit)
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
*bolt.Tx
|
||||
}
|
||||
|
||||
func (s *snapshot) Close() error { return s.Tx.Rollback() }
|
||||
50
vendor/github.com/coreos/etcd/mvcc/backend/backend_bench_test.go
generated
vendored
Normal file
50
vendor/github.com/coreos/etcd/mvcc/backend/backend_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func BenchmarkBackendPut(b *testing.B) {
|
||||
backend := New("test", 100*time.Millisecond, 10000)
|
||||
defer backend.Close()
|
||||
defer os.Remove("test")
|
||||
|
||||
// prepare keys
|
||||
keys := make([][]byte, b.N)
|
||||
for i := 0; i < b.N; i++ {
|
||||
keys[i] = make([]byte, 64)
|
||||
rand.Read(keys[i])
|
||||
}
|
||||
value := make([]byte, 128)
|
||||
rand.Read(value)
|
||||
|
||||
batchTx := backend.BatchTx()
|
||||
|
||||
batchTx.Lock()
|
||||
batchTx.UnsafeCreateBucket([]byte("test"))
|
||||
batchTx.Unlock()
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
batchTx.Lock()
|
||||
batchTx.UnsafePut([]byte("test"), keys[i], value)
|
||||
batchTx.Unlock()
|
||||
}
|
||||
}
|
||||
179
vendor/github.com/coreos/etcd/mvcc/backend/backend_test.go
generated
vendored
Normal file
179
vendor/github.com/coreos/etcd/mvcc/backend/backend_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,179 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
func TestBackendClose(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
// check close could work
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
err := b.Close()
|
||||
if err != nil {
|
||||
t.Errorf("close error = %v, want nil", err)
|
||||
}
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Errorf("failed to close database in 10s")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackendSnapshot(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
// write snapshot to a new file
|
||||
f, err := ioutil.TempFile(os.TempDir(), "etcd_backend_test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
snap := b.Snapshot()
|
||||
defer snap.Close()
|
||||
if _, err := snap.WriteTo(f); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
// bootstrap new backend from the snapshot
|
||||
nb := New(f.Name(), time.Hour, 10000)
|
||||
defer cleanup(nb, f.Name())
|
||||
|
||||
newTx := b.BatchTx()
|
||||
newTx.Lock()
|
||||
ks, _ := newTx.UnsafeRange([]byte("test"), []byte("foo"), []byte("goo"), 0)
|
||||
if len(ks) != 1 {
|
||||
t.Errorf("len(kvs) = %d, want 1", len(ks))
|
||||
}
|
||||
newTx.Unlock()
|
||||
}
|
||||
|
||||
func TestBackendBatchIntervalCommit(t *testing.T) {
|
||||
// start backend with super short batch interval so
|
||||
// we do not need to wait long before commit to happen.
|
||||
b, tmpPath := NewTmpBackend(time.Nanosecond, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
pc := b.Commits()
|
||||
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
|
||||
tx.Unlock()
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
if b.Commits() >= pc+1 {
|
||||
break
|
||||
}
|
||||
time.Sleep(time.Duration(i*100) * time.Millisecond)
|
||||
}
|
||||
|
||||
// check whether put happens via db view
|
||||
b.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte("test"))
|
||||
if bucket == nil {
|
||||
t.Errorf("bucket test does not exit")
|
||||
return nil
|
||||
}
|
||||
v := bucket.Get([]byte("foo"))
|
||||
if v == nil {
|
||||
t.Errorf("foo key failed to written in backend")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestBackendDefrag(t *testing.T) {
|
||||
b, tmpPath := NewDefaultTmpBackend()
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
for i := 0; i < defragLimit+100; i++ {
|
||||
tx.UnsafePut([]byte("test"), []byte(fmt.Sprintf("foo_%d", i)), []byte("bar"))
|
||||
}
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
// remove some keys to ensure the disk space will be reclaimed after defrag
|
||||
tx = b.BatchTx()
|
||||
tx.Lock()
|
||||
for i := 0; i < 50; i++ {
|
||||
tx.UnsafeDelete([]byte("test"), []byte(fmt.Sprintf("foo_%d", i)))
|
||||
}
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
size := b.Size()
|
||||
|
||||
// shrink and check hash
|
||||
oh, err := b.Hash(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
err = b.Defrag()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
nh, err := b.Hash(nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if oh != nh {
|
||||
t.Errorf("hash = %v, want %v", nh, oh)
|
||||
}
|
||||
|
||||
nsize := b.Size()
|
||||
if nsize >= size {
|
||||
t.Errorf("new size = %v, want < %d", nsize, size)
|
||||
}
|
||||
|
||||
// try put more keys after shrink.
|
||||
tx = b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("more"), []byte("bar"))
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
}
|
||||
|
||||
func cleanup(b Backend, path string) {
|
||||
b.Close()
|
||||
os.Remove(path)
|
||||
}
|
||||
206
vendor/github.com/coreos/etcd/mvcc/backend/batch_tx.go
generated
vendored
Normal file
206
vendor/github.com/coreos/etcd/mvcc/backend/batch_tx.go
generated
vendored
Normal file
|
|
@ -0,0 +1,206 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
type BatchTx interface {
|
||||
Lock()
|
||||
Unlock()
|
||||
UnsafeCreateBucket(name []byte)
|
||||
UnsafePut(bucketName []byte, key []byte, value []byte)
|
||||
UnsafeSeqPut(bucketName []byte, key []byte, value []byte)
|
||||
UnsafeRange(bucketName []byte, key, endKey []byte, limit int64) (keys [][]byte, vals [][]byte)
|
||||
UnsafeDelete(bucketName []byte, key []byte)
|
||||
UnsafeForEach(bucketName []byte, visitor func(k, v []byte) error) error
|
||||
Commit()
|
||||
CommitAndStop()
|
||||
}
|
||||
|
||||
type batchTx struct {
|
||||
sync.Mutex
|
||||
tx *bolt.Tx
|
||||
backend *backend
|
||||
pending int
|
||||
}
|
||||
|
||||
func newBatchTx(backend *backend) *batchTx {
|
||||
tx := &batchTx{backend: backend}
|
||||
tx.Commit()
|
||||
return tx
|
||||
}
|
||||
|
||||
func (t *batchTx) UnsafeCreateBucket(name []byte) {
|
||||
_, err := t.tx.CreateBucket(name)
|
||||
if err != nil && err != bolt.ErrBucketExists {
|
||||
plog.Fatalf("cannot create bucket %s (%v)", name, err)
|
||||
}
|
||||
t.pending++
|
||||
}
|
||||
|
||||
// UnsafePut must be called holding the lock on the tx.
|
||||
func (t *batchTx) UnsafePut(bucketName []byte, key []byte, value []byte) {
|
||||
t.unsafePut(bucketName, key, value, false)
|
||||
}
|
||||
|
||||
// UnsafeSeqPut must be called holding the lock on the tx.
|
||||
func (t *batchTx) UnsafeSeqPut(bucketName []byte, key []byte, value []byte) {
|
||||
t.unsafePut(bucketName, key, value, true)
|
||||
}
|
||||
|
||||
func (t *batchTx) unsafePut(bucketName []byte, key []byte, value []byte, seq bool) {
|
||||
bucket := t.tx.Bucket(bucketName)
|
||||
if bucket == nil {
|
||||
plog.Fatalf("bucket %s does not exist", bucketName)
|
||||
}
|
||||
if seq {
|
||||
// it is useful to increase fill percent when the workloads are mostly append-only.
|
||||
// this can delay the page split and reduce space usage.
|
||||
bucket.FillPercent = 0.9
|
||||
}
|
||||
if err := bucket.Put(key, value); err != nil {
|
||||
plog.Fatalf("cannot put key into bucket (%v)", err)
|
||||
}
|
||||
t.pending++
|
||||
}
|
||||
|
||||
// UnsafeRange must be called holding the lock on the tx.
|
||||
func (t *batchTx) UnsafeRange(bucketName []byte, key, endKey []byte, limit int64) (keys [][]byte, vs [][]byte) {
|
||||
bucket := t.tx.Bucket(bucketName)
|
||||
if bucket == nil {
|
||||
plog.Fatalf("bucket %s does not exist", bucketName)
|
||||
}
|
||||
|
||||
if len(endKey) == 0 {
|
||||
if v := bucket.Get(key); v == nil {
|
||||
return keys, vs
|
||||
} else {
|
||||
return append(keys, key), append(vs, v)
|
||||
}
|
||||
}
|
||||
|
||||
c := bucket.Cursor()
|
||||
for ck, cv := c.Seek(key); ck != nil && bytes.Compare(ck, endKey) < 0; ck, cv = c.Next() {
|
||||
vs = append(vs, cv)
|
||||
keys = append(keys, ck)
|
||||
if limit > 0 && limit == int64(len(keys)) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
return keys, vs
|
||||
}
|
||||
|
||||
// UnsafeDelete must be called holding the lock on the tx.
|
||||
func (t *batchTx) UnsafeDelete(bucketName []byte, key []byte) {
|
||||
bucket := t.tx.Bucket(bucketName)
|
||||
if bucket == nil {
|
||||
plog.Fatalf("bucket %s does not exist", bucketName)
|
||||
}
|
||||
err := bucket.Delete(key)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot delete key from bucket (%v)", err)
|
||||
}
|
||||
t.pending++
|
||||
}
|
||||
|
||||
// UnsafeForEach must be called holding the lock on the tx.
|
||||
func (t *batchTx) UnsafeForEach(bucketName []byte, visitor func(k, v []byte) error) error {
|
||||
b := t.tx.Bucket(bucketName)
|
||||
if b == nil {
|
||||
// bucket does not exist
|
||||
return nil
|
||||
}
|
||||
return b.ForEach(visitor)
|
||||
}
|
||||
|
||||
// Commit commits a previous tx and begins a new writable one.
|
||||
func (t *batchTx) Commit() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.commit(false)
|
||||
}
|
||||
|
||||
// CommitAndStop commits the previous tx and do not create a new one.
|
||||
func (t *batchTx) CommitAndStop() {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
t.commit(true)
|
||||
}
|
||||
|
||||
func (t *batchTx) Unlock() {
|
||||
if t.pending >= t.backend.batchLimit {
|
||||
t.commit(false)
|
||||
t.pending = 0
|
||||
}
|
||||
t.Mutex.Unlock()
|
||||
}
|
||||
|
||||
func (t *batchTx) commit(stop bool) {
|
||||
var err error
|
||||
// commit the last tx
|
||||
if t.tx != nil {
|
||||
if t.pending == 0 && !stop {
|
||||
t.backend.mu.RLock()
|
||||
defer t.backend.mu.RUnlock()
|
||||
|
||||
// batchTx.commit(true) calls *bolt.Tx.Commit, which
|
||||
// initializes *bolt.Tx.db and *bolt.Tx.meta as nil,
|
||||
// and subsequent *bolt.Tx.Size() call panics.
|
||||
//
|
||||
// This nil pointer reference panic happens when:
|
||||
// 1. batchTx.commit(false) from newBatchTx
|
||||
// 2. batchTx.commit(true) from stopping backend
|
||||
// 3. batchTx.commit(false) from inflight mvcc Hash call
|
||||
//
|
||||
// Check if db is nil to prevent this panic
|
||||
if t.tx.DB() != nil {
|
||||
atomic.StoreInt64(&t.backend.size, t.tx.Size())
|
||||
}
|
||||
return
|
||||
}
|
||||
start := time.Now()
|
||||
// gofail: var beforeCommit struct{}
|
||||
err = t.tx.Commit()
|
||||
// gofail: var afterCommit struct{}
|
||||
commitDurations.Observe(time.Since(start).Seconds())
|
||||
atomic.AddInt64(&t.backend.commits, 1)
|
||||
|
||||
t.pending = 0
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot commit tx (%s)", err)
|
||||
}
|
||||
}
|
||||
|
||||
if stop {
|
||||
return
|
||||
}
|
||||
|
||||
t.backend.mu.RLock()
|
||||
defer t.backend.mu.RUnlock()
|
||||
// begin a new tx
|
||||
t.tx, err = t.backend.db.Begin(true)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot begin tx (%s)", err)
|
||||
}
|
||||
atomic.StoreInt64(&t.backend.size, t.tx.Size())
|
||||
}
|
||||
197
vendor/github.com/coreos/etcd/mvcc/backend/batch_tx_test.go
generated
vendored
Normal file
197
vendor/github.com/coreos/etcd/mvcc/backend/batch_tx_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,197 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
func TestBatchTxPut(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.batchTx
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
// create bucket
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
|
||||
// put
|
||||
v := []byte("bar")
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), v)
|
||||
|
||||
// check put result before and after tx is committed
|
||||
for k := 0; k < 2; k++ {
|
||||
_, gv := tx.UnsafeRange([]byte("test"), []byte("foo"), nil, 0)
|
||||
if !reflect.DeepEqual(gv[0], v) {
|
||||
t.Errorf("v = %s, want %s", string(gv[0]), string(v))
|
||||
}
|
||||
tx.commit(false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchTxRange(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.batchTx
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
// put keys
|
||||
allKeys := [][]byte{[]byte("foo"), []byte("foo1"), []byte("foo2")}
|
||||
allVals := [][]byte{[]byte("bar"), []byte("bar1"), []byte("bar2")}
|
||||
for i := range allKeys {
|
||||
tx.UnsafePut([]byte("test"), allKeys[i], allVals[i])
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
key []byte
|
||||
endKey []byte
|
||||
limit int64
|
||||
|
||||
wkeys [][]byte
|
||||
wvals [][]byte
|
||||
}{
|
||||
// single key
|
||||
{
|
||||
[]byte("foo"), nil, 0,
|
||||
allKeys[:1], allVals[:1],
|
||||
},
|
||||
// single key, bad
|
||||
{
|
||||
[]byte("doo"), nil, 0,
|
||||
nil, nil,
|
||||
},
|
||||
// key range
|
||||
{
|
||||
[]byte("foo"), []byte("foo1"), 0,
|
||||
allKeys[:1], allVals[:1],
|
||||
},
|
||||
// key range, get all keys
|
||||
{
|
||||
[]byte("foo"), []byte("foo3"), 0,
|
||||
allKeys, allVals,
|
||||
},
|
||||
// key range, bad
|
||||
{
|
||||
[]byte("goo"), []byte("goo3"), 0,
|
||||
nil, nil,
|
||||
},
|
||||
// key range with effective limit
|
||||
{
|
||||
[]byte("foo"), []byte("foo3"), 1,
|
||||
allKeys[:1], allVals[:1],
|
||||
},
|
||||
// key range with limit
|
||||
{
|
||||
[]byte("foo"), []byte("foo3"), 4,
|
||||
allKeys, allVals,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
keys, vals := tx.UnsafeRange([]byte("test"), tt.key, tt.endKey, tt.limit)
|
||||
if !reflect.DeepEqual(keys, tt.wkeys) {
|
||||
t.Errorf("#%d: keys = %+v, want %+v", i, keys, tt.wkeys)
|
||||
}
|
||||
if !reflect.DeepEqual(vals, tt.wvals) {
|
||||
t.Errorf("#%d: vals = %+v, want %+v", i, vals, tt.wvals)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchTxDelete(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.batchTx
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
|
||||
|
||||
tx.UnsafeDelete([]byte("test"), []byte("foo"))
|
||||
|
||||
// check put result before and after tx is committed
|
||||
for k := 0; k < 2; k++ {
|
||||
ks, _ := tx.UnsafeRange([]byte("test"), []byte("foo"), nil, 0)
|
||||
if len(ks) != 0 {
|
||||
t.Errorf("keys on foo = %v, want nil", ks)
|
||||
}
|
||||
tx.commit(false)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBatchTxCommit(t *testing.T) {
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 10000)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.batchTx
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
|
||||
tx.Unlock()
|
||||
|
||||
tx.Commit()
|
||||
|
||||
// check whether put happens via db view
|
||||
b.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte("test"))
|
||||
if bucket == nil {
|
||||
t.Errorf("bucket test does not exit")
|
||||
return nil
|
||||
}
|
||||
v := bucket.Get([]byte("foo"))
|
||||
if v == nil {
|
||||
t.Errorf("foo key failed to written in backend")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func TestBatchTxBatchLimitCommit(t *testing.T) {
|
||||
// start backend with batch limit 1 so one write can
|
||||
// trigger a commit
|
||||
b, tmpPath := NewTmpBackend(time.Hour, 1)
|
||||
defer cleanup(b, tmpPath)
|
||||
|
||||
tx := b.batchTx
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket([]byte("test"))
|
||||
tx.UnsafePut([]byte("test"), []byte("foo"), []byte("bar"))
|
||||
tx.Unlock()
|
||||
|
||||
// batch limit commit should have been triggered
|
||||
// check whether put happens via db view
|
||||
b.db.View(func(tx *bolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte("test"))
|
||||
if bucket == nil {
|
||||
t.Errorf("bucket test does not exit")
|
||||
return nil
|
||||
}
|
||||
v := bucket.Get([]byte("foo"))
|
||||
if v == nil {
|
||||
t.Errorf("foo key failed to written in backend")
|
||||
}
|
||||
return nil
|
||||
})
|
||||
}
|
||||
21
vendor/github.com/coreos/etcd/mvcc/backend/boltoption_default.go
generated
vendored
Normal file
21
vendor/github.com/coreos/etcd/mvcc/backend/boltoption_default.go
generated
vendored
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright 2016 The etcd 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.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package backend
|
||||
|
||||
import "github.com/boltdb/bolt"
|
||||
|
||||
var boltOpenOptions *bolt.Options = nil
|
||||
32
vendor/github.com/coreos/etcd/mvcc/backend/boltoption_linux.go
generated
vendored
Normal file
32
vendor/github.com/coreos/etcd/mvcc/backend/boltoption_linux.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
// Copyright 2015 The etcd 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 backend
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
|
||||
"github.com/boltdb/bolt"
|
||||
)
|
||||
|
||||
// syscall.MAP_POPULATE on linux 2.6.23+ does sequential read-ahead
|
||||
// which can speed up entire-database read with boltdb. We want to
|
||||
// enable MAP_POPULATE for faster key-value store recovery in storage
|
||||
// package. If your kernel version is lower than 2.6.23
|
||||
// (https://github.com/torvalds/linux/releases/tag/v2.6.23), mmap might
|
||||
// silently ignore this flag. Please update your kernel to prevent this.
|
||||
var boltOpenOptions = &bolt.Options{
|
||||
MmapFlags: syscall.MAP_POPULATE,
|
||||
InitialMmapSize: int(InitialMmapSize),
|
||||
}
|
||||
16
vendor/github.com/coreos/etcd/mvcc/backend/doc.go
generated
vendored
Normal file
16
vendor/github.com/coreos/etcd/mvcc/backend/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2015 The etcd 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 backend defines a standard interface for etcd's backend MVCC storage.
|
||||
package backend
|
||||
31
vendor/github.com/coreos/etcd/mvcc/backend/metrics.go
generated
vendored
Normal file
31
vendor/github.com/coreos/etcd/mvcc/backend/metrics.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
// Copyright 2016 The etcd 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 backend
|
||||
|
||||
import "github.com/prometheus/client_golang/prometheus"
|
||||
|
||||
var (
|
||||
commitDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "disk",
|
||||
Name: "backend_commit_duration_seconds",
|
||||
Help: "The latency distributions of commit called by backend.",
|
||||
Buckets: prometheus.ExponentialBuckets(0.001, 2, 14),
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(commitDurations)
|
||||
}
|
||||
16
vendor/github.com/coreos/etcd/mvcc/doc.go
generated
vendored
Normal file
16
vendor/github.com/coreos/etcd/mvcc/doc.go
generated
vendored
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2015 The etcd 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 mvcc defines etcd's stable MVCC storage.
|
||||
package mvcc
|
||||
208
vendor/github.com/coreos/etcd/mvcc/index.go
generated
vendored
Normal file
208
vendor/github.com/coreos/etcd/mvcc/index.go
generated
vendored
Normal file
|
|
@ -0,0 +1,208 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/google/btree"
|
||||
)
|
||||
|
||||
type index interface {
|
||||
Get(key []byte, atRev int64) (rev, created revision, ver int64, err error)
|
||||
Range(key, end []byte, atRev int64) ([][]byte, []revision)
|
||||
Put(key []byte, rev revision)
|
||||
Tombstone(key []byte, rev revision) error
|
||||
RangeSince(key, end []byte, rev int64) []revision
|
||||
Compact(rev int64) map[revision]struct{}
|
||||
Equal(b index) bool
|
||||
Insert(ki *keyIndex)
|
||||
}
|
||||
|
||||
type treeIndex struct {
|
||||
sync.RWMutex
|
||||
tree *btree.BTree
|
||||
}
|
||||
|
||||
func newTreeIndex() index {
|
||||
return &treeIndex{
|
||||
tree: btree.New(32),
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Put(key []byte, rev revision) {
|
||||
keyi := &keyIndex{key: key}
|
||||
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
keyi.put(rev.main, rev.sub)
|
||||
ti.tree.ReplaceOrInsert(keyi)
|
||||
return
|
||||
}
|
||||
okeyi := item.(*keyIndex)
|
||||
okeyi.put(rev.main, rev.sub)
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Get(key []byte, atRev int64) (modified, created revision, ver int64, err error) {
|
||||
keyi := &keyIndex{key: key}
|
||||
|
||||
ti.RLock()
|
||||
defer ti.RUnlock()
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
return revision{}, revision{}, 0, ErrRevisionNotFound
|
||||
}
|
||||
|
||||
keyi = item.(*keyIndex)
|
||||
return keyi.get(atRev)
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Range(key, end []byte, atRev int64) (keys [][]byte, revs []revision) {
|
||||
if end == nil {
|
||||
rev, _, _, err := ti.Get(key, atRev)
|
||||
if err != nil {
|
||||
return nil, nil
|
||||
}
|
||||
return [][]byte{key}, []revision{rev}
|
||||
}
|
||||
|
||||
keyi := &keyIndex{key: key}
|
||||
endi := &keyIndex{key: end}
|
||||
|
||||
ti.RLock()
|
||||
defer ti.RUnlock()
|
||||
|
||||
ti.tree.AscendGreaterOrEqual(keyi, func(item btree.Item) bool {
|
||||
if len(endi.key) > 0 && !item.Less(endi) {
|
||||
return false
|
||||
}
|
||||
curKeyi := item.(*keyIndex)
|
||||
rev, _, _, err := curKeyi.get(atRev)
|
||||
if err != nil {
|
||||
return true
|
||||
}
|
||||
revs = append(revs, rev)
|
||||
keys = append(keys, curKeyi.key)
|
||||
return true
|
||||
})
|
||||
|
||||
return keys, revs
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Tombstone(key []byte, rev revision) error {
|
||||
keyi := &keyIndex{key: key}
|
||||
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
return ErrRevisionNotFound
|
||||
}
|
||||
|
||||
ki := item.(*keyIndex)
|
||||
return ki.tombstone(rev.main, rev.sub)
|
||||
}
|
||||
|
||||
// RangeSince returns all revisions from key(including) to end(excluding)
|
||||
// at or after the given rev. The returned slice is sorted in the order
|
||||
// of revision.
|
||||
func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision {
|
||||
ti.RLock()
|
||||
defer ti.RUnlock()
|
||||
|
||||
keyi := &keyIndex{key: key}
|
||||
if end == nil {
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
return nil
|
||||
}
|
||||
keyi = item.(*keyIndex)
|
||||
return keyi.since(rev)
|
||||
}
|
||||
|
||||
endi := &keyIndex{key: end}
|
||||
var revs []revision
|
||||
ti.tree.AscendGreaterOrEqual(keyi, func(item btree.Item) bool {
|
||||
if len(endi.key) > 0 && !item.Less(endi) {
|
||||
return false
|
||||
}
|
||||
curKeyi := item.(*keyIndex)
|
||||
revs = append(revs, curKeyi.since(rev)...)
|
||||
return true
|
||||
})
|
||||
sort.Sort(revisions(revs))
|
||||
|
||||
return revs
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Compact(rev int64) map[revision]struct{} {
|
||||
available := make(map[revision]struct{})
|
||||
var emptyki []*keyIndex
|
||||
plog.Printf("store.index: compact %d", rev)
|
||||
// TODO: do not hold the lock for long time?
|
||||
// This is probably OK. Compacting 10M keys takes O(10ms).
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
ti.tree.Ascend(compactIndex(rev, available, &emptyki))
|
||||
for _, ki := range emptyki {
|
||||
item := ti.tree.Delete(ki)
|
||||
if item == nil {
|
||||
plog.Panic("store.index: unexpected delete failure during compaction")
|
||||
}
|
||||
}
|
||||
return available
|
||||
}
|
||||
|
||||
func compactIndex(rev int64, available map[revision]struct{}, emptyki *[]*keyIndex) func(i btree.Item) bool {
|
||||
return func(i btree.Item) bool {
|
||||
keyi := i.(*keyIndex)
|
||||
keyi.compact(rev, available)
|
||||
if keyi.isEmpty() {
|
||||
*emptyki = append(*emptyki, keyi)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (a *treeIndex) Equal(bi index) bool {
|
||||
b := bi.(*treeIndex)
|
||||
|
||||
if a.tree.Len() != b.tree.Len() {
|
||||
return false
|
||||
}
|
||||
|
||||
equal := true
|
||||
|
||||
a.tree.Ascend(func(item btree.Item) bool {
|
||||
aki := item.(*keyIndex)
|
||||
bki := b.tree.Get(item).(*keyIndex)
|
||||
if !aki.equal(bki) {
|
||||
equal = false
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
return equal
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Insert(ki *keyIndex) {
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
ti.tree.ReplaceOrInsert(ki)
|
||||
}
|
||||
286
vendor/github.com/coreos/etcd/mvcc/index_test.go
generated
vendored
Normal file
286
vendor/github.com/coreos/etcd/mvcc/index_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/google/btree"
|
||||
)
|
||||
|
||||
func TestIndexGet(t *testing.T) {
|
||||
ti := newTreeIndex()
|
||||
ti.Put([]byte("foo"), revision{main: 2})
|
||||
ti.Put([]byte("foo"), revision{main: 4})
|
||||
ti.Tombstone([]byte("foo"), revision{main: 6})
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
|
||||
wrev revision
|
||||
wcreated revision
|
||||
wver int64
|
||||
werr error
|
||||
}{
|
||||
{0, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{1, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{2, revision{main: 2}, revision{main: 2}, 1, nil},
|
||||
{3, revision{main: 2}, revision{main: 2}, 1, nil},
|
||||
{4, revision{main: 4}, revision{main: 2}, 2, nil},
|
||||
{5, revision{main: 4}, revision{main: 2}, 2, nil},
|
||||
{6, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
rev, created, ver, err := ti.Get([]byte("foo"), tt.rev)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if rev != tt.wrev {
|
||||
t.Errorf("#%d: rev = %+v, want %+v", i, rev, tt.wrev)
|
||||
}
|
||||
if created != tt.wcreated {
|
||||
t.Errorf("#%d: created = %+v, want %+v", i, created, tt.wcreated)
|
||||
}
|
||||
if ver != tt.wver {
|
||||
t.Errorf("#%d: ver = %d, want %d", i, ver, tt.wver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexRange(t *testing.T) {
|
||||
allKeys := [][]byte{[]byte("foo"), []byte("foo1"), []byte("foo2")}
|
||||
allRevs := []revision{{main: 1}, {main: 2}, {main: 3}}
|
||||
|
||||
ti := newTreeIndex()
|
||||
for i := range allKeys {
|
||||
ti.Put(allKeys[i], allRevs[i])
|
||||
}
|
||||
|
||||
atRev := int64(3)
|
||||
tests := []struct {
|
||||
key, end []byte
|
||||
wkeys [][]byte
|
||||
wrevs []revision
|
||||
}{
|
||||
// single key that not found
|
||||
{
|
||||
[]byte("bar"), nil, nil, nil,
|
||||
},
|
||||
// single key that found
|
||||
{
|
||||
[]byte("foo"), nil, allKeys[:1], allRevs[:1],
|
||||
},
|
||||
// range keys, return first member
|
||||
{
|
||||
[]byte("foo"), []byte("foo1"), allKeys[:1], allRevs[:1],
|
||||
},
|
||||
// range keys, return first two members
|
||||
{
|
||||
[]byte("foo"), []byte("foo2"), allKeys[:2], allRevs[:2],
|
||||
},
|
||||
// range keys, return all members
|
||||
{
|
||||
[]byte("foo"), []byte("fop"), allKeys, allRevs,
|
||||
},
|
||||
// range keys, return last two members
|
||||
{
|
||||
[]byte("foo1"), []byte("fop"), allKeys[1:], allRevs[1:],
|
||||
},
|
||||
// range keys, return last member
|
||||
{
|
||||
[]byte("foo2"), []byte("fop"), allKeys[2:], allRevs[2:],
|
||||
},
|
||||
// range keys, return nothing
|
||||
{
|
||||
[]byte("foo3"), []byte("fop"), nil, nil,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
keys, revs := ti.Range(tt.key, tt.end, atRev)
|
||||
if !reflect.DeepEqual(keys, tt.wkeys) {
|
||||
t.Errorf("#%d: keys = %+v, want %+v", i, keys, tt.wkeys)
|
||||
}
|
||||
if !reflect.DeepEqual(revs, tt.wrevs) {
|
||||
t.Errorf("#%d: revs = %+v, want %+v", i, revs, tt.wrevs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexTombstone(t *testing.T) {
|
||||
ti := newTreeIndex()
|
||||
ti.Put([]byte("foo"), revision{main: 1})
|
||||
|
||||
err := ti.Tombstone([]byte("foo"), revision{main: 2})
|
||||
if err != nil {
|
||||
t.Errorf("tombstone error = %v, want nil", err)
|
||||
}
|
||||
|
||||
_, _, _, err = ti.Get([]byte("foo"), 2)
|
||||
if err != ErrRevisionNotFound {
|
||||
t.Errorf("get error = %v, want nil", err)
|
||||
}
|
||||
err = ti.Tombstone([]byte("foo"), revision{main: 3})
|
||||
if err != ErrRevisionNotFound {
|
||||
t.Errorf("tombstone error = %v, want %v", err, ErrRevisionNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexRangeSince(t *testing.T) {
|
||||
allKeys := [][]byte{[]byte("foo"), []byte("foo1"), []byte("foo2"), []byte("foo2"), []byte("foo1"), []byte("foo")}
|
||||
allRevs := []revision{{main: 1}, {main: 2}, {main: 3}, {main: 4}, {main: 5}, {main: 6}}
|
||||
|
||||
ti := newTreeIndex()
|
||||
for i := range allKeys {
|
||||
ti.Put(allKeys[i], allRevs[i])
|
||||
}
|
||||
|
||||
atRev := int64(1)
|
||||
tests := []struct {
|
||||
key, end []byte
|
||||
wrevs []revision
|
||||
}{
|
||||
// single key that not found
|
||||
{
|
||||
[]byte("bar"), nil, nil,
|
||||
},
|
||||
// single key that found
|
||||
{
|
||||
[]byte("foo"), nil, []revision{{main: 1}, {main: 6}},
|
||||
},
|
||||
// range keys, return first member
|
||||
{
|
||||
[]byte("foo"), []byte("foo1"), []revision{{main: 1}, {main: 6}},
|
||||
},
|
||||
// range keys, return first two members
|
||||
{
|
||||
[]byte("foo"), []byte("foo2"), []revision{{main: 1}, {main: 2}, {main: 5}, {main: 6}},
|
||||
},
|
||||
// range keys, return all members
|
||||
{
|
||||
[]byte("foo"), []byte("fop"), allRevs,
|
||||
},
|
||||
// range keys, return last two members
|
||||
{
|
||||
[]byte("foo1"), []byte("fop"), []revision{{main: 2}, {main: 3}, {main: 4}, {main: 5}},
|
||||
},
|
||||
// range keys, return last member
|
||||
{
|
||||
[]byte("foo2"), []byte("fop"), []revision{{main: 3}, {main: 4}},
|
||||
},
|
||||
// range keys, return nothing
|
||||
{
|
||||
[]byte("foo3"), []byte("fop"), nil,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
revs := ti.RangeSince(tt.key, tt.end, atRev)
|
||||
if !reflect.DeepEqual(revs, tt.wrevs) {
|
||||
t.Errorf("#%d: revs = %+v, want %+v", i, revs, tt.wrevs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexCompact(t *testing.T) {
|
||||
maxRev := int64(20)
|
||||
tests := []struct {
|
||||
key []byte
|
||||
remove bool
|
||||
rev revision
|
||||
created revision
|
||||
ver int64
|
||||
}{
|
||||
{[]byte("foo"), false, revision{main: 1}, revision{main: 1}, 1},
|
||||
{[]byte("foo1"), false, revision{main: 2}, revision{main: 2}, 1},
|
||||
{[]byte("foo2"), false, revision{main: 3}, revision{main: 3}, 1},
|
||||
{[]byte("foo2"), false, revision{main: 4}, revision{main: 3}, 2},
|
||||
{[]byte("foo"), false, revision{main: 5}, revision{main: 1}, 2},
|
||||
{[]byte("foo1"), false, revision{main: 6}, revision{main: 2}, 2},
|
||||
{[]byte("foo1"), true, revision{main: 7}, revision{}, 0},
|
||||
{[]byte("foo2"), true, revision{main: 8}, revision{}, 0},
|
||||
{[]byte("foo"), true, revision{main: 9}, revision{}, 0},
|
||||
{[]byte("foo"), false, revision{10, 0}, revision{10, 0}, 1},
|
||||
{[]byte("foo1"), false, revision{10, 1}, revision{10, 1}, 1},
|
||||
}
|
||||
|
||||
// Continuous Compact
|
||||
ti := newTreeIndex()
|
||||
for _, tt := range tests {
|
||||
if tt.remove {
|
||||
ti.Tombstone(tt.key, tt.rev)
|
||||
} else {
|
||||
ti.Put(tt.key, tt.rev)
|
||||
}
|
||||
}
|
||||
for i := int64(1); i < maxRev; i++ {
|
||||
am := ti.Compact(i)
|
||||
|
||||
wti := &treeIndex{tree: btree.New(32)}
|
||||
for _, tt := range tests {
|
||||
if _, ok := am[tt.rev]; ok || tt.rev.GreaterThan(revision{main: i}) {
|
||||
if tt.remove {
|
||||
wti.Tombstone(tt.key, tt.rev)
|
||||
} else {
|
||||
restore(wti, tt.key, tt.created, tt.rev, tt.ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ti.Equal(wti) {
|
||||
t.Errorf("#%d: not equal ti", i)
|
||||
}
|
||||
}
|
||||
|
||||
// Once Compact
|
||||
for i := int64(1); i < maxRev; i++ {
|
||||
ti := newTreeIndex()
|
||||
for _, tt := range tests {
|
||||
if tt.remove {
|
||||
ti.Tombstone(tt.key, tt.rev)
|
||||
} else {
|
||||
ti.Put(tt.key, tt.rev)
|
||||
}
|
||||
}
|
||||
am := ti.Compact(i)
|
||||
|
||||
wti := &treeIndex{tree: btree.New(32)}
|
||||
for _, tt := range tests {
|
||||
if _, ok := am[tt.rev]; ok || tt.rev.GreaterThan(revision{main: i}) {
|
||||
if tt.remove {
|
||||
wti.Tombstone(tt.key, tt.rev)
|
||||
} else {
|
||||
restore(wti, tt.key, tt.created, tt.rev, tt.ver)
|
||||
}
|
||||
}
|
||||
}
|
||||
if !ti.Equal(wti) {
|
||||
t.Errorf("#%d: not equal ti", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func restore(ti *treeIndex, key []byte, created, modified revision, ver int64) {
|
||||
keyi := &keyIndex{key: key}
|
||||
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
keyi.restore(created, modified, ver)
|
||||
ti.tree.ReplaceOrInsert(keyi)
|
||||
return
|
||||
}
|
||||
okeyi := item.(*keyIndex)
|
||||
okeyi.put(modified.main, modified.sub)
|
||||
}
|
||||
333
vendor/github.com/coreos/etcd/mvcc/key_index.go
generated
vendored
Normal file
333
vendor/github.com/coreos/etcd/mvcc/key_index.go
generated
vendored
Normal file
|
|
@ -0,0 +1,333 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/btree"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrRevisionNotFound = errors.New("mvcc: revision not found")
|
||||
)
|
||||
|
||||
// keyIndex stores the revisions of a key in the backend.
|
||||
// Each keyIndex has at least one key generation.
|
||||
// Each generation might have several key versions.
|
||||
// Tombstone on a key appends an tombstone version at the end
|
||||
// of the current generation and creates a new empty generation.
|
||||
// Each version of a key has an index pointing to the backend.
|
||||
//
|
||||
// For example: put(1.0);put(2.0);tombstone(3.0);put(4.0);tombstone(5.0) on key "foo"
|
||||
// generate a keyIndex:
|
||||
// key: "foo"
|
||||
// rev: 5
|
||||
// generations:
|
||||
// {empty}
|
||||
// {4.0, 5.0(t)}
|
||||
// {1.0, 2.0, 3.0(t)}
|
||||
//
|
||||
// Compact a keyIndex removes the versions with smaller or equal to
|
||||
// rev except the largest one. If the generation becomes empty
|
||||
// during compaction, it will be removed. if all the generations get
|
||||
// removed, the keyIndex should be removed.
|
||||
|
||||
// For example:
|
||||
// compact(2) on the previous example
|
||||
// generations:
|
||||
// {empty}
|
||||
// {4.0, 5.0(t)}
|
||||
// {2.0, 3.0(t)}
|
||||
//
|
||||
// compact(4)
|
||||
// generations:
|
||||
// {empty}
|
||||
// {4.0, 5.0(t)}
|
||||
//
|
||||
// compact(5):
|
||||
// generations:
|
||||
// {empty} -> key SHOULD be removed.
|
||||
//
|
||||
// compact(6):
|
||||
// generations:
|
||||
// {empty} -> key SHOULD be removed.
|
||||
type keyIndex struct {
|
||||
key []byte
|
||||
modified revision // the main rev of the last modification
|
||||
generations []generation
|
||||
}
|
||||
|
||||
// put puts a revision to the keyIndex.
|
||||
func (ki *keyIndex) put(main int64, sub int64) {
|
||||
rev := revision{main: main, sub: sub}
|
||||
|
||||
if !rev.GreaterThan(ki.modified) {
|
||||
plog.Panicf("store.keyindex: put with unexpected smaller revision [%v / %v]", rev, ki.modified)
|
||||
}
|
||||
if len(ki.generations) == 0 {
|
||||
ki.generations = append(ki.generations, generation{})
|
||||
}
|
||||
g := &ki.generations[len(ki.generations)-1]
|
||||
if len(g.revs) == 0 { // create a new key
|
||||
keysGauge.Inc()
|
||||
g.created = rev
|
||||
}
|
||||
g.revs = append(g.revs, rev)
|
||||
g.ver++
|
||||
ki.modified = rev
|
||||
}
|
||||
|
||||
func (ki *keyIndex) restore(created, modified revision, ver int64) {
|
||||
if len(ki.generations) != 0 {
|
||||
plog.Panicf("store.keyindex: cannot restore non-empty keyIndex")
|
||||
}
|
||||
|
||||
ki.modified = modified
|
||||
g := generation{created: created, ver: ver, revs: []revision{modified}}
|
||||
ki.generations = append(ki.generations, g)
|
||||
keysGauge.Inc()
|
||||
}
|
||||
|
||||
// tombstone puts a revision, pointing to a tombstone, to the keyIndex.
|
||||
// It also creates a new empty generation in the keyIndex.
|
||||
// It returns ErrRevisionNotFound when tombstone on an empty generation.
|
||||
func (ki *keyIndex) tombstone(main int64, sub int64) error {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected tombstone on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
if ki.generations[len(ki.generations)-1].isEmpty() {
|
||||
return ErrRevisionNotFound
|
||||
}
|
||||
ki.put(main, sub)
|
||||
ki.generations = append(ki.generations, generation{})
|
||||
keysGauge.Dec()
|
||||
return nil
|
||||
}
|
||||
|
||||
// get gets the modified, created revision and version of the key that satisfies the given atRev.
|
||||
// Rev must be higher than or equal to the given atRev.
|
||||
func (ki *keyIndex) get(atRev int64) (modified, created revision, ver int64, err error) {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
g := ki.findGeneration(atRev)
|
||||
if g.isEmpty() {
|
||||
return revision{}, revision{}, 0, ErrRevisionNotFound
|
||||
}
|
||||
|
||||
n := g.walk(func(rev revision) bool { return rev.main > atRev })
|
||||
if n != -1 {
|
||||
return g.revs[n], g.created, g.ver - int64(len(g.revs)-n-1), nil
|
||||
}
|
||||
|
||||
return revision{}, revision{}, 0, ErrRevisionNotFound
|
||||
}
|
||||
|
||||
// since returns revisions since the given rev. Only the revision with the
|
||||
// largest sub revision will be returned if multiple revisions have the same
|
||||
// main revision.
|
||||
func (ki *keyIndex) since(rev int64) []revision {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
since := revision{rev, 0}
|
||||
var gi int
|
||||
// find the generations to start checking
|
||||
for gi = len(ki.generations) - 1; gi > 0; gi-- {
|
||||
g := ki.generations[gi]
|
||||
if g.isEmpty() {
|
||||
continue
|
||||
}
|
||||
if since.GreaterThan(g.created) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var revs []revision
|
||||
var last int64
|
||||
for ; gi < len(ki.generations); gi++ {
|
||||
for _, r := range ki.generations[gi].revs {
|
||||
if since.GreaterThan(r) {
|
||||
continue
|
||||
}
|
||||
if r.main == last {
|
||||
// replace the revision with a new one that has higher sub value,
|
||||
// because the original one should not be seen by external
|
||||
revs[len(revs)-1] = r
|
||||
continue
|
||||
}
|
||||
revs = append(revs, r)
|
||||
last = r.main
|
||||
}
|
||||
}
|
||||
return revs
|
||||
}
|
||||
|
||||
// compact compacts a keyIndex by removing the versions with smaller or equal
|
||||
// revision than the given atRev except the largest one (If the largest one is
|
||||
// a tombstone, it will not be kept).
|
||||
// If a generation becomes empty during compaction, it will be removed.
|
||||
func (ki *keyIndex) compact(atRev int64, available map[revision]struct{}) {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected compact on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
|
||||
// walk until reaching the first revision that has an revision smaller or equal to
|
||||
// the atRev.
|
||||
// add it to the available map
|
||||
f := func(rev revision) bool {
|
||||
if rev.main <= atRev {
|
||||
available[rev] = struct{}{}
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
i, g := 0, &ki.generations[0]
|
||||
// find first generation includes atRev or created after atRev
|
||||
for i < len(ki.generations)-1 {
|
||||
if tomb := g.revs[len(g.revs)-1].main; tomb > atRev {
|
||||
break
|
||||
}
|
||||
i++
|
||||
g = &ki.generations[i]
|
||||
}
|
||||
|
||||
if !g.isEmpty() {
|
||||
n := g.walk(f)
|
||||
// remove the previous contents.
|
||||
if n != -1 {
|
||||
g.revs = g.revs[n:]
|
||||
}
|
||||
// remove any tombstone
|
||||
if len(g.revs) == 1 && i != len(ki.generations)-1 {
|
||||
delete(available, g.revs[0])
|
||||
i++
|
||||
}
|
||||
}
|
||||
// remove the previous generations.
|
||||
ki.generations = ki.generations[i:]
|
||||
return
|
||||
}
|
||||
|
||||
func (ki *keyIndex) isEmpty() bool {
|
||||
return len(ki.generations) == 1 && ki.generations[0].isEmpty()
|
||||
}
|
||||
|
||||
// findGeneration finds out the generation of the keyIndex that the
|
||||
// given rev belongs to. If the given rev is at the gap of two generations,
|
||||
// which means that the key does not exist at the given rev, it returns nil.
|
||||
func (ki *keyIndex) findGeneration(rev int64) *generation {
|
||||
lastg := len(ki.generations) - 1
|
||||
cg := lastg
|
||||
|
||||
for cg >= 0 {
|
||||
if len(ki.generations[cg].revs) == 0 {
|
||||
cg--
|
||||
continue
|
||||
}
|
||||
g := ki.generations[cg]
|
||||
if cg != lastg {
|
||||
if tomb := g.revs[len(g.revs)-1].main; tomb <= rev {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
if g.revs[0].main <= rev {
|
||||
return &ki.generations[cg]
|
||||
}
|
||||
cg--
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *keyIndex) Less(b btree.Item) bool {
|
||||
return bytes.Compare(a.key, b.(*keyIndex).key) == -1
|
||||
}
|
||||
|
||||
func (a *keyIndex) equal(b *keyIndex) bool {
|
||||
if !bytes.Equal(a.key, b.key) {
|
||||
return false
|
||||
}
|
||||
if a.modified != b.modified {
|
||||
return false
|
||||
}
|
||||
if len(a.generations) != len(b.generations) {
|
||||
return false
|
||||
}
|
||||
for i := range a.generations {
|
||||
ag, bg := a.generations[i], b.generations[i]
|
||||
if !ag.equal(bg) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (ki *keyIndex) String() string {
|
||||
var s string
|
||||
for _, g := range ki.generations {
|
||||
s += g.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// generation contains multiple revisions of a key.
|
||||
type generation struct {
|
||||
ver int64
|
||||
created revision // when the generation is created (put in first revision).
|
||||
revs []revision
|
||||
}
|
||||
|
||||
func (g *generation) isEmpty() bool { return g == nil || len(g.revs) == 0 }
|
||||
|
||||
// walk walks through the revisions in the generation in descending order.
|
||||
// It passes the revision to the given function.
|
||||
// walk returns until: 1. it finishes walking all pairs 2. the function returns false.
|
||||
// walk returns the position at where it stopped. If it stopped after
|
||||
// finishing walking, -1 will be returned.
|
||||
func (g *generation) walk(f func(rev revision) bool) int {
|
||||
l := len(g.revs)
|
||||
for i := range g.revs {
|
||||
ok := f(g.revs[l-i-1])
|
||||
if !ok {
|
||||
return l - i - 1
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func (g *generation) String() string {
|
||||
return fmt.Sprintf("g: created[%d] ver[%d], revs %#v\n", g.created, g.ver, g.revs)
|
||||
}
|
||||
|
||||
func (a generation) equal(b generation) bool {
|
||||
if a.ver != b.ver {
|
||||
return false
|
||||
}
|
||||
if len(a.revs) != len(b.revs) {
|
||||
return false
|
||||
}
|
||||
|
||||
for i := range a.revs {
|
||||
ar, br := a.revs[i], b.revs[i]
|
||||
if ar != br {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
654
vendor/github.com/coreos/etcd/mvcc/key_index_test.go
generated
vendored
Normal file
654
vendor/github.com/coreos/etcd/mvcc/key_index_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,654 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestKeyIndexGet(t *testing.T) {
|
||||
// key: "foo"
|
||||
// rev: 16
|
||||
// generations:
|
||||
// {empty}
|
||||
// {{14, 0}[1], {14, 1}[2], {16, 0}(t)[3]}
|
||||
// {{8, 0}[1], {10, 0}[2], {12, 0}(t)[3]}
|
||||
// {{2, 0}[1], {4, 0}[2], {6, 0}(t)[3]}
|
||||
ki := newTestKeyIndex()
|
||||
ki.compact(4, make(map[revision]struct{}))
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
|
||||
wmod revision
|
||||
wcreat revision
|
||||
wver int64
|
||||
werr error
|
||||
}{
|
||||
{17, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{16, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
|
||||
// get on generation 3
|
||||
{15, revision{14, 1}, revision{14, 0}, 2, nil},
|
||||
{14, revision{14, 1}, revision{14, 0}, 2, nil},
|
||||
|
||||
{13, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{12, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
|
||||
// get on generation 2
|
||||
{11, revision{10, 0}, revision{8, 0}, 2, nil},
|
||||
{10, revision{10, 0}, revision{8, 0}, 2, nil},
|
||||
{9, revision{8, 0}, revision{8, 0}, 1, nil},
|
||||
{8, revision{8, 0}, revision{8, 0}, 1, nil},
|
||||
|
||||
{7, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{6, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
|
||||
// get on generation 1
|
||||
{5, revision{4, 0}, revision{2, 0}, 2, nil},
|
||||
{4, revision{4, 0}, revision{2, 0}, 2, nil},
|
||||
|
||||
{3, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{2, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{1, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
{0, revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
mod, creat, ver, err := ki.get(tt.rev)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
if mod != tt.wmod {
|
||||
t.Errorf("#%d: modified = %+v, want %+v", i, mod, tt.wmod)
|
||||
}
|
||||
if creat != tt.wcreat {
|
||||
t.Errorf("#%d: created = %+v, want %+v", i, creat, tt.wcreat)
|
||||
}
|
||||
if ver != tt.wver {
|
||||
t.Errorf("#%d: version = %d, want %d", i, ver, tt.wver)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexSince(t *testing.T) {
|
||||
ki := newTestKeyIndex()
|
||||
ki.compact(4, make(map[revision]struct{}))
|
||||
|
||||
allRevs := []revision{{4, 0}, {6, 0}, {8, 0}, {10, 0}, {12, 0}, {14, 1}, {16, 0}}
|
||||
tests := []struct {
|
||||
rev int64
|
||||
|
||||
wrevs []revision
|
||||
}{
|
||||
{17, nil},
|
||||
{16, allRevs[6:]},
|
||||
{15, allRevs[6:]},
|
||||
{14, allRevs[5:]},
|
||||
{13, allRevs[5:]},
|
||||
{12, allRevs[4:]},
|
||||
{11, allRevs[4:]},
|
||||
{10, allRevs[3:]},
|
||||
{9, allRevs[3:]},
|
||||
{8, allRevs[2:]},
|
||||
{7, allRevs[2:]},
|
||||
{6, allRevs[1:]},
|
||||
{5, allRevs[1:]},
|
||||
{4, allRevs},
|
||||
{3, allRevs},
|
||||
{2, allRevs},
|
||||
{1, allRevs},
|
||||
{0, allRevs},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
revs := ki.since(tt.rev)
|
||||
if !reflect.DeepEqual(revs, tt.wrevs) {
|
||||
t.Errorf("#%d: revs = %+v, want %+v", i, revs, tt.wrevs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexPut(t *testing.T) {
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
ki.put(5, 0)
|
||||
|
||||
wki := &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{5, 0},
|
||||
generations: []generation{{created: revision{5, 0}, ver: 1, revs: []revision{{main: 5}}}},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
|
||||
ki.put(7, 0)
|
||||
|
||||
wki = &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{7, 0},
|
||||
generations: []generation{{created: revision{5, 0}, ver: 2, revs: []revision{{main: 5}, {main: 7}}}},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexRestore(t *testing.T) {
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
ki.restore(revision{5, 0}, revision{7, 0}, 2)
|
||||
|
||||
wki := &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{7, 0},
|
||||
generations: []generation{{created: revision{5, 0}, ver: 2, revs: []revision{{main: 7}}}},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexTombstone(t *testing.T) {
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
ki.put(5, 0)
|
||||
|
||||
err := ki.tombstone(7, 0)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected tombstone error: %v", err)
|
||||
}
|
||||
|
||||
wki := &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{7, 0},
|
||||
generations: []generation{{created: revision{5, 0}, ver: 2, revs: []revision{{main: 5}, {main: 7}}}, {}},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
|
||||
ki.put(8, 0)
|
||||
ki.put(9, 0)
|
||||
err = ki.tombstone(15, 0)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected tombstone error: %v", err)
|
||||
}
|
||||
|
||||
wki = &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{15, 0},
|
||||
generations: []generation{
|
||||
{created: revision{5, 0}, ver: 2, revs: []revision{{main: 5}, {main: 7}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 9}, {main: 15}}},
|
||||
{},
|
||||
},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
|
||||
err = ki.tombstone(16, 0)
|
||||
if err != ErrRevisionNotFound {
|
||||
t.Errorf("tombstone error = %v, want %v", err, ErrRevisionNotFound)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexCompact(t *testing.T) {
|
||||
tests := []struct {
|
||||
compact int64
|
||||
|
||||
wki *keyIndex
|
||||
wam map[revision]struct{}
|
||||
}{
|
||||
{
|
||||
1,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{2, 0}, ver: 3, revs: []revision{{main: 2}, {main: 4}, {main: 6}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
{
|
||||
2,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{2, 0}, ver: 3, revs: []revision{{main: 2}, {main: 4}, {main: 6}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 2}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
3,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{2, 0}, ver: 3, revs: []revision{{main: 2}, {main: 4}, {main: 6}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 2}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
4,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{2, 0}, ver: 3, revs: []revision{{main: 4}, {main: 6}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 4}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
5,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{2, 0}, ver: 3, revs: []revision{{main: 4}, {main: 6}}},
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 4}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
6,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
{
|
||||
7,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
{
|
||||
8,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 8}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
9,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 8}, {main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 8}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
10,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 10}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
11,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{8, 0}, ver: 3, revs: []revision{{main: 10}, {main: 12}}},
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 10}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
12,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
{
|
||||
13,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14}, {main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
{
|
||||
14,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 14, sub: 1}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
15,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{created: revision{14, 0}, ver: 3, revs: []revision{{main: 14, sub: 1}, {main: 16}}},
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{
|
||||
{main: 14, sub: 1}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
16,
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{16, 0},
|
||||
generations: []generation{
|
||||
{},
|
||||
},
|
||||
},
|
||||
map[revision]struct{}{},
|
||||
},
|
||||
}
|
||||
|
||||
// Continuous Compaction
|
||||
ki := newTestKeyIndex()
|
||||
for i, tt := range tests {
|
||||
am := make(map[revision]struct{})
|
||||
ki.compact(tt.compact, am)
|
||||
if !reflect.DeepEqual(ki, tt.wki) {
|
||||
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
|
||||
}
|
||||
if !reflect.DeepEqual(am, tt.wam) {
|
||||
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
|
||||
}
|
||||
}
|
||||
|
||||
// Jump Compaction
|
||||
ki = newTestKeyIndex()
|
||||
for i, tt := range tests {
|
||||
if (i%2 == 0 && i < 6) || (i%2 == 1 && i > 6) {
|
||||
am := make(map[revision]struct{})
|
||||
ki.compact(tt.compact, am)
|
||||
if !reflect.DeepEqual(ki, tt.wki) {
|
||||
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
|
||||
}
|
||||
if !reflect.DeepEqual(am, tt.wam) {
|
||||
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Once Compaction
|
||||
for i, tt := range tests {
|
||||
ki := newTestKeyIndex()
|
||||
am := make(map[revision]struct{})
|
||||
ki.compact(tt.compact, am)
|
||||
if !reflect.DeepEqual(ki, tt.wki) {
|
||||
t.Errorf("#%d: ki = %+v, want %+v", i, ki, tt.wki)
|
||||
}
|
||||
if !reflect.DeepEqual(am, tt.wam) {
|
||||
t.Errorf("#%d: am = %+v, want %+v", i, am, tt.wam)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test that compact on version that higher than last modified version works well
|
||||
func TestKeyIndexCompactOnFurtherRev(t *testing.T) {
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
ki.put(1, 0)
|
||||
ki.put(2, 0)
|
||||
am := make(map[revision]struct{})
|
||||
ki.compact(3, am)
|
||||
|
||||
wki := &keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{2, 0},
|
||||
generations: []generation{
|
||||
{created: revision{1, 0}, ver: 2, revs: []revision{{main: 2}}},
|
||||
},
|
||||
}
|
||||
wam := map[revision]struct{}{
|
||||
{main: 2}: {},
|
||||
}
|
||||
if !reflect.DeepEqual(ki, wki) {
|
||||
t.Errorf("ki = %+v, want %+v", ki, wki)
|
||||
}
|
||||
if !reflect.DeepEqual(am, wam) {
|
||||
t.Errorf("am = %+v, want %+v", am, wam)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexIsEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
ki *keyIndex
|
||||
w bool
|
||||
}{
|
||||
{
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
generations: []generation{{}},
|
||||
},
|
||||
true,
|
||||
},
|
||||
{
|
||||
&keyIndex{
|
||||
key: []byte("foo"),
|
||||
modified: revision{2, 0},
|
||||
generations: []generation{
|
||||
{created: revision{1, 0}, ver: 2, revs: []revision{{main: 2}}},
|
||||
},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := tt.ki.isEmpty()
|
||||
if g != tt.w {
|
||||
t.Errorf("#%d: isEmpty = %v, want %v", i, g, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexFindGeneration(t *testing.T) {
|
||||
ki := newTestKeyIndex()
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
wg *generation
|
||||
}{
|
||||
{0, nil},
|
||||
{1, nil},
|
||||
{2, &ki.generations[0]},
|
||||
{3, &ki.generations[0]},
|
||||
{4, &ki.generations[0]},
|
||||
{5, &ki.generations[0]},
|
||||
{6, nil},
|
||||
{7, nil},
|
||||
{8, &ki.generations[1]},
|
||||
{9, &ki.generations[1]},
|
||||
{10, &ki.generations[1]},
|
||||
{11, &ki.generations[1]},
|
||||
{12, nil},
|
||||
{13, nil},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := ki.findGeneration(tt.rev)
|
||||
if g != tt.wg {
|
||||
t.Errorf("#%d: generation = %+v, want %+v", i, g, tt.wg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKeyIndexLess(t *testing.T) {
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
|
||||
tests := []struct {
|
||||
ki *keyIndex
|
||||
w bool
|
||||
}{
|
||||
{&keyIndex{key: []byte("doo")}, false},
|
||||
{&keyIndex{key: []byte("foo")}, false},
|
||||
{&keyIndex{key: []byte("goo")}, true},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := ki.Less(tt.ki)
|
||||
if g != tt.w {
|
||||
t.Errorf("#%d: Less = %v, want %v", i, g, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerationIsEmpty(t *testing.T) {
|
||||
tests := []struct {
|
||||
g *generation
|
||||
w bool
|
||||
}{
|
||||
{nil, true},
|
||||
{&generation{}, true},
|
||||
{&generation{revs: []revision{{main: 1}}}, false},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
g := tt.g.isEmpty()
|
||||
if g != tt.w {
|
||||
t.Errorf("#%d: isEmpty = %v, want %v", i, g, tt.w)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerationWalk(t *testing.T) {
|
||||
g := &generation{
|
||||
ver: 3,
|
||||
created: revision{2, 0},
|
||||
revs: []revision{{main: 2}, {main: 4}, {main: 6}},
|
||||
}
|
||||
tests := []struct {
|
||||
f func(rev revision) bool
|
||||
wi int
|
||||
}{
|
||||
{func(rev revision) bool { return rev.main >= 7 }, 2},
|
||||
{func(rev revision) bool { return rev.main >= 6 }, 1},
|
||||
{func(rev revision) bool { return rev.main >= 5 }, 1},
|
||||
{func(rev revision) bool { return rev.main >= 4 }, 0},
|
||||
{func(rev revision) bool { return rev.main >= 3 }, 0},
|
||||
{func(rev revision) bool { return rev.main >= 2 }, -1},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
idx := g.walk(tt.f)
|
||||
if idx != tt.wi {
|
||||
t.Errorf("#%d: index = %d, want %d", i, idx, tt.wi)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newTestKeyIndex() *keyIndex {
|
||||
// key: "foo"
|
||||
// rev: 16
|
||||
// generations:
|
||||
// {empty}
|
||||
// {{14, 0}[1], {14, 1}[2], {16, 0}(t)[3]}
|
||||
// {{8, 0}[1], {10, 0}[2], {12, 0}(t)[3]}
|
||||
// {{2, 0}[1], {4, 0}[2], {6, 0}(t)[3]}
|
||||
|
||||
ki := &keyIndex{key: []byte("foo")}
|
||||
ki.put(2, 0)
|
||||
ki.put(4, 0)
|
||||
ki.tombstone(6, 0)
|
||||
ki.put(8, 0)
|
||||
ki.put(10, 0)
|
||||
ki.tombstone(12, 0)
|
||||
ki.put(14, 0)
|
||||
ki.put(14, 1)
|
||||
ki.tombstone(16, 0)
|
||||
return ki
|
||||
}
|
||||
119
vendor/github.com/coreos/etcd/mvcc/kv.go
generated
vendored
Normal file
119
vendor/github.com/coreos/etcd/mvcc/kv.go
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
type RangeOptions struct {
|
||||
Limit int64
|
||||
Rev int64
|
||||
Count bool
|
||||
}
|
||||
|
||||
type RangeResult struct {
|
||||
KVs []mvccpb.KeyValue
|
||||
Rev int64
|
||||
Count int
|
||||
}
|
||||
|
||||
type KV interface {
|
||||
// Rev returns the current revision of the KV.
|
||||
Rev() int64
|
||||
|
||||
// FirstRev returns the first revision of the KV.
|
||||
// After a compaction, the first revision increases to the compaction
|
||||
// revision.
|
||||
FirstRev() int64
|
||||
|
||||
// Range gets the keys in the range at rangeRev.
|
||||
// The returned rev is the current revision of the KV when the operation is executed.
|
||||
// If rangeRev <=0, range gets the keys at currentRev.
|
||||
// If `end` is nil, the request returns the key.
|
||||
// If `end` is not nil and not empty, it gets the keys in range [key, range_end).
|
||||
// If `end` is not nil and empty, it gets the keys greater than or equal to key.
|
||||
// Limit limits the number of keys returned.
|
||||
// If the required rev is compacted, ErrCompacted will be returned.
|
||||
Range(key, end []byte, ro RangeOptions) (r *RangeResult, err error)
|
||||
|
||||
// Put puts the given key, value into the store. Put also takes additional argument lease to
|
||||
// attach a lease to a key-value pair as meta-data. KV implementation does not validate the lease
|
||||
// id.
|
||||
// A put also increases the rev of the store, and generates one event in the event history.
|
||||
// The returned rev is the current revision of the KV when the operation is executed.
|
||||
Put(key, value []byte, lease lease.LeaseID) (rev int64)
|
||||
|
||||
// DeleteRange deletes the given range from the store.
|
||||
// A deleteRange increases the rev of the store if any key in the range exists.
|
||||
// The number of key deleted will be returned.
|
||||
// The returned rev is the current revision of the KV when the operation is executed.
|
||||
// It also generates one event for each key delete in the event history.
|
||||
// if the `end` is nil, deleteRange deletes the key.
|
||||
// if the `end` is not nil, deleteRange deletes the keys in range [key, range_end).
|
||||
DeleteRange(key, end []byte) (n, rev int64)
|
||||
|
||||
// TxnBegin begins a txn. Only Txn prefixed operation can be executed, others will be blocked
|
||||
// until txn ends. Only one on-going txn is allowed.
|
||||
// TxnBegin returns an int64 txn ID.
|
||||
// All txn prefixed operations with same txn ID will be done with the same rev.
|
||||
TxnBegin() int64
|
||||
// TxnEnd ends the on-going txn with txn ID. If the on-going txn ID is not matched, error is returned.
|
||||
TxnEnd(txnID int64) error
|
||||
// TxnRange returns the current revision of the KV when the operation is executed.
|
||||
TxnRange(txnID int64, key, end []byte, ro RangeOptions) (r *RangeResult, err error)
|
||||
TxnPut(txnID int64, key, value []byte, lease lease.LeaseID) (rev int64, err error)
|
||||
TxnDeleteRange(txnID int64, key, end []byte) (n, rev int64, err error)
|
||||
|
||||
// Compact frees all superseded keys with revisions less than rev.
|
||||
Compact(rev int64) (<-chan struct{}, error)
|
||||
|
||||
// Hash retrieves the hash of KV state and revision.
|
||||
// This method is designed for consistency checking purpose.
|
||||
Hash() (hash uint32, revision int64, err error)
|
||||
|
||||
// Commit commits txns into the underlying backend.
|
||||
Commit()
|
||||
|
||||
// Restore restores the KV store from a backend.
|
||||
Restore(b backend.Backend) error
|
||||
Close() error
|
||||
}
|
||||
|
||||
// WatchableKV is a KV that can be watched.
|
||||
type WatchableKV interface {
|
||||
KV
|
||||
Watchable
|
||||
}
|
||||
|
||||
// Watchable is the interface that wraps the NewWatchStream function.
|
||||
type Watchable interface {
|
||||
// NewWatchStream returns a WatchStream that can be used to
|
||||
// watch events happened or happening on the KV.
|
||||
NewWatchStream() WatchStream
|
||||
}
|
||||
|
||||
// ConsistentWatchableKV is a WatchableKV that understands the consistency
|
||||
// algorithm and consistent index.
|
||||
// If the consistent index of executing entry is not larger than the
|
||||
// consistent index of ConsistentWatchableKV, all operations in
|
||||
// this entry are skipped and return empty response.
|
||||
type ConsistentWatchableKV interface {
|
||||
WatchableKV
|
||||
// ConsistentIndex returns the current consistent index of the KV.
|
||||
ConsistentIndex() uint64
|
||||
}
|
||||
842
vendor/github.com/coreos/etcd/mvcc/kv_test.go
generated
vendored
Normal file
842
vendor/github.com/coreos/etcd/mvcc/kv_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,842 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
)
|
||||
|
||||
// Functional tests for features implemented in v3 store. It treats v3 store
|
||||
// as a black box, and tests it by feeding the input and validating the output.
|
||||
|
||||
// TODO: add similar tests on operations in one txn/rev
|
||||
|
||||
type (
|
||||
rangeFunc func(kv KV, key, end []byte, ro RangeOptions) (*RangeResult, error)
|
||||
putFunc func(kv KV, key, value []byte, lease lease.LeaseID) int64
|
||||
deleteRangeFunc func(kv KV, key, end []byte) (n, rev int64)
|
||||
)
|
||||
|
||||
var (
|
||||
normalRangeFunc = func(kv KV, key, end []byte, ro RangeOptions) (*RangeResult, error) {
|
||||
return kv.Range(key, end, ro)
|
||||
}
|
||||
txnRangeFunc = func(kv KV, key, end []byte, ro RangeOptions) (*RangeResult, error) {
|
||||
id := kv.TxnBegin()
|
||||
defer kv.TxnEnd(id)
|
||||
return kv.TxnRange(id, key, end, ro)
|
||||
}
|
||||
|
||||
normalPutFunc = func(kv KV, key, value []byte, lease lease.LeaseID) int64 {
|
||||
return kv.Put(key, value, lease)
|
||||
}
|
||||
txnPutFunc = func(kv KV, key, value []byte, lease lease.LeaseID) int64 {
|
||||
id := kv.TxnBegin()
|
||||
defer kv.TxnEnd(id)
|
||||
rev, err := kv.TxnPut(id, key, value, lease)
|
||||
if err != nil {
|
||||
panic("txn put error")
|
||||
}
|
||||
return rev
|
||||
}
|
||||
|
||||
normalDeleteRangeFunc = func(kv KV, key, end []byte) (n, rev int64) {
|
||||
return kv.DeleteRange(key, end)
|
||||
}
|
||||
txnDeleteRangeFunc = func(kv KV, key, end []byte) (n, rev int64) {
|
||||
id := kv.TxnBegin()
|
||||
defer kv.TxnEnd(id)
|
||||
n, rev, err := kv.TxnDeleteRange(id, key, end)
|
||||
if err != nil {
|
||||
panic("txn delete error")
|
||||
}
|
||||
return n, rev
|
||||
}
|
||||
)
|
||||
|
||||
func TestKVRange(t *testing.T) { testKVRange(t, normalRangeFunc) }
|
||||
func TestKVTxnRange(t *testing.T) { testKVRange(t, txnRangeFunc) }
|
||||
|
||||
func testKVRange(t *testing.T, f rangeFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
kvs := put3TestKVs(s)
|
||||
|
||||
wrev := int64(4)
|
||||
tests := []struct {
|
||||
key, end []byte
|
||||
wkvs []mvccpb.KeyValue
|
||||
}{
|
||||
// get no keys
|
||||
{
|
||||
[]byte("doo"), []byte("foo"),
|
||||
nil,
|
||||
},
|
||||
// get no keys when key == end
|
||||
{
|
||||
[]byte("foo"), []byte("foo"),
|
||||
nil,
|
||||
},
|
||||
// get no keys when ranging single key
|
||||
{
|
||||
[]byte("doo"), nil,
|
||||
nil,
|
||||
},
|
||||
// get all keys
|
||||
{
|
||||
[]byte("foo"), []byte("foo3"),
|
||||
kvs,
|
||||
},
|
||||
// get partial keys
|
||||
{
|
||||
[]byte("foo"), []byte("foo1"),
|
||||
kvs[:1],
|
||||
},
|
||||
// get single key
|
||||
{
|
||||
[]byte("foo"), nil,
|
||||
kvs[:1],
|
||||
},
|
||||
// get entire keyspace
|
||||
{
|
||||
[]byte(""), []byte(""),
|
||||
kvs,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r, err := f(s, tt.key, tt.end, RangeOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Rev != wrev {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, r.Rev, wrev)
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, tt.wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, tt.wkvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVRangeRev(t *testing.T) { testKVRangeRev(t, normalRangeFunc) }
|
||||
func TestKVTxnRangeRev(t *testing.T) { testKVRangeRev(t, normalRangeFunc) }
|
||||
|
||||
func testKVRangeRev(t *testing.T, f rangeFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
kvs := put3TestKVs(s)
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
wrev int64
|
||||
wkvs []mvccpb.KeyValue
|
||||
}{
|
||||
{-1, 4, kvs},
|
||||
{0, 4, kvs},
|
||||
{2, 4, kvs[:1]},
|
||||
{3, 4, kvs[:2]},
|
||||
{4, 4, kvs},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
r, err := f(s, []byte("foo"), []byte("foo3"), RangeOptions{Rev: tt.rev})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.Rev != tt.wrev {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, r.Rev, tt.wrev)
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, tt.wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, tt.wkvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVRangeBadRev(t *testing.T) { testKVRangeBadRev(t, normalRangeFunc) }
|
||||
func TestKVTxnRangeBadRev(t *testing.T) { testKVRangeBadRev(t, normalRangeFunc) }
|
||||
|
||||
func testKVRangeBadRev(t *testing.T, f rangeFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
put3TestKVs(s)
|
||||
if _, err := s.Compact(4); err != nil {
|
||||
t.Fatalf("compact error (%v)", err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
werr error
|
||||
}{
|
||||
{-1, nil}, // <= 0 is most recent store
|
||||
{0, nil},
|
||||
{1, ErrCompacted},
|
||||
{2, ErrCompacted},
|
||||
{4, nil},
|
||||
{5, ErrFutureRev},
|
||||
{100, ErrFutureRev},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
_, err := f(s, []byte("foo"), []byte("foo3"), RangeOptions{Rev: tt.rev})
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: error = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVRangeLimit(t *testing.T) { testKVRangeLimit(t, normalRangeFunc) }
|
||||
func TestKVTxnRangeLimit(t *testing.T) { testKVRangeLimit(t, txnRangeFunc) }
|
||||
|
||||
func testKVRangeLimit(t *testing.T, f rangeFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
kvs := put3TestKVs(s)
|
||||
|
||||
wrev := int64(4)
|
||||
tests := []struct {
|
||||
limit int64
|
||||
wkvs []mvccpb.KeyValue
|
||||
}{
|
||||
// no limit
|
||||
{-1, kvs},
|
||||
// no limit
|
||||
{0, kvs},
|
||||
{1, kvs[:1]},
|
||||
{2, kvs[:2]},
|
||||
{3, kvs},
|
||||
{100, kvs},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
r, err := f(s, []byte("foo"), []byte("foo3"), RangeOptions{Limit: tt.limit})
|
||||
if err != nil {
|
||||
t.Fatalf("#%d: range error (%v)", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, tt.wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, tt.wkvs)
|
||||
}
|
||||
if r.Rev != wrev {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, r.Rev, wrev)
|
||||
}
|
||||
if r.Count != len(kvs) {
|
||||
t.Errorf("#%d: count = %d, want %d", i, r.Count, len(kvs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVPutMultipleTimes(t *testing.T) { testKVPutMultipleTimes(t, normalPutFunc) }
|
||||
func TestKVTxnPutMultipleTimes(t *testing.T) { testKVPutMultipleTimes(t, txnPutFunc) }
|
||||
|
||||
func testKVPutMultipleTimes(t *testing.T, f putFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
base := int64(i + 1)
|
||||
|
||||
rev := f(s, []byte("foo"), []byte("bar"), lease.LeaseID(base))
|
||||
if rev != base+1 {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, rev, base+1)
|
||||
}
|
||||
|
||||
r, err := s.Range([]byte("foo"), nil, RangeOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wkvs := []mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar"), CreateRevision: 2, ModRevision: base + 1, Version: base, Lease: base},
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, wkvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVDeleteRange(t *testing.T) { testKVDeleteRange(t, normalDeleteRangeFunc) }
|
||||
func TestKVTxnDeleteRange(t *testing.T) { testKVDeleteRange(t, txnDeleteRangeFunc) }
|
||||
|
||||
func testKVDeleteRange(t *testing.T, f deleteRangeFunc) {
|
||||
tests := []struct {
|
||||
key, end []byte
|
||||
|
||||
wrev int64
|
||||
wN int64
|
||||
}{
|
||||
{
|
||||
[]byte("foo"), nil,
|
||||
5, 1,
|
||||
},
|
||||
{
|
||||
[]byte("foo"), []byte("foo1"),
|
||||
5, 1,
|
||||
},
|
||||
{
|
||||
[]byte("foo"), []byte("foo2"),
|
||||
5, 2,
|
||||
},
|
||||
{
|
||||
[]byte("foo"), []byte("foo3"),
|
||||
5, 3,
|
||||
},
|
||||
{
|
||||
[]byte("foo3"), []byte("foo8"),
|
||||
4, 0,
|
||||
},
|
||||
{
|
||||
[]byte("foo3"), nil,
|
||||
4, 0,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
s.Put([]byte("foo1"), []byte("bar1"), lease.NoLease)
|
||||
s.Put([]byte("foo2"), []byte("bar2"), lease.NoLease)
|
||||
|
||||
n, rev := f(s, tt.key, tt.end)
|
||||
if n != tt.wN || rev != tt.wrev {
|
||||
t.Errorf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, tt.wN, tt.wrev)
|
||||
}
|
||||
|
||||
cleanup(s, b, tmpPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVDeleteMultipleTimes(t *testing.T) { testKVDeleteMultipleTimes(t, normalDeleteRangeFunc) }
|
||||
func TestKVTxnDeleteMultipleTimes(t *testing.T) { testKVDeleteMultipleTimes(t, txnDeleteRangeFunc) }
|
||||
|
||||
func testKVDeleteMultipleTimes(t *testing.T, f deleteRangeFunc) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
|
||||
n, rev := f(s, []byte("foo"), nil)
|
||||
if n != 1 || rev != 3 {
|
||||
t.Fatalf("n = %d, rev = %d, want (%d, %d)", n, rev, 1, 3)
|
||||
}
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
n, rev := f(s, []byte("foo"), nil)
|
||||
if n != 0 || rev != 3 {
|
||||
t.Fatalf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, 0, 3)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// test that range, put, delete on single key in sequence repeatedly works correctly.
|
||||
func TestKVOperationInSequence(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
base := int64(i*2 + 1)
|
||||
|
||||
// put foo
|
||||
rev := s.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
if rev != base+1 {
|
||||
t.Errorf("#%d: put rev = %d, want %d", i, rev, base+1)
|
||||
}
|
||||
|
||||
r, err := s.Range([]byte("foo"), nil, RangeOptions{Rev: base + 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wkvs := []mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar"), CreateRevision: base + 1, ModRevision: base + 1, Version: 1, Lease: int64(lease.NoLease)},
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, wkvs)
|
||||
}
|
||||
if r.Rev != base+1 {
|
||||
t.Errorf("#%d: range rev = %d, want %d", i, rev, base+1)
|
||||
}
|
||||
|
||||
// delete foo
|
||||
n, rev := s.DeleteRange([]byte("foo"), nil)
|
||||
if n != 1 || rev != base+2 {
|
||||
t.Errorf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, 1, base+2)
|
||||
}
|
||||
|
||||
r, err = s.Range([]byte("foo"), nil, RangeOptions{Rev: base + 2})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if r.KVs != nil {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, nil)
|
||||
}
|
||||
if r.Rev != base+2 {
|
||||
t.Errorf("#%d: range rev = %d, want %d", i, r.Rev, base+2)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVTxnBlockNonTxnOperations(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
tests := []func(){
|
||||
func() { s.Range([]byte("foo"), nil, RangeOptions{}) },
|
||||
func() { s.Put([]byte("foo"), nil, lease.NoLease) },
|
||||
func() { s.DeleteRange([]byte("foo"), nil) },
|
||||
}
|
||||
for i, tt := range tests {
|
||||
id := s.TxnBegin()
|
||||
done := make(chan struct{}, 1)
|
||||
go func() {
|
||||
tt()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("#%d: operation failed to be blocked", i)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
|
||||
s.TxnEnd(id)
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
testutil.FatalStack(t, fmt.Sprintf("#%d: operation failed to be unblocked", i))
|
||||
}
|
||||
}
|
||||
|
||||
// only close backend when we know all the tx are finished
|
||||
cleanup(s, b, tmpPath)
|
||||
}
|
||||
|
||||
func TestKVTxnWrongID(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
id := s.TxnBegin()
|
||||
wrongid := id + 1
|
||||
|
||||
tests := []func() error{
|
||||
func() error {
|
||||
_, err := s.TxnRange(wrongid, []byte("foo"), nil, RangeOptions{})
|
||||
return err
|
||||
},
|
||||
func() error {
|
||||
_, err := s.TxnPut(wrongid, []byte("foo"), nil, lease.NoLease)
|
||||
return err
|
||||
},
|
||||
func() error {
|
||||
_, _, err := s.TxnDeleteRange(wrongid, []byte("foo"), nil)
|
||||
return err
|
||||
},
|
||||
func() error { return s.TxnEnd(wrongid) },
|
||||
}
|
||||
for i, tt := range tests {
|
||||
err := tt()
|
||||
if err != ErrTxnIDMismatch {
|
||||
t.Fatalf("#%d: err = %+v, want %+v", i, err, ErrTxnIDMismatch)
|
||||
}
|
||||
}
|
||||
|
||||
err := s.TxnEnd(id)
|
||||
if err != nil {
|
||||
t.Fatalf("end err = %+v, want %+v", err, nil)
|
||||
}
|
||||
}
|
||||
|
||||
// test that txn range, put, delete on single key in sequence repeatedly works correctly.
|
||||
func TestKVTxnOperationInSequence(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := s.TxnBegin()
|
||||
base := int64(i + 1)
|
||||
|
||||
// put foo
|
||||
rev, err := s.TxnPut(id, []byte("foo"), []byte("bar"), lease.NoLease)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if rev != base+1 {
|
||||
t.Errorf("#%d: put rev = %d, want %d", i, rev, base+1)
|
||||
}
|
||||
|
||||
r, err := s.TxnRange(id, []byte("foo"), nil, RangeOptions{Rev: base + 1})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wkvs := []mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar"), CreateRevision: base + 1, ModRevision: base + 1, Version: 1, Lease: int64(lease.NoLease)},
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, wkvs)
|
||||
}
|
||||
if r.Rev != base+1 {
|
||||
t.Errorf("#%d: range rev = %d, want %d", i, r.Rev, base+1)
|
||||
}
|
||||
|
||||
// delete foo
|
||||
n, rev, err := s.TxnDeleteRange(id, []byte("foo"), nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 || rev != base+1 {
|
||||
t.Errorf("#%d: n = %d, rev = %d, want (%d, %d)", i, n, rev, 1, base+1)
|
||||
}
|
||||
|
||||
r, err = s.TxnRange(id, []byte("foo"), nil, RangeOptions{Rev: base + 1})
|
||||
if err != nil {
|
||||
t.Errorf("#%d: range error (%v)", i, err)
|
||||
}
|
||||
if r.KVs != nil {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, nil)
|
||||
}
|
||||
if r.Rev != base+1 {
|
||||
t.Errorf("#%d: range rev = %d, want %d", i, r.Rev, base+1)
|
||||
}
|
||||
|
||||
s.TxnEnd(id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVCompactReserveLastValue(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar0"), 1)
|
||||
s.Put([]byte("foo"), []byte("bar1"), 2)
|
||||
s.DeleteRange([]byte("foo"), nil)
|
||||
s.Put([]byte("foo"), []byte("bar2"), 3)
|
||||
|
||||
// rev in tests will be called in Compact() one by one on the same store
|
||||
tests := []struct {
|
||||
rev int64
|
||||
// wanted kvs right after the compacted rev
|
||||
wkvs []mvccpb.KeyValue
|
||||
}{
|
||||
{
|
||||
1,
|
||||
[]mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar0"), CreateRevision: 2, ModRevision: 2, Version: 1, Lease: 1},
|
||||
},
|
||||
},
|
||||
{
|
||||
2,
|
||||
[]mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar1"), CreateRevision: 2, ModRevision: 3, Version: 2, Lease: 2},
|
||||
},
|
||||
},
|
||||
{
|
||||
3,
|
||||
nil,
|
||||
},
|
||||
{
|
||||
4,
|
||||
[]mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar2"), CreateRevision: 5, ModRevision: 5, Version: 1, Lease: 3},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
_, err := s.Compact(tt.rev)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: unexpect compact error %v", i, err)
|
||||
}
|
||||
r, err := s.Range([]byte("foo"), nil, RangeOptions{Rev: tt.rev + 1})
|
||||
if err != nil {
|
||||
t.Errorf("#%d: unexpect range error %v", i, err)
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, tt.wkvs) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, r.KVs, tt.wkvs)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVCompactBad(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar0"), lease.NoLease)
|
||||
s.Put([]byte("foo"), []byte("bar1"), lease.NoLease)
|
||||
s.Put([]byte("foo"), []byte("bar2"), lease.NoLease)
|
||||
|
||||
// rev in tests will be called in Compact() one by one on the same store
|
||||
tests := []struct {
|
||||
rev int64
|
||||
werr error
|
||||
}{
|
||||
{0, nil},
|
||||
{1, nil},
|
||||
{1, ErrCompacted},
|
||||
{4, nil},
|
||||
{5, ErrFutureRev},
|
||||
{100, ErrFutureRev},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
_, err := s.Compact(tt.rev)
|
||||
if err != tt.werr {
|
||||
t.Errorf("#%d: compact error = %v, want %v", i, err, tt.werr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVHash(t *testing.T) {
|
||||
hashes := make([]uint32, 3)
|
||||
|
||||
for i := 0; i < len(hashes); i++ {
|
||||
var err error
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
kv := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
kv.Put([]byte("foo0"), []byte("bar0"), lease.NoLease)
|
||||
kv.Put([]byte("foo1"), []byte("bar0"), lease.NoLease)
|
||||
hashes[i], _, err = kv.Hash()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to get hash: %v", err)
|
||||
}
|
||||
cleanup(kv, b, tmpPath)
|
||||
}
|
||||
|
||||
for i := 1; i < len(hashes); i++ {
|
||||
if hashes[i-1] != hashes[i] {
|
||||
t.Errorf("hash[%d](%d) != hash[%d](%d)", i-1, hashes[i-1], i, hashes[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVRestore(t *testing.T) {
|
||||
tests := []func(kv KV){
|
||||
func(kv KV) {
|
||||
kv.Put([]byte("foo"), []byte("bar0"), 1)
|
||||
kv.Put([]byte("foo"), []byte("bar1"), 2)
|
||||
kv.Put([]byte("foo"), []byte("bar2"), 3)
|
||||
},
|
||||
func(kv KV) {
|
||||
kv.Put([]byte("foo"), []byte("bar0"), 1)
|
||||
kv.DeleteRange([]byte("foo"), nil)
|
||||
kv.Put([]byte("foo"), []byte("bar1"), 2)
|
||||
},
|
||||
func(kv KV) {
|
||||
kv.Put([]byte("foo"), []byte("bar0"), 1)
|
||||
kv.Put([]byte("foo"), []byte("bar1"), 2)
|
||||
kv.Compact(1)
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
tt(s)
|
||||
var kvss [][]mvccpb.KeyValue
|
||||
for k := int64(0); k < 10; k++ {
|
||||
r, _ := s.Range([]byte("a"), []byte("z"), RangeOptions{Rev: k})
|
||||
kvss = append(kvss, r.KVs)
|
||||
}
|
||||
s.Close()
|
||||
|
||||
// ns should recover the the previous state from backend.
|
||||
ns := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
// wait for possible compaction to finish
|
||||
testutil.WaitSchedule()
|
||||
var nkvss [][]mvccpb.KeyValue
|
||||
for k := int64(0); k < 10; k++ {
|
||||
r, _ := ns.Range([]byte("a"), []byte("z"), RangeOptions{Rev: k})
|
||||
nkvss = append(nkvss, r.KVs)
|
||||
}
|
||||
cleanup(ns, b, tmpPath)
|
||||
|
||||
if !reflect.DeepEqual(nkvss, kvss) {
|
||||
t.Errorf("#%d: kvs history = %+v, want %+v", i, nkvss, kvss)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestKVSnapshot(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
wkvs := put3TestKVs(s)
|
||||
|
||||
newPath := "new_test"
|
||||
f, err := os.Create(newPath)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(newPath)
|
||||
|
||||
snap := s.b.Snapshot()
|
||||
defer snap.Close()
|
||||
_, err = snap.WriteTo(f)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f.Close()
|
||||
|
||||
ns := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer ns.Close()
|
||||
r, err := ns.Range([]byte("a"), []byte("z"), RangeOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpect range error (%v)", err)
|
||||
}
|
||||
if !reflect.DeepEqual(r.KVs, wkvs) {
|
||||
t.Errorf("kvs = %+v, want %+v", r.KVs, wkvs)
|
||||
}
|
||||
if r.Rev != 4 {
|
||||
t.Errorf("rev = %d, want %d", r.Rev, 4)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchableKVWatch(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
wid := w.Watch([]byte("foo"), []byte("fop"), 0)
|
||||
|
||||
wev := []mvccpb.Event{
|
||||
{Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 2,
|
||||
ModRevision: 2,
|
||||
Version: 1,
|
||||
Lease: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{
|
||||
Key: []byte("foo1"),
|
||||
Value: []byte("bar1"),
|
||||
CreateRevision: 3,
|
||||
ModRevision: 3,
|
||||
Version: 1,
|
||||
Lease: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{
|
||||
Key: []byte("foo1"),
|
||||
Value: []byte("bar11"),
|
||||
CreateRevision: 3,
|
||||
ModRevision: 4,
|
||||
Version: 2,
|
||||
Lease: 3,
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar"), 1)
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.WatchID != wid {
|
||||
t.Errorf("resp.WatchID got = %d, want = %d", resp.WatchID, wid)
|
||||
}
|
||||
ev := resp.Events[0]
|
||||
if !reflect.DeepEqual(ev, wev[0]) {
|
||||
t.Errorf("watched event = %+v, want %+v", ev, wev[0])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
// CPU might be too slow, and the routine is not able to switch around
|
||||
testutil.FatalStack(t, "failed to watch the event")
|
||||
}
|
||||
|
||||
s.Put([]byte("foo1"), []byte("bar1"), 2)
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.WatchID != wid {
|
||||
t.Errorf("resp.WatchID got = %d, want = %d", resp.WatchID, wid)
|
||||
}
|
||||
ev := resp.Events[0]
|
||||
if !reflect.DeepEqual(ev, wev[1]) {
|
||||
t.Errorf("watched event = %+v, want %+v", ev, wev[1])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
testutil.FatalStack(t, "failed to watch the event")
|
||||
}
|
||||
|
||||
w = s.NewWatchStream()
|
||||
wid = w.Watch([]byte("foo1"), []byte("foo2"), 3)
|
||||
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.WatchID != wid {
|
||||
t.Errorf("resp.WatchID got = %d, want = %d", resp.WatchID, wid)
|
||||
}
|
||||
ev := resp.Events[0]
|
||||
if !reflect.DeepEqual(ev, wev[1]) {
|
||||
t.Errorf("watched event = %+v, want %+v", ev, wev[1])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
testutil.FatalStack(t, "failed to watch the event")
|
||||
}
|
||||
|
||||
s.Put([]byte("foo1"), []byte("bar11"), 3)
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.WatchID != wid {
|
||||
t.Errorf("resp.WatchID got = %d, want = %d", resp.WatchID, wid)
|
||||
}
|
||||
ev := resp.Events[0]
|
||||
if !reflect.DeepEqual(ev, wev[2]) {
|
||||
t.Errorf("watched event = %+v, want %+v", ev, wev[2])
|
||||
}
|
||||
case <-time.After(5 * time.Second):
|
||||
testutil.FatalStack(t, "failed to watch the event")
|
||||
}
|
||||
}
|
||||
|
||||
func cleanup(s KV, b backend.Backend, path string) {
|
||||
s.Close()
|
||||
b.Close()
|
||||
os.Remove(path)
|
||||
}
|
||||
|
||||
func put3TestKVs(s KV) []mvccpb.KeyValue {
|
||||
s.Put([]byte("foo"), []byte("bar"), 1)
|
||||
s.Put([]byte("foo1"), []byte("bar1"), 2)
|
||||
s.Put([]byte("foo2"), []byte("bar2"), 3)
|
||||
return []mvccpb.KeyValue{
|
||||
{Key: []byte("foo"), Value: []byte("bar"), CreateRevision: 2, ModRevision: 2, Version: 1, Lease: 1},
|
||||
{Key: []byte("foo1"), Value: []byte("bar1"), CreateRevision: 3, ModRevision: 3, Version: 1, Lease: 2},
|
||||
{Key: []byte("foo2"), Value: []byte("bar2"), CreateRevision: 4, ModRevision: 4, Version: 1, Lease: 3},
|
||||
}
|
||||
}
|
||||
710
vendor/github.com/coreos/etcd/mvcc/kvstore.go
generated
vendored
Normal file
710
vendor/github.com/coreos/etcd/mvcc/kvstore.go
generated
vendored
Normal file
|
|
@ -0,0 +1,710 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/schedule"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
keyBucketName = []byte("key")
|
||||
metaBucketName = []byte("meta")
|
||||
|
||||
// markedRevBytesLen is the byte length of marked revision.
|
||||
// The first `revBytesLen` bytes represents a normal revision. The last
|
||||
// one byte is the mark.
|
||||
markedRevBytesLen = revBytesLen + 1
|
||||
markBytePosition = markedRevBytesLen - 1
|
||||
markTombstone byte = 't'
|
||||
|
||||
consistentIndexKeyName = []byte("consistent_index")
|
||||
scheduledCompactKeyName = []byte("scheduledCompactRev")
|
||||
finishedCompactKeyName = []byte("finishedCompactRev")
|
||||
|
||||
ErrTxnIDMismatch = errors.New("mvcc: txn id mismatch")
|
||||
ErrCompacted = errors.New("mvcc: required revision has been compacted")
|
||||
ErrFutureRev = errors.New("mvcc: required revision is a future revision")
|
||||
ErrCanceled = errors.New("mvcc: watcher is canceled")
|
||||
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "mvcc")
|
||||
)
|
||||
|
||||
// ConsistentIndexGetter is an interface that wraps the Get method.
|
||||
// Consistent index is the offset of an entry in a consistent replicated log.
|
||||
type ConsistentIndexGetter interface {
|
||||
// ConsistentIndex returns the consistent index of current executing entry.
|
||||
ConsistentIndex() uint64
|
||||
}
|
||||
|
||||
type store struct {
|
||||
mu sync.Mutex // guards the following
|
||||
|
||||
ig ConsistentIndexGetter
|
||||
|
||||
b backend.Backend
|
||||
kvindex index
|
||||
|
||||
le lease.Lessor
|
||||
|
||||
currentRev revision
|
||||
// the main revision of the last compaction
|
||||
compactMainRev int64
|
||||
|
||||
tx backend.BatchTx
|
||||
txnID int64 // tracks the current txnID to verify txn operations
|
||||
txnModify bool
|
||||
|
||||
// bytesBuf8 is a byte slice of length 8
|
||||
// to avoid a repetitive allocation in saveIndex.
|
||||
bytesBuf8 []byte
|
||||
|
||||
changes []mvccpb.KeyValue
|
||||
fifoSched schedule.Scheduler
|
||||
|
||||
stopc chan struct{}
|
||||
}
|
||||
|
||||
// NewStore returns a new store. It is useful to create a store inside
|
||||
// mvcc pkg. It should only be used for testing externally.
|
||||
func NewStore(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *store {
|
||||
s := &store{
|
||||
b: b,
|
||||
ig: ig,
|
||||
kvindex: newTreeIndex(),
|
||||
|
||||
le: le,
|
||||
|
||||
currentRev: revision{main: 1},
|
||||
compactMainRev: -1,
|
||||
|
||||
bytesBuf8: make([]byte, 8, 8),
|
||||
fifoSched: schedule.NewFIFOScheduler(),
|
||||
|
||||
stopc: make(chan struct{}),
|
||||
}
|
||||
|
||||
if s.le != nil {
|
||||
s.le.SetRangeDeleter(s)
|
||||
}
|
||||
|
||||
tx := s.b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket(keyBucketName)
|
||||
tx.UnsafeCreateBucket(metaBucketName)
|
||||
tx.Unlock()
|
||||
s.b.ForceCommit()
|
||||
|
||||
if err := s.restore(); err != nil {
|
||||
// TODO: return the error instead of panic here?
|
||||
panic("failed to recover store from backend")
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *store) Rev() int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.currentRev.main
|
||||
}
|
||||
|
||||
func (s *store) FirstRev() int64 {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.compactMainRev
|
||||
}
|
||||
|
||||
func (s *store) Put(key, value []byte, lease lease.LeaseID) int64 {
|
||||
id := s.TxnBegin()
|
||||
s.put(key, value, lease)
|
||||
s.txnEnd(id)
|
||||
|
||||
putCounter.Inc()
|
||||
|
||||
return int64(s.currentRev.main)
|
||||
}
|
||||
|
||||
func (s *store) Range(key, end []byte, ro RangeOptions) (r *RangeResult, err error) {
|
||||
id := s.TxnBegin()
|
||||
kvs, count, rev, err := s.rangeKeys(key, end, ro.Limit, ro.Rev, ro.Count)
|
||||
s.txnEnd(id)
|
||||
|
||||
rangeCounter.Inc()
|
||||
|
||||
r = &RangeResult{
|
||||
KVs: kvs,
|
||||
Count: count,
|
||||
Rev: rev,
|
||||
}
|
||||
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (s *store) DeleteRange(key, end []byte) (n, rev int64) {
|
||||
id := s.TxnBegin()
|
||||
n = s.deleteRange(key, end)
|
||||
s.txnEnd(id)
|
||||
|
||||
deleteCounter.Inc()
|
||||
|
||||
return n, int64(s.currentRev.main)
|
||||
}
|
||||
|
||||
func (s *store) TxnBegin() int64 {
|
||||
s.mu.Lock()
|
||||
s.currentRev.sub = 0
|
||||
s.tx = s.b.BatchTx()
|
||||
s.tx.Lock()
|
||||
|
||||
s.txnID = rand.Int63()
|
||||
return s.txnID
|
||||
}
|
||||
|
||||
func (s *store) TxnEnd(txnID int64) error {
|
||||
err := s.txnEnd(txnID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
txnCounter.Inc()
|
||||
return nil
|
||||
}
|
||||
|
||||
// txnEnd is used for unlocking an internal txn. It does
|
||||
// not increase the txnCounter.
|
||||
func (s *store) txnEnd(txnID int64) error {
|
||||
if txnID != s.txnID {
|
||||
return ErrTxnIDMismatch
|
||||
}
|
||||
|
||||
// only update index if the txn modifies the mvcc state.
|
||||
// read only txn might execute with one write txn concurrently,
|
||||
// it should not write its index to mvcc.
|
||||
if s.txnModify {
|
||||
s.saveIndex()
|
||||
}
|
||||
s.txnModify = false
|
||||
|
||||
s.tx.Unlock()
|
||||
if s.currentRev.sub != 0 {
|
||||
s.currentRev.main += 1
|
||||
}
|
||||
s.currentRev.sub = 0
|
||||
|
||||
dbTotalSize.Set(float64(s.b.Size()))
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) TxnRange(txnID int64, key, end []byte, ro RangeOptions) (r *RangeResult, err error) {
|
||||
if txnID != s.txnID {
|
||||
return nil, ErrTxnIDMismatch
|
||||
}
|
||||
|
||||
kvs, count, rev, err := s.rangeKeys(key, end, ro.Limit, ro.Rev, ro.Count)
|
||||
|
||||
r = &RangeResult{
|
||||
KVs: kvs,
|
||||
Count: count,
|
||||
Rev: rev,
|
||||
}
|
||||
return r, err
|
||||
}
|
||||
|
||||
func (s *store) TxnPut(txnID int64, key, value []byte, lease lease.LeaseID) (rev int64, err error) {
|
||||
if txnID != s.txnID {
|
||||
return 0, ErrTxnIDMismatch
|
||||
}
|
||||
|
||||
s.put(key, value, lease)
|
||||
return int64(s.currentRev.main + 1), nil
|
||||
}
|
||||
|
||||
func (s *store) TxnDeleteRange(txnID int64, key, end []byte) (n, rev int64, err error) {
|
||||
if txnID != s.txnID {
|
||||
return 0, 0, ErrTxnIDMismatch
|
||||
}
|
||||
|
||||
n = s.deleteRange(key, end)
|
||||
if n != 0 || s.currentRev.sub != 0 {
|
||||
rev = int64(s.currentRev.main + 1)
|
||||
} else {
|
||||
rev = int64(s.currentRev.main)
|
||||
}
|
||||
return n, rev, nil
|
||||
}
|
||||
|
||||
func (s *store) compactBarrier(ctx context.Context, ch chan struct{}) {
|
||||
if ctx == nil || ctx.Err() != nil {
|
||||
s.mu.Lock()
|
||||
select {
|
||||
case <-s.stopc:
|
||||
default:
|
||||
f := func(ctx context.Context) { s.compactBarrier(ctx, ch) }
|
||||
s.fifoSched.Schedule(f)
|
||||
}
|
||||
s.mu.Unlock()
|
||||
return
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
|
||||
func (s *store) Compact(rev int64) (<-chan struct{}, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
if rev <= s.compactMainRev {
|
||||
ch := make(chan struct{})
|
||||
f := func(ctx context.Context) { s.compactBarrier(ctx, ch) }
|
||||
s.fifoSched.Schedule(f)
|
||||
return ch, ErrCompacted
|
||||
}
|
||||
if rev > s.currentRev.main {
|
||||
return nil, ErrFutureRev
|
||||
}
|
||||
|
||||
start := time.Now()
|
||||
|
||||
s.compactMainRev = rev
|
||||
|
||||
rbytes := newRevBytes()
|
||||
revToBytes(revision{main: rev}, rbytes)
|
||||
|
||||
tx := s.b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafePut(metaBucketName, scheduledCompactKeyName, rbytes)
|
||||
tx.Unlock()
|
||||
// ensure that desired compaction is persisted
|
||||
s.b.ForceCommit()
|
||||
|
||||
keep := s.kvindex.Compact(rev)
|
||||
ch := make(chan struct{})
|
||||
var j = func(ctx context.Context) {
|
||||
if ctx.Err() != nil {
|
||||
s.compactBarrier(ctx, ch)
|
||||
return
|
||||
}
|
||||
if !s.scheduleCompaction(rev, keep) {
|
||||
s.compactBarrier(nil, ch)
|
||||
return
|
||||
}
|
||||
close(ch)
|
||||
}
|
||||
|
||||
s.fifoSched.Schedule(j)
|
||||
|
||||
indexCompactionPauseDurations.Observe(float64(time.Since(start) / time.Millisecond))
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
// DefaultIgnores is a map of keys to ignore in hash checking.
|
||||
var DefaultIgnores map[backend.IgnoreKey]struct{}
|
||||
|
||||
func init() {
|
||||
DefaultIgnores = map[backend.IgnoreKey]struct{}{
|
||||
// consistent index might be changed due to v2 internal sync, which
|
||||
// is not controllable by the user.
|
||||
{Bucket: string(metaBucketName), Key: string(consistentIndexKeyName)}: {},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) Hash() (uint32, int64, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.b.ForceCommit()
|
||||
|
||||
h, err := s.b.Hash(DefaultIgnores)
|
||||
rev := s.currentRev.main
|
||||
return h, rev, err
|
||||
}
|
||||
|
||||
func (s *store) Commit() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
s.tx = s.b.BatchTx()
|
||||
s.tx.Lock()
|
||||
s.saveIndex()
|
||||
s.tx.Unlock()
|
||||
s.b.ForceCommit()
|
||||
}
|
||||
|
||||
func (s *store) Restore(b backend.Backend) error {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
close(s.stopc)
|
||||
s.fifoSched.Stop()
|
||||
|
||||
s.b = b
|
||||
s.kvindex = newTreeIndex()
|
||||
s.currentRev = revision{main: 1}
|
||||
s.compactMainRev = -1
|
||||
s.tx = b.BatchTx()
|
||||
s.txnID = -1
|
||||
s.fifoSched = schedule.NewFIFOScheduler()
|
||||
s.stopc = make(chan struct{})
|
||||
|
||||
return s.restore()
|
||||
}
|
||||
|
||||
func (s *store) restore() error {
|
||||
min, max := newRevBytes(), newRevBytes()
|
||||
revToBytes(revision{main: 1}, min)
|
||||
revToBytes(revision{main: math.MaxInt64, sub: math.MaxInt64}, max)
|
||||
|
||||
keyToLease := make(map[string]lease.LeaseID)
|
||||
|
||||
// use an unordered map to hold the temp index data to speed up
|
||||
// the initial key index recovery.
|
||||
// we will convert this unordered map into the tree index later.
|
||||
unordered := make(map[string]*keyIndex, 100000)
|
||||
|
||||
// restore index
|
||||
tx := s.b.BatchTx()
|
||||
tx.Lock()
|
||||
_, finishedCompactBytes := tx.UnsafeRange(metaBucketName, finishedCompactKeyName, nil, 0)
|
||||
if len(finishedCompactBytes) != 0 {
|
||||
s.compactMainRev = bytesToRev(finishedCompactBytes[0]).main
|
||||
plog.Printf("restore compact to %d", s.compactMainRev)
|
||||
}
|
||||
|
||||
// TODO: limit N to reduce max memory usage
|
||||
keys, vals := tx.UnsafeRange(keyBucketName, min, max, 0)
|
||||
for i, key := range keys {
|
||||
var kv mvccpb.KeyValue
|
||||
if err := kv.Unmarshal(vals[i]); err != nil {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
|
||||
rev := bytesToRev(key[:revBytesLen])
|
||||
|
||||
// restore index
|
||||
switch {
|
||||
case isTombstone(key):
|
||||
if ki, ok := unordered[string(kv.Key)]; ok {
|
||||
ki.tombstone(rev.main, rev.sub)
|
||||
}
|
||||
delete(keyToLease, string(kv.Key))
|
||||
|
||||
default:
|
||||
ki, ok := unordered[string(kv.Key)]
|
||||
if ok {
|
||||
ki.put(rev.main, rev.sub)
|
||||
} else {
|
||||
ki = &keyIndex{key: kv.Key}
|
||||
ki.restore(revision{kv.CreateRevision, 0}, rev, kv.Version)
|
||||
unordered[string(kv.Key)] = ki
|
||||
}
|
||||
|
||||
if lid := lease.LeaseID(kv.Lease); lid != lease.NoLease {
|
||||
keyToLease[string(kv.Key)] = lid
|
||||
} else {
|
||||
delete(keyToLease, string(kv.Key))
|
||||
}
|
||||
}
|
||||
|
||||
// update revision
|
||||
s.currentRev = rev
|
||||
}
|
||||
|
||||
// restore the tree index from the unordered index.
|
||||
for _, v := range unordered {
|
||||
s.kvindex.Insert(v)
|
||||
}
|
||||
|
||||
// keys in the range [compacted revision -N, compaction] might all be deleted due to compaction.
|
||||
// the correct revision should be set to compaction revision in the case, not the largest revision
|
||||
// we have seen.
|
||||
if s.currentRev.main < s.compactMainRev {
|
||||
s.currentRev.main = s.compactMainRev
|
||||
}
|
||||
|
||||
for key, lid := range keyToLease {
|
||||
if s.le == nil {
|
||||
panic("no lessor to attach lease")
|
||||
}
|
||||
err := s.le.Attach(lid, []lease.LeaseItem{{Key: key}})
|
||||
if err != nil {
|
||||
plog.Errorf("unexpected Attach error: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
_, scheduledCompactBytes := tx.UnsafeRange(metaBucketName, scheduledCompactKeyName, nil, 0)
|
||||
scheduledCompact := int64(0)
|
||||
if len(scheduledCompactBytes) != 0 {
|
||||
scheduledCompact = bytesToRev(scheduledCompactBytes[0]).main
|
||||
if scheduledCompact <= s.compactMainRev {
|
||||
scheduledCompact = 0
|
||||
}
|
||||
}
|
||||
|
||||
tx.Unlock()
|
||||
|
||||
if scheduledCompact != 0 {
|
||||
s.Compact(scheduledCompact)
|
||||
plog.Printf("resume scheduled compaction at %d", scheduledCompact)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) Close() error {
|
||||
close(s.stopc)
|
||||
s.fifoSched.Stop()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *store) Equal(b *store) bool {
|
||||
if a.currentRev != b.currentRev {
|
||||
return false
|
||||
}
|
||||
if a.compactMainRev != b.compactMainRev {
|
||||
return false
|
||||
}
|
||||
return a.kvindex.Equal(b.kvindex)
|
||||
}
|
||||
|
||||
// range is a keyword in Go, add Keys suffix.
|
||||
func (s *store) rangeKeys(key, end []byte, limit, rangeRev int64, countOnly bool) (kvs []mvccpb.KeyValue, count int, curRev int64, err error) {
|
||||
curRev = int64(s.currentRev.main)
|
||||
if s.currentRev.sub > 0 {
|
||||
curRev += 1
|
||||
}
|
||||
|
||||
if rangeRev > curRev {
|
||||
return nil, -1, s.currentRev.main, ErrFutureRev
|
||||
}
|
||||
var rev int64
|
||||
if rangeRev <= 0 {
|
||||
rev = curRev
|
||||
} else {
|
||||
rev = rangeRev
|
||||
}
|
||||
if rev < s.compactMainRev {
|
||||
return nil, -1, 0, ErrCompacted
|
||||
}
|
||||
|
||||
_, revpairs := s.kvindex.Range(key, end, int64(rev))
|
||||
if len(revpairs) == 0 {
|
||||
return nil, 0, curRev, nil
|
||||
}
|
||||
if countOnly {
|
||||
return nil, len(revpairs), curRev, nil
|
||||
}
|
||||
|
||||
for _, revpair := range revpairs {
|
||||
start, end := revBytesRange(revpair)
|
||||
|
||||
_, vs := s.tx.UnsafeRange(keyBucketName, start, end, 0)
|
||||
if len(vs) != 1 {
|
||||
plog.Fatalf("range cannot find rev (%d,%d)", revpair.main, revpair.sub)
|
||||
}
|
||||
|
||||
var kv mvccpb.KeyValue
|
||||
if err := kv.Unmarshal(vs[0]); err != nil {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
kvs = append(kvs, kv)
|
||||
if limit > 0 && len(kvs) >= int(limit) {
|
||||
break
|
||||
}
|
||||
}
|
||||
return kvs, len(revpairs), curRev, nil
|
||||
}
|
||||
|
||||
func (s *store) put(key, value []byte, leaseID lease.LeaseID) {
|
||||
s.txnModify = true
|
||||
|
||||
rev := s.currentRev.main + 1
|
||||
c := rev
|
||||
oldLease := lease.NoLease
|
||||
|
||||
// if the key exists before, use its previous created and
|
||||
// get its previous leaseID
|
||||
_, created, ver, err := s.kvindex.Get(key, rev)
|
||||
if err == nil {
|
||||
c = created.main
|
||||
oldLease = s.le.GetLease(lease.LeaseItem{Key: string(key)})
|
||||
}
|
||||
|
||||
ibytes := newRevBytes()
|
||||
revToBytes(revision{main: rev, sub: s.currentRev.sub}, ibytes)
|
||||
|
||||
ver = ver + 1
|
||||
kv := mvccpb.KeyValue{
|
||||
Key: key,
|
||||
Value: value,
|
||||
CreateRevision: c,
|
||||
ModRevision: rev,
|
||||
Version: ver,
|
||||
Lease: int64(leaseID),
|
||||
}
|
||||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
}
|
||||
|
||||
s.tx.UnsafeSeqPut(keyBucketName, ibytes, d)
|
||||
s.kvindex.Put(key, revision{main: rev, sub: s.currentRev.sub})
|
||||
s.changes = append(s.changes, kv)
|
||||
s.currentRev.sub += 1
|
||||
|
||||
if oldLease != lease.NoLease {
|
||||
if s.le == nil {
|
||||
panic("no lessor to detach lease")
|
||||
}
|
||||
|
||||
err = s.le.Detach(oldLease, []lease.LeaseItem{{Key: string(key)}})
|
||||
if err != nil {
|
||||
plog.Errorf("unexpected error from lease detach: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if leaseID != lease.NoLease {
|
||||
if s.le == nil {
|
||||
panic("no lessor to attach lease")
|
||||
}
|
||||
|
||||
err = s.le.Attach(leaseID, []lease.LeaseItem{{Key: string(key)}})
|
||||
if err != nil {
|
||||
panic("unexpected error from lease Attach")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) deleteRange(key, end []byte) int64 {
|
||||
s.txnModify = true
|
||||
|
||||
rrev := s.currentRev.main
|
||||
if s.currentRev.sub > 0 {
|
||||
rrev += 1
|
||||
}
|
||||
keys, revs := s.kvindex.Range(key, end, rrev)
|
||||
|
||||
if len(keys) == 0 {
|
||||
return 0
|
||||
}
|
||||
|
||||
for i, key := range keys {
|
||||
s.delete(key, revs[i])
|
||||
}
|
||||
return int64(len(keys))
|
||||
}
|
||||
|
||||
func (s *store) delete(key []byte, rev revision) {
|
||||
mainrev := s.currentRev.main + 1
|
||||
|
||||
ibytes := newRevBytes()
|
||||
revToBytes(revision{main: mainrev, sub: s.currentRev.sub}, ibytes)
|
||||
ibytes = appendMarkTombstone(ibytes)
|
||||
|
||||
kv := mvccpb.KeyValue{
|
||||
Key: key,
|
||||
}
|
||||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
}
|
||||
|
||||
s.tx.UnsafeSeqPut(keyBucketName, ibytes, d)
|
||||
err = s.kvindex.Tombstone(key, revision{main: mainrev, sub: s.currentRev.sub})
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot tombstone an existing key (%s): %v", string(key), err)
|
||||
}
|
||||
s.changes = append(s.changes, kv)
|
||||
s.currentRev.sub += 1
|
||||
|
||||
item := lease.LeaseItem{Key: string(key)}
|
||||
leaseID := s.le.GetLease(item)
|
||||
|
||||
if leaseID != lease.NoLease {
|
||||
err = s.le.Detach(leaseID, []lease.LeaseItem{item})
|
||||
if err != nil {
|
||||
plog.Errorf("cannot detach %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *store) getChanges() []mvccpb.KeyValue {
|
||||
changes := s.changes
|
||||
s.changes = make([]mvccpb.KeyValue, 0, 4)
|
||||
return changes
|
||||
}
|
||||
|
||||
func (s *store) saveIndex() {
|
||||
if s.ig == nil {
|
||||
return
|
||||
}
|
||||
tx := s.tx
|
||||
bs := s.bytesBuf8
|
||||
binary.BigEndian.PutUint64(bs, s.ig.ConsistentIndex())
|
||||
// put the index into the underlying backend
|
||||
// tx has been locked in TxnBegin, so there is no need to lock it again
|
||||
tx.UnsafePut(metaBucketName, consistentIndexKeyName, bs)
|
||||
}
|
||||
|
||||
func (s *store) ConsistentIndex() uint64 {
|
||||
// TODO: cache index in a uint64 field?
|
||||
tx := s.b.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
_, vs := tx.UnsafeRange(metaBucketName, consistentIndexKeyName, nil, 0)
|
||||
if len(vs) == 0 {
|
||||
return 0
|
||||
}
|
||||
return binary.BigEndian.Uint64(vs[0])
|
||||
}
|
||||
|
||||
// appendMarkTombstone appends tombstone mark to normal revision bytes.
|
||||
func appendMarkTombstone(b []byte) []byte {
|
||||
if len(b) != revBytesLen {
|
||||
plog.Panicf("cannot append mark to non normal revision bytes")
|
||||
}
|
||||
return append(b, markTombstone)
|
||||
}
|
||||
|
||||
// isTombstone checks whether the revision bytes is a tombstone.
|
||||
func isTombstone(b []byte) bool {
|
||||
return len(b) == markedRevBytesLen && b[markBytePosition] == markTombstone
|
||||
}
|
||||
|
||||
// revBytesRange returns the range of revision bytes at
|
||||
// the given revision.
|
||||
func revBytesRange(rev revision) (start, end []byte) {
|
||||
start = newRevBytes()
|
||||
revToBytes(rev, start)
|
||||
|
||||
end = newRevBytes()
|
||||
endRev := revision{main: rev.main, sub: rev.sub + 1}
|
||||
revToBytes(endRev, end)
|
||||
|
||||
return start, end
|
||||
}
|
||||
124
vendor/github.com/coreos/etcd/mvcc/kvstore_bench_test.go
generated
vendored
Normal file
124
vendor/github.com/coreos/etcd/mvcc/kvstore_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
)
|
||||
|
||||
type fakeConsistentIndex uint64
|
||||
|
||||
func (i *fakeConsistentIndex) ConsistentIndex() uint64 {
|
||||
return atomic.LoadUint64((*uint64)(i))
|
||||
}
|
||||
|
||||
func BenchmarkStorePut(b *testing.B) {
|
||||
var i fakeConsistentIndex
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(be, &lease.FakeLessor{}, &i)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
bytesN := 64
|
||||
keys := createBytesSlice(bytesN, b.N)
|
||||
vals := createBytesSlice(bytesN, b.N)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Put(keys[i], vals[i], lease.NoLease)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStoreTxnPutUpdate is same as above, but instead updates single key
|
||||
func BenchmarkStorePutUpdate(b *testing.B) {
|
||||
var i fakeConsistentIndex
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(be, &lease.FakeLessor{}, &i)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
keys := createBytesSlice(64, 1)
|
||||
vals := createBytesSlice(1024, 1)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Put(keys[0], vals[0], lease.NoLease)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkStoreTxnPut benchmarks the Put operation
|
||||
// with transaction begin and end, where transaction involves
|
||||
// some synchronization operations, such as mutex locking.
|
||||
func BenchmarkStoreTxnPut(b *testing.B) {
|
||||
var i fakeConsistentIndex
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(be, &lease.FakeLessor{}, &i)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
bytesN := 64
|
||||
keys := createBytesSlice(bytesN, b.N)
|
||||
vals := createBytesSlice(bytesN, b.N)
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := s.TxnBegin()
|
||||
if _, err := s.TxnPut(id, keys[i], vals[i], lease.NoLease); err != nil {
|
||||
plog.Fatalf("txn put error: %v", err)
|
||||
}
|
||||
s.TxnEnd(id)
|
||||
}
|
||||
}
|
||||
|
||||
// benchmarkStoreRestore benchmarks the restore operation
|
||||
func benchmarkStoreRestore(revsPerKey int, b *testing.B) {
|
||||
var i fakeConsistentIndex
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(be, &lease.FakeLessor{}, &i)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
bytesN := 64
|
||||
keys := createBytesSlice(bytesN, b.N)
|
||||
vals := createBytesSlice(bytesN, b.N)
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
for j := 0; j < revsPerKey; j++ {
|
||||
id := s.TxnBegin()
|
||||
if _, err := s.TxnPut(id, keys[i], vals[i], lease.NoLease); err != nil {
|
||||
plog.Fatalf("txn put error: %v", err)
|
||||
}
|
||||
s.TxnEnd(id)
|
||||
}
|
||||
}
|
||||
b.ResetTimer()
|
||||
s = NewStore(be, &lease.FakeLessor{}, &i)
|
||||
}
|
||||
|
||||
func BenchmarkStoreRestoreRevs1(b *testing.B) {
|
||||
benchmarkStoreRestore(1, b)
|
||||
}
|
||||
|
||||
func BenchmarkStoreRestoreRevs10(b *testing.B) {
|
||||
benchmarkStoreRestore(10, b)
|
||||
}
|
||||
|
||||
func BenchmarkStoreRestoreRevs20(b *testing.B) {
|
||||
benchmarkStoreRestore(20, b)
|
||||
}
|
||||
66
vendor/github.com/coreos/etcd/mvcc/kvstore_compaction.go
generated
vendored
Normal file
66
vendor/github.com/coreos/etcd/mvcc/kvstore_compaction.go
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struct{}) bool {
|
||||
totalStart := time.Now()
|
||||
defer dbCompactionTotalDurations.Observe(float64(time.Since(totalStart) / time.Millisecond))
|
||||
|
||||
end := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(end, uint64(compactMainRev+1))
|
||||
|
||||
batchsize := int64(10000)
|
||||
last := make([]byte, 8+1+8)
|
||||
for {
|
||||
var rev revision
|
||||
|
||||
start := time.Now()
|
||||
tx := s.b.BatchTx()
|
||||
tx.Lock()
|
||||
|
||||
keys, _ := tx.UnsafeRange(keyBucketName, last, end, batchsize)
|
||||
for _, key := range keys {
|
||||
rev = bytesToRev(key)
|
||||
if _, ok := keep[rev]; !ok {
|
||||
tx.UnsafeDelete(keyBucketName, key)
|
||||
}
|
||||
}
|
||||
|
||||
if len(keys) < int(batchsize) {
|
||||
rbytes := make([]byte, 8+1+8)
|
||||
revToBytes(revision{main: compactMainRev}, rbytes)
|
||||
tx.UnsafePut(metaBucketName, finishedCompactKeyName, rbytes)
|
||||
tx.Unlock()
|
||||
plog.Printf("finished scheduled compaction at %d (took %v)", compactMainRev, time.Since(totalStart))
|
||||
return true
|
||||
}
|
||||
|
||||
// update last
|
||||
revToBytes(revision{main: rev.main, sub: rev.sub + 1}, last)
|
||||
tx.Unlock()
|
||||
dbCompactionPauseDurations.Observe(float64(time.Since(start) / time.Millisecond))
|
||||
|
||||
select {
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
case <-s.stopc:
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
135
vendor/github.com/coreos/etcd/mvcc/kvstore_compaction_test.go
generated
vendored
Normal file
135
vendor/github.com/coreos/etcd/mvcc/kvstore_compaction_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
)
|
||||
|
||||
func TestScheduleCompaction(t *testing.T) {
|
||||
revs := []revision{{1, 0}, {2, 0}, {3, 0}}
|
||||
|
||||
tests := []struct {
|
||||
rev int64
|
||||
keep map[revision]struct{}
|
||||
wrevs []revision
|
||||
}{
|
||||
// compact at 1 and discard all history
|
||||
{
|
||||
1,
|
||||
nil,
|
||||
revs[1:],
|
||||
},
|
||||
// compact at 3 and discard all history
|
||||
{
|
||||
3,
|
||||
nil,
|
||||
nil,
|
||||
},
|
||||
// compact at 1 and keeps history one step earlier
|
||||
{
|
||||
1,
|
||||
map[revision]struct{}{
|
||||
{main: 1}: {},
|
||||
},
|
||||
revs,
|
||||
},
|
||||
// compact at 1 and keeps history two steps earlier
|
||||
{
|
||||
3,
|
||||
map[revision]struct{}{
|
||||
{main: 2}: {},
|
||||
{main: 3}: {},
|
||||
},
|
||||
revs[1:],
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
tx := s.b.BatchTx()
|
||||
|
||||
tx.Lock()
|
||||
ibytes := newRevBytes()
|
||||
for _, rev := range revs {
|
||||
revToBytes(rev, ibytes)
|
||||
tx.UnsafePut(keyBucketName, ibytes, []byte("bar"))
|
||||
}
|
||||
tx.Unlock()
|
||||
|
||||
s.scheduleCompaction(tt.rev, tt.keep)
|
||||
|
||||
tx.Lock()
|
||||
for _, rev := range tt.wrevs {
|
||||
revToBytes(rev, ibytes)
|
||||
keys, _ := tx.UnsafeRange(keyBucketName, ibytes, nil, 0)
|
||||
if len(keys) != 1 {
|
||||
t.Errorf("#%d: range on %v = %d, want 1", i, rev, len(keys))
|
||||
}
|
||||
}
|
||||
_, vals := tx.UnsafeRange(metaBucketName, finishedCompactKeyName, nil, 0)
|
||||
revToBytes(revision{main: tt.rev}, ibytes)
|
||||
if w := [][]byte{ibytes}; !reflect.DeepEqual(vals, w) {
|
||||
t.Errorf("#%d: vals on %v = %+v, want %+v", i, finishedCompactKeyName, vals, w)
|
||||
}
|
||||
tx.Unlock()
|
||||
|
||||
cleanup(s, b, tmpPath)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCompactAllAndRestore(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s0 := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
s0.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
s0.Put([]byte("foo"), []byte("bar1"), lease.NoLease)
|
||||
s0.Put([]byte("foo"), []byte("bar2"), lease.NoLease)
|
||||
s0.DeleteRange([]byte("foo"), nil)
|
||||
|
||||
rev := s0.Rev()
|
||||
// compact all keys
|
||||
done, err := s0.Compact(rev)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("timeout waiting for compaction to finish")
|
||||
}
|
||||
|
||||
err = s0.Close()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
s1 := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
if s1.Rev() != rev {
|
||||
t.Errorf("rev = %v, want %v", s1.Rev(), rev)
|
||||
}
|
||||
_, err = s1.Range([]byte("foo"), nil, RangeOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("unexpect range error %v", err)
|
||||
}
|
||||
}
|
||||
687
vendor/github.com/coreos/etcd/mvcc/kvstore_test.go
generated
vendored
Normal file
687
vendor/github.com/coreos/etcd/mvcc/kvstore_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,687 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"encoding/binary"
|
||||
"math"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/schedule"
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
)
|
||||
|
||||
func TestStoreRev(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer s.Close()
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
for i := 1; i <= 3; i++ {
|
||||
s.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
if r := s.Rev(); r != int64(i+1) {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, r, i+1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStorePut(t *testing.T) {
|
||||
kv := mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 1,
|
||||
ModRevision: 2,
|
||||
Version: 1,
|
||||
}
|
||||
kvb, err := kv.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
rev revision
|
||||
r indexGetResp
|
||||
rr *rangeResp
|
||||
|
||||
wrev revision
|
||||
wkey []byte
|
||||
wkv mvccpb.KeyValue
|
||||
wputrev revision
|
||||
}{
|
||||
{
|
||||
revision{1, 0},
|
||||
indexGetResp{revision{}, revision{}, 0, ErrRevisionNotFound},
|
||||
nil,
|
||||
|
||||
revision{1, 1},
|
||||
newTestKeyBytes(revision{2, 0}, false),
|
||||
mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 2,
|
||||
ModRevision: 2,
|
||||
Version: 1,
|
||||
Lease: 1,
|
||||
},
|
||||
revision{2, 0},
|
||||
},
|
||||
{
|
||||
revision{1, 1},
|
||||
indexGetResp{revision{2, 0}, revision{2, 0}, 1, nil},
|
||||
&rangeResp{[][]byte{newTestKeyBytes(revision{2, 1}, false)}, [][]byte{kvb}},
|
||||
|
||||
revision{1, 2},
|
||||
newTestKeyBytes(revision{2, 1}, false),
|
||||
mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 2,
|
||||
ModRevision: 2,
|
||||
Version: 2,
|
||||
Lease: 2,
|
||||
},
|
||||
revision{2, 1},
|
||||
},
|
||||
{
|
||||
revision{2, 0},
|
||||
indexGetResp{revision{2, 1}, revision{2, 0}, 2, nil},
|
||||
&rangeResp{[][]byte{newTestKeyBytes(revision{2, 1}, false)}, [][]byte{kvb}},
|
||||
|
||||
revision{2, 1},
|
||||
newTestKeyBytes(revision{3, 0}, false),
|
||||
mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 2,
|
||||
ModRevision: 3,
|
||||
Version: 3,
|
||||
Lease: 3,
|
||||
},
|
||||
revision{3, 0},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
s := newFakeStore()
|
||||
b := s.b.(*fakeBackend)
|
||||
fi := s.kvindex.(*fakeIndex)
|
||||
|
||||
s.currentRev = tt.rev
|
||||
s.tx = b.BatchTx()
|
||||
fi.indexGetRespc <- tt.r
|
||||
if tt.rr != nil {
|
||||
b.tx.rangeRespc <- *tt.rr
|
||||
}
|
||||
|
||||
s.put([]byte("foo"), []byte("bar"), lease.LeaseID(i+1))
|
||||
|
||||
data, err := tt.wkv.Marshal()
|
||||
if err != nil {
|
||||
t.Errorf("#%d: marshal err = %v, want nil", i, err)
|
||||
}
|
||||
|
||||
wact := []testutil.Action{
|
||||
{"seqput", []interface{}{keyBucketName, tt.wkey, data}},
|
||||
}
|
||||
|
||||
if tt.rr != nil {
|
||||
wact = []testutil.Action{
|
||||
{"seqput", []interface{}{keyBucketName, tt.wkey, data}},
|
||||
}
|
||||
}
|
||||
|
||||
if g := b.tx.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: tx action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
wact = []testutil.Action{
|
||||
{"get", []interface{}{[]byte("foo"), tt.wputrev.main}},
|
||||
{"put", []interface{}{[]byte("foo"), tt.wputrev}},
|
||||
}
|
||||
if g := fi.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: index action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
if s.currentRev != tt.wrev {
|
||||
t.Errorf("#%d: rev = %+v, want %+v", i, s.currentRev, tt.wrev)
|
||||
}
|
||||
|
||||
s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreRange(t *testing.T) {
|
||||
key := newTestKeyBytes(revision{2, 0}, false)
|
||||
kv := mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 1,
|
||||
ModRevision: 2,
|
||||
Version: 1,
|
||||
}
|
||||
kvb, err := kv.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
currev := revision{1, 1}
|
||||
wrev := int64(2)
|
||||
|
||||
tests := []struct {
|
||||
idxr indexRangeResp
|
||||
r rangeResp
|
||||
}{
|
||||
{
|
||||
indexRangeResp{[][]byte{[]byte("foo")}, []revision{{2, 0}}},
|
||||
rangeResp{[][]byte{key}, [][]byte{kvb}},
|
||||
},
|
||||
{
|
||||
indexRangeResp{[][]byte{[]byte("foo"), []byte("foo1")}, []revision{{2, 0}, {3, 0}}},
|
||||
rangeResp{[][]byte{key}, [][]byte{kvb}},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
s := newFakeStore()
|
||||
b := s.b.(*fakeBackend)
|
||||
fi := s.kvindex.(*fakeIndex)
|
||||
|
||||
s.currentRev = currev
|
||||
s.tx = b.BatchTx()
|
||||
b.tx.rangeRespc <- tt.r
|
||||
fi.indexRangeRespc <- tt.idxr
|
||||
|
||||
kvs, _, rev, err := s.rangeKeys([]byte("foo"), []byte("goo"), 1, 0, false)
|
||||
if err != nil {
|
||||
t.Errorf("#%d: err = %v, want nil", i, err)
|
||||
}
|
||||
if w := []mvccpb.KeyValue{kv}; !reflect.DeepEqual(kvs, w) {
|
||||
t.Errorf("#%d: kvs = %+v, want %+v", i, kvs, w)
|
||||
}
|
||||
if rev != wrev {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, rev, wrev)
|
||||
}
|
||||
|
||||
wstart, wend := revBytesRange(tt.idxr.revs[0])
|
||||
wact := []testutil.Action{
|
||||
{"range", []interface{}{keyBucketName, wstart, wend, int64(0)}},
|
||||
}
|
||||
if g := b.tx.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: tx action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
wact = []testutil.Action{
|
||||
{"range", []interface{}{[]byte("foo"), []byte("goo"), wrev}},
|
||||
}
|
||||
if g := fi.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: index action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
if s.currentRev != currev {
|
||||
t.Errorf("#%d: current rev = %+v, want %+v", i, s.currentRev, currev)
|
||||
}
|
||||
|
||||
s.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreDeleteRange(t *testing.T) {
|
||||
key := newTestKeyBytes(revision{2, 0}, false)
|
||||
kv := mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 1,
|
||||
ModRevision: 2,
|
||||
Version: 1,
|
||||
}
|
||||
kvb, err := kv.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
rev revision
|
||||
r indexRangeResp
|
||||
rr rangeResp
|
||||
|
||||
wkey []byte
|
||||
wrev revision
|
||||
wrrev int64
|
||||
wdelrev revision
|
||||
}{
|
||||
{
|
||||
revision{2, 0},
|
||||
indexRangeResp{[][]byte{[]byte("foo")}, []revision{{2, 0}}},
|
||||
rangeResp{[][]byte{key}, [][]byte{kvb}},
|
||||
|
||||
newTestKeyBytes(revision{3, 0}, true),
|
||||
revision{2, 1},
|
||||
2,
|
||||
revision{3, 0},
|
||||
},
|
||||
{
|
||||
revision{2, 1},
|
||||
indexRangeResp{[][]byte{[]byte("foo")}, []revision{{2, 0}}},
|
||||
rangeResp{[][]byte{key}, [][]byte{kvb}},
|
||||
|
||||
newTestKeyBytes(revision{3, 1}, true),
|
||||
revision{2, 2},
|
||||
3,
|
||||
revision{3, 1},
|
||||
},
|
||||
}
|
||||
for i, tt := range tests {
|
||||
s := newFakeStore()
|
||||
b := s.b.(*fakeBackend)
|
||||
fi := s.kvindex.(*fakeIndex)
|
||||
|
||||
s.currentRev = tt.rev
|
||||
s.tx = b.BatchTx()
|
||||
fi.indexRangeRespc <- tt.r
|
||||
b.tx.rangeRespc <- tt.rr
|
||||
|
||||
n := s.deleteRange([]byte("foo"), []byte("goo"))
|
||||
if n != 1 {
|
||||
t.Errorf("#%d: n = %d, want 1", i, n)
|
||||
}
|
||||
|
||||
data, err := (&mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
}).Marshal()
|
||||
if err != nil {
|
||||
t.Errorf("#%d: marshal err = %v, want nil", i, err)
|
||||
}
|
||||
wact := []testutil.Action{
|
||||
{"seqput", []interface{}{keyBucketName, tt.wkey, data}},
|
||||
}
|
||||
if g := b.tx.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: tx action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
wact = []testutil.Action{
|
||||
{"range", []interface{}{[]byte("foo"), []byte("goo"), tt.wrrev}},
|
||||
{"tombstone", []interface{}{[]byte("foo"), tt.wdelrev}},
|
||||
}
|
||||
if g := fi.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("#%d: index action = %+v, want %+v", i, g, wact)
|
||||
}
|
||||
if s.currentRev != tt.wrev {
|
||||
t.Errorf("#%d: rev = %+v, want %+v", i, s.currentRev, tt.wrev)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreCompact(t *testing.T) {
|
||||
s := newFakeStore()
|
||||
defer s.Close()
|
||||
b := s.b.(*fakeBackend)
|
||||
fi := s.kvindex.(*fakeIndex)
|
||||
|
||||
s.currentRev = revision{3, 0}
|
||||
fi.indexCompactRespc <- map[revision]struct{}{{1, 0}: {}}
|
||||
key1 := newTestKeyBytes(revision{1, 0}, false)
|
||||
key2 := newTestKeyBytes(revision{2, 0}, false)
|
||||
b.tx.rangeRespc <- rangeResp{[][]byte{key1, key2}, nil}
|
||||
|
||||
s.Compact(3)
|
||||
s.fifoSched.WaitFinish(1)
|
||||
|
||||
if s.compactMainRev != 3 {
|
||||
t.Errorf("compact main rev = %d, want 3", s.compactMainRev)
|
||||
}
|
||||
end := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(end, uint64(4))
|
||||
wact := []testutil.Action{
|
||||
{"put", []interface{}{metaBucketName, scheduledCompactKeyName, newTestRevBytes(revision{3, 0})}},
|
||||
{"range", []interface{}{keyBucketName, make([]byte, 17), end, int64(10000)}},
|
||||
{"delete", []interface{}{keyBucketName, key2}},
|
||||
{"put", []interface{}{metaBucketName, finishedCompactKeyName, newTestRevBytes(revision{3, 0})}},
|
||||
}
|
||||
if g := b.tx.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("tx actions = %+v, want %+v", g, wact)
|
||||
}
|
||||
wact = []testutil.Action{
|
||||
{"compact", []interface{}{int64(3)}},
|
||||
}
|
||||
if g := fi.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("index action = %+v, want %+v", g, wact)
|
||||
}
|
||||
}
|
||||
|
||||
func TestStoreRestore(t *testing.T) {
|
||||
s := newFakeStore()
|
||||
b := s.b.(*fakeBackend)
|
||||
fi := s.kvindex.(*fakeIndex)
|
||||
|
||||
putkey := newTestKeyBytes(revision{3, 0}, false)
|
||||
putkv := mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
Value: []byte("bar"),
|
||||
CreateRevision: 4,
|
||||
ModRevision: 4,
|
||||
Version: 1,
|
||||
}
|
||||
putkvb, err := putkv.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
delkey := newTestKeyBytes(revision{5, 0}, true)
|
||||
delkv := mvccpb.KeyValue{
|
||||
Key: []byte("foo"),
|
||||
}
|
||||
delkvb, err := delkv.Marshal()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
b.tx.rangeRespc <- rangeResp{[][]byte{finishedCompactKeyName}, [][]byte{newTestRevBytes(revision{3, 0})}}
|
||||
b.tx.rangeRespc <- rangeResp{[][]byte{putkey, delkey}, [][]byte{putkvb, delkvb}}
|
||||
b.tx.rangeRespc <- rangeResp{[][]byte{scheduledCompactKeyName}, [][]byte{newTestRevBytes(revision{3, 0})}}
|
||||
|
||||
s.restore()
|
||||
|
||||
if s.compactMainRev != 3 {
|
||||
t.Errorf("compact rev = %d, want 5", s.compactMainRev)
|
||||
}
|
||||
wrev := revision{5, 0}
|
||||
if !reflect.DeepEqual(s.currentRev, wrev) {
|
||||
t.Errorf("current rev = %v, want %v", s.currentRev, wrev)
|
||||
}
|
||||
wact := []testutil.Action{
|
||||
{"range", []interface{}{metaBucketName, finishedCompactKeyName, []byte(nil), int64(0)}},
|
||||
{"range", []interface{}{keyBucketName, newTestRevBytes(revision{1, 0}), newTestRevBytes(revision{math.MaxInt64, math.MaxInt64}), int64(0)}},
|
||||
{"range", []interface{}{metaBucketName, scheduledCompactKeyName, []byte(nil), int64(0)}},
|
||||
}
|
||||
if g := b.tx.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("tx actions = %+v, want %+v", g, wact)
|
||||
}
|
||||
|
||||
gens := []generation{
|
||||
{created: revision{4, 0}, ver: 2, revs: []revision{{3, 0}, {5, 0}}},
|
||||
{created: revision{0, 0}, ver: 0, revs: nil},
|
||||
}
|
||||
ki := &keyIndex{key: []byte("foo"), modified: revision{5, 0}, generations: gens}
|
||||
wact = []testutil.Action{
|
||||
{"insert", []interface{}{ki}},
|
||||
}
|
||||
if g := fi.Action(); !reflect.DeepEqual(g, wact) {
|
||||
t.Errorf("index action = %+v, want %+v", g, wact)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRestoreContinueUnfinishedCompaction(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s0 := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
s0.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
s0.Put([]byte("foo"), []byte("bar1"), lease.NoLease)
|
||||
s0.Put([]byte("foo"), []byte("bar2"), lease.NoLease)
|
||||
|
||||
// write scheduled compaction, but not do compaction
|
||||
rbytes := newRevBytes()
|
||||
revToBytes(revision{main: 2}, rbytes)
|
||||
tx := s0.b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafePut(metaBucketName, scheduledCompactKeyName, rbytes)
|
||||
tx.Unlock()
|
||||
|
||||
s0.Close()
|
||||
|
||||
s1 := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
// wait for scheduled compaction to be finished
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
|
||||
if _, err := s1.Range([]byte("foo"), nil, RangeOptions{Rev: 1}); err != ErrCompacted {
|
||||
t.Errorf("range on compacted rev error = %v, want %v", err, ErrCompacted)
|
||||
}
|
||||
// check the key in backend is deleted
|
||||
revbytes := newRevBytes()
|
||||
revToBytes(revision{main: 1}, revbytes)
|
||||
|
||||
// The disk compaction is done asynchronously and requires more time on slow disk.
|
||||
// try 5 times for CI with slow IO.
|
||||
for i := 0; i < 5; i++ {
|
||||
tx = s1.b.BatchTx()
|
||||
tx.Lock()
|
||||
ks, _ := tx.UnsafeRange(keyBucketName, revbytes, nil, 0)
|
||||
tx.Unlock()
|
||||
if len(ks) != 0 {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
continue
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
t.Errorf("key for rev %+v still exists, want deleted", bytesToRev(revbytes))
|
||||
}
|
||||
|
||||
func TestTxnPut(t *testing.T) {
|
||||
// assign arbitrary size
|
||||
bytesN := 30
|
||||
sliceN := 100
|
||||
keys := createBytesSlice(bytesN, sliceN)
|
||||
vals := createBytesSlice(bytesN, sliceN)
|
||||
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
for i := 0; i < sliceN; i++ {
|
||||
id := s.TxnBegin()
|
||||
base := int64(i + 2)
|
||||
|
||||
rev, err := s.TxnPut(id, keys[i], vals[i], lease.NoLease)
|
||||
if err != nil {
|
||||
t.Error("txn put error")
|
||||
}
|
||||
if rev != base {
|
||||
t.Errorf("#%d: rev = %d, want %d", i, rev, base)
|
||||
}
|
||||
|
||||
s.TxnEnd(id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTxnBlockBackendForceCommit(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(b, &lease.FakeLessor{}, nil)
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
id := s.TxnBegin()
|
||||
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
s.b.ForceCommit()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatalf("failed to block ForceCommit")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
s.TxnEnd(id)
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(5 * time.Second): // wait 5 seconds for CI with slow IO
|
||||
testutil.FatalStack(t, "failed to execute ForceCommit")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test attach key to lessor
|
||||
|
||||
func newTestRevBytes(rev revision) []byte {
|
||||
bytes := newRevBytes()
|
||||
revToBytes(rev, bytes)
|
||||
return bytes
|
||||
}
|
||||
|
||||
func newTestKeyBytes(rev revision, tombstone bool) []byte {
|
||||
bytes := newRevBytes()
|
||||
revToBytes(rev, bytes)
|
||||
if tombstone {
|
||||
bytes = appendMarkTombstone(bytes)
|
||||
}
|
||||
return bytes
|
||||
}
|
||||
|
||||
// TestStoreHashAfterForceCommit ensures that later Hash call to
|
||||
// closed backend with ForceCommit does not panic.
|
||||
func TestStoreHashAfterForceCommit(t *testing.T) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
kv := NewStore(be, &lease.FakeLessor{}, nil)
|
||||
defer os.Remove(tmpPath)
|
||||
|
||||
// as in EtcdServer.HardStop
|
||||
kv.Close()
|
||||
be.Close()
|
||||
|
||||
kv.Hash()
|
||||
}
|
||||
|
||||
func newFakeStore() *store {
|
||||
b := &fakeBackend{&fakeBatchTx{
|
||||
Recorder: &testutil.RecorderBuffered{},
|
||||
rangeRespc: make(chan rangeResp, 5)}}
|
||||
fi := &fakeIndex{
|
||||
Recorder: &testutil.RecorderBuffered{},
|
||||
indexGetRespc: make(chan indexGetResp, 1),
|
||||
indexRangeRespc: make(chan indexRangeResp, 1),
|
||||
indexRangeEventsRespc: make(chan indexRangeEventsResp, 1),
|
||||
indexCompactRespc: make(chan map[revision]struct{}, 1),
|
||||
}
|
||||
return &store{
|
||||
b: b,
|
||||
le: &lease.FakeLessor{},
|
||||
kvindex: fi,
|
||||
currentRev: revision{},
|
||||
compactMainRev: -1,
|
||||
fifoSched: schedule.NewFIFOScheduler(),
|
||||
stopc: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
type rangeResp struct {
|
||||
keys [][]byte
|
||||
vals [][]byte
|
||||
}
|
||||
|
||||
type fakeBatchTx struct {
|
||||
testutil.Recorder
|
||||
rangeRespc chan rangeResp
|
||||
}
|
||||
|
||||
func (b *fakeBatchTx) Lock() {}
|
||||
func (b *fakeBatchTx) Unlock() {}
|
||||
func (b *fakeBatchTx) UnsafeCreateBucket(name []byte) {}
|
||||
func (b *fakeBatchTx) UnsafePut(bucketName []byte, key []byte, value []byte) {
|
||||
b.Recorder.Record(testutil.Action{Name: "put", Params: []interface{}{bucketName, key, value}})
|
||||
}
|
||||
func (b *fakeBatchTx) UnsafeSeqPut(bucketName []byte, key []byte, value []byte) {
|
||||
b.Recorder.Record(testutil.Action{Name: "seqput", Params: []interface{}{bucketName, key, value}})
|
||||
}
|
||||
func (b *fakeBatchTx) UnsafeRange(bucketName []byte, key, endKey []byte, limit int64) (keys [][]byte, vals [][]byte) {
|
||||
b.Recorder.Record(testutil.Action{Name: "range", Params: []interface{}{bucketName, key, endKey, limit}})
|
||||
r := <-b.rangeRespc
|
||||
return r.keys, r.vals
|
||||
}
|
||||
func (b *fakeBatchTx) UnsafeDelete(bucketName []byte, key []byte) {
|
||||
b.Recorder.Record(testutil.Action{Name: "delete", Params: []interface{}{bucketName, key}})
|
||||
}
|
||||
func (b *fakeBatchTx) UnsafeForEach(bucketName []byte, visitor func(k, v []byte) error) error {
|
||||
return nil
|
||||
}
|
||||
func (b *fakeBatchTx) Commit() {}
|
||||
func (b *fakeBatchTx) CommitAndStop() {}
|
||||
|
||||
type fakeBackend struct {
|
||||
tx *fakeBatchTx
|
||||
}
|
||||
|
||||
func (b *fakeBackend) BatchTx() backend.BatchTx { return b.tx }
|
||||
func (b *fakeBackend) Hash(ignores map[backend.IgnoreKey]struct{}) (uint32, error) { return 0, nil }
|
||||
func (b *fakeBackend) Size() int64 { return 0 }
|
||||
func (b *fakeBackend) Snapshot() backend.Snapshot { return nil }
|
||||
func (b *fakeBackend) ForceCommit() {}
|
||||
func (b *fakeBackend) Defrag() error { return nil }
|
||||
func (b *fakeBackend) Close() error { return nil }
|
||||
|
||||
type indexGetResp struct {
|
||||
rev revision
|
||||
created revision
|
||||
ver int64
|
||||
err error
|
||||
}
|
||||
|
||||
type indexRangeResp struct {
|
||||
keys [][]byte
|
||||
revs []revision
|
||||
}
|
||||
|
||||
type indexRangeEventsResp struct {
|
||||
revs []revision
|
||||
}
|
||||
|
||||
type fakeIndex struct {
|
||||
testutil.Recorder
|
||||
indexGetRespc chan indexGetResp
|
||||
indexRangeRespc chan indexRangeResp
|
||||
indexRangeEventsRespc chan indexRangeEventsResp
|
||||
indexCompactRespc chan map[revision]struct{}
|
||||
}
|
||||
|
||||
func (i *fakeIndex) Get(key []byte, atRev int64) (rev, created revision, ver int64, err error) {
|
||||
i.Recorder.Record(testutil.Action{Name: "get", Params: []interface{}{key, atRev}})
|
||||
r := <-i.indexGetRespc
|
||||
return r.rev, r.created, r.ver, r.err
|
||||
}
|
||||
func (i *fakeIndex) Range(key, end []byte, atRev int64) ([][]byte, []revision) {
|
||||
i.Recorder.Record(testutil.Action{Name: "range", Params: []interface{}{key, end, atRev}})
|
||||
r := <-i.indexRangeRespc
|
||||
return r.keys, r.revs
|
||||
}
|
||||
func (i *fakeIndex) Put(key []byte, rev revision) {
|
||||
i.Recorder.Record(testutil.Action{Name: "put", Params: []interface{}{key, rev}})
|
||||
}
|
||||
func (i *fakeIndex) Tombstone(key []byte, rev revision) error {
|
||||
i.Recorder.Record(testutil.Action{Name: "tombstone", Params: []interface{}{key, rev}})
|
||||
return nil
|
||||
}
|
||||
func (i *fakeIndex) RangeSince(key, end []byte, rev int64) []revision {
|
||||
i.Recorder.Record(testutil.Action{Name: "rangeEvents", Params: []interface{}{key, end, rev}})
|
||||
r := <-i.indexRangeEventsRespc
|
||||
return r.revs
|
||||
}
|
||||
func (i *fakeIndex) Compact(rev int64) map[revision]struct{} {
|
||||
i.Recorder.Record(testutil.Action{Name: "compact", Params: []interface{}{rev}})
|
||||
return <-i.indexCompactRespc
|
||||
}
|
||||
func (i *fakeIndex) Equal(b index) bool { return false }
|
||||
|
||||
func (i *fakeIndex) Insert(ki *keyIndex) {
|
||||
i.Recorder.Record(testutil.Action{Name: "insert", Params: []interface{}{ki}})
|
||||
}
|
||||
|
||||
func createBytesSlice(bytesN, sliceN int) [][]byte {
|
||||
rs := [][]byte{}
|
||||
for len(rs) != sliceN {
|
||||
v := make([]byte, bytesN)
|
||||
if _, err := rand.Read(v); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
rs = append(rs, v)
|
||||
}
|
||||
return rs
|
||||
}
|
||||
163
vendor/github.com/coreos/etcd/mvcc/metrics.go
generated
vendored
Normal file
163
vendor/github.com/coreos/etcd/mvcc/metrics.go
generated
vendored
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
rangeCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "range_total",
|
||||
Help: "Total number of ranges seen by this member.",
|
||||
})
|
||||
|
||||
putCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "put_total",
|
||||
Help: "Total number of puts seen by this member.",
|
||||
})
|
||||
|
||||
deleteCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "delete_total",
|
||||
Help: "Total number of deletes seen by this member.",
|
||||
})
|
||||
|
||||
txnCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "txn_total",
|
||||
Help: "Total number of txns seen by this member.",
|
||||
})
|
||||
|
||||
keysGauge = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "keys_total",
|
||||
Help: "Total number of keys.",
|
||||
})
|
||||
|
||||
watchStreamGauge = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "watch_stream_total",
|
||||
Help: "Total number of watch streams.",
|
||||
})
|
||||
|
||||
watcherGauge = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "watcher_total",
|
||||
Help: "Total number of watchers.",
|
||||
})
|
||||
|
||||
slowWatcherGauge = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "slow_watcher_total",
|
||||
Help: "Total number of unsynced slow watchers.",
|
||||
})
|
||||
|
||||
totalEventsCounter = prometheus.NewCounter(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "events_total",
|
||||
Help: "Total number of events sent by this member.",
|
||||
})
|
||||
|
||||
pendingEventsGauge = prometheus.NewGauge(
|
||||
prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "pending_events_total",
|
||||
Help: "Total number of pending events to be sent.",
|
||||
})
|
||||
|
||||
indexCompactionPauseDurations = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "index_compaction_pause_duration_milliseconds",
|
||||
Help: "Bucketed histogram of index compaction pause duration.",
|
||||
// 0.5ms -> 1second
|
||||
Buckets: prometheus.ExponentialBuckets(0.5, 2, 12),
|
||||
})
|
||||
|
||||
dbCompactionPauseDurations = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "db_compaction_pause_duration_milliseconds",
|
||||
Help: "Bucketed histogram of db compaction pause duration.",
|
||||
// 1ms -> 4second
|
||||
Buckets: prometheus.ExponentialBuckets(1, 2, 13),
|
||||
})
|
||||
|
||||
dbCompactionTotalDurations = prometheus.NewHistogram(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "db_compaction_total_duration_milliseconds",
|
||||
Help: "Bucketed histogram of db compaction total duration.",
|
||||
// 100ms -> 800second
|
||||
Buckets: prometheus.ExponentialBuckets(100, 2, 14),
|
||||
})
|
||||
|
||||
dbTotalSize = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "db_total_size_in_bytes",
|
||||
Help: "Total size of the underlying database in bytes.",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(rangeCounter)
|
||||
prometheus.MustRegister(putCounter)
|
||||
prometheus.MustRegister(deleteCounter)
|
||||
prometheus.MustRegister(txnCounter)
|
||||
prometheus.MustRegister(keysGauge)
|
||||
prometheus.MustRegister(watchStreamGauge)
|
||||
prometheus.MustRegister(watcherGauge)
|
||||
prometheus.MustRegister(slowWatcherGauge)
|
||||
prometheus.MustRegister(totalEventsCounter)
|
||||
prometheus.MustRegister(pendingEventsGauge)
|
||||
prometheus.MustRegister(indexCompactionPauseDurations)
|
||||
prometheus.MustRegister(dbCompactionPauseDurations)
|
||||
prometheus.MustRegister(dbCompactionTotalDurations)
|
||||
prometheus.MustRegister(dbTotalSize)
|
||||
}
|
||||
|
||||
// ReportEventReceived reports that an event is received.
|
||||
// This function should be called when the external systems received an
|
||||
// event from mvcc.Watcher.
|
||||
func ReportEventReceived(n int) {
|
||||
pendingEventsGauge.Sub(float64(n))
|
||||
totalEventsCounter.Add(float64(n))
|
||||
}
|
||||
735
vendor/github.com/coreos/etcd/mvcc/mvccpb/kv.pb.go
generated
vendored
Normal file
735
vendor/github.com/coreos/etcd/mvcc/mvccpb/kv.pb.go
generated
vendored
Normal file
|
|
@ -0,0 +1,735 @@
|
|||
// Code generated by protoc-gen-gogo.
|
||||
// source: kv.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package mvccpb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
kv.proto
|
||||
|
||||
It has these top-level messages:
|
||||
KeyValue
|
||||
Event
|
||||
*/
|
||||
package mvccpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
proto "github.com/golang/protobuf/proto"
|
||||
|
||||
math "math"
|
||||
|
||||
io "io"
|
||||
)
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the proto package it is being compiled against.
|
||||
// A compilation error at this line likely means your copy of the
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||
|
||||
type Event_EventType int32
|
||||
|
||||
const (
|
||||
PUT Event_EventType = 0
|
||||
DELETE Event_EventType = 1
|
||||
)
|
||||
|
||||
var Event_EventType_name = map[int32]string{
|
||||
0: "PUT",
|
||||
1: "DELETE",
|
||||
}
|
||||
var Event_EventType_value = map[string]int32{
|
||||
"PUT": 0,
|
||||
"DELETE": 1,
|
||||
}
|
||||
|
||||
func (x Event_EventType) String() string {
|
||||
return proto.EnumName(Event_EventType_name, int32(x))
|
||||
}
|
||||
func (Event_EventType) EnumDescriptor() ([]byte, []int) { return fileDescriptorKv, []int{1, 0} }
|
||||
|
||||
type KeyValue struct {
|
||||
// key is the key in bytes. An empty key is not allowed.
|
||||
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
|
||||
// create_revision is the revision of last creation on this key.
|
||||
CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`
|
||||
// mod_revision is the revision of last modification on this key.
|
||||
ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`
|
||||
// version is the version of the key. A deletion resets
|
||||
// the version to zero and any modification of the key
|
||||
// increases its version.
|
||||
Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
|
||||
// value is the value held by the key, in bytes.
|
||||
Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
|
||||
// lease is the ID of the lease that attached to key.
|
||||
// When the attached lease expires, the key will be deleted.
|
||||
// If lease is 0, then no lease is attached to the key.
|
||||
Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
|
||||
}
|
||||
|
||||
func (m *KeyValue) Reset() { *m = KeyValue{} }
|
||||
func (m *KeyValue) String() string { return proto.CompactTextString(m) }
|
||||
func (*KeyValue) ProtoMessage() {}
|
||||
func (*KeyValue) Descriptor() ([]byte, []int) { return fileDescriptorKv, []int{0} }
|
||||
|
||||
type Event struct {
|
||||
// type is the kind of event. If type is a PUT, it indicates
|
||||
// new data has been stored to the key. If type is a DELETE,
|
||||
// it indicates the key was deleted.
|
||||
Type Event_EventType `protobuf:"varint,1,opt,name=type,proto3,enum=mvccpb.Event_EventType" json:"type,omitempty"`
|
||||
// kv holds the KeyValue for the event.
|
||||
// A PUT event contains current kv pair.
|
||||
// A PUT event with kv.Version=1 indicates the creation of a key.
|
||||
// A DELETE/EXPIRE event contains the deleted key with
|
||||
// its modification revision set to the revision of deletion.
|
||||
Kv *KeyValue `protobuf:"bytes,2,opt,name=kv" json:"kv,omitempty"`
|
||||
// prev_kv holds the key-value pair before the event happens.
|
||||
PrevKv *KeyValue `protobuf:"bytes,3,opt,name=prev_kv,json=prevKv" json:"prev_kv,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Event) Reset() { *m = Event{} }
|
||||
func (m *Event) String() string { return proto.CompactTextString(m) }
|
||||
func (*Event) ProtoMessage() {}
|
||||
func (*Event) Descriptor() ([]byte, []int) { return fileDescriptorKv, []int{1} }
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*KeyValue)(nil), "mvccpb.KeyValue")
|
||||
proto.RegisterType((*Event)(nil), "mvccpb.Event")
|
||||
proto.RegisterEnum("mvccpb.Event_EventType", Event_EventType_name, Event_EventType_value)
|
||||
}
|
||||
func (m *KeyValue) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *KeyValue) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.Key) > 0 {
|
||||
dAtA[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(len(m.Key)))
|
||||
i += copy(dAtA[i:], m.Key)
|
||||
}
|
||||
if m.CreateRevision != 0 {
|
||||
dAtA[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.CreateRevision))
|
||||
}
|
||||
if m.ModRevision != 0 {
|
||||
dAtA[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.ModRevision))
|
||||
}
|
||||
if m.Version != 0 {
|
||||
dAtA[i] = 0x20
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.Version))
|
||||
}
|
||||
if len(m.Value) > 0 {
|
||||
dAtA[i] = 0x2a
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(len(m.Value)))
|
||||
i += copy(dAtA[i:], m.Value)
|
||||
}
|
||||
if m.Lease != 0 {
|
||||
dAtA[i] = 0x30
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.Lease))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *Event) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalTo(dAtA)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *Event) MarshalTo(dAtA []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Type != 0 {
|
||||
dAtA[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.Type))
|
||||
}
|
||||
if m.Kv != nil {
|
||||
dAtA[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.Kv.Size()))
|
||||
n1, err := m.Kv.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n1
|
||||
}
|
||||
if m.PrevKv != nil {
|
||||
dAtA[i] = 0x1a
|
||||
i++
|
||||
i = encodeVarintKv(dAtA, i, uint64(m.PrevKv.Size()))
|
||||
n2, err := m.PrevKv.MarshalTo(dAtA[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n2
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Kv(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
dAtA[offset+4] = uint8(v >> 32)
|
||||
dAtA[offset+5] = uint8(v >> 40)
|
||||
dAtA[offset+6] = uint8(v >> 48)
|
||||
dAtA[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Kv(dAtA []byte, offset int, v uint32) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
dAtA[offset+1] = uint8(v >> 8)
|
||||
dAtA[offset+2] = uint8(v >> 16)
|
||||
dAtA[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintKv(dAtA []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
dAtA[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
dAtA[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *KeyValue) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Key)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovKv(uint64(l))
|
||||
}
|
||||
if m.CreateRevision != 0 {
|
||||
n += 1 + sovKv(uint64(m.CreateRevision))
|
||||
}
|
||||
if m.ModRevision != 0 {
|
||||
n += 1 + sovKv(uint64(m.ModRevision))
|
||||
}
|
||||
if m.Version != 0 {
|
||||
n += 1 + sovKv(uint64(m.Version))
|
||||
}
|
||||
l = len(m.Value)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovKv(uint64(l))
|
||||
}
|
||||
if m.Lease != 0 {
|
||||
n += 1 + sovKv(uint64(m.Lease))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Event) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.Type != 0 {
|
||||
n += 1 + sovKv(uint64(m.Type))
|
||||
}
|
||||
if m.Kv != nil {
|
||||
l = m.Kv.Size()
|
||||
n += 1 + l + sovKv(uint64(l))
|
||||
}
|
||||
if m.PrevKv != nil {
|
||||
l = m.PrevKv.Size()
|
||||
n += 1 + l + sovKv(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovKv(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozKv(x uint64) (n int) {
|
||||
return sovKv(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *KeyValue) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: KeyValue: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: KeyValue: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Key == nil {
|
||||
m.Key = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field CreateRevision", wireType)
|
||||
}
|
||||
m.CreateRevision = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.CreateRevision |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ModRevision", wireType)
|
||||
}
|
||||
m.ModRevision = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.ModRevision |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 4:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType)
|
||||
}
|
||||
m.Version = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Version |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 5:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Value == nil {
|
||||
m.Value = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 6:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Lease", wireType)
|
||||
}
|
||||
m.Lease = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Lease |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipKv(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Event) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Event: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Event: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType)
|
||||
}
|
||||
m.Type = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.Type |= (Event_EventType(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Kv", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Kv == nil {
|
||||
m.Kv = &KeyValue{}
|
||||
}
|
||||
if err := m.Kv.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PrevKv", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.PrevKv == nil {
|
||||
m.PrevKv = &KeyValue{}
|
||||
}
|
||||
if err := m.PrevKv.Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipKv(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthKv
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipKv(dAtA []byte) (n int, err error) {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if dAtA[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthKv
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowKv
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipKv(dAtA[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthKv = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowKv = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
|
||||
func init() { proto.RegisterFile("kv.proto", fileDescriptorKv) }
|
||||
|
||||
var fileDescriptorKv = []byte{
|
||||
// 303 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4e, 0xc2, 0x40,
|
||||
0x14, 0x86, 0x3b, 0x14, 0x0a, 0x3e, 0x08, 0x36, 0x13, 0x12, 0x27, 0x2e, 0x26, 0x95, 0x8d, 0x18,
|
||||
0x13, 0x4c, 0xf0, 0x06, 0xc6, 0xae, 0x70, 0x61, 0x1a, 0x74, 0x4b, 0x4a, 0x79, 0x21, 0xa4, 0x94,
|
||||
0x69, 0x4a, 0x9d, 0xa4, 0x37, 0x71, 0xef, 0xde, 0x73, 0xb0, 0xe4, 0x08, 0x52, 0x2f, 0x62, 0xfa,
|
||||
0xc6, 0xe2, 0xc6, 0xcd, 0xe4, 0xfd, 0xff, 0xff, 0x65, 0xe6, 0x7f, 0x03, 0x9d, 0x58, 0x8f, 0xd3,
|
||||
0x4c, 0xe5, 0x8a, 0x3b, 0x89, 0x8e, 0xa2, 0x74, 0x71, 0x39, 0x58, 0xa9, 0x95, 0x22, 0xeb, 0xae,
|
||||
0x9a, 0x4c, 0x3a, 0xfc, 0x64, 0xd0, 0x99, 0x62, 0xf1, 0x1a, 0x6e, 0xde, 0x90, 0xbb, 0x60, 0xc7,
|
||||
0x58, 0x08, 0xe6, 0xb1, 0x51, 0x2f, 0xa8, 0x46, 0x7e, 0x0d, 0xe7, 0x51, 0x86, 0x61, 0x8e, 0xf3,
|
||||
0x0c, 0xf5, 0x7a, 0xb7, 0x56, 0x5b, 0xd1, 0xf0, 0xd8, 0xc8, 0x0e, 0xfa, 0xc6, 0x0e, 0x7e, 0x5d,
|
||||
0x7e, 0x05, 0xbd, 0x44, 0x2d, 0xff, 0x28, 0x9b, 0xa8, 0x6e, 0xa2, 0x96, 0x27, 0x44, 0x40, 0x5b,
|
||||
0x63, 0x46, 0x69, 0x93, 0xd2, 0x5a, 0xf2, 0x01, 0xb4, 0x74, 0x55, 0x40, 0xb4, 0xe8, 0x65, 0x23,
|
||||
0x2a, 0x77, 0x83, 0xe1, 0x0e, 0x85, 0x43, 0xb4, 0x11, 0xc3, 0x0f, 0x06, 0x2d, 0x5f, 0xe3, 0x36,
|
||||
0xe7, 0xb7, 0xd0, 0xcc, 0x8b, 0x14, 0xa9, 0x6e, 0x7f, 0x72, 0x31, 0x36, 0x7b, 0x8e, 0x29, 0x34,
|
||||
0xe7, 0xac, 0x48, 0x31, 0x20, 0x88, 0x7b, 0xd0, 0x88, 0x35, 0x75, 0xef, 0x4e, 0xdc, 0x1a, 0xad,
|
||||
0x17, 0x0f, 0x1a, 0xb1, 0xe6, 0x37, 0xd0, 0x4e, 0x33, 0xd4, 0xf3, 0x58, 0x53, 0xf9, 0xff, 0x30,
|
||||
0xa7, 0x02, 0xa6, 0x7a, 0xe8, 0xc1, 0xd9, 0xe9, 0x7e, 0xde, 0x06, 0xfb, 0xf9, 0x65, 0xe6, 0x5a,
|
||||
0x1c, 0xc0, 0x79, 0xf4, 0x9f, 0xfc, 0x99, 0xef, 0xb2, 0x07, 0xb1, 0x3f, 0x4a, 0xeb, 0x70, 0x94,
|
||||
0xd6, 0xbe, 0x94, 0xec, 0x50, 0x4a, 0xf6, 0x55, 0x4a, 0xf6, 0xfe, 0x2d, 0xad, 0x85, 0x43, 0xff,
|
||||
0x7e, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x45, 0x92, 0x5d, 0xa1, 0x01, 0x00, 0x00,
|
||||
}
|
||||
49
vendor/github.com/coreos/etcd/mvcc/mvccpb/kv.proto
generated
vendored
Normal file
49
vendor/github.com/coreos/etcd/mvcc/mvccpb/kv.proto
generated
vendored
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
syntax = "proto3";
|
||||
package mvccpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
option (gogoproto.goproto_enum_prefix_all) = false;
|
||||
|
||||
message KeyValue {
|
||||
// key is the key in bytes. An empty key is not allowed.
|
||||
bytes key = 1;
|
||||
// create_revision is the revision of last creation on this key.
|
||||
int64 create_revision = 2;
|
||||
// mod_revision is the revision of last modification on this key.
|
||||
int64 mod_revision = 3;
|
||||
// version is the version of the key. A deletion resets
|
||||
// the version to zero and any modification of the key
|
||||
// increases its version.
|
||||
int64 version = 4;
|
||||
// value is the value held by the key, in bytes.
|
||||
bytes value = 5;
|
||||
// lease is the ID of the lease that attached to key.
|
||||
// When the attached lease expires, the key will be deleted.
|
||||
// If lease is 0, then no lease is attached to the key.
|
||||
int64 lease = 6;
|
||||
}
|
||||
|
||||
message Event {
|
||||
enum EventType {
|
||||
PUT = 0;
|
||||
DELETE = 1;
|
||||
}
|
||||
// type is the kind of event. If type is a PUT, it indicates
|
||||
// new data has been stored to the key. If type is a DELETE,
|
||||
// it indicates the key was deleted.
|
||||
EventType type = 1;
|
||||
// kv holds the KeyValue for the event.
|
||||
// A PUT event contains current kv pair.
|
||||
// A PUT event with kv.Version=1 indicates the creation of a key.
|
||||
// A DELETE/EXPIRE event contains the deleted key with
|
||||
// its modification revision set to the revision of deletion.
|
||||
KeyValue kv = 2;
|
||||
|
||||
// prev_kv holds the key-value pair before the event happens.
|
||||
KeyValue prev_kv = 3;
|
||||
}
|
||||
67
vendor/github.com/coreos/etcd/mvcc/revision.go
generated
vendored
Normal file
67
vendor/github.com/coreos/etcd/mvcc/revision.go
generated
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import "encoding/binary"
|
||||
|
||||
// revBytesLen is the byte length of a normal revision.
|
||||
// First 8 bytes is the revision.main in big-endian format. The 9th byte
|
||||
// is a '_'. The last 8 bytes is the revision.sub in big-endian format.
|
||||
const revBytesLen = 8 + 1 + 8
|
||||
|
||||
// A revision indicates modification of the key-value space.
|
||||
// The set of changes that share same main revision changes the key-value space atomically.
|
||||
type revision struct {
|
||||
// main is the main revision of a set of changes that happen atomically.
|
||||
main int64
|
||||
|
||||
// sub is the the sub revision of a change in a set of changes that happen
|
||||
// atomically. Each change has different increasing sub revision in that
|
||||
// set.
|
||||
sub int64
|
||||
}
|
||||
|
||||
func (a revision) GreaterThan(b revision) bool {
|
||||
if a.main > b.main {
|
||||
return true
|
||||
}
|
||||
if a.main < b.main {
|
||||
return false
|
||||
}
|
||||
return a.sub > b.sub
|
||||
}
|
||||
|
||||
func newRevBytes() []byte {
|
||||
return make([]byte, revBytesLen, markedRevBytesLen)
|
||||
}
|
||||
|
||||
func revToBytes(rev revision, bytes []byte) {
|
||||
binary.BigEndian.PutUint64(bytes, uint64(rev.main))
|
||||
bytes[8] = '_'
|
||||
binary.BigEndian.PutUint64(bytes[9:], uint64(rev.sub))
|
||||
}
|
||||
|
||||
func bytesToRev(bytes []byte) revision {
|
||||
return revision{
|
||||
main: int64(binary.BigEndian.Uint64(bytes[0:8])),
|
||||
sub: int64(binary.BigEndian.Uint64(bytes[9:])),
|
||||
}
|
||||
}
|
||||
|
||||
type revisions []revision
|
||||
|
||||
func (a revisions) Len() int { return len(a) }
|
||||
func (a revisions) Less(i, j int) bool { return a[j].GreaterThan(a[i]) }
|
||||
func (a revisions) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
53
vendor/github.com/coreos/etcd/mvcc/revision_test.go
generated
vendored
Normal file
53
vendor/github.com/coreos/etcd/mvcc/revision_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestRevision tests that revision could be encoded to and decoded from
|
||||
// bytes slice. Moreover, the lexicographical order of its byte slice representation
|
||||
// follows the order of (main, sub).
|
||||
func TestRevision(t *testing.T) {
|
||||
tests := []revision{
|
||||
// order in (main, sub)
|
||||
{},
|
||||
{main: 1, sub: 0},
|
||||
{main: 1, sub: 1},
|
||||
{main: 2, sub: 0},
|
||||
{main: math.MaxInt64, sub: math.MaxInt64},
|
||||
}
|
||||
|
||||
bs := make([][]byte, len(tests))
|
||||
for i, tt := range tests {
|
||||
b := newRevBytes()
|
||||
revToBytes(tt, b)
|
||||
bs[i] = b
|
||||
|
||||
if grev := bytesToRev(b); !reflect.DeepEqual(grev, tt) {
|
||||
t.Errorf("#%d: revision = %+v, want %+v", i, grev, tt)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(tests)-1; i++ {
|
||||
if bytes.Compare(bs[i], bs[i+1]) >= 0 {
|
||||
t.Errorf("#%d: %v (%+v) should be smaller than %v (%+v)", i, bs[i], tests[i], bs[i+1], tests[i+1])
|
||||
}
|
||||
}
|
||||
}
|
||||
56
vendor/github.com/coreos/etcd/mvcc/util.go
generated
vendored
Normal file
56
vendor/github.com/coreos/etcd/mvcc/util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
// Copyright 2016 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func UpdateConsistentIndex(be backend.Backend, index uint64) {
|
||||
tx := be.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
var oldi uint64
|
||||
_, vs := tx.UnsafeRange(metaBucketName, consistentIndexKeyName, nil, 0)
|
||||
if len(vs) != 0 {
|
||||
oldi = binary.BigEndian.Uint64(vs[0])
|
||||
}
|
||||
|
||||
if index <= oldi {
|
||||
return
|
||||
}
|
||||
|
||||
bs := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bs, index)
|
||||
tx.UnsafePut(metaBucketName, consistentIndexKeyName, bs)
|
||||
}
|
||||
|
||||
func WriteKV(be backend.Backend, kv mvccpb.KeyValue) {
|
||||
ibytes := newRevBytes()
|
||||
revToBytes(revision{main: kv.ModRevision}, ibytes)
|
||||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
}
|
||||
|
||||
be.BatchTx().Lock()
|
||||
be.BatchTx().UnsafePut(keyBucketName, ibytes, d)
|
||||
be.BatchTx().Unlock()
|
||||
}
|
||||
577
vendor/github.com/coreos/etcd/mvcc/watchable_store.go
generated
vendored
Normal file
577
vendor/github.com/coreos/etcd/mvcc/watchable_store.go
generated
vendored
Normal file
|
|
@ -0,0 +1,577 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
const (
|
||||
// chanBufLen is the length of the buffered chan
|
||||
// for sending out watched events.
|
||||
// TODO: find a good buf value. 1024 is just a random one that
|
||||
// seems to be reasonable.
|
||||
chanBufLen = 1024
|
||||
|
||||
// maxWatchersPerSync is the number of watchers to sync in a single batch
|
||||
maxWatchersPerSync = 512
|
||||
)
|
||||
|
||||
type watchable interface {
|
||||
watch(key, end []byte, startRev int64, id WatchID, ch chan<- WatchResponse, fcs ...FilterFunc) (*watcher, cancelFunc)
|
||||
progress(w *watcher)
|
||||
rev() int64
|
||||
}
|
||||
|
||||
type watchableStore struct {
|
||||
mu sync.Mutex
|
||||
|
||||
*store
|
||||
|
||||
// victims are watcher batches that were blocked on the watch channel
|
||||
victims []watcherBatch
|
||||
victimc chan struct{}
|
||||
|
||||
// contains all unsynced watchers that needs to sync with events that have happened
|
||||
unsynced watcherGroup
|
||||
|
||||
// contains all synced watchers that are in sync with the progress of the store.
|
||||
// The key of the map is the key that the watcher watches on.
|
||||
synced watcherGroup
|
||||
|
||||
stopc chan struct{}
|
||||
wg sync.WaitGroup
|
||||
}
|
||||
|
||||
// cancelFunc updates unsynced and synced maps when running
|
||||
// cancel operations.
|
||||
type cancelFunc func()
|
||||
|
||||
func New(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) ConsistentWatchableKV {
|
||||
return newWatchableStore(b, le, ig)
|
||||
}
|
||||
|
||||
func newWatchableStore(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *watchableStore {
|
||||
s := &watchableStore{
|
||||
store: NewStore(b, le, ig),
|
||||
victimc: make(chan struct{}, 1),
|
||||
unsynced: newWatcherGroup(),
|
||||
synced: newWatcherGroup(),
|
||||
stopc: make(chan struct{}),
|
||||
}
|
||||
if s.le != nil {
|
||||
// use this store as the deleter so revokes trigger watch events
|
||||
s.le.SetRangeDeleter(s)
|
||||
}
|
||||
s.wg.Add(2)
|
||||
go s.syncWatchersLoop()
|
||||
go s.syncVictimsLoop()
|
||||
return s
|
||||
}
|
||||
|
||||
func (s *watchableStore) Put(key, value []byte, lease lease.LeaseID) (rev int64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
rev = s.store.Put(key, value, lease)
|
||||
changes := s.store.getChanges()
|
||||
if len(changes) != 1 {
|
||||
plog.Panicf("unexpected len(changes) != 1 after put")
|
||||
}
|
||||
|
||||
ev := mvccpb.Event{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &changes[0],
|
||||
}
|
||||
s.notify(rev, []mvccpb.Event{ev})
|
||||
return rev
|
||||
}
|
||||
|
||||
func (s *watchableStore) DeleteRange(key, end []byte) (n, rev int64) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
n, rev = s.store.DeleteRange(key, end)
|
||||
changes := s.store.getChanges()
|
||||
|
||||
if len(changes) != int(n) {
|
||||
plog.Panicf("unexpected len(changes) != n after deleteRange")
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return n, rev
|
||||
}
|
||||
|
||||
evs := make([]mvccpb.Event, n)
|
||||
for i := range changes {
|
||||
evs[i] = mvccpb.Event{
|
||||
Type: mvccpb.DELETE,
|
||||
Kv: &changes[i]}
|
||||
evs[i].Kv.ModRevision = rev
|
||||
}
|
||||
s.notify(rev, evs)
|
||||
return n, rev
|
||||
}
|
||||
|
||||
func (s *watchableStore) TxnBegin() int64 {
|
||||
s.mu.Lock()
|
||||
return s.store.TxnBegin()
|
||||
}
|
||||
|
||||
func (s *watchableStore) TxnEnd(txnID int64) error {
|
||||
err := s.store.TxnEnd(txnID)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
changes := s.getChanges()
|
||||
if len(changes) == 0 {
|
||||
s.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
|
||||
rev := s.store.Rev()
|
||||
evs := make([]mvccpb.Event, len(changes))
|
||||
for i, change := range changes {
|
||||
switch change.CreateRevision {
|
||||
case 0:
|
||||
evs[i] = mvccpb.Event{
|
||||
Type: mvccpb.DELETE,
|
||||
Kv: &changes[i]}
|
||||
evs[i].Kv.ModRevision = rev
|
||||
default:
|
||||
evs[i] = mvccpb.Event{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &changes[i]}
|
||||
}
|
||||
}
|
||||
|
||||
s.notify(rev, evs)
|
||||
s.mu.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *watchableStore) Close() error {
|
||||
close(s.stopc)
|
||||
s.wg.Wait()
|
||||
return s.store.Close()
|
||||
}
|
||||
|
||||
func (s *watchableStore) NewWatchStream() WatchStream {
|
||||
watchStreamGauge.Inc()
|
||||
return &watchStream{
|
||||
watchable: s,
|
||||
ch: make(chan WatchResponse, chanBufLen),
|
||||
cancels: make(map[WatchID]cancelFunc),
|
||||
watchers: make(map[WatchID]*watcher),
|
||||
}
|
||||
}
|
||||
|
||||
func (s *watchableStore) watch(key, end []byte, startRev int64, id WatchID, ch chan<- WatchResponse, fcs ...FilterFunc) (*watcher, cancelFunc) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
wa := &watcher{
|
||||
key: key,
|
||||
end: end,
|
||||
minRev: startRev,
|
||||
id: id,
|
||||
ch: ch,
|
||||
fcs: fcs,
|
||||
}
|
||||
|
||||
s.store.mu.Lock()
|
||||
synced := startRev > s.store.currentRev.main || startRev == 0
|
||||
if synced {
|
||||
wa.minRev = s.store.currentRev.main + 1
|
||||
if startRev > wa.minRev {
|
||||
wa.minRev = startRev
|
||||
}
|
||||
}
|
||||
s.store.mu.Unlock()
|
||||
if synced {
|
||||
s.synced.add(wa)
|
||||
} else {
|
||||
slowWatcherGauge.Inc()
|
||||
s.unsynced.add(wa)
|
||||
}
|
||||
watcherGauge.Inc()
|
||||
|
||||
return wa, func() { s.cancelWatcher(wa) }
|
||||
}
|
||||
|
||||
// cancelWatcher removes references of the watcher from the watchableStore
|
||||
func (s *watchableStore) cancelWatcher(wa *watcher) {
|
||||
for {
|
||||
s.mu.Lock()
|
||||
|
||||
if s.unsynced.delete(wa) {
|
||||
slowWatcherGauge.Dec()
|
||||
break
|
||||
} else if s.synced.delete(wa) {
|
||||
break
|
||||
} else if wa.compacted {
|
||||
break
|
||||
}
|
||||
|
||||
if !wa.victim {
|
||||
panic("watcher not victim but not in watch groups")
|
||||
}
|
||||
|
||||
var victimBatch watcherBatch
|
||||
for _, wb := range s.victims {
|
||||
if wb[wa] != nil {
|
||||
victimBatch = wb
|
||||
break
|
||||
}
|
||||
}
|
||||
if victimBatch != nil {
|
||||
slowWatcherGauge.Dec()
|
||||
delete(victimBatch, wa)
|
||||
break
|
||||
}
|
||||
|
||||
// victim being processed so not accessible; retry
|
||||
s.mu.Unlock()
|
||||
time.Sleep(time.Millisecond)
|
||||
}
|
||||
|
||||
watcherGauge.Dec()
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
// syncWatchersLoop syncs the watcher in the unsynced map every 100ms.
|
||||
func (s *watchableStore) syncWatchersLoop() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
s.mu.Lock()
|
||||
st := time.Now()
|
||||
lastUnsyncedWatchers := s.unsynced.size()
|
||||
s.syncWatchers()
|
||||
unsyncedWatchers := s.unsynced.size()
|
||||
s.mu.Unlock()
|
||||
syncDuration := time.Since(st)
|
||||
|
||||
waitDuration := 100 * time.Millisecond
|
||||
// more work pending?
|
||||
if unsyncedWatchers != 0 && lastUnsyncedWatchers > unsyncedWatchers {
|
||||
// be fair to other store operations by yielding time taken
|
||||
waitDuration = syncDuration
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(waitDuration):
|
||||
case <-s.stopc:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncVictimsLoop tries to write precomputed watcher responses to
|
||||
// watchers that had a blocked watcher channel
|
||||
func (s *watchableStore) syncVictimsLoop() {
|
||||
defer s.wg.Done()
|
||||
|
||||
for {
|
||||
for s.moveVictims() != 0 {
|
||||
// try to update all victim watchers
|
||||
}
|
||||
s.mu.Lock()
|
||||
isEmpty := len(s.victims) == 0
|
||||
s.mu.Unlock()
|
||||
|
||||
var tickc <-chan time.Time
|
||||
if !isEmpty {
|
||||
tickc = time.After(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-tickc:
|
||||
case <-s.victimc:
|
||||
case <-s.stopc:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// moveVictims tries to update watches with already pending event data
|
||||
func (s *watchableStore) moveVictims() (moved int) {
|
||||
s.mu.Lock()
|
||||
victims := s.victims
|
||||
s.victims = nil
|
||||
s.mu.Unlock()
|
||||
|
||||
var newVictim watcherBatch
|
||||
for _, wb := range victims {
|
||||
// try to send responses again
|
||||
for w, eb := range wb {
|
||||
// watcher has observed the store up to, but not including, w.minRev
|
||||
rev := w.minRev - 1
|
||||
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) {
|
||||
pendingEventsGauge.Add(float64(len(eb.evs)))
|
||||
} else {
|
||||
if newVictim == nil {
|
||||
newVictim = make(watcherBatch)
|
||||
}
|
||||
newVictim[w] = eb
|
||||
continue
|
||||
}
|
||||
moved++
|
||||
}
|
||||
|
||||
// assign completed victim watchers to unsync/sync
|
||||
s.mu.Lock()
|
||||
s.store.mu.Lock()
|
||||
curRev := s.store.currentRev.main
|
||||
for w, eb := range wb {
|
||||
if newVictim != nil && newVictim[w] != nil {
|
||||
// couldn't send watch response; stays victim
|
||||
continue
|
||||
}
|
||||
w.victim = false
|
||||
if eb.moreRev != 0 {
|
||||
w.minRev = eb.moreRev
|
||||
}
|
||||
if w.minRev <= curRev {
|
||||
s.unsynced.add(w)
|
||||
} else {
|
||||
slowWatcherGauge.Dec()
|
||||
s.synced.add(w)
|
||||
}
|
||||
}
|
||||
s.store.mu.Unlock()
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
if len(newVictim) > 0 {
|
||||
s.mu.Lock()
|
||||
s.victims = append(s.victims, newVictim)
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
return moved
|
||||
}
|
||||
|
||||
// syncWatchers syncs unsynced watchers by:
|
||||
// 1. choose a set of watchers from the unsynced watcher group
|
||||
// 2. iterate over the set to get the minimum revision and remove compacted watchers
|
||||
// 3. use minimum revision to get all key-value pairs and send those events to watchers
|
||||
// 4. remove synced watchers in set from unsynced group and move to synced group
|
||||
func (s *watchableStore) syncWatchers() {
|
||||
if s.unsynced.size() == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
s.store.mu.Lock()
|
||||
defer s.store.mu.Unlock()
|
||||
|
||||
// in order to find key-value pairs from unsynced watchers, we need to
|
||||
// find min revision index, and these revisions can be used to
|
||||
// query the backend store of key-value pairs
|
||||
curRev := s.store.currentRev.main
|
||||
compactionRev := s.store.compactMainRev
|
||||
wg, minRev := s.unsynced.choose(maxWatchersPerSync, curRev, compactionRev)
|
||||
minBytes, maxBytes := newRevBytes(), newRevBytes()
|
||||
revToBytes(revision{main: minRev}, minBytes)
|
||||
revToBytes(revision{main: curRev + 1}, maxBytes)
|
||||
|
||||
// UnsafeRange returns keys and values. And in boltdb, keys are revisions.
|
||||
// values are actual key-value pairs in backend.
|
||||
tx := s.store.b.BatchTx()
|
||||
tx.Lock()
|
||||
revs, vs := tx.UnsafeRange(keyBucketName, minBytes, maxBytes, 0)
|
||||
evs := kvsToEvents(wg, revs, vs)
|
||||
tx.Unlock()
|
||||
|
||||
var victims watcherBatch
|
||||
wb := newWatcherBatch(wg, evs)
|
||||
for w := range wg.watchers {
|
||||
w.minRev = curRev + 1
|
||||
|
||||
eb, ok := wb[w]
|
||||
if !ok {
|
||||
// bring un-notified watcher to synced
|
||||
s.synced.add(w)
|
||||
s.unsynced.delete(w)
|
||||
continue
|
||||
}
|
||||
|
||||
if eb.moreRev != 0 {
|
||||
w.minRev = eb.moreRev
|
||||
}
|
||||
|
||||
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: curRev}) {
|
||||
pendingEventsGauge.Add(float64(len(eb.evs)))
|
||||
} else {
|
||||
if victims == nil {
|
||||
victims = make(watcherBatch)
|
||||
}
|
||||
w.victim = true
|
||||
}
|
||||
|
||||
if w.victim {
|
||||
victims[w] = eb
|
||||
} else {
|
||||
if eb.moreRev != 0 {
|
||||
// stay unsynced; more to read
|
||||
continue
|
||||
}
|
||||
s.synced.add(w)
|
||||
}
|
||||
s.unsynced.delete(w)
|
||||
}
|
||||
s.addVictim(victims)
|
||||
|
||||
vsz := 0
|
||||
for _, v := range s.victims {
|
||||
vsz += len(v)
|
||||
}
|
||||
slowWatcherGauge.Set(float64(s.unsynced.size() + vsz))
|
||||
}
|
||||
|
||||
// kvsToEvents gets all events for the watchers from all key-value pairs
|
||||
func kvsToEvents(wg *watcherGroup, revs, vals [][]byte) (evs []mvccpb.Event) {
|
||||
for i, v := range vals {
|
||||
var kv mvccpb.KeyValue
|
||||
if err := kv.Unmarshal(v); err != nil {
|
||||
plog.Panicf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
|
||||
if !wg.contains(string(kv.Key)) {
|
||||
continue
|
||||
}
|
||||
|
||||
ty := mvccpb.PUT
|
||||
if isTombstone(revs[i]) {
|
||||
ty = mvccpb.DELETE
|
||||
// patch in mod revision so watchers won't skip
|
||||
kv.ModRevision = bytesToRev(revs[i]).main
|
||||
}
|
||||
evs = append(evs, mvccpb.Event{Kv: &kv, Type: ty})
|
||||
}
|
||||
return evs
|
||||
}
|
||||
|
||||
// notify notifies the fact that given event at the given rev just happened to
|
||||
// watchers that watch on the key of the event.
|
||||
func (s *watchableStore) notify(rev int64, evs []mvccpb.Event) {
|
||||
var victim watcherBatch
|
||||
for w, eb := range newWatcherBatch(&s.synced, evs) {
|
||||
if eb.revs != 1 {
|
||||
plog.Panicf("unexpected multiple revisions in notification")
|
||||
}
|
||||
|
||||
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) {
|
||||
pendingEventsGauge.Add(float64(len(eb.evs)))
|
||||
} else {
|
||||
// move slow watcher to victims
|
||||
w.minRev = rev + 1
|
||||
if victim == nil {
|
||||
victim = make(watcherBatch)
|
||||
}
|
||||
w.victim = true
|
||||
victim[w] = eb
|
||||
s.synced.delete(w)
|
||||
slowWatcherGauge.Inc()
|
||||
}
|
||||
}
|
||||
s.addVictim(victim)
|
||||
}
|
||||
|
||||
func (s *watchableStore) addVictim(victim watcherBatch) {
|
||||
if victim == nil {
|
||||
return
|
||||
}
|
||||
s.victims = append(s.victims, victim)
|
||||
select {
|
||||
case s.victimc <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *watchableStore) rev() int64 { return s.store.Rev() }
|
||||
|
||||
func (s *watchableStore) progress(w *watcher) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if _, ok := s.synced.watchers[w]; ok {
|
||||
w.send(WatchResponse{WatchID: w.id, Revision: s.rev()})
|
||||
// If the ch is full, this watcher is receiving events.
|
||||
// We do not need to send progress at all.
|
||||
}
|
||||
}
|
||||
|
||||
type watcher struct {
|
||||
// the watcher key
|
||||
key []byte
|
||||
// end indicates the end of the range to watch.
|
||||
// If end is set, the watcher is on a range.
|
||||
end []byte
|
||||
|
||||
// victim is set when ch is blocked and undergoing victim processing
|
||||
victim bool
|
||||
|
||||
// compacted is set when the watcher is removed because of compaction
|
||||
compacted bool
|
||||
|
||||
// minRev is the minimum revision update the watcher will accept
|
||||
minRev int64
|
||||
id WatchID
|
||||
|
||||
fcs []FilterFunc
|
||||
// a chan to send out the watch response.
|
||||
// The chan might be shared with other watchers.
|
||||
ch chan<- WatchResponse
|
||||
}
|
||||
|
||||
func (w *watcher) send(wr WatchResponse) bool {
|
||||
progressEvent := len(wr.Events) == 0
|
||||
|
||||
if len(w.fcs) != 0 {
|
||||
ne := make([]mvccpb.Event, 0, len(wr.Events))
|
||||
for i := range wr.Events {
|
||||
filtered := false
|
||||
for _, filter := range w.fcs {
|
||||
if filter(wr.Events[i]) {
|
||||
filtered = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !filtered {
|
||||
ne = append(ne, wr.Events[i])
|
||||
}
|
||||
}
|
||||
wr.Events = ne
|
||||
}
|
||||
|
||||
// if all events are filtered out, we should send nothing.
|
||||
if !progressEvent && len(wr.Events) == 0 {
|
||||
return true
|
||||
}
|
||||
select {
|
||||
case w.ch <- wr:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
202
vendor/github.com/coreos/etcd/mvcc/watchable_store_bench_test.go
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/mvcc/watchable_store_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
)
|
||||
|
||||
func BenchmarkWatchableStorePut(b *testing.B) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := New(be, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
bytesN := 64
|
||||
keys := createBytesSlice(bytesN, b.N)
|
||||
vals := createBytesSlice(bytesN, b.N)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
s.Put(keys[i], vals[i], lease.NoLease)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWatchableStoreTxnPut benchmarks the Put operation
|
||||
// with transaction begin and end, where transaction involves
|
||||
// some synchronization operations, such as mutex locking.
|
||||
func BenchmarkWatchableStoreTxnPut(b *testing.B) {
|
||||
var i fakeConsistentIndex
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := New(be, &lease.FakeLessor{}, &i)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
// arbitrary number of bytes
|
||||
bytesN := 64
|
||||
keys := createBytesSlice(bytesN, b.N)
|
||||
vals := createBytesSlice(bytesN, b.N)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
for i := 0; i < b.N; i++ {
|
||||
id := s.TxnBegin()
|
||||
if _, err := s.TxnPut(id, keys[i], vals[i], lease.NoLease); err != nil {
|
||||
plog.Fatalf("txn put error: %v", err)
|
||||
}
|
||||
s.TxnEnd(id)
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkWatchableStoreWatchSyncPut benchmarks the case of
|
||||
// many synced watchers receiving a Put notification.
|
||||
func BenchmarkWatchableStoreWatchSyncPut(b *testing.B) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(be, &lease.FakeLessor{}, nil)
|
||||
defer cleanup(s, be, tmpPath)
|
||||
|
||||
k := []byte("testkey")
|
||||
v := []byte("testval")
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
watchIDs := make([]WatchID, b.N)
|
||||
for i := range watchIDs {
|
||||
// non-0 value to keep watchers in unsynced
|
||||
watchIDs[i] = w.Watch(k, nil, 1)
|
||||
}
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
// trigger watchers
|
||||
s.Put(k, v, lease.NoLease)
|
||||
for range watchIDs {
|
||||
<-w.Chan()
|
||||
}
|
||||
select {
|
||||
case wc := <-w.Chan():
|
||||
b.Fatalf("unexpected data %v", wc)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
// Benchmarks on cancel function performance for unsynced watchers
|
||||
// in a WatchableStore. It creates k*N watchers to populate unsynced
|
||||
// with a reasonably large number of watchers. And measures the time it
|
||||
// takes to cancel N watchers out of k*N watchers. The performance is
|
||||
// expected to differ depending on the unsynced member implementation.
|
||||
// TODO: k is an arbitrary constant. We need to figure out what factor
|
||||
// we should put to simulate the real-world use cases.
|
||||
func BenchmarkWatchableStoreUnsyncedCancel(b *testing.B) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := NewStore(be, &lease.FakeLessor{}, nil)
|
||||
|
||||
// manually create watchableStore instead of newWatchableStore
|
||||
// because newWatchableStore periodically calls syncWatchersLoop
|
||||
// method to sync watchers in unsynced map. We want to keep watchers
|
||||
// in unsynced for this benchmark.
|
||||
ws := &watchableStore{
|
||||
store: s,
|
||||
unsynced: newWatcherGroup(),
|
||||
|
||||
// to make the test not crash from assigning to nil map.
|
||||
// 'synced' doesn't get populated in this test.
|
||||
synced: newWatcherGroup(),
|
||||
}
|
||||
|
||||
defer func() {
|
||||
ws.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
// Put a key so that we can spawn watchers on that key
|
||||
// (testKey in this test). This increases the rev to 1,
|
||||
// and later we can we set the watcher's startRev to 1,
|
||||
// and force watchers to be in unsynced.
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := ws.NewWatchStream()
|
||||
|
||||
const k int = 2
|
||||
benchSampleN := b.N
|
||||
watcherN := k * benchSampleN
|
||||
|
||||
watchIDs := make([]WatchID, watcherN)
|
||||
for i := 0; i < watcherN; i++ {
|
||||
// non-0 value to keep watchers in unsynced
|
||||
watchIDs[i] = w.Watch(testKey, nil, 1)
|
||||
}
|
||||
|
||||
// random-cancel N watchers to make it not biased towards
|
||||
// data structures with an order, such as slice.
|
||||
ix := rand.Perm(watcherN)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
// cancel N watchers
|
||||
for _, idx := range ix[:benchSampleN] {
|
||||
if err := w.Cancel(watchIDs[idx]); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func BenchmarkWatchableStoreSyncedCancel(b *testing.B) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(be, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
// Put a key so that we can spawn watchers on that key
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
|
||||
// put 1 million watchers on the same key
|
||||
const watcherN = 1000000
|
||||
|
||||
watchIDs := make([]WatchID, watcherN)
|
||||
for i := 0; i < watcherN; i++ {
|
||||
// 0 for startRev to keep watchers in synced
|
||||
watchIDs[i] = w.Watch(testKey, nil, 0)
|
||||
}
|
||||
|
||||
// randomly cancel watchers to make it not biased towards
|
||||
// data structures with an order, such as slice.
|
||||
ix := rand.Perm(watcherN)
|
||||
|
||||
b.ResetTimer()
|
||||
b.ReportAllocs()
|
||||
|
||||
for _, idx := range ix {
|
||||
if err := w.Cancel(watchIDs[idx]); err != nil {
|
||||
b.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
426
vendor/github.com/coreos/etcd/mvcc/watchable_store_test.go
generated
vendored
Normal file
426
vendor/github.com/coreos/etcd/mvcc/watchable_store_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,426 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func TestWatch(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
w.Watch(testKey, nil, 0)
|
||||
|
||||
if !s.synced.contains(string(testKey)) {
|
||||
// the key must have had an entry in synced
|
||||
t.Errorf("existence = false, want true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewWatcherCancel(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
wt := w.Watch(testKey, nil, 0)
|
||||
|
||||
if err := w.Cancel(wt); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if s.synced.contains(string(testKey)) {
|
||||
// the key shoud have been deleted
|
||||
t.Errorf("existence = true, want false")
|
||||
}
|
||||
}
|
||||
|
||||
// TestCancelUnsynced tests if running CancelFunc removes watchers from unsynced.
|
||||
func TestCancelUnsynced(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
|
||||
// manually create watchableStore instead of newWatchableStore
|
||||
// because newWatchableStore automatically calls syncWatchers
|
||||
// method to sync watchers in unsynced map. We want to keep watchers
|
||||
// in unsynced to test if syncWatchers works as expected.
|
||||
s := &watchableStore{
|
||||
store: NewStore(b, &lease.FakeLessor{}, nil),
|
||||
unsynced: newWatcherGroup(),
|
||||
|
||||
// to make the test not crash from assigning to nil map.
|
||||
// 'synced' doesn't get populated in this test.
|
||||
synced: newWatcherGroup(),
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
// Put a key so that we can spawn watchers on that key.
|
||||
// (testKey in this test). This increases the rev to 1,
|
||||
// and later we can we set the watcher's startRev to 1,
|
||||
// and force watchers to be in unsynced.
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
|
||||
// arbitrary number for watchers
|
||||
watcherN := 100
|
||||
|
||||
// create watcherN of watch ids to cancel
|
||||
watchIDs := make([]WatchID, watcherN)
|
||||
for i := 0; i < watcherN; i++ {
|
||||
// use 1 to keep watchers in unsynced
|
||||
watchIDs[i] = w.Watch(testKey, nil, 1)
|
||||
}
|
||||
|
||||
for _, idx := range watchIDs {
|
||||
if err := w.Cancel(idx); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
// After running CancelFunc
|
||||
//
|
||||
// unsynced should be empty
|
||||
// because cancel removes watcher from unsynced
|
||||
if size := s.unsynced.size(); size != 0 {
|
||||
t.Errorf("unsynced size = %d, want 0", size)
|
||||
}
|
||||
}
|
||||
|
||||
// TestSyncWatchers populates unsynced watcher map and tests syncWatchers
|
||||
// method to see if it correctly sends events to channel of unsynced watchers
|
||||
// and moves these watchers to synced.
|
||||
func TestSyncWatchers(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
|
||||
s := &watchableStore{
|
||||
store: NewStore(b, &lease.FakeLessor{}, nil),
|
||||
unsynced: newWatcherGroup(),
|
||||
synced: newWatcherGroup(),
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
|
||||
// arbitrary number for watchers
|
||||
watcherN := 100
|
||||
|
||||
for i := 0; i < watcherN; i++ {
|
||||
// specify rev as 1 to keep watchers in unsynced
|
||||
w.Watch(testKey, nil, 1)
|
||||
}
|
||||
|
||||
// Before running s.syncWatchers() synced should be empty because we manually
|
||||
// populate unsynced only
|
||||
sws := s.synced.watcherSetByKey(string(testKey))
|
||||
uws := s.unsynced.watcherSetByKey(string(testKey))
|
||||
|
||||
if len(sws) != 0 {
|
||||
t.Fatalf("synced[string(testKey)] size = %d, want 0", len(sws))
|
||||
}
|
||||
// unsynced should not be empty because we manually populated unsynced only
|
||||
if len(uws) != watcherN {
|
||||
t.Errorf("unsynced size = %d, want %d", len(uws), watcherN)
|
||||
}
|
||||
|
||||
// this should move all unsynced watchers to synced ones
|
||||
s.syncWatchers()
|
||||
|
||||
sws = s.synced.watcherSetByKey(string(testKey))
|
||||
uws = s.unsynced.watcherSetByKey(string(testKey))
|
||||
|
||||
// After running s.syncWatchers(), synced should not be empty because syncwatchers
|
||||
// populates synced in this test case
|
||||
if len(sws) != watcherN {
|
||||
t.Errorf("synced[string(testKey)] size = %d, want %d", len(sws), watcherN)
|
||||
}
|
||||
|
||||
// unsynced should be empty because syncwatchers is expected to move all watchers
|
||||
// from unsynced to synced in this test case
|
||||
if len(uws) != 0 {
|
||||
t.Errorf("unsynced size = %d, want 0", len(uws))
|
||||
}
|
||||
|
||||
for w := range sws {
|
||||
if w.minRev != s.Rev()+1 {
|
||||
t.Errorf("w.minRev = %d, want %d", w.minRev, s.Rev()+1)
|
||||
}
|
||||
}
|
||||
|
||||
if len(w.(*watchStream).ch) != watcherN {
|
||||
t.Errorf("watched event size = %d, want %d", len(w.(*watchStream).ch), watcherN)
|
||||
}
|
||||
|
||||
evs := (<-w.(*watchStream).ch).Events
|
||||
if len(evs) != 1 {
|
||||
t.Errorf("len(evs) got = %d, want = 1", len(evs))
|
||||
}
|
||||
if evs[0].Type != mvccpb.PUT {
|
||||
t.Errorf("got = %v, want = %v", evs[0].Type, mvccpb.PUT)
|
||||
}
|
||||
if !bytes.Equal(evs[0].Kv.Key, testKey) {
|
||||
t.Errorf("got = %s, want = %s", evs[0].Kv.Key, testKey)
|
||||
}
|
||||
if !bytes.Equal(evs[0].Kv.Value, testValue) {
|
||||
t.Errorf("got = %s, want = %s", evs[0].Kv.Value, testValue)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatchCompacted tests a watcher that watches on a compacted revision.
|
||||
func TestWatchCompacted(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
|
||||
maxRev := 10
|
||||
compactRev := int64(5)
|
||||
for i := 0; i < maxRev; i++ {
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
}
|
||||
_, err := s.Compact(compactRev)
|
||||
if err != nil {
|
||||
t.Fatalf("failed to compact kv (%v)", err)
|
||||
}
|
||||
|
||||
w := s.NewWatchStream()
|
||||
wt := w.Watch(testKey, nil, compactRev-1)
|
||||
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.WatchID != wt {
|
||||
t.Errorf("resp.WatchID = %x, want %x", resp.WatchID, wt)
|
||||
}
|
||||
if resp.CompactRevision == 0 {
|
||||
t.Errorf("resp.Compacted = %v, want %v", resp.CompactRevision, compactRev)
|
||||
}
|
||||
case <-time.After(1 * time.Second):
|
||||
t.Fatalf("failed to receive response (timeout)")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchFutureRev(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
testKey := []byte("foo")
|
||||
testValue := []byte("bar")
|
||||
|
||||
w := s.NewWatchStream()
|
||||
wrev := int64(10)
|
||||
w.Watch(testKey, nil, wrev)
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
rev := s.Put(testKey, testValue, lease.NoLease)
|
||||
if rev >= wrev {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if resp.Revision != wrev {
|
||||
t.Fatalf("rev = %d, want %d", resp.Revision, wrev)
|
||||
}
|
||||
if len(resp.Events) != 1 {
|
||||
t.Fatalf("failed to get events from the response")
|
||||
}
|
||||
if resp.Events[0].Kv.ModRevision != wrev {
|
||||
t.Fatalf("kv.rev = %d, want %d", resp.Events[0].Kv.ModRevision, wrev)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("failed to receive event in 1 second.")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatchBatchUnsynced tests batching on unsynced watchers
|
||||
func TestWatchBatchUnsynced(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
oldMaxRevs := watchBatchMaxRevs
|
||||
defer func() {
|
||||
watchBatchMaxRevs = oldMaxRevs
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
batches := 3
|
||||
watchBatchMaxRevs = 4
|
||||
|
||||
v := []byte("foo")
|
||||
for i := 0; i < watchBatchMaxRevs*batches; i++ {
|
||||
s.Put(v, v, lease.NoLease)
|
||||
}
|
||||
|
||||
w := s.NewWatchStream()
|
||||
w.Watch(v, nil, 1)
|
||||
for i := 0; i < batches; i++ {
|
||||
if resp := <-w.Chan(); len(resp.Events) != watchBatchMaxRevs {
|
||||
t.Fatalf("len(events) = %d, want %d", len(resp.Events), watchBatchMaxRevs)
|
||||
}
|
||||
}
|
||||
|
||||
s.store.mu.Lock()
|
||||
defer s.store.mu.Unlock()
|
||||
if size := s.synced.size(); size != 1 {
|
||||
t.Errorf("synced size = %d, want 1", size)
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewMapwatcherToEventMap(t *testing.T) {
|
||||
k0, k1, k2 := []byte("foo0"), []byte("foo1"), []byte("foo2")
|
||||
v0, v1, v2 := []byte("bar0"), []byte("bar1"), []byte("bar2")
|
||||
|
||||
ws := []*watcher{{key: k0}, {key: k1}, {key: k2}}
|
||||
|
||||
evs := []mvccpb.Event{
|
||||
{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{Key: k0, Value: v0},
|
||||
},
|
||||
{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{Key: k1, Value: v1},
|
||||
},
|
||||
{
|
||||
Type: mvccpb.PUT,
|
||||
Kv: &mvccpb.KeyValue{Key: k2, Value: v2},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
sync []*watcher
|
||||
evs []mvccpb.Event
|
||||
|
||||
wwe map[*watcher][]mvccpb.Event
|
||||
}{
|
||||
// no watcher in sync, some events should return empty wwe
|
||||
{
|
||||
nil,
|
||||
evs,
|
||||
map[*watcher][]mvccpb.Event{},
|
||||
},
|
||||
|
||||
// one watcher in sync, one event that does not match the key of that
|
||||
// watcher should return empty wwe
|
||||
{
|
||||
[]*watcher{ws[2]},
|
||||
evs[:1],
|
||||
map[*watcher][]mvccpb.Event{},
|
||||
},
|
||||
|
||||
// one watcher in sync, one event that matches the key of that
|
||||
// watcher should return wwe with that matching watcher
|
||||
{
|
||||
[]*watcher{ws[1]},
|
||||
evs[1:2],
|
||||
map[*watcher][]mvccpb.Event{
|
||||
ws[1]: evs[1:2],
|
||||
},
|
||||
},
|
||||
|
||||
// two watchers in sync that watches two different keys, one event
|
||||
// that matches the key of only one of the watcher should return wwe
|
||||
// with the matching watcher
|
||||
{
|
||||
[]*watcher{ws[0], ws[2]},
|
||||
evs[2:],
|
||||
map[*watcher][]mvccpb.Event{
|
||||
ws[2]: evs[2:],
|
||||
},
|
||||
},
|
||||
|
||||
// two watchers in sync that watches the same key, two events that
|
||||
// match the keys should return wwe with those two watchers
|
||||
{
|
||||
[]*watcher{ws[0], ws[1]},
|
||||
evs[:2],
|
||||
map[*watcher][]mvccpb.Event{
|
||||
ws[0]: evs[:1],
|
||||
ws[1]: evs[1:2],
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
wg := newWatcherGroup()
|
||||
for _, w := range tt.sync {
|
||||
wg.add(w)
|
||||
}
|
||||
|
||||
gwe := newWatcherBatch(&wg, tt.evs)
|
||||
if len(gwe) != len(tt.wwe) {
|
||||
t.Errorf("#%d: len(gwe) got = %d, want = %d", i, len(gwe), len(tt.wwe))
|
||||
}
|
||||
// compare gwe and tt.wwe
|
||||
for w, eb := range gwe {
|
||||
if len(eb.evs) != len(tt.wwe[w]) {
|
||||
t.Errorf("#%d: len(eb.evs) got = %d, want = %d", i, len(eb.evs), len(tt.wwe[w]))
|
||||
}
|
||||
if !reflect.DeepEqual(eb.evs, tt.wwe[w]) {
|
||||
t.Errorf("#%d: reflect.DeepEqual events got = %v, want = true", i, false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
171
vendor/github.com/coreos/etcd/mvcc/watcher.go
generated
vendored
Normal file
171
vendor/github.com/coreos/etcd/mvcc/watcher.go
generated
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrWatcherNotExist = errors.New("mvcc: watcher does not exist")
|
||||
)
|
||||
|
||||
type WatchID int64
|
||||
|
||||
// FilterFunc returns true if the given event should be filtered out.
|
||||
type FilterFunc func(e mvccpb.Event) bool
|
||||
|
||||
type WatchStream interface {
|
||||
// Watch creates a watcher. The watcher watches the events happening or
|
||||
// happened on the given key or range [key, end) from the given startRev.
|
||||
//
|
||||
// The whole event history can be watched unless compacted.
|
||||
// If `startRev` <=0, watch observes events after currentRev.
|
||||
//
|
||||
// The returned `id` is the ID of this watcher. It appears as WatchID
|
||||
// in events that are sent to the created watcher through stream channel.
|
||||
//
|
||||
Watch(key, end []byte, startRev int64, fcs ...FilterFunc) WatchID
|
||||
|
||||
// Chan returns a chan. All watch response will be sent to the returned chan.
|
||||
Chan() <-chan WatchResponse
|
||||
|
||||
// RequestProgress requests the progress of the watcher with given ID. The response
|
||||
// will only be sent if the watcher is currently synced.
|
||||
// The responses will be sent through the WatchRespone Chan attached
|
||||
// with this stream to ensure correct ordering.
|
||||
// The responses contains no events. The revision in the response is the progress
|
||||
// of the watchers since the watcher is currently synced.
|
||||
RequestProgress(id WatchID)
|
||||
|
||||
// Cancel cancels a watcher by giving its ID. If watcher does not exist, an error will be
|
||||
// returned.
|
||||
Cancel(id WatchID) error
|
||||
|
||||
// Close closes Chan and release all related resources.
|
||||
Close()
|
||||
|
||||
// Rev returns the current revision of the KV the stream watches on.
|
||||
Rev() int64
|
||||
}
|
||||
|
||||
type WatchResponse struct {
|
||||
// WatchID is the WatchID of the watcher this response sent to.
|
||||
WatchID WatchID
|
||||
|
||||
// Events contains all the events that needs to send.
|
||||
Events []mvccpb.Event
|
||||
|
||||
// Revision is the revision of the KV when the watchResponse is created.
|
||||
// For a normal response, the revision should be the same as the last
|
||||
// modified revision inside Events. For a delayed response to a unsynced
|
||||
// watcher, the revision is greater than the last modified revision
|
||||
// inside Events.
|
||||
Revision int64
|
||||
|
||||
// CompactRevision is set when the watcher is cancelled due to compaction.
|
||||
CompactRevision int64
|
||||
}
|
||||
|
||||
// watchStream contains a collection of watchers that share
|
||||
// one streaming chan to send out watched events and other control events.
|
||||
type watchStream struct {
|
||||
watchable watchable
|
||||
ch chan WatchResponse
|
||||
|
||||
mu sync.Mutex // guards fields below it
|
||||
// nextID is the ID pre-allocated for next new watcher in this stream
|
||||
nextID WatchID
|
||||
closed bool
|
||||
cancels map[WatchID]cancelFunc
|
||||
watchers map[WatchID]*watcher
|
||||
}
|
||||
|
||||
// Watch creates a new watcher in the stream and returns its WatchID.
|
||||
// TODO: return error if ws is closed?
|
||||
func (ws *watchStream) Watch(key, end []byte, startRev int64, fcs ...FilterFunc) WatchID {
|
||||
// prevent wrong range where key >= end lexicographically
|
||||
// watch request with 'WithFromKey' has empty-byte range end
|
||||
if len(end) != 0 && bytes.Compare(key, end) != -1 {
|
||||
return -1
|
||||
}
|
||||
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
if ws.closed {
|
||||
return -1
|
||||
}
|
||||
|
||||
id := ws.nextID
|
||||
ws.nextID++
|
||||
|
||||
w, c := ws.watchable.watch(key, end, startRev, id, ws.ch, fcs...)
|
||||
|
||||
ws.cancels[id] = c
|
||||
ws.watchers[id] = w
|
||||
return id
|
||||
}
|
||||
|
||||
func (ws *watchStream) Chan() <-chan WatchResponse {
|
||||
return ws.ch
|
||||
}
|
||||
|
||||
func (ws *watchStream) Cancel(id WatchID) error {
|
||||
ws.mu.Lock()
|
||||
cancel, ok := ws.cancels[id]
|
||||
ok = ok && !ws.closed
|
||||
if ok {
|
||||
delete(ws.cancels, id)
|
||||
delete(ws.watchers, id)
|
||||
}
|
||||
ws.mu.Unlock()
|
||||
if !ok {
|
||||
return ErrWatcherNotExist
|
||||
}
|
||||
cancel()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ws *watchStream) Close() {
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
|
||||
for _, cancel := range ws.cancels {
|
||||
cancel()
|
||||
}
|
||||
ws.closed = true
|
||||
close(ws.ch)
|
||||
watchStreamGauge.Dec()
|
||||
}
|
||||
|
||||
func (ws *watchStream) Rev() int64 {
|
||||
ws.mu.Lock()
|
||||
defer ws.mu.Unlock()
|
||||
return ws.watchable.rev()
|
||||
}
|
||||
|
||||
func (ws *watchStream) RequestProgress(id WatchID) {
|
||||
ws.mu.Lock()
|
||||
w, ok := ws.watchers[id]
|
||||
ws.mu.Unlock()
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
ws.watchable.progress(w)
|
||||
}
|
||||
38
vendor/github.com/coreos/etcd/mvcc/watcher_bench_test.go
generated
vendored
Normal file
38
vendor/github.com/coreos/etcd/mvcc/watcher_bench_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
)
|
||||
|
||||
func BenchmarkKVWatcherMemoryUsage(b *testing.B) {
|
||||
be, tmpPath := backend.NewDefaultTmpBackend()
|
||||
watchable := newWatchableStore(be, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer cleanup(watchable, be, tmpPath)
|
||||
|
||||
w := watchable.NewWatchStream()
|
||||
|
||||
b.ReportAllocs()
|
||||
b.StartTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
w.Watch([]byte(fmt.Sprint("foo", i)), nil, 0)
|
||||
}
|
||||
}
|
||||
283
vendor/github.com/coreos/etcd/mvcc/watcher_group.go
generated
vendored
Normal file
283
vendor/github.com/coreos/etcd/mvcc/watcher_group.go
generated
vendored
Normal file
|
|
@ -0,0 +1,283 @@
|
|||
// Copyright 2016 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"math"
|
||||
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/adt"
|
||||
)
|
||||
|
||||
var (
|
||||
// watchBatchMaxRevs is the maximum distinct revisions that
|
||||
// may be sent to an unsynced watcher at a time. Declared as
|
||||
// var instead of const for testing purposes.
|
||||
watchBatchMaxRevs = 1000
|
||||
)
|
||||
|
||||
type eventBatch struct {
|
||||
// evs is a batch of revision-ordered events
|
||||
evs []mvccpb.Event
|
||||
// revs is the minimum unique revisions observed for this batch
|
||||
revs int
|
||||
// moreRev is first revision with more events following this batch
|
||||
moreRev int64
|
||||
}
|
||||
|
||||
func (eb *eventBatch) add(ev mvccpb.Event) {
|
||||
if eb.revs > watchBatchMaxRevs {
|
||||
// maxed out batch size
|
||||
return
|
||||
}
|
||||
|
||||
if len(eb.evs) == 0 {
|
||||
// base case
|
||||
eb.revs = 1
|
||||
eb.evs = append(eb.evs, ev)
|
||||
return
|
||||
}
|
||||
|
||||
// revision accounting
|
||||
ebRev := eb.evs[len(eb.evs)-1].Kv.ModRevision
|
||||
evRev := ev.Kv.ModRevision
|
||||
if evRev > ebRev {
|
||||
eb.revs++
|
||||
if eb.revs > watchBatchMaxRevs {
|
||||
eb.moreRev = evRev
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
eb.evs = append(eb.evs, ev)
|
||||
}
|
||||
|
||||
type watcherBatch map[*watcher]*eventBatch
|
||||
|
||||
func (wb watcherBatch) add(w *watcher, ev mvccpb.Event) {
|
||||
eb := wb[w]
|
||||
if eb == nil {
|
||||
eb = &eventBatch{}
|
||||
wb[w] = eb
|
||||
}
|
||||
eb.add(ev)
|
||||
}
|
||||
|
||||
// newWatcherBatch maps watchers to their matched events. It enables quick
|
||||
// events look up by watcher.
|
||||
func newWatcherBatch(wg *watcherGroup, evs []mvccpb.Event) watcherBatch {
|
||||
if len(wg.watchers) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
wb := make(watcherBatch)
|
||||
for _, ev := range evs {
|
||||
for w := range wg.watcherSetByKey(string(ev.Kv.Key)) {
|
||||
if ev.Kv.ModRevision >= w.minRev {
|
||||
// don't double notify
|
||||
wb.add(w, ev)
|
||||
}
|
||||
}
|
||||
}
|
||||
return wb
|
||||
}
|
||||
|
||||
type watcherSet map[*watcher]struct{}
|
||||
|
||||
func (w watcherSet) add(wa *watcher) {
|
||||
if _, ok := w[wa]; ok {
|
||||
panic("add watcher twice!")
|
||||
}
|
||||
w[wa] = struct{}{}
|
||||
}
|
||||
|
||||
func (w watcherSet) union(ws watcherSet) {
|
||||
for wa := range ws {
|
||||
w.add(wa)
|
||||
}
|
||||
}
|
||||
|
||||
func (w watcherSet) delete(wa *watcher) {
|
||||
if _, ok := w[wa]; !ok {
|
||||
panic("removing missing watcher!")
|
||||
}
|
||||
delete(w, wa)
|
||||
}
|
||||
|
||||
type watcherSetByKey map[string]watcherSet
|
||||
|
||||
func (w watcherSetByKey) add(wa *watcher) {
|
||||
set := w[string(wa.key)]
|
||||
if set == nil {
|
||||
set = make(watcherSet)
|
||||
w[string(wa.key)] = set
|
||||
}
|
||||
set.add(wa)
|
||||
}
|
||||
|
||||
func (w watcherSetByKey) delete(wa *watcher) bool {
|
||||
k := string(wa.key)
|
||||
if v, ok := w[k]; ok {
|
||||
if _, ok := v[wa]; ok {
|
||||
delete(v, wa)
|
||||
if len(v) == 0 {
|
||||
// remove the set; nothing left
|
||||
delete(w, k)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// watcherGroup is a collection of watchers organized by their ranges
|
||||
type watcherGroup struct {
|
||||
// keyWatchers has the watchers that watch on a single key
|
||||
keyWatchers watcherSetByKey
|
||||
// ranges has the watchers that watch a range; it is sorted by interval
|
||||
ranges adt.IntervalTree
|
||||
// watchers is the set of all watchers
|
||||
watchers watcherSet
|
||||
}
|
||||
|
||||
func newWatcherGroup() watcherGroup {
|
||||
return watcherGroup{
|
||||
keyWatchers: make(watcherSetByKey),
|
||||
watchers: make(watcherSet),
|
||||
}
|
||||
}
|
||||
|
||||
// add puts a watcher in the group.
|
||||
func (wg *watcherGroup) add(wa *watcher) {
|
||||
wg.watchers.add(wa)
|
||||
if wa.end == nil {
|
||||
wg.keyWatchers.add(wa)
|
||||
return
|
||||
}
|
||||
|
||||
// interval already registered?
|
||||
ivl := adt.NewStringAffineInterval(string(wa.key), string(wa.end))
|
||||
if iv := wg.ranges.Find(ivl); iv != nil {
|
||||
iv.Val.(watcherSet).add(wa)
|
||||
return
|
||||
}
|
||||
|
||||
// not registered, put in interval tree
|
||||
ws := make(watcherSet)
|
||||
ws.add(wa)
|
||||
wg.ranges.Insert(ivl, ws)
|
||||
}
|
||||
|
||||
// contains is whether the given key has a watcher in the group.
|
||||
func (wg *watcherGroup) contains(key string) bool {
|
||||
_, ok := wg.keyWatchers[key]
|
||||
return ok || wg.ranges.Contains(adt.NewStringAffinePoint(key))
|
||||
}
|
||||
|
||||
// size gives the number of unique watchers in the group.
|
||||
func (wg *watcherGroup) size() int { return len(wg.watchers) }
|
||||
|
||||
// delete removes a watcher from the group.
|
||||
func (wg *watcherGroup) delete(wa *watcher) bool {
|
||||
if _, ok := wg.watchers[wa]; !ok {
|
||||
return false
|
||||
}
|
||||
wg.watchers.delete(wa)
|
||||
if wa.end == nil {
|
||||
wg.keyWatchers.delete(wa)
|
||||
return true
|
||||
}
|
||||
|
||||
ivl := adt.NewStringAffineInterval(string(wa.key), string(wa.end))
|
||||
iv := wg.ranges.Find(ivl)
|
||||
if iv == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ws := iv.Val.(watcherSet)
|
||||
delete(ws, wa)
|
||||
if len(ws) == 0 {
|
||||
// remove interval missing watchers
|
||||
if ok := wg.ranges.Delete(ivl); !ok {
|
||||
panic("could not remove watcher from interval tree")
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// choose selects watchers from the watcher group to update
|
||||
func (wg *watcherGroup) choose(maxWatchers int, curRev, compactRev int64) (*watcherGroup, int64) {
|
||||
if len(wg.watchers) < maxWatchers {
|
||||
return wg, wg.chooseAll(curRev, compactRev)
|
||||
}
|
||||
ret := newWatcherGroup()
|
||||
for w := range wg.watchers {
|
||||
if maxWatchers <= 0 {
|
||||
break
|
||||
}
|
||||
maxWatchers--
|
||||
ret.add(w)
|
||||
}
|
||||
return &ret, ret.chooseAll(curRev, compactRev)
|
||||
}
|
||||
|
||||
func (wg *watcherGroup) chooseAll(curRev, compactRev int64) int64 {
|
||||
minRev := int64(math.MaxInt64)
|
||||
for w := range wg.watchers {
|
||||
if w.minRev > curRev {
|
||||
panic("watcher current revision should not exceed current revision")
|
||||
}
|
||||
if w.minRev < compactRev {
|
||||
select {
|
||||
case w.ch <- WatchResponse{WatchID: w.id, CompactRevision: compactRev}:
|
||||
w.compacted = true
|
||||
wg.delete(w)
|
||||
default:
|
||||
// retry next time
|
||||
}
|
||||
continue
|
||||
}
|
||||
if minRev > w.minRev {
|
||||
minRev = w.minRev
|
||||
}
|
||||
}
|
||||
return minRev
|
||||
}
|
||||
|
||||
// watcherSetByKey gets the set of watchers that receive events on the given key.
|
||||
func (wg *watcherGroup) watcherSetByKey(key string) watcherSet {
|
||||
wkeys := wg.keyWatchers[key]
|
||||
wranges := wg.ranges.Stab(adt.NewStringAffinePoint(key))
|
||||
|
||||
// zero-copy cases
|
||||
switch {
|
||||
case len(wranges) == 0:
|
||||
// no need to merge ranges or copy; reuse single-key set
|
||||
return wkeys
|
||||
case len(wranges) == 0 && len(wkeys) == 0:
|
||||
return nil
|
||||
case len(wranges) == 1 && len(wkeys) == 0:
|
||||
return wranges[0].Val.(watcherSet)
|
||||
}
|
||||
|
||||
// copy case
|
||||
ret := make(watcherSet)
|
||||
ret.union(wg.keyWatchers[key])
|
||||
for _, item := range wranges {
|
||||
ret.union(item.Val.(watcherSet))
|
||||
}
|
||||
return ret
|
||||
}
|
||||
344
vendor/github.com/coreos/etcd/mvcc/watcher_test.go
generated
vendored
Normal file
344
vendor/github.com/coreos/etcd/mvcc/watcher_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
// Copyright 2015 The etcd 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 mvcc
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
// TestWatcherWatchID tests that each watcher provides unique watchID,
|
||||
// and the watched event attaches the correct watchID.
|
||||
func TestWatcherWatchID(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
idm := make(map[WatchID]struct{})
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := w.Watch([]byte("foo"), nil, 0)
|
||||
if _, ok := idm[id]; ok {
|
||||
t.Errorf("#%d: id %d exists", i, id)
|
||||
}
|
||||
idm[id] = struct{}{}
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar"), lease.NoLease)
|
||||
|
||||
resp := <-w.Chan()
|
||||
if resp.WatchID != id {
|
||||
t.Errorf("#%d: watch id in event = %d, want %d", i, resp.WatchID, id)
|
||||
}
|
||||
|
||||
if err := w.Cancel(id); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
s.Put([]byte("foo2"), []byte("bar"), lease.NoLease)
|
||||
|
||||
// unsynced watchers
|
||||
for i := 10; i < 20; i++ {
|
||||
id := w.Watch([]byte("foo2"), nil, 1)
|
||||
if _, ok := idm[id]; ok {
|
||||
t.Errorf("#%d: id %d exists", i, id)
|
||||
}
|
||||
idm[id] = struct{}{}
|
||||
|
||||
resp := <-w.Chan()
|
||||
if resp.WatchID != id {
|
||||
t.Errorf("#%d: watch id in event = %d, want %d", i, resp.WatchID, id)
|
||||
}
|
||||
|
||||
if err := w.Cancel(id); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatcherWatchPrefix tests if Watch operation correctly watches
|
||||
// and returns events with matching prefixes.
|
||||
func TestWatcherWatchPrefix(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
idm := make(map[WatchID]struct{})
|
||||
|
||||
val := []byte("bar")
|
||||
keyWatch, keyEnd, keyPut := []byte("foo"), []byte("fop"), []byte("foobar")
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
id := w.Watch(keyWatch, keyEnd, 0)
|
||||
if _, ok := idm[id]; ok {
|
||||
t.Errorf("#%d: unexpected duplicated id %x", i, id)
|
||||
}
|
||||
idm[id] = struct{}{}
|
||||
|
||||
s.Put(keyPut, val, lease.NoLease)
|
||||
|
||||
resp := <-w.Chan()
|
||||
if resp.WatchID != id {
|
||||
t.Errorf("#%d: watch id in event = %d, want %d", i, resp.WatchID, id)
|
||||
}
|
||||
|
||||
if err := w.Cancel(id); err != nil {
|
||||
t.Errorf("#%d: unexpected cancel error %v", i, err)
|
||||
}
|
||||
|
||||
if len(resp.Events) != 1 {
|
||||
t.Errorf("#%d: len(resp.Events) got = %d, want = 1", i, len(resp.Events))
|
||||
}
|
||||
if len(resp.Events) == 1 {
|
||||
if !bytes.Equal(resp.Events[0].Kv.Key, keyPut) {
|
||||
t.Errorf("#%d: resp.Events got = %s, want = %s", i, resp.Events[0].Kv.Key, keyPut)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
keyWatch1, keyEnd1, keyPut1 := []byte("foo1"), []byte("foo2"), []byte("foo1bar")
|
||||
s.Put(keyPut1, val, lease.NoLease)
|
||||
|
||||
// unsynced watchers
|
||||
for i := 10; i < 15; i++ {
|
||||
id := w.Watch(keyWatch1, keyEnd1, 1)
|
||||
if _, ok := idm[id]; ok {
|
||||
t.Errorf("#%d: id %d exists", i, id)
|
||||
}
|
||||
idm[id] = struct{}{}
|
||||
|
||||
resp := <-w.Chan()
|
||||
if resp.WatchID != id {
|
||||
t.Errorf("#%d: watch id in event = %d, want %d", i, resp.WatchID, id)
|
||||
}
|
||||
|
||||
if err := w.Cancel(id); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
|
||||
if len(resp.Events) != 1 {
|
||||
t.Errorf("#%d: len(resp.Events) got = %d, want = 1", i, len(resp.Events))
|
||||
}
|
||||
if len(resp.Events) == 1 {
|
||||
if !bytes.Equal(resp.Events[0].Kv.Key, keyPut1) {
|
||||
t.Errorf("#%d: resp.Events got = %s, want = %s", i, resp.Events[0].Kv.Key, keyPut1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatcherWatchWrongRange ensures that watcher with wrong 'end' range
|
||||
// does not create watcher, which panics when canceling in range tree.
|
||||
func TestWatcherWatchWrongRange(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
if id := w.Watch([]byte("foa"), []byte("foa"), 1); id != -1 {
|
||||
t.Fatalf("key == end range given; id expected -1, got %d", id)
|
||||
}
|
||||
if id := w.Watch([]byte("fob"), []byte("foa"), 1); id != -1 {
|
||||
t.Fatalf("key > end range given; id expected -1, got %d", id)
|
||||
}
|
||||
// watch request with 'WithFromKey' has empty-byte range end
|
||||
if id := w.Watch([]byte("foo"), []byte{}, 1); id != 0 {
|
||||
t.Fatalf("\x00 is range given; id expected 0, got %d", id)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatchDeleteRange(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := newWatchableStore(b, &lease.FakeLessor{}, nil)
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
testKeyPrefix := []byte("foo")
|
||||
|
||||
for i := 0; i < 3; i++ {
|
||||
s.Put([]byte(fmt.Sprintf("%s_%d", testKeyPrefix, i)), []byte("bar"), lease.NoLease)
|
||||
}
|
||||
|
||||
w := s.NewWatchStream()
|
||||
from, to := []byte(testKeyPrefix), []byte(fmt.Sprintf("%s_%d", testKeyPrefix, 99))
|
||||
w.Watch(from, to, 0)
|
||||
|
||||
s.DeleteRange(from, to)
|
||||
|
||||
we := []mvccpb.Event{
|
||||
{Type: mvccpb.DELETE, Kv: &mvccpb.KeyValue{Key: []byte("foo_0"), ModRevision: 5}},
|
||||
{Type: mvccpb.DELETE, Kv: &mvccpb.KeyValue{Key: []byte("foo_1"), ModRevision: 5}},
|
||||
{Type: mvccpb.DELETE, Kv: &mvccpb.KeyValue{Key: []byte("foo_2"), ModRevision: 5}},
|
||||
}
|
||||
|
||||
select {
|
||||
case r := <-w.Chan():
|
||||
if !reflect.DeepEqual(r.Events, we) {
|
||||
t.Errorf("event = %v, want %v", r.Events, we)
|
||||
}
|
||||
case <-time.After(10 * time.Second):
|
||||
t.Fatal("failed to receive event after 10 seconds!")
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatchStreamCancelWatcherByID ensures cancel calls the cancel func of the watcher
|
||||
// with given id inside watchStream.
|
||||
func TestWatchStreamCancelWatcherByID(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
id := w.Watch([]byte("foo"), nil, 0)
|
||||
|
||||
tests := []struct {
|
||||
cancelID WatchID
|
||||
werr error
|
||||
}{
|
||||
// no error should be returned when cancel the created watcher.
|
||||
{id, nil},
|
||||
// not exist error should be returned when cancel again.
|
||||
{id, ErrWatcherNotExist},
|
||||
// not exist error should be returned when cancel a bad id.
|
||||
{id + 1, ErrWatcherNotExist},
|
||||
}
|
||||
|
||||
for i, tt := range tests {
|
||||
gerr := w.Cancel(tt.cancelID)
|
||||
|
||||
if gerr != tt.werr {
|
||||
t.Errorf("#%d: err = %v, want %v", i, gerr, tt.werr)
|
||||
}
|
||||
}
|
||||
|
||||
if l := len(w.(*watchStream).cancels); l != 0 {
|
||||
t.Errorf("cancels = %d, want 0", l)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWatcherRequestProgress ensures synced watcher can correctly
|
||||
// report its correct progress.
|
||||
func TestWatcherRequestProgress(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
|
||||
// manually create watchableStore instead of newWatchableStore
|
||||
// because newWatchableStore automatically calls syncWatchers
|
||||
// method to sync watchers in unsynced map. We want to keep watchers
|
||||
// in unsynced to test if syncWatchers works as expected.
|
||||
s := &watchableStore{
|
||||
store: NewStore(b, &lease.FakeLessor{}, nil),
|
||||
unsynced: newWatcherGroup(),
|
||||
synced: newWatcherGroup(),
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.store.Close()
|
||||
os.Remove(tmpPath)
|
||||
}()
|
||||
|
||||
testKey := []byte("foo")
|
||||
notTestKey := []byte("bad")
|
||||
testValue := []byte("bar")
|
||||
s.Put(testKey, testValue, lease.NoLease)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
|
||||
badID := WatchID(1000)
|
||||
w.RequestProgress(badID)
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
t.Fatalf("unexpected %+v", resp)
|
||||
default:
|
||||
}
|
||||
|
||||
id := w.Watch(notTestKey, nil, 1)
|
||||
w.RequestProgress(id)
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
t.Fatalf("unexpected %+v", resp)
|
||||
default:
|
||||
}
|
||||
|
||||
s.syncWatchers()
|
||||
|
||||
w.RequestProgress(id)
|
||||
wrs := WatchResponse{WatchID: 0, Revision: 2}
|
||||
select {
|
||||
case resp := <-w.Chan():
|
||||
if !reflect.DeepEqual(resp, wrs) {
|
||||
t.Fatalf("got %+v, expect %+v", resp, wrs)
|
||||
}
|
||||
case <-time.After(time.Second):
|
||||
t.Fatal("failed to receive progress")
|
||||
}
|
||||
}
|
||||
|
||||
func TestWatcherWatchWithFilter(t *testing.T) {
|
||||
b, tmpPath := backend.NewDefaultTmpBackend()
|
||||
s := WatchableKV(newWatchableStore(b, &lease.FakeLessor{}, nil))
|
||||
defer cleanup(s, b, tmpPath)
|
||||
|
||||
w := s.NewWatchStream()
|
||||
defer w.Close()
|
||||
|
||||
filterPut := func(e mvccpb.Event) bool {
|
||||
return e.Type == mvccpb.PUT
|
||||
}
|
||||
|
||||
w.Watch([]byte("foo"), nil, 0, filterPut)
|
||||
done := make(chan struct{})
|
||||
|
||||
go func() {
|
||||
<-w.Chan()
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
s.Put([]byte("foo"), []byte("bar"), 0)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
t.Fatal("failed to filter put request")
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
}
|
||||
|
||||
s.DeleteRange([]byte("foo"), nil)
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(100 * time.Millisecond):
|
||||
t.Fatal("failed to receive delete request")
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue