Commit 5f008faa authored by Timothy St. Clair's avatar Timothy St. Clair

Update godeps for etcd 3.0.4

parent 456c43c2
Bolt [![Build Status](https://drone.io/github.com/boltdb/bolt/status.png)](https://drone.io/github.com/boltdb/bolt/latest) [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg) Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg)
==== ====
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
...@@ -427,6 +427,8 @@ db.View(func(tx *bolt.Tx) error { ...@@ -427,6 +427,8 @@ db.View(func(tx *bolt.Tx) error {
}) })
``` ```
Note that, while RFC3339 is sortable, the Golang implementation of RFC3339Nano does not use a fixed number of digits after the decimal point and is therefore not sortable.
#### ForEach() #### ForEach()
...@@ -437,7 +439,7 @@ all the keys in a bucket: ...@@ -437,7 +439,7 @@ all the keys in a bucket:
db.View(func(tx *bolt.Tx) error { db.View(func(tx *bolt.Tx) error {
// Assume bucket exists and has keys // Assume bucket exists and has keys
b := tx.Bucket([]byte("MyBucket")) b := tx.Bucket([]byte("MyBucket"))
b.ForEach(func(k, v []byte) error { b.ForEach(func(k, v []byte) error {
fmt.Printf("key=%s, value=%s\n", k, v) fmt.Printf("key=%s, value=%s\n", k, v)
return nil return nil
...@@ -617,7 +619,7 @@ Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path) ...@@ -617,7 +619,7 @@ Boltmobiledemo.BoltDB boltDB = Boltmobiledemo.NewBoltDB(path)
{ {
NSURL* URL= [NSURL fileURLWithPath: filePathString]; NSURL* URL= [NSURL fileURLWithPath: filePathString];
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]); assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil; NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES] BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error]; forKey: NSURLIsExcludedFromBackupKey error: &error];
...@@ -835,6 +837,14 @@ Below is a list of public, open source projects that use Bolt: ...@@ -835,6 +837,14 @@ Below is a list of public, open source projects that use Bolt:
backed by boltdb. backed by boltdb.
* [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining * [buckets](https://github.com/joyrexus/buckets) - a bolt wrapper streamlining
simple tx and key scans. simple tx and key scans.
* [mbuckets](https://github.com/abhigupta912/mbuckets) - A Bolt wrapper that allows easy operations on multi level (nested) buckets.
* [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service * [Request Baskets](https://github.com/darklynx/request-baskets) - A web service to collect arbitrary HTTP requests and inspect them via REST API or simple web UI, similar to [RequestBin](http://requestb.in/) service
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB.
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings.
* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
If you are using Bolt in a project please send a pull request to add it to the list. If you are using Bolt in a project please send a pull request to add it to the list.
version: "{build}"
os: Windows Server 2012 R2
clone_folder: c:\gopath\src\github.com\boltdb\bolt
environment:
GOPATH: c:\gopath
install:
- echo %PATH%
- echo %GOPATH%
- go version
- go env
- go get -v -t ./...
build_script:
- go test -v ./...
// +build ppc
package bolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0x7FFFFFFF // 2GB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0xFFFFFFF
// +build ppc64
package bolt
// maxMapSize represents the largest mmap size supported by Bolt.
const maxMapSize = 0xFFFFFFFFFFFF // 256TB
// maxAllocSize is the size used when creating array pointers.
const maxAllocSize = 0x7FFFFFFF
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, exclusive bool, timeout time.Duration) error { func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time var t time.Time
for { for {
// If we're beyond our timeout then return an error. // If we're beyond our timeout then return an error.
...@@ -27,7 +27,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -27,7 +27,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} }
// Otherwise attempt to obtain an exclusive lock. // Otherwise attempt to obtain an exclusive lock.
err := syscall.Flock(int(f.Fd()), flag|syscall.LOCK_NB) err := syscall.Flock(int(db.file.Fd()), flag|syscall.LOCK_NB)
if err == nil { if err == nil {
return nil return nil
} else if err != syscall.EWOULDBLOCK { } else if err != syscall.EWOULDBLOCK {
...@@ -40,8 +40,8 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -40,8 +40,8 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} }
// funlock releases an advisory lock on a file descriptor. // funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error { func funlock(db *DB) error {
return syscall.Flock(int(f.Fd()), syscall.LOCK_UN) return syscall.Flock(int(db.file.Fd()), syscall.LOCK_UN)
} }
// mmap memory maps a DB's data file. // mmap memory maps a DB's data file.
......
...@@ -11,7 +11,7 @@ import ( ...@@ -11,7 +11,7 @@ import (
) )
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, exclusive bool, timeout time.Duration) error { func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
var t time.Time var t time.Time
for { for {
// If we're beyond our timeout then return an error. // If we're beyond our timeout then return an error.
...@@ -32,7 +32,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -32,7 +32,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} else { } else {
lock.Type = syscall.F_RDLCK lock.Type = syscall.F_RDLCK
} }
err := syscall.FcntlFlock(f.Fd(), syscall.F_SETLK, &lock) err := syscall.FcntlFlock(db.file.Fd(), syscall.F_SETLK, &lock)
if err == nil { if err == nil {
return nil return nil
} else if err != syscall.EAGAIN { } else if err != syscall.EAGAIN {
...@@ -45,13 +45,13 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -45,13 +45,13 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} }
// funlock releases an advisory lock on a file descriptor. // funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error { func funlock(db *DB) error {
var lock syscall.Flock_t var lock syscall.Flock_t
lock.Start = 0 lock.Start = 0
lock.Len = 0 lock.Len = 0
lock.Type = syscall.F_UNLCK lock.Type = syscall.F_UNLCK
lock.Whence = 0 lock.Whence = 0
return syscall.FcntlFlock(uintptr(f.Fd()), syscall.F_SETLK, &lock) return syscall.FcntlFlock(uintptr(db.file.Fd()), syscall.F_SETLK, &lock)
} }
// mmap memory maps a DB's data file. // mmap memory maps a DB's data file.
......
...@@ -16,6 +16,8 @@ var ( ...@@ -16,6 +16,8 @@ var (
) )
const ( const (
lockExt = ".lock"
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx // see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
flagLockExclusive = 2 flagLockExclusive = 2
flagLockFailImmediately = 1 flagLockFailImmediately = 1
...@@ -46,7 +48,16 @@ func fdatasync(db *DB) error { ...@@ -46,7 +48,16 @@ func fdatasync(db *DB) error {
} }
// flock acquires an advisory lock on a file descriptor. // flock acquires an advisory lock on a file descriptor.
func flock(f *os.File, exclusive bool, timeout time.Duration) error { func flock(db *DB, mode os.FileMode, exclusive bool, timeout time.Duration) error {
// Create a separate lock file on windows because a process
// cannot share an exclusive lock on the same file. This is
// needed during Tx.WriteTo().
f, err := os.OpenFile(db.path+lockExt, os.O_CREATE, mode)
if err != nil {
return err
}
db.lockfile = f
var t time.Time var t time.Time
for { for {
// If we're beyond our timeout then return an error. // If we're beyond our timeout then return an error.
...@@ -62,7 +73,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -62,7 +73,7 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
flag |= flagLockExclusive flag |= flagLockExclusive
} }
err := lockFileEx(syscall.Handle(f.Fd()), flag, 0, 1, 0, &syscall.Overlapped{}) err := lockFileEx(syscall.Handle(db.lockfile.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
if err == nil { if err == nil {
return nil return nil
} else if err != errLockViolation { } else if err != errLockViolation {
...@@ -75,8 +86,11 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error { ...@@ -75,8 +86,11 @@ func flock(f *os.File, exclusive bool, timeout time.Duration) error {
} }
// funlock releases an advisory lock on a file descriptor. // funlock releases an advisory lock on a file descriptor.
func funlock(f *os.File) error { func funlock(db *DB) error {
return unlockFileEx(syscall.Handle(f.Fd()), 0, 1, 0, &syscall.Overlapped{}) err := unlockFileEx(syscall.Handle(db.lockfile.Fd()), 0, 1, 0, &syscall.Overlapped{})
db.lockfile.Close()
os.Remove(db.path+lockExt)
return err
} }
// mmap memory maps a DB's data file. // mmap memory maps a DB's data file.
......
...@@ -36,6 +36,9 @@ const ( ...@@ -36,6 +36,9 @@ const (
DefaultAllocSize = 16 * 1024 * 1024 DefaultAllocSize = 16 * 1024 * 1024
) )
// default page size for db is set to the OS page size.
var defaultPageSize = os.Getpagesize()
// DB represents a collection of buckets persisted to a file on disk. // DB represents a collection of buckets persisted to a file on disk.
// All data access is performed through transactions which can be obtained through the DB. // All data access is performed through transactions which can be obtained through the DB.
// All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called. // All the functions on DB will return a ErrDatabaseNotOpen if accessed before Open() is called.
...@@ -93,7 +96,8 @@ type DB struct { ...@@ -93,7 +96,8 @@ type DB struct {
path string path string
file *os.File file *os.File
dataref []byte // mmap'ed readonly, write throws SEGV lockfile *os.File // windows only
dataref []byte // mmap'ed readonly, write throws SEGV
data *[maxMapSize]byte data *[maxMapSize]byte
datasz int datasz int
filesz int // current on disk file size filesz int // current on disk file size
...@@ -106,6 +110,8 @@ type DB struct { ...@@ -106,6 +110,8 @@ type DB struct {
freelist *freelist freelist *freelist
stats Stats stats Stats
pagePool sync.Pool
batchMu sync.Mutex batchMu sync.Mutex
batch *batch batch *batch
...@@ -177,7 +183,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { ...@@ -177,7 +183,7 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
// if !options.ReadOnly. // if !options.ReadOnly.
// The database file is locked using the shared lock (more than one process may // The database file is locked using the shared lock (more than one process may
// hold a lock at the same time) otherwise (options.ReadOnly is set). // hold a lock at the same time) otherwise (options.ReadOnly is set).
if err := flock(db.file, !db.readOnly, options.Timeout); err != nil { if err := flock(db, mode, !db.readOnly, options.Timeout); err != nil {
_ = db.close() _ = db.close()
return nil, err return nil, err
} }
...@@ -199,12 +205,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) { ...@@ -199,12 +205,27 @@ func Open(path string, mode os.FileMode, options *Options) (*DB, error) {
if _, err := db.file.ReadAt(buf[:], 0); err == nil { if _, err := db.file.ReadAt(buf[:], 0); err == nil {
m := db.pageInBuffer(buf[:], 0).meta() m := db.pageInBuffer(buf[:], 0).meta()
if err := m.validate(); err != nil { if err := m.validate(); err != nil {
return nil, err // If we can't read the page size, we can assume it's the same
// as the OS -- since that's how the page size was chosen in the
// first place.
//
// If the first page is invalid and this OS uses a different
// page size than what the database was created with then we
// are out of luck and cannot access the database.
db.pageSize = os.Getpagesize()
} else {
db.pageSize = int(m.pageSize)
} }
db.pageSize = int(m.pageSize)
} }
} }
// Initialize page pool.
db.pagePool = sync.Pool{
New: func() interface{} {
return make([]byte, db.pageSize)
},
}
// Memory map the data file. // Memory map the data file.
if err := db.mmap(options.InitialMmapSize); err != nil { if err := db.mmap(options.InitialMmapSize); err != nil {
_ = db.close() _ = db.close()
...@@ -261,12 +282,13 @@ func (db *DB) mmap(minsz int) error { ...@@ -261,12 +282,13 @@ func (db *DB) mmap(minsz int) error {
db.meta0 = db.page(0).meta() db.meta0 = db.page(0).meta()
db.meta1 = db.page(1).meta() db.meta1 = db.page(1).meta()
// Validate the meta pages. // Validate the meta pages. We only return an error if both meta pages fail
if err := db.meta0.validate(); err != nil { // validation, since meta0 failing validation means that it wasn't saved
return err // properly -- but we can recover using meta1. And vice-versa.
} err0 := db.meta0.validate()
if err := db.meta1.validate(); err != nil { err1 := db.meta1.validate()
return err if err0 != nil && err1 != nil {
return err0
} }
return nil return nil
...@@ -338,6 +360,7 @@ func (db *DB) init() error { ...@@ -338,6 +360,7 @@ func (db *DB) init() error {
m.root = bucket{root: 3} m.root = bucket{root: 3}
m.pgid = 4 m.pgid = 4
m.txid = txid(i) m.txid = txid(i)
m.checksum = m.sum64()
} }
// Write an empty freelist at page 3. // Write an empty freelist at page 3.
...@@ -379,10 +402,13 @@ func (db *DB) Close() error { ...@@ -379,10 +402,13 @@ func (db *DB) Close() error {
} }
func (db *DB) close() error { func (db *DB) close() error {
if !db.opened {
return nil
}
db.opened = false db.opened = false
db.freelist = nil db.freelist = nil
db.path = ""
// Clear ops. // Clear ops.
db.ops.writeAt = nil db.ops.writeAt = nil
...@@ -397,7 +423,7 @@ func (db *DB) close() error { ...@@ -397,7 +423,7 @@ func (db *DB) close() error {
// No need to unlock read-only file. // No need to unlock read-only file.
if !db.readOnly { if !db.readOnly {
// Unlock the file. // Unlock the file.
if err := funlock(db.file); err != nil { if err := funlock(db); err != nil {
log.Printf("bolt.Close(): funlock error: %s", err) log.Printf("bolt.Close(): funlock error: %s", err)
} }
} }
...@@ -409,6 +435,7 @@ func (db *DB) close() error { ...@@ -409,6 +435,7 @@ func (db *DB) close() error {
db.file = nil db.file = nil
} }
db.path = ""
return nil return nil
} }
...@@ -773,16 +800,37 @@ func (db *DB) pageInBuffer(b []byte, id pgid) *page { ...@@ -773,16 +800,37 @@ func (db *DB) pageInBuffer(b []byte, id pgid) *page {
// meta retrieves the current meta page reference. // meta retrieves the current meta page reference.
func (db *DB) meta() *meta { func (db *DB) meta() *meta {
if db.meta0.txid > db.meta1.txid { // We have to return the meta with the highest txid which doesn't fail
return db.meta0 // validation. Otherwise, we can cause errors when in fact the database is
// in a consistent state. metaA is the one with the higher txid.
metaA := db.meta0
metaB := db.meta1
if db.meta1.txid > db.meta0.txid {
metaA = db.meta1
metaB = db.meta0
} }
return db.meta1
// Use higher meta page if valid. Otherwise fallback to previous, if valid.
if err := metaA.validate(); err == nil {
return metaA
} else if err := metaB.validate(); err == nil {
return metaB
}
// This should never be reached, because both meta1 and meta0 were validated
// on mmap() and we do fsync() on every write.
panic("bolt.DB.meta(): invalid meta pages")
} }
// allocate returns a contiguous block of memory starting at a given page. // allocate returns a contiguous block of memory starting at a given page.
func (db *DB) allocate(count int) (*page, error) { func (db *DB) allocate(count int) (*page, error) {
// Allocate a temporary buffer for the page. // Allocate a temporary buffer for the page.
buf := make([]byte, count*db.pageSize) var buf []byte
if count == 1 {
buf = db.pagePool.Get().([]byte)
} else {
buf = make([]byte, count*db.pageSize)
}
p := (*page)(unsafe.Pointer(&buf[0])) p := (*page)(unsafe.Pointer(&buf[0]))
p.overflow = uint32(count - 1) p.overflow = uint32(count - 1)
...@@ -824,8 +872,10 @@ func (db *DB) grow(sz int) error { ...@@ -824,8 +872,10 @@ func (db *DB) grow(sz int) error {
// Truncate and fsync to ensure file size metadata is flushed. // Truncate and fsync to ensure file size metadata is flushed.
// https://github.com/boltdb/bolt/issues/284 // https://github.com/boltdb/bolt/issues/284
if !db.NoGrowSync && !db.readOnly { if !db.NoGrowSync && !db.readOnly {
if err := db.file.Truncate(int64(sz)); err != nil { if runtime.GOOS != "windows" {
return fmt.Errorf("file resize error: %s", err) if err := db.file.Truncate(int64(sz)); err != nil {
return fmt.Errorf("file resize error: %s", err)
}
} }
if err := db.file.Sync(); err != nil { if err := db.file.Sync(); err != nil {
return fmt.Errorf("file sync error: %s", err) return fmt.Errorf("file sync error: %s", err)
...@@ -930,12 +980,12 @@ type meta struct { ...@@ -930,12 +980,12 @@ type meta struct {
// validate checks the marker bytes and version of the meta page to ensure it matches this binary. // validate checks the marker bytes and version of the meta page to ensure it matches this binary.
func (m *meta) validate() error { func (m *meta) validate() error {
if m.checksum != 0 && m.checksum != m.sum64() { if m.magic != magic {
return ErrChecksum
} else if m.magic != magic {
return ErrInvalid return ErrInvalid
} else if m.version != version { } else if m.version != version {
return ErrVersionMismatch return ErrVersionMismatch
} else if m.checksum != 0 && m.checksum != m.sum64() {
return ErrChecksum
} }
return nil return nil
} }
......
...@@ -12,7 +12,8 @@ var ( ...@@ -12,7 +12,8 @@ var (
// already open. // already open.
ErrDatabaseOpen = errors.New("database already open") ErrDatabaseOpen = errors.New("database already open")
// ErrInvalid is returned when a data file is not a Bolt-formatted database. // ErrInvalid is returned when both meta pages on a database are invalid.
// This typically occurs when a file is not a bolt database.
ErrInvalid = errors.New("invalid database") ErrInvalid = errors.New("invalid database")
// ErrVersionMismatch is returned when the data file was created with a // ErrVersionMismatch is returned when the data file was created with a
......
...@@ -463,43 +463,6 @@ func (n *node) rebalance() { ...@@ -463,43 +463,6 @@ func (n *node) rebalance() {
target = n.prevSibling() target = n.prevSibling()
} }
// If target node has extra nodes then just move one over.
if target.numChildren() > target.minKeys() {
if useNextSibling {
// Reparent and move node.
if child, ok := n.bucket.nodes[target.inodes[0].pgid]; ok {
child.parent.removeChild(child)
child.parent = n
child.parent.children = append(child.parent.children, child)
}
n.inodes = append(n.inodes, target.inodes[0])
target.inodes = target.inodes[1:]
// Update target key on parent.
target.parent.put(target.key, target.inodes[0].key, nil, target.pgid, 0)
target.key = target.inodes[0].key
_assert(len(target.key) > 0, "rebalance(1): zero-length node key")
} else {
// Reparent and move node.
if child, ok := n.bucket.nodes[target.inodes[len(target.inodes)-1].pgid]; ok {
child.parent.removeChild(child)
child.parent = n
child.parent.children = append(child.parent.children, child)
}
n.inodes = append(n.inodes, inode{})
copy(n.inodes[1:], n.inodes)
n.inodes[0] = target.inodes[len(target.inodes)-1]
target.inodes = target.inodes[:len(target.inodes)-1]
}
// Update parent key for node.
n.parent.put(n.key, n.inodes[0].key, nil, n.pgid, 0)
n.key = n.inodes[0].key
_assert(len(n.key) > 0, "rebalance(2): zero-length node key")
return
}
// If both this node and the target node are too small then merge them. // If both this node and the target node are too small then merge them.
if useNextSibling { if useNextSibling {
// Reparent all child nodes being moved. // Reparent all child nodes being moved.
......
...@@ -111,13 +111,13 @@ type leafPageElement struct { ...@@ -111,13 +111,13 @@ type leafPageElement struct {
// key returns a byte slice of the node key. // key returns a byte slice of the node key.
func (n *leafPageElement) key() []byte { func (n *leafPageElement) key() []byte {
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize] return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos]))[:n.ksize:n.ksize]
} }
// value returns a byte slice of the node value. // value returns a byte slice of the node value.
func (n *leafPageElement) value() []byte { func (n *leafPageElement) value() []byte {
buf := (*[maxAllocSize]byte)(unsafe.Pointer(n)) buf := (*[maxAllocSize]byte)(unsafe.Pointer(n))
return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize] return (*[maxAllocSize]byte)(unsafe.Pointer(&buf[n.pos+n.ksize]))[:n.vsize:n.vsize]
} }
// PageInfo represents human readable information about a page. // PageInfo represents human readable information about a page.
......
...@@ -5,6 +5,7 @@ import ( ...@@ -5,6 +5,7 @@ import (
"io" "io"
"os" "os"
"sort" "sort"
"strings"
"time" "time"
"unsafe" "unsafe"
) )
...@@ -202,8 +203,17 @@ func (tx *Tx) Commit() error { ...@@ -202,8 +203,17 @@ func (tx *Tx) Commit() error {
// If strict mode is enabled then perform a consistency check. // If strict mode is enabled then perform a consistency check.
// Only the first consistency error is reported in the panic. // Only the first consistency error is reported in the panic.
if tx.db.StrictMode { if tx.db.StrictMode {
if err, ok := <-tx.Check(); ok { ch := tx.Check()
panic("check fail: " + err.Error()) var errs []string
for {
err, ok := <-ch
if !ok {
break
}
errs = append(errs, err.Error())
}
if len(errs) > 0 {
panic("check fail: " + strings.Join(errs, "\n"))
} }
} }
...@@ -297,12 +307,34 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) { ...@@ -297,12 +307,34 @@ func (tx *Tx) WriteTo(w io.Writer) (n int64, err error) {
} }
defer func() { _ = f.Close() }() defer func() { _ = f.Close() }()
// Copy the meta pages. // Generate a meta page. We use the same page data for both meta pages.
tx.db.metalock.Lock() buf := make([]byte, tx.db.pageSize)
n, err = io.CopyN(w, f, int64(tx.db.pageSize*2)) page := (*page)(unsafe.Pointer(&buf[0]))
tx.db.metalock.Unlock() page.flags = metaPageFlag
*page.meta() = *tx.meta
// Write meta 0.
page.id = 0
page.meta().checksum = page.meta().sum64()
nn, err := w.Write(buf)
n += int64(nn)
if err != nil { if err != nil {
return n, fmt.Errorf("meta copy: %s", err) return n, fmt.Errorf("meta 0 copy: %s", err)
}
// Write meta 1 with a lower transaction id.
page.id = 1
page.meta().txid -= 1
page.meta().checksum = page.meta().sum64()
nn, err = w.Write(buf)
n += int64(nn)
if err != nil {
return n, fmt.Errorf("meta 1 copy: %s", err)
}
// Move past the meta pages in the file.
if _, err := f.Seek(int64(tx.db.pageSize*2), os.SEEK_SET); err != nil {
return n, fmt.Errorf("seek: %s", err)
} }
// Copy data pages. // Copy data pages.
...@@ -441,6 +473,8 @@ func (tx *Tx) write() error { ...@@ -441,6 +473,8 @@ func (tx *Tx) write() error {
for _, p := range tx.pages { for _, p := range tx.pages {
pages = append(pages, p) pages = append(pages, p)
} }
// Clear out page cache early.
tx.pages = make(map[pgid]*page)
sort.Sort(pages) sort.Sort(pages)
// Write pages to disk in order. // Write pages to disk in order.
...@@ -485,8 +519,22 @@ func (tx *Tx) write() error { ...@@ -485,8 +519,22 @@ func (tx *Tx) write() error {
} }
} }
// Clear out page cache. // Put small pages back to page pool.
tx.pages = make(map[pgid]*page) for _, p := range pages {
// Ignore page sizes over 1 page.
// These are allocated using make() instead of the page pool.
if int(p.overflow) != 0 {
continue
}
buf := (*[maxAllocSize]byte)(unsafe.Pointer(p))[:tx.db.pageSize]
// See https://go.googlesource.com/go/+/f03c9202c43e0abb130669852082117ca50aa9b1
for i := range buf {
buf[i] = 0
}
tx.db.pagePool.Put(buf)
}
return nil return nil
} }
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -19,8 +19,8 @@ import ( ...@@ -19,8 +19,8 @@ import (
"sync" "sync"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/storage/backend"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
) )
......
...@@ -13,10 +13,25 @@ option (gogoproto.goproto_enum_prefix_all) = false; ...@@ -13,10 +13,25 @@ option (gogoproto.goproto_enum_prefix_all) = false;
message User { message User {
bytes name = 1; bytes name = 1;
bytes password = 2; bytes password = 2;
int64 tombstone = 3; repeated string roles = 3;
}
// Permission is a single entity
message Permission {
enum Type {
READ = 0;
WRITE = 1;
READWRITE = 2;
}
Type permType = 1;
bytes key = 2;
bytes range_end = 3;
} }
// Role is a single entry in the bucket authRoles // Role is a single entry in the bucket authRoles
message Role { message Role {
bytes name = 2; bytes name = 1;
repeated Permission keyPermission = 2;
} }
// 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 auth provides client role authentication for accessing keys in etcd.
package auth
// 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 auth
import (
"bytes"
"sort"
"github.com/coreos/etcd/auth/authpb"
"github.com/coreos/etcd/mvcc/backend"
)
// isSubset returns true if a is a subset of b
func isSubset(a, b *rangePerm) bool {
switch {
case len(a.end) == 0 && len(b.end) == 0:
// a, b are both keys
return bytes.Equal(a.begin, b.begin)
case len(b.end) == 0:
// b is a key, a is a range
return false
case len(a.end) == 0:
return 0 <= bytes.Compare(a.begin, b.begin) && bytes.Compare(a.begin, b.end) <= 0
default:
return 0 <= bytes.Compare(a.begin, b.begin) && bytes.Compare(a.end, b.end) <= 0
}
}
func isRangeEqual(a, b *rangePerm) bool {
return bytes.Equal(a.begin, b.begin) && bytes.Equal(a.end, b.end)
}
// removeSubsetRangePerms removes any rangePerms that are subsets of other rangePerms.
// If there are equal ranges, removeSubsetRangePerms only keeps one of them.
func removeSubsetRangePerms(perms []*rangePerm) []*rangePerm {
// TODO(mitake): currently it is O(n^2), we need a better algorithm
newp := make([]*rangePerm, 0)
for i := range perms {
skip := false
for j := range perms {
if i == j {
continue
}
if isRangeEqual(perms[i], perms[j]) {
// if ranges are equal, we only keep the first range.
if i > j {
skip = true
break
}
} else if isSubset(perms[i], perms[j]) {
// if a range is a strict subset of the other one, we skip the subset.
skip = true
break
}
}
if skip {
continue
}
newp = append(newp, perms[i])
}
return newp
}
// mergeRangePerms merges adjacent rangePerms.
func mergeRangePerms(perms []*rangePerm) []*rangePerm {
merged := make([]*rangePerm, 0)
perms = removeSubsetRangePerms(perms)
sort.Sort(RangePermSliceByBegin(perms))
i := 0
for i < len(perms) {
begin, next := i, i
for next+1 < len(perms) && bytes.Compare(perms[next].end, perms[next+1].begin) != -1 {
next++
}
merged = append(merged, &rangePerm{begin: perms[begin].begin, end: perms[next].end})
i = next + 1
}
return merged
}
func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermissions {
user := getUser(tx, userName)
if user == nil {
plog.Errorf("invalid user name %s", userName)
return nil
}
var readPerms, writePerms []*rangePerm
for _, roleName := range user.Roles {
role := getRole(tx, roleName)
if role == nil {
continue
}
for _, perm := range role.KeyPermission {
rp := &rangePerm{begin: perm.Key, end: perm.RangeEnd}
switch perm.PermType {
case authpb.READWRITE:
readPerms = append(readPerms, rp)
writePerms = append(writePerms, rp)
case authpb.READ:
readPerms = append(readPerms, rp)
case authpb.WRITE:
writePerms = append(writePerms, rp)
}
}
}
return &unifiedRangePermissions{
readPerms: mergeRangePerms(readPerms),
writePerms: mergeRangePerms(writePerms),
}
}
func checkKeyPerm(cachedPerms *unifiedRangePermissions, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
var tocheck []*rangePerm
switch permtyp {
case authpb.READ:
tocheck = cachedPerms.readPerms
case authpb.WRITE:
tocheck = cachedPerms.writePerms
default:
plog.Panicf("unknown auth type: %v", permtyp)
}
requiredPerm := &rangePerm{begin: key, end: rangeEnd}
for _, perm := range tocheck {
if isSubset(requiredPerm, perm) {
return true
}
}
return false
}
func (as *authStore) isRangeOpPermitted(tx backend.BatchTx, userName string, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
// assumption: tx is Lock()ed
_, ok := as.rangePermCache[userName]
if !ok {
perms := getMergedPerms(tx, userName)
if perms == nil {
plog.Errorf("failed to create a unified permission of user %s", userName)
return false
}
as.rangePermCache[userName] = perms
}
return checkKeyPerm(as.rangePermCache[userName], key, rangeEnd, permtyp)
}
func (as *authStore) clearCachedPerm() {
as.rangePermCache = make(map[string]*unifiedRangePermissions)
}
func (as *authStore) invalidateCachedPerm(userName string) {
delete(as.rangePermCache, userName)
}
type unifiedRangePermissions struct {
// readPerms[i] and readPerms[j] (i != j) don't overlap
readPerms []*rangePerm
// writePerms[i] and writePerms[j] (i != j) don't overlap, too
writePerms []*rangePerm
}
type rangePerm struct {
begin, end []byte
}
type RangePermSliceByBegin []*rangePerm
func (slice RangePermSliceByBegin) Len() int {
return len(slice)
}
func (slice RangePermSliceByBegin) Less(i, j int) bool {
switch bytes.Compare(slice[i].begin, slice[j].begin) {
case 0: // begin(i) == begin(j)
return bytes.Compare(slice[i].end, slice[j].end) == -1
case -1: // begin(i) < begin(j)
return true
default:
return false
}
}
func (slice RangePermSliceByBegin) Swap(i, j int) {
slice[i], slice[j] = slice[j], slice[i]
}
// 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 auth
// CAUTION: This randum number based token mechanism is only for testing purpose.
// JWT based mechanism will be added in the near future.
import (
"crypto/rand"
"math/big"
)
const (
letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
defaultSimpleTokenLength = 16
)
func (as *authStore) GenSimpleToken() (string, error) {
ret := make([]byte, defaultSimpleTokenLength)
for i := 0; i < defaultSimpleTokenLength; i++ {
bInt, err := rand.Int(rand.Reader, big.NewInt(int64(len(letters))))
if err != nil {
return "", err
}
ret[i] = letters[bInt.Int64()]
}
return string(ret), nil
}
func (as *authStore) assignSimpleTokenToUser(username, token string) {
as.simpleTokensMu.Lock()
_, ok := as.simpleTokens[token]
if ok {
plog.Panicf("token %s is alredy used", token)
}
as.simpleTokens[token] = username
as.simpleTokensMu.Unlock()
}
...@@ -4,9 +4,11 @@ etcd/client is the Go client library for etcd. ...@@ -4,9 +4,11 @@ etcd/client is the Go client library for etcd.
[![GoDoc](https://godoc.org/github.com/coreos/etcd/client?status.png)](https://godoc.org/github.com/coreos/etcd/client) [![GoDoc](https://godoc.org/github.com/coreos/etcd/client?status.png)](https://godoc.org/github.com/coreos/etcd/client)
etcd uses go's `vendor` directory to manage external dependencies. If `client` is imported etcd uses `cmd/vendor` directory to store external dependencies, which are
outside of etcd, simply copy `client` to the `vendor` directory or use tools like godep to to be compiled into etcd release binaries. `client` can be imported without
manage your own dependency, as in [vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories). vendoring. For full compatibility, it is recommended to vendor builds using
etcd's vendored packages, using tools like godep, as in
[vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories).
For more detail, please read [Go vendor design](https://golang.org/s/go15vendor). For more detail, please read [Go vendor design](https://golang.org/s/go15vendor).
## Install ## Install
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -36,6 +36,12 @@ type User struct { ...@@ -36,6 +36,12 @@ type User struct {
Revoke []string `json:"revoke,omitempty"` Revoke []string `json:"revoke,omitempty"`
} }
// userListEntry is the user representation given by the server for ListUsers
type userListEntry struct {
User string `json:"user"`
Roles []Role `json:"roles"`
}
type UserRoles struct { type UserRoles struct {
User string `json:"user"` User string `json:"user"`
Roles []Role `json:"roles"` Roles []Role `json:"roles"`
...@@ -194,7 +200,7 @@ func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) { ...@@ -194,7 +200,7 @@ func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
} }
var userList struct { var userList struct {
Users []User `json:"users"` Users []userListEntry `json:"users"`
} }
if err = json.Unmarshal(body, &userList); err != nil { if err = json.Unmarshal(body, &userList); err != nil {
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -37,6 +37,10 @@ var ( ...@@ -37,6 +37,10 @@ var (
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured") ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available") ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
errTooManyRedirectChecks = errors.New("client: too many redirect checks") errTooManyRedirectChecks = errors.New("client: too many redirect checks")
// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so
// that Do() will not retry a request
oneShotCtxValue interface{}
) )
var DefaultRequestTimeout = 5 * time.Second var DefaultRequestTimeout = 5 * time.Second
...@@ -335,6 +339,7 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo ...@@ -335,6 +339,7 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
var body []byte var body []byte
var err error var err error
cerr := &ClusterError{} cerr := &ClusterError{}
isOneShot := ctx.Value(&oneShotCtxValue) != nil
for i := pinned; i < leps+pinned; i++ { for i := pinned; i < leps+pinned; i++ {
k := i % leps k := i % leps
...@@ -348,6 +353,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo ...@@ -348,6 +353,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
if err == context.Canceled || err == context.DeadlineExceeded { if err == context.Canceled || err == context.DeadlineExceeded {
return nil, nil, err return nil, nil, err
} }
if isOneShot {
return nil, nil, err
}
continue continue
} }
if resp.StatusCode/100 == 5 { if resp.StatusCode/100 == 5 {
...@@ -358,6 +366,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo ...@@ -358,6 +366,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
default: default:
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode))) cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
} }
if isOneShot {
return nil, nil, cerr.Errors[0]
}
continue continue
} }
if k != pinned { if k != pinned {
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -337,7 +337,11 @@ func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions ...@@ -337,7 +337,11 @@ func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions
act.Dir = opts.Dir act.Dir = opts.Dir
} }
resp, body, err := k.client.Do(ctx, act) doCtx := ctx
if act.PrevExist == PrevNoExist {
doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
}
resp, body, err := k.client.Do(doCtx, act)
if err != nil { if err != nil {
return nil, err return nil, err
} }
...@@ -385,7 +389,8 @@ func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOption ...@@ -385,7 +389,8 @@ func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOption
act.Recursive = opts.Recursive act.Recursive = opts.Recursive
} }
resp, body, err := k.client.Do(ctx, act) doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
resp, body, err := k.client.Do(doCtx, act)
if err != nil { if err != nil {
return nil, err return nil, err
} }
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
...@@ -40,9 +40,11 @@ if err != nil { ...@@ -40,9 +40,11 @@ if err != nil {
// use the response // use the response
``` ```
etcd uses go's `vendor` directory to manage external dependencies. If `clientv3` is imported etcd uses `cmd/vendor` directory to store external dependencies, which are
outside of etcd, simply copy `clientv3` to the `vendor` directory or use tools like godep to to be compiled into etcd release binaries. `client` can be imported without
manage your own dependency, as in [vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories). vendoring. For full compatibility, it is recommended to vendor builds using
etcd's vendored packages, using tools like godep, as in
[vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories).
For more detail, please read [Go vendor design](https://golang.org/s/go15vendor). For more detail, please read [Go vendor design](https://golang.org/s/go15vendor).
## Error Handling ## Error Handling
...@@ -50,21 +52,22 @@ For more detail, please read [Go vendor design](https://golang.org/s/go15vendor) ...@@ -50,21 +52,22 @@ For more detail, please read [Go vendor design](https://golang.org/s/go15vendor)
etcd client returns 2 types of errors: etcd client returns 2 types of errors:
1. context error: canceled or deadline exceeded. 1. context error: canceled or deadline exceeded.
2. gRPC error: see [v3rpc/error](https://github.com/coreos/etcd/blob/master/etcdserver/api/v3rpc/error.go). 2. gRPC error: see [api/v3rpc/rpctypes](https://godoc.org/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes).
Here is the example code to handle client errors: Here is the example code to handle client errors:
```go ```go
resp, err := kvc.Put(ctx, "", "") resp, err := kvc.Put(ctx, "", "")
if err != nil { if err != nil {
if err == context.Canceled { switch err {
// ctx is canceled by another routine case context.Canceled:
} else if err == context.DeadlineExceeded { log.Fatalf("ctx is canceled by another routine: %v", err)
// ctx is attached with a deadline and it exceeded case context.DeadlineExceeded:
} else if verr, ok := err.(*v3rpc.ErrEmptyKey); ok { log.Fatalf("ctx is attached with a deadline is exceeded: %v", err)
// process (verr.Errors) case rpctypes.ErrEmptyKey:
} else { log.Fatalf("client-side error: %v", err)
// bad cluster endpoints, which are not etcd servers default:
log.Fatalf("bad cluster endpoints, which are not etcd servers: %v", err)
} }
} }
``` ```
......
// Copyright 2016 Nippon Telegraph and Telephone Corporation. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,23 +15,49 @@ ...@@ -15,23 +15,49 @@
package clientv3 package clientv3
import ( import (
"fmt"
"strings"
"github.com/coreos/etcd/auth/authpb"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type ( type (
AuthEnableResponse pb.AuthEnableResponse AuthEnableResponse pb.AuthEnableResponse
AuthUserAddResponse pb.AuthUserAddResponse AuthDisableResponse pb.AuthDisableResponse
AuthUserDeleteResponse pb.AuthUserDeleteResponse AuthenticateResponse pb.AuthenticateResponse
AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse AuthUserAddResponse pb.AuthUserAddResponse
AuthRoleAddResponse pb.AuthRoleAddResponse AuthUserDeleteResponse pb.AuthUserDeleteResponse
AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
AuthUserGrantRoleResponse pb.AuthUserGrantRoleResponse
AuthUserGetResponse pb.AuthUserGetResponse
AuthUserRevokeRoleResponse pb.AuthUserRevokeRoleResponse
AuthRoleAddResponse pb.AuthRoleAddResponse
AuthRoleGrantPermissionResponse pb.AuthRoleGrantPermissionResponse
AuthRoleGetResponse pb.AuthRoleGetResponse
AuthRoleRevokePermissionResponse pb.AuthRoleRevokePermissionResponse
AuthRoleDeleteResponse pb.AuthRoleDeleteResponse
AuthUserListResponse pb.AuthUserListResponse
AuthRoleListResponse pb.AuthRoleListResponse
PermissionType authpb.Permission_Type
)
const (
PermRead = authpb.READ
PermWrite = authpb.WRITE
PermReadWrite = authpb.READWRITE
) )
type Auth interface { type Auth interface {
// AuthEnable enables auth of an etcd cluster. // AuthEnable enables auth of an etcd cluster.
AuthEnable(ctx context.Context) (*AuthEnableResponse, error) AuthEnable(ctx context.Context) (*AuthEnableResponse, error)
// AuthDisable disables auth of an etcd cluster.
AuthDisable(ctx context.Context) (*AuthDisableResponse, error)
// UserAdd adds a new user to an etcd cluster. // UserAdd adds a new user to an etcd cluster.
UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
...@@ -41,8 +67,35 @@ type Auth interface { ...@@ -41,8 +67,35 @@ type Auth interface {
// UserChangePassword changes a password of a user. // UserChangePassword changes a password of a user.
UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
// RoleAdd adds a new user to an etcd cluster. // UserGrantRole grants a role to a user.
UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error)
// UserGet gets a detailed information of a user.
UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error)
// UserList gets a list of all users.
UserList(ctx context.Context) (*AuthUserListResponse, error)
// UserRevokeRole revokes a role of a user.
UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error)
// RoleAdd adds a new role to an etcd cluster.
RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error)
// RoleGrantPermission grants a permission to a role.
RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error)
// RoleGet gets a detailed information of a role.
RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error)
// RoleList gets a list of all roles.
RoleList(ctx context.Context) (*AuthRoleListResponse, error)
// RoleRevokePermission revokes a permission from a role.
RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error)
// RoleDelete deletes a role.
RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error)
} }
type auth struct { type auth struct {
...@@ -62,26 +115,115 @@ func NewAuth(c *Client) Auth { ...@@ -62,26 +115,115 @@ func NewAuth(c *Client) Auth {
} }
func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) { func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}) resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}, grpc.FailFast(false))
return (*AuthEnableResponse)(resp), err return (*AuthEnableResponse)(resp), toErr(ctx, err)
}
func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) {
resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, grpc.FailFast(false))
return (*AuthDisableResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) { func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password}) resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthUserAddResponse)(resp), err return (*AuthUserAddResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) { func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name}) resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name}, grpc.FailFast(false))
return (*AuthUserDeleteResponse)(resp), err return (*AuthUserDeleteResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) { func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}) resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthUserChangePasswordResponse)(resp), err return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err)
}
func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) {
resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}, grpc.FailFast(false))
return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err)
}
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, grpc.FailFast(false))
return (*AuthUserGetResponse)(resp), toErr(ctx, err)
}
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) {
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, grpc.FailFast(false))
return (*AuthUserListResponse)(resp), toErr(ctx, err)
}
func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) {
resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}, grpc.FailFast(false))
return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) { func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}) resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}, grpc.FailFast(false))
return (*AuthRoleAddResponse)(resp), err return (*AuthRoleAddResponse)(resp), toErr(ctx, err)
}
func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error) {
perm := &authpb.Permission{
Key: []byte(key),
RangeEnd: []byte(rangeEnd),
PermType: authpb.Permission_Type(permType),
}
resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}, grpc.FailFast(false))
return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err)
}
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, grpc.FailFast(false))
return (*AuthRoleGetResponse)(resp), toErr(ctx, err)
}
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, grpc.FailFast(false))
return (*AuthRoleListResponse)(resp), toErr(ctx, err)
}
func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) {
resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}, grpc.FailFast(false))
return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err)
}
func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) {
resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}, grpc.FailFast(false))
return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err)
}
func StrToPermissionType(s string) (PermissionType, error) {
val, ok := authpb.Permission_Type_value[strings.ToUpper(s)]
if ok {
return PermissionType(val), nil
}
return PermissionType(-1), fmt.Errorf("invalid permission type: %s", s)
}
type authenticator struct {
conn *grpc.ClientConn // conn in-use
remote pb.AuthClient
}
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthenticateResponse)(resp), toErr(ctx, err)
}
func (auth *authenticator) close() {
auth.conn.Close()
}
func newAuthenticator(endpoint string, opts []grpc.DialOption) (*authenticator, error) {
conn, err := grpc.Dial(endpoint, opts...)
if err != nil {
return nil, err
}
return &authenticator{
conn: conn,
remote: pb.NewAuthClient(conn),
}, nil
} }
// 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 clientv3
import (
"net/url"
"strings"
"sync/atomic"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
// simpleBalancer does the bare minimum to expose multiple eps
// to the grpc reconnection code path
type simpleBalancer struct {
// eps are the client's endpoints stripped of any URL scheme
eps []string
ch chan []grpc.Address
numGets uint32
}
func newSimpleBalancer(eps []string) grpc.Balancer {
ch := make(chan []grpc.Address, 1)
addrs := make([]grpc.Address, len(eps))
for i := range eps {
addrs[i].Addr = getHost(eps[i])
}
ch <- addrs
return &simpleBalancer{eps: eps, ch: ch}
}
func (b *simpleBalancer) Start(target string) error { return nil }
func (b *simpleBalancer) Up(addr grpc.Address) func(error) { return func(error) {} }
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
v := atomic.AddUint32(&b.numGets, 1)
ep := b.eps[v%uint32(len(b.eps))]
return grpc.Address{Addr: getHost(ep)}, func() {}, nil
}
func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.ch }
func (b *simpleBalancer) Close() error {
close(b.ch)
return nil
}
func getHost(ep string) string {
url, uerr := url.Parse(ep)
if uerr != nil || !strings.Contains(ep, "://") {
return ep
}
return url.Host
}
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,18 +15,22 @@ ...@@ -15,18 +15,22 @@
package clientv3 package clientv3
import ( import (
"crypto/tls"
"errors" "errors"
"fmt"
"io/ioutil" "io/ioutil"
"log" "log"
"net" "net"
"net/url" "net/url"
"strings" "strings"
"sync"
"time" "time"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
) )
var ( var (
...@@ -42,21 +46,21 @@ type Client struct { ...@@ -42,21 +46,21 @@ type Client struct {
Auth Auth
Maintenance Maintenance
conn *grpc.ClientConn conn *grpc.ClientConn
cfg Config cfg Config
creds *credentials.TransportAuthenticator creds *credentials.TransportCredentials
mu sync.RWMutex // protects connection selection and error list
errors []error // errors passed to retryConnection
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
// Username is a username for authentication
Username string
// Password is a password for authentication
Password string
} }
// New creates a new etcdv3 client from a given configuration. // New creates a new etcdv3 client from a given configuration.
func New(cfg Config) (*Client, error) { func New(cfg Config) (*Client, error) {
if cfg.RetryDialer == nil {
cfg.RetryDialer = dialEndpointList
}
if len(cfg.Endpoints) == 0 { if len(cfg.Endpoints) == 0 {
return nil, ErrNoAvailableEndpoints return nil, ErrNoAvailableEndpoints
} }
...@@ -80,17 +84,8 @@ func NewFromConfigFile(path string) (*Client, error) { ...@@ -80,17 +84,8 @@ func NewFromConfigFile(path string) (*Client, error) {
// Close shuts down the client's etcd connections. // Close shuts down the client's etcd connections.
func (c *Client) Close() error { func (c *Client) Close() error {
c.mu.Lock()
if c.cancel == nil {
c.mu.Unlock()
return nil
}
c.cancel() c.cancel()
c.cancel = nil return toErr(c.ctx, c.conn.Close())
c.mu.Unlock()
c.Watcher.Close()
c.Lease.Close()
return c.conn.Close()
} }
// Ctx is a context for "out of band" messages (e.g., for sending // Ctx is a context for "out of band" messages (e.g., for sending
...@@ -101,72 +96,157 @@ func (c *Client) Ctx() context.Context { return c.ctx } ...@@ -101,72 +96,157 @@ func (c *Client) Ctx() context.Context { return c.ctx }
// Endpoints lists the registered endpoints for the client. // Endpoints lists the registered endpoints for the client.
func (c *Client) Endpoints() []string { return c.cfg.Endpoints } func (c *Client) Endpoints() []string { return c.cfg.Endpoints }
// Errors returns all errors that have been observed since called last. type authTokenCredential struct {
func (c *Client) Errors() (errs []error) { token string
c.mu.Lock()
defer c.mu.Unlock()
errs = c.errors
c.errors = nil
return errs
} }
// Dial establishes a connection for a given endpoint using the client's config func (cred authTokenCredential) RequireTransportSecurity() bool {
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) { return false
}
func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
return map[string]string{
"token": cred.token,
}, nil
}
func (c *Client) dialTarget(endpoint string) (proto string, host string, creds *credentials.TransportCredentials) {
proto = "tcp"
host = endpoint
creds = c.creds
url, uerr := url.Parse(endpoint)
if uerr != nil || !strings.Contains(endpoint, "://") {
return
}
// strip scheme:// prefix since grpc dials by host
host = url.Host
switch url.Scheme {
case "unix":
proto = "unix"
case "http":
creds = nil
case "https":
if creds != nil {
break
}
tlsconfig := &tls.Config{}
emptyCreds := credentials.NewTLS(tlsconfig)
creds = &emptyCreds
default:
return "", "", nil
}
return
}
// dialSetupOpts gives the dial opts prioer to any authentication
func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) []grpc.DialOption {
opts := []grpc.DialOption{ opts := []grpc.DialOption{
grpc.WithBlock(), grpc.WithBlock(),
grpc.WithTimeout(c.cfg.DialTimeout), grpc.WithTimeout(c.cfg.DialTimeout),
} }
if c.creds != nil { opts = append(opts, dopts...)
opts = append(opts, grpc.WithTransportCredentials(*c.creds))
} else {
opts = append(opts, grpc.WithInsecure())
}
proto := "tcp" // grpc issues TLS cert checks using the string passed into dial so
if url, uerr := url.Parse(endpoint); uerr == nil && url.Scheme == "unix" { // that string must be the host. To recover the full scheme://host URL,
proto = "unix" // have a map from hosts to the original endpoint.
// strip unix:// prefix so certs work host2ep := make(map[string]string)
endpoint = url.Host for i := range c.cfg.Endpoints {
_, host, _ := c.dialTarget(c.cfg.Endpoints[i])
host2ep[host] = c.cfg.Endpoints[i]
} }
f := func(a string, t time.Duration) (net.Conn, error) {
f := func(host string, t time.Duration) (net.Conn, error) {
proto, host, _ := c.dialTarget(host2ep[host])
if proto == "" {
return nil, fmt.Errorf("unknown scheme for %q", host)
}
select { select {
case <-c.ctx.Done(): case <-c.ctx.Done():
return nil, c.ctx.Err() return nil, c.ctx.Err()
default: default:
} }
return net.DialTimeout(proto, a, t) return net.DialTimeout(proto, host, t)
} }
opts = append(opts, grpc.WithDialer(f)) opts = append(opts, grpc.WithDialer(f))
conn, err := grpc.Dial(endpoint, opts...) _, _, creds := c.dialTarget(endpoint)
if creds != nil {
opts = append(opts, grpc.WithTransportCredentials(*creds))
} else {
opts = append(opts, grpc.WithInsecure())
}
return opts
}
// Dial connects to a single endpoint using the client's config.
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
return c.dial(endpoint)
}
func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) {
opts := c.dialSetupOpts(endpoint, dopts...)
host := getHost(endpoint)
if c.Username != "" && c.Password != "" {
// use dial options without dopts to avoid reusing the client balancer
auth, err := newAuthenticator(host, c.dialSetupOpts(endpoint))
if err != nil {
return nil, err
}
defer auth.close()
resp, err := auth.authenticate(c.ctx, c.Username, c.Password)
if err != nil {
return nil, err
}
opts = append(opts, grpc.WithPerRPCCredentials(authTokenCredential{token: resp.Token}))
}
conn, err := grpc.Dial(host, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return conn, nil return conn, nil
} }
// WithRequireLeader requires client requests to only succeed
// when the cluster has a leader.
func WithRequireLeader(ctx context.Context) context.Context {
md := metadata.Pairs(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader)
return metadata.NewContext(ctx, md)
}
func newClient(cfg *Config) (*Client, error) { func newClient(cfg *Config) (*Client, error) {
if cfg == nil { if cfg == nil {
cfg = &Config{RetryDialer: dialEndpointList} cfg = &Config{}
} }
var creds *credentials.TransportAuthenticator var creds *credentials.TransportCredentials
if cfg.TLS != nil { if cfg.TLS != nil {
c := credentials.NewTLS(cfg.TLS) c := credentials.NewTLS(cfg.TLS)
creds = &c creds = &c
} }
// use a temporary skeleton client to bootstrap first connection // use a temporary skeleton client to bootstrap first connection
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
conn, err := cfg.RetryDialer(&Client{cfg: *cfg, creds: creds, ctx: ctx})
if err != nil {
return nil, err
}
client := &Client{ client := &Client{
conn: conn, conn: nil,
cfg: *cfg, cfg: *cfg,
creds: creds, creds: creds,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
} }
if cfg.Username != "" && cfg.Password != "" {
client.Username = cfg.Username
client.Password = cfg.Password
}
b := newSimpleBalancer(cfg.Endpoints)
conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(b))
if err != nil {
return nil, err
}
client.conn = conn
client.Cluster = NewCluster(client) client.Cluster = NewCluster(client)
client.KV = NewKV(client) client.KV = NewKV(client)
client.Lease = NewLease(client) client.Lease = NewLease(client)
...@@ -184,60 +264,35 @@ func newClient(cfg *Config) (*Client, error) { ...@@ -184,60 +264,35 @@ func newClient(cfg *Config) (*Client, error) {
} }
// ActiveConnection returns the current in-use connection // ActiveConnection returns the current in-use connection
func (c *Client) ActiveConnection() *grpc.ClientConn { func (c *Client) ActiveConnection() *grpc.ClientConn { return c.conn }
c.mu.RLock()
defer c.mu.RUnlock()
return c.conn
}
// retryConnection establishes a new connection // isHaltErr returns true if the given error and context indicate no forward
func (c *Client) retryConnection(oldConn *grpc.ClientConn, err error) (*grpc.ClientConn, error) { // progress can be made, even after reconnecting.
c.mu.Lock() func isHaltErr(ctx context.Context, err error) bool {
defer c.mu.Unlock() if ctx != nil && ctx.Err() != nil {
if err != nil { return true
c.errors = append(c.errors, err)
}
if c.cancel == nil {
return nil, c.ctx.Err()
} }
if oldConn != c.conn { if err == nil {
// conn has already been updated return false
return c.conn, nil
} }
eErr := rpctypes.Error(err)
oldConn.Close() if _, ok := eErr.(rpctypes.EtcdError); ok {
if st, _ := oldConn.State(); st != grpc.Shutdown { return eErr != rpctypes.ErrStopped && eErr != rpctypes.ErrNoLeader
// wait for shutdown so grpc doesn't leak sleeping goroutines
oldConn.WaitForStateChange(c.ctx, st)
} }
// treat etcdserver errors not recognized by the client as halting
conn, dialErr := c.cfg.RetryDialer(c) return strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error()) ||
if dialErr != nil { strings.Contains(err.Error(), "etcdserver:")
c.errors = append(c.errors, dialErr)
return nil, dialErr
}
c.conn = conn
return c.conn, nil
} }
// dialEndpointList attempts to connect to each endpoint in order until a func toErr(ctx context.Context, err error) error {
// connection is established. if err == nil {
func dialEndpointList(c *Client) (*grpc.ClientConn, error) { return nil
var err error
for _, ep := range c.Endpoints() {
conn, curErr := c.Dial(ep)
if curErr != nil {
err = curErr
} else {
return conn, nil
}
} }
return nil, err err = rpctypes.Error(err)
} if ctx.Err() != nil && strings.Contains(err.Error(), "context") {
err = ctx.Err()
// isHalted returns true if the given error and context indicate no forward } else if strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error()) {
// progress can be made, even after reconnecting. err = grpc.ErrClientConnClosing
func isHalted(ctx context.Context, err error) bool { }
isRPCError := strings.HasPrefix(grpc.ErrorDesc(err), "etcdserver: ") return err
return isRPCError || ctx.Err() != nil
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,8 +15,6 @@ ...@@ -15,8 +15,6 @@
package clientv3 package clientv3
import ( import (
"sync"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
...@@ -34,9 +32,6 @@ type Cluster interface { ...@@ -34,9 +32,6 @@ type Cluster interface {
// MemberList lists the current cluster membership. // MemberList lists the current cluster membership.
MemberList(ctx context.Context) (*MemberListResponse, error) MemberList(ctx context.Context) (*MemberListResponse, error)
// MemberLeader returns the current leader member.
MemberLeader(ctx context.Context) (*Member, error)
// MemberAdd adds a new member into the cluster. // MemberAdd adds a new member into the cluster.
MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
...@@ -48,70 +43,47 @@ type Cluster interface { ...@@ -48,70 +43,47 @@ type Cluster interface {
} }
type cluster struct { type cluster struct {
c *Client
mu sync.Mutex
conn *grpc.ClientConn // conn in-use
remote pb.ClusterClient remote pb.ClusterClient
} }
func NewCluster(c *Client) Cluster { func NewCluster(c *Client) Cluster {
conn := c.ActiveConnection() return &cluster{remote: pb.NewClusterClient(c.conn)}
return &cluster{
c: c,
conn: conn,
remote: pb.NewClusterClient(conn),
}
} }
func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
r := &pb.MemberAddRequest{PeerURLs: peerAddrs} r := &pb.MemberAddRequest{PeerURLs: peerAddrs}
resp, err := c.getRemote().MemberAdd(ctx, r) resp, err := c.remote.MemberAdd(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberAddResponse)(resp), nil return (*MemberAddResponse)(resp), nil
} }
if isHaltErr(ctx, err) {
if isHalted(ctx, err) { return nil, toErr(ctx, err)
return nil, err
} }
return nil, toErr(ctx, err)
go c.switchRemote(err)
return nil, err
} }
func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) { func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) {
r := &pb.MemberRemoveRequest{ID: id} r := &pb.MemberRemoveRequest{ID: id}
resp, err := c.getRemote().MemberRemove(ctx, r) resp, err := c.remote.MemberRemove(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberRemoveResponse)(resp), nil return (*MemberRemoveResponse)(resp), nil
} }
if isHaltErr(ctx, err) {
if isHalted(ctx, err) { return nil, toErr(ctx, err)
return nil, err
} }
return nil, toErr(ctx, err)
go c.switchRemote(err)
return nil, err
} }
func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) { func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) {
// it is safe to retry on update. // it is safe to retry on update.
for { for {
r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs} r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs}
resp, err := c.getRemote().MemberUpdate(ctx, r) resp, err := c.remote.MemberUpdate(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberUpdateResponse)(resp), nil return (*MemberUpdateResponse)(resp), nil
} }
if isHaltErr(ctx, err) {
if isHalted(ctx, err) { return nil, toErr(ctx, err)
return nil, err
}
err = c.switchRemote(err)
if err != nil {
return nil, err
} }
} }
} }
...@@ -119,52 +91,12 @@ func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []strin ...@@ -119,52 +91,12 @@ func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []strin
func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) { func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
// it is safe to retry on list. // it is safe to retry on list.
for { for {
resp, err := c.getRemote().MemberList(ctx, &pb.MemberListRequest{}) resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberListResponse)(resp), nil return (*MemberListResponse)(resp), nil
} }
if isHaltErr(ctx, err) {
if isHalted(ctx, err) { return nil, toErr(ctx, err)
return nil, err
} }
err = c.switchRemote(err)
if err != nil {
return nil, err
}
}
}
func (c *cluster) MemberLeader(ctx context.Context) (*Member, error) {
resp, err := c.MemberList(ctx)
if err != nil {
return nil, err
}
for _, m := range resp.Members {
if m.IsLeader {
return (*Member)(m), nil
}
}
return nil, nil
}
func (c *cluster) getRemote() pb.ClusterClient {
c.mu.Lock()
defer c.mu.Unlock()
return c.remote
}
func (c *cluster) switchRemote(prevErr error) error {
newConn, err := c.c.retryConnection(c.conn, prevErr)
if err != nil {
return err
} }
c.mu.Lock()
defer c.mu.Unlock()
c.conn = newConn
c.remote = pb.NewClusterClient(c.conn)
return nil
} }
// 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 clientv3
import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
// CompactOp represents a compact operation.
type CompactOp struct {
revision int64
physical bool
}
// CompactOption configures compact operation.
type CompactOption func(*CompactOp)
func (op *CompactOp) applyCompactOpts(opts []CompactOption) {
for _, opt := range opts {
opt(op)
}
}
// OpCompact wraps slice CompactOption to create a CompactOp.
func OpCompact(rev int64, opts ...CompactOption) CompactOp {
ret := CompactOp{revision: rev}
ret.applyCompactOpts(opts)
return ret
}
func (op CompactOp) toRequest() *pb.CompactionRequest {
return &pb.CompactionRequest{Revision: op.revision, Physical: op.physical}
}
// WithCompactPhysical makes compact RPC call wait until
// the compaction is physically applied to the local database
// such that compacted entries are totally removed from the
// backend database.
func WithCompactPhysical() CompactOption {
return func(op *CompactOp) { op.physical = true }
}
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -22,19 +22,12 @@ import ( ...@@ -22,19 +22,12 @@ import (
"github.com/coreos/etcd/pkg/tlsutil" "github.com/coreos/etcd/pkg/tlsutil"
"github.com/ghodss/yaml" "github.com/ghodss/yaml"
"google.golang.org/grpc"
) )
// EndpointDialer is a policy for choosing which endpoint to dial next
type EndpointDialer func(*Client) (*grpc.ClientConn, error)
type Config struct { type Config struct {
// Endpoints is a list of URLs // Endpoints is a list of URLs
Endpoints []string Endpoints []string
// RetryDialer chooses the next endpoint to use
RetryDialer EndpointDialer
// DialTimeout is the timeout for failing to establish a connection. // DialTimeout is the timeout for failing to establish a connection.
DialTimeout time.Duration DialTimeout time.Duration
...@@ -43,9 +36,15 @@ type Config struct { ...@@ -43,9 +36,15 @@ type Config struct {
// Logger is the logger used by client library. // Logger is the logger used by client library.
Logger Logger Logger Logger
// Username is a username for authentication
Username string
// Password is a password for authentication
Password string
} }
type YamlConfig struct { type yamlConfig struct {
Endpoints []string `json:"endpoints"` Endpoints []string `json:"endpoints"`
DialTimeout time.Duration `json:"dial-timeout"` DialTimeout time.Duration `json:"dial-timeout"`
InsecureTransport bool `json:"insecure-transport"` InsecureTransport bool `json:"insecure-transport"`
...@@ -61,7 +60,7 @@ func configFromFile(fpath string) (*Config, error) { ...@@ -61,7 +60,7 @@ func configFromFile(fpath string) (*Config, error) {
return nil, err return nil, err
} }
yc := &YamlConfig{} yc := &yamlConfig{}
err = yaml.Unmarshal(b, yc) err = yaml.Unmarshal(b, yc)
if err != nil { if err != nil {
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,18 +15,17 @@ ...@@ -15,18 +15,17 @@
package clientv3 package clientv3
import ( import (
"sync"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
type ( type (
PutResponse pb.PutResponse CompactResponse pb.CompactionResponse
GetResponse pb.RangeResponse PutResponse pb.PutResponse
DeleteResponse pb.DeleteRangeResponse GetResponse pb.RangeResponse
TxnResponse pb.TxnResponse DeleteResponse pb.DeleteRangeResponse
TxnResponse pb.TxnResponse
) )
type KV interface { type KV interface {
...@@ -50,7 +49,7 @@ type KV interface { ...@@ -50,7 +49,7 @@ type KV interface {
Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)
// Compact compacts etcd KV history before the given rev. // Compact compacts etcd KV history before the given rev.
Compact(ctx context.Context, rev int64) error Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)
// Do applies a single Op on KV without a transaction. // Do applies a single Op on KV without a transaction.
// Do is useful when declaring operations to be issued at a later time // Do is useful when declaring operations to be issued at a later time
...@@ -61,7 +60,7 @@ type KV interface { ...@@ -61,7 +60,7 @@ type KV interface {
// Do is useful when creating arbitrary operations to be issued at a // Do is useful when creating arbitrary operations to be issued at a
// later time; the user can range over the operations, calling Do to // later time; the user can range over the operations, calling Do to
// execute them. Get/Put/Delete, on the other hand, are best suited // execute them. Get/Put/Delete, on the other hand, are best suited
// for when the operation should be issued at the time of declaration. // for when the operation should be issued at the time of declaration.
Do(ctx context.Context, op Op) (OpResponse, error) Do(ctx context.Context, op Op) (OpResponse, error)
// Txn creates a transaction. // Txn creates a transaction.
...@@ -74,54 +73,39 @@ type OpResponse struct { ...@@ -74,54 +73,39 @@ type OpResponse struct {
del *DeleteResponse del *DeleteResponse
} }
type kv struct { func (op OpResponse) Put() *PutResponse { return op.put }
c *Client func (op OpResponse) Get() *GetResponse { return op.get }
func (op OpResponse) Del() *DeleteResponse { return op.del }
mu sync.Mutex // guards all fields type kv struct {
conn *grpc.ClientConn // conn in-use
remote pb.KVClient remote pb.KVClient
} }
func NewKV(c *Client) KV { func NewKV(c *Client) KV {
conn := c.ActiveConnection() return &kv{remote: pb.NewKVClient(c.conn)}
remote := pb.NewKVClient(conn)
return &kv{
conn: c.ActiveConnection(),
remote: remote,
c: c,
}
} }
func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) { func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
r, err := kv.Do(ctx, OpPut(key, val, opts...)) r, err := kv.Do(ctx, OpPut(key, val, opts...))
return r.put, err return r.put, toErr(ctx, err)
} }
func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) { func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
r, err := kv.Do(ctx, OpGet(key, opts...)) r, err := kv.Do(ctx, OpGet(key, opts...))
return r.get, err return r.get, toErr(ctx, err)
} }
func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) { func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
r, err := kv.Do(ctx, OpDelete(key, opts...)) r, err := kv.Do(ctx, OpDelete(key, opts...))
return r.del, err return r.del, toErr(ctx, err)
} }
func (kv *kv) Compact(ctx context.Context, rev int64) error { func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
r := &pb.CompactionRequest{Revision: rev} resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest(), grpc.FailFast(false))
_, err := kv.getRemote().Compact(ctx, r) if err != nil {
if err == nil { return nil, toErr(ctx, err)
return nil
}
if isHalted(ctx, err) {
return err
} }
return (*CompactResponse)(resp), err
go kv.switchRemote(err)
return err
} }
func (kv *kv) Txn(ctx context.Context) Txn { func (kv *kv) Txn(ctx context.Context) Txn {
...@@ -133,75 +117,60 @@ func (kv *kv) Txn(ctx context.Context) Txn { ...@@ -133,75 +117,60 @@ func (kv *kv) Txn(ctx context.Context) Txn {
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) { func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
for { for {
var err error resp, err := kv.do(ctx, op)
switch op.t { if err == nil {
// TODO: handle other ops return resp, nil
case tRange:
var resp *pb.RangeResponse
r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev, Serializable: op.serializable}
if op.sort != nil {
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
}
resp, err = kv.getRemote().Range(ctx, r)
if err == nil {
return OpResponse{get: (*GetResponse)(resp)}, nil
}
case tPut:
var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
resp, err = kv.getRemote().Put(ctx, r)
if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil
}
case tDeleteRange:
var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
resp, err = kv.getRemote().DeleteRange(ctx, r)
if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil
}
default:
panic("Unknown op")
} }
if isHaltErr(ctx, err) {
if isHalted(ctx, err) { return resp, toErr(ctx, err)
return OpResponse{}, err
} }
// do not retry on modifications // do not retry on modifications
if op.isWrite() { if op.isWrite() {
go kv.switchRemote(err) return resp, toErr(ctx, err)
return OpResponse{}, err
}
if nerr := kv.switchRemote(err); nerr != nil {
return OpResponse{}, nerr
} }
} }
} }
func (kv *kv) switchRemote(prevErr error) error { func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
// Usually it's a bad idea to lock on network i/o but here it's OK var err error
// since the link is down and new requests can't be processed anyway. switch op.t {
// Likewise, if connecting stalls, closing the Client can break the // TODO: handle other ops
// lock via context cancelation. case tRange:
kv.mu.Lock() var resp *pb.RangeResponse
defer kv.mu.Unlock() r := &pb.RangeRequest{
Key: op.key,
RangeEnd: op.end,
Limit: op.limit,
Revision: op.rev,
Serializable: op.serializable,
KeysOnly: op.keysOnly,
CountOnly: op.countOnly,
}
if op.sort != nil {
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
}
newConn, err := kv.c.retryConnection(kv.conn, prevErr) resp, err = kv.remote.Range(ctx, r, grpc.FailFast(false))
if err != nil { if err == nil {
return err return OpResponse{get: (*GetResponse)(resp)}, nil
}
case tPut:
var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
resp, err = kv.remote.Put(ctx, r, grpc.FailFast(false))
if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil
}
case tDeleteRange:
var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
resp, err = kv.remote.DeleteRange(ctx, r, grpc.FailFast(false))
if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil
}
default:
panic("Unknown op")
} }
return OpResponse{}, err
kv.conn = newConn
kv.remote = pb.NewKVClient(kv.conn)
return nil
}
func (kv *kv) getRemote() pb.KVClient {
kv.mu.Lock()
defer kv.mu.Unlock()
return kv.remote
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
package clientv3 package clientv3
import ( import (
"sync" "io"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
...@@ -45,25 +45,20 @@ type Maintenance interface { ...@@ -45,25 +45,20 @@ type Maintenance interface {
// times with different endpoints. // times with different endpoints.
Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error) Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error)
// Status gets the status of the member. // Status gets the status of the endpoint.
Status(ctx context.Context, endpoint string) (*StatusResponse, error) Status(ctx context.Context, endpoint string) (*StatusResponse, error)
// Snapshot provides a reader for a snapshot of a backend.
Snapshot(ctx context.Context) (io.ReadCloser, error)
} }
type maintenance struct { type maintenance struct {
c *Client c *Client
mu sync.Mutex
conn *grpc.ClientConn // conn in-use
remote pb.MaintenanceClient remote pb.MaintenanceClient
} }
func NewMaintenance(c *Client) Maintenance { func NewMaintenance(c *Client) Maintenance {
conn := c.ActiveConnection() return &maintenance{c: c, remote: pb.NewMaintenanceClient(c.conn)}
return &maintenance{
c: c,
conn: conn,
remote: pb.NewMaintenanceClient(conn),
}
} }
func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) { func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
...@@ -73,15 +68,12 @@ func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) { ...@@ -73,15 +68,12 @@ func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
Alarm: pb.AlarmType_NONE, // all Alarm: pb.AlarmType_NONE, // all
} }
for { for {
resp, err := m.getRemote().Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
if isHalted(ctx, err) { if isHaltErr(ctx, err) {
return nil, err return nil, toErr(ctx, err)
}
if err = m.switchRemote(err); err != nil {
return nil, err
} }
} }
} }
...@@ -96,38 +88,36 @@ func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmR ...@@ -96,38 +88,36 @@ func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmR
if req.MemberID == 0 && req.Alarm == pb.AlarmType_NONE { if req.MemberID == 0 && req.Alarm == pb.AlarmType_NONE {
ar, err := m.AlarmList(ctx) ar, err := m.AlarmList(ctx)
if err != nil { if err != nil {
return nil, err return nil, toErr(ctx, err)
} }
ret := AlarmResponse{} ret := AlarmResponse{}
for _, am := range ar.Alarms { for _, am := range ar.Alarms {
dresp, derr := m.AlarmDisarm(ctx, (*AlarmMember)(am)) dresp, derr := m.AlarmDisarm(ctx, (*AlarmMember)(am))
if derr != nil { if derr != nil {
return nil, derr return nil, toErr(ctx, derr)
} }
ret.Alarms = append(ret.Alarms, dresp.Alarms...) ret.Alarms = append(ret.Alarms, dresp.Alarms...)
} }
return &ret, nil return &ret, nil
} }
resp, err := m.getRemote().Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
if !isHalted(ctx, err) { return nil, toErr(ctx, err)
go m.switchRemote(err)
}
return nil, err
} }
func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error) { func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error) {
conn, err := m.c.Dial(endpoint) conn, err := m.c.Dial(endpoint)
if err != nil { if err != nil {
return nil, err return nil, toErr(ctx, err)
} }
defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}) resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, err return nil, toErr(ctx, err)
} }
return (*DefragmentResponse)(resp), nil return (*DefragmentResponse)(resp), nil
} }
...@@ -135,30 +125,40 @@ func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*Defragm ...@@ -135,30 +125,40 @@ func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*Defragm
func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) { func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) {
conn, err := m.c.Dial(endpoint) conn, err := m.c.Dial(endpoint)
if err != nil { if err != nil {
return nil, err return nil, toErr(ctx, err)
} }
defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Status(ctx, &pb.StatusRequest{}) resp, err := remote.Status(ctx, &pb.StatusRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, err return nil, toErr(ctx, err)
} }
return (*StatusResponse)(resp), nil return (*StatusResponse)(resp), nil
} }
func (m *maintenance) getRemote() pb.MaintenanceClient { func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
m.mu.Lock() ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, grpc.FailFast(false))
defer m.mu.Unlock()
return m.remote
}
func (m *maintenance) switchRemote(prevErr error) error {
m.mu.Lock()
defer m.mu.Unlock()
newConn, err := m.c.retryConnection(m.conn, prevErr)
if err != nil { if err != nil {
return err return nil, toErr(ctx, err)
} }
m.conn = newConn
m.remote = pb.NewMaintenanceClient(m.conn) pr, pw := io.Pipe()
return nil go func() {
for {
resp, err := ss.Recv()
if err != nil {
pw.CloseWithError(err)
return
}
if resp == nil && err == nil {
break
}
if _, werr := pw.Write(resp.Blob); werr != nil {
pw.CloseWithError(werr)
return
}
}
pw.Close()
}()
return pr, nil
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -41,6 +41,8 @@ type Op struct { ...@@ -41,6 +41,8 @@ type Op struct {
limit int64 limit int64
sort *SortOption sort *SortOption
serializable bool serializable bool
keysOnly bool
countOnly bool
// for range, watch // for range, watch
rev int64 rev int64
...@@ -53,21 +55,29 @@ type Op struct { ...@@ -53,21 +55,29 @@ type Op struct {
leaseID LeaseID leaseID LeaseID
} }
func (op Op) toRequestUnion() *pb.RequestUnion { func (op Op) toRequestOp() *pb.RequestOp {
switch op.t { switch op.t {
case tRange: case tRange:
r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev, Serializable: op.serializable} r := &pb.RangeRequest{
Key: op.key,
RangeEnd: op.end,
Limit: op.limit,
Revision: op.rev,
Serializable: op.serializable,
KeysOnly: op.keysOnly,
CountOnly: op.countOnly,
}
if op.sort != nil { if op.sort != nil {
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order) r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target) r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
} }
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestRange{RequestRange: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: r}}
case tPut: case tPut:
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)} r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestPut{RequestPut: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}}
case tDeleteRange: case tDeleteRange:
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end} r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestDeleteRange{RequestDeleteRange: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestDeleteRange{RequestDeleteRange: r}}
default: default:
panic("Unknown Op") panic("Unknown Op")
} }
...@@ -97,6 +107,8 @@ func OpDelete(key string, opts ...OpOption) Op { ...@@ -97,6 +107,8 @@ func OpDelete(key string, opts ...OpOption) Op {
panic("unexpected sort in delete") panic("unexpected sort in delete")
case ret.serializable: case ret.serializable:
panic("unexpected serializable in delete") panic("unexpected serializable in delete")
case ret.countOnly:
panic("unexpected countOnly in delete")
} }
return ret return ret
} }
...@@ -114,7 +126,9 @@ func OpPut(key, val string, opts ...OpOption) Op { ...@@ -114,7 +126,9 @@ func OpPut(key, val string, opts ...OpOption) Op {
case ret.sort != nil: case ret.sort != nil:
panic("unexpected sort in put") panic("unexpected sort in put")
case ret.serializable: case ret.serializable:
panic("unexpected serializable in delete") panic("unexpected serializable in put")
case ret.countOnly:
panic("unexpected countOnly in delete")
} }
return ret return ret
} }
...@@ -131,6 +145,8 @@ func opWatch(key string, opts ...OpOption) Op { ...@@ -131,6 +145,8 @@ func opWatch(key string, opts ...OpOption) Op {
panic("unexpected sort in watch") panic("unexpected sort in watch")
case ret.serializable: case ret.serializable:
panic("unexpected serializable in watch") panic("unexpected serializable in watch")
case ret.countOnly:
panic("unexpected countOnly in delete")
} }
return ret return ret
} }
...@@ -166,6 +182,12 @@ func WithSort(target SortTarget, order SortOrder) OpOption { ...@@ -166,6 +182,12 @@ func WithSort(target SortTarget, order SortOrder) OpOption {
} }
} }
// GetPrefixRangeEnd gets the range end of the prefix.
// 'Get(foo, WithPrefix())' is equal to 'Get(foo, WithRange(GetPrefixRangeEnd(foo))'.
func GetPrefixRangeEnd(prefix string) string {
return string(getPrefix([]byte(prefix)))
}
func getPrefix(key []byte) []byte { func getPrefix(key []byte) []byte {
end := make([]byte, len(key)) end := make([]byte, len(key))
copy(end, key) copy(end, key)
...@@ -198,7 +220,7 @@ func WithRange(endKey string) OpOption { ...@@ -198,7 +220,7 @@ func WithRange(endKey string) OpOption {
} }
// WithFromKey specifies the range of 'Get' or 'Delete' requests // WithFromKey specifies the range of 'Get' or 'Delete' requests
// to be equal or greater than they key in the argument. // to be equal or greater than the key in the argument.
func WithFromKey() OpOption { return WithRange("\x00") } func WithFromKey() OpOption { return WithRange("\x00") }
// WithSerializable makes 'Get' request serializable. By default, // WithSerializable makes 'Get' request serializable. By default,
...@@ -208,6 +230,17 @@ func WithSerializable() OpOption { ...@@ -208,6 +230,17 @@ func WithSerializable() OpOption {
return func(op *Op) { op.serializable = true } return func(op *Op) { op.serializable = true }
} }
// WithKeysOnly makes the 'Get' request return only the keys and the corresponding
// values will be omitted.
func WithKeysOnly() OpOption {
return func(op *Op) { op.keysOnly = true }
}
// WithCountOnly makes the 'Get' request return only the count of keys.
func WithCountOnly() OpOption {
return func(op *Op) { op.countOnly = true }
}
// WithFirstCreate gets the key with the oldest creation revision in the request range. // WithFirstCreate gets the key with the oldest creation revision in the request range.
func WithFirstCreate() []OpOption { return withTop(SortByCreateRevision, SortAscend) } func WithFirstCreate() []OpOption { return withTop(SortByCreateRevision, SortAscend) }
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -19,17 +19,20 @@ import ( ...@@ -19,17 +19,20 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
// Txn is the interface that wraps mini-transactions.
//
// Tx.If(
// Compare(Value(k1), ">", v1),
// Compare(Version(k1), "=", 2)
// ).Then(
// OpPut(k2,v2), OpPut(k3,v3)
// ).Else(
// OpPut(k4,v4), OpPut(k5,v5)
// ).Commit()
// //
// Tx.If(
// Compare(Value(k1), ">", v1),
// Compare(Version(k1), "=", 2)
// ).Then(
// OpPut(k2,v2), OpPut(k3,v3)
// ).Else(
// OpPut(k4,v4), OpPut(k5,v5)
// ).Commit()
type Txn interface { type Txn interface {
// If takes a list of comparison. If all comparisons passed in succeed, // If takes a list of comparison. If all comparisons passed in succeed,
// the operations passed into Then() will be executed. Or the operations // the operations passed into Then() will be executed. Or the operations
...@@ -63,8 +66,8 @@ type txn struct { ...@@ -63,8 +66,8 @@ type txn struct {
cmps []*pb.Compare cmps []*pb.Compare
sus []*pb.RequestUnion sus []*pb.RequestOp
fas []*pb.RequestUnion fas []*pb.RequestOp
} }
func (txn *txn) If(cs ...Cmp) Txn { func (txn *txn) If(cs ...Cmp) Txn {
...@@ -107,7 +110,7 @@ func (txn *txn) Then(ops ...Op) Txn { ...@@ -107,7 +110,7 @@ func (txn *txn) Then(ops ...Op) Txn {
for _, op := range ops { for _, op := range ops {
txn.isWrite = txn.isWrite || op.isWrite() txn.isWrite = txn.isWrite || op.isWrite()
txn.sus = append(txn.sus, op.toRequestUnion()) txn.sus = append(txn.sus, op.toRequestOp())
} }
return txn return txn
...@@ -125,7 +128,7 @@ func (txn *txn) Else(ops ...Op) Txn { ...@@ -125,7 +128,7 @@ func (txn *txn) Else(ops ...Op) Txn {
for _, op := range ops { for _, op := range ops {
txn.isWrite = txn.isWrite || op.isWrite() txn.isWrite = txn.isWrite || op.isWrite()
txn.fas = append(txn.fas, op.toRequestUnion()) txn.fas = append(txn.fas, op.toRequestOp())
} }
return txn return txn
...@@ -134,27 +137,25 @@ func (txn *txn) Else(ops ...Op) Txn { ...@@ -134,27 +137,25 @@ func (txn *txn) Else(ops ...Op) Txn {
func (txn *txn) Commit() (*TxnResponse, error) { func (txn *txn) Commit() (*TxnResponse, error) {
txn.mu.Lock() txn.mu.Lock()
defer txn.mu.Unlock() defer txn.mu.Unlock()
kv := txn.kv
for { for {
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas} resp, err := txn.commit()
resp, err := kv.getRemote().Txn(txn.ctx, r)
if err == nil { if err == nil {
return (*TxnResponse)(resp), nil return resp, err
} }
if isHaltErr(txn.ctx, err) {
if isHalted(txn.ctx, err) { return nil, toErr(txn.ctx, err)
return nil, err
} }
if txn.isWrite { if txn.isWrite {
go kv.switchRemote(err) return nil, toErr(txn.ctx, err)
return nil, err
} }
}
}
if nerr := kv.switchRemote(err); nerr != nil { func (txn *txn) commit() (*TxnResponse, error) {
return nil, nerr r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
} resp, err := txn.kv.remote.Txn(txn.ctx, r, grpc.FailFast(false))
if err != nil {
return nil, err
} }
return (*TxnResponse)(resp), nil
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -19,7 +19,7 @@ import ( ...@@ -19,7 +19,7 @@ import (
"time" "time"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/storage" "github.com/coreos/etcd/mvcc"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
"golang.org/x/net/context" "golang.org/x/net/context"
...@@ -96,7 +96,7 @@ func (t *Periodic) Run() { ...@@ -96,7 +96,7 @@ func (t *Periodic) Run() {
plog.Noticef("Starting auto-compaction at revision %d", rev) plog.Noticef("Starting auto-compaction at revision %d", rev)
_, err := t.c.Compact(t.ctx, &pb.CompactionRequest{Revision: rev}) _, err := t.c.Compact(t.ctx, &pb.CompactionRequest{Revision: rev})
if err == nil || err == storage.ErrCompacted { if err == nil || err == mvcc.ErrCompacted {
t.revs = make([]int64, 0) t.revs = make([]int64, 0)
last = clock.Now() last = clock.Now()
plog.Noticef("Finished auto-compaction at revision %d", rev) plog.Noticef("Finished auto-compaction at revision %d", rev)
......
// 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 compactor implements automated policies for compacting etcd's mvcc storage.
package compactor
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -20,7 +20,6 @@ import ( ...@@ -20,7 +20,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"math" "math"
"net"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
...@@ -30,6 +29,7 @@ import ( ...@@ -30,6 +29,7 @@ import (
"time" "time"
"github.com/coreos/etcd/client" "github.com/coreos/etcd/client"
"github.com/coreos/etcd/pkg/transport"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"github.com/jonboulle/clockwork" "github.com/jonboulle/clockwork"
...@@ -124,16 +124,15 @@ func newDiscovery(durl, dproxyurl string, id types.ID) (*discovery, error) { ...@@ -124,16 +124,15 @@ func newDiscovery(durl, dproxyurl string, id types.ID) (*discovery, error) {
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO: add ResponseHeaderTimeout back when watch on discovery service writes header early
tr, err := transport.NewTransport(transport.TLSInfo{}, 30*time.Second)
if err != nil {
return nil, err
}
tr.Proxy = pf
cfg := client.Config{ cfg := client.Config{
Transport: &http.Transport{ Transport: tr,
Proxy: pf,
Dial: (&net.Dialer{
Timeout: 30 * time.Second,
KeepAlive: 30 * time.Second,
}).Dial,
TLSHandshakeTimeout: 10 * time.Second,
// TODO: add ResponseHeaderTimeout back when watch on discovery service writes header early
},
Endpoints: []string{u.String()}, Endpoints: []string{u.String()},
} }
c, err := client.New(cfg) c, err := client.New(cfg)
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
// limitations under the License. // limitations under the License.
// Package error describes errors in etcd project. When any change happens, // Package error describes errors in etcd project. When any change happens,
// Documentation/errorcode.md needs to be updated correspondingly. // Documentation/v2/errorcode.md needs to be updated correspondingly.
package error package error
import ( import (
......
// 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 api
import (
"sync"
"github.com/coreos/etcd/version"
"github.com/coreos/go-semver/semver"
"github.com/coreos/pkg/capnslog"
)
type Capability string
const (
AuthCapability Capability = "auth"
V3rpcCapability Capability = "v3rpc"
)
var (
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver", "api")
// capabilityMaps is a static map of version to capability map.
// the base capabilities is the set of capability 2.0 supports.
capabilityMaps = map[string]map[Capability]bool{
"2.1.0": {AuthCapability: true},
"2.2.0": {AuthCapability: true},
"2.3.0": {AuthCapability: true},
"3.0.0": {AuthCapability: true, V3rpcCapability: true},
}
enableMapMu sync.RWMutex
// enabledMap points to a map in capabilityMaps
enabledMap map[Capability]bool
curVersion *semver.Version
)
func init() {
enabledMap = make(map[Capability]bool)
}
// UpdateCapability updates the enabledMap when the cluster version increases.
func UpdateCapability(v *semver.Version) {
if v == nil {
// if recovered but version was never set by cluster
return
}
enableMapMu.Lock()
if curVersion != nil && !curVersion.LessThan(*v) {
enableMapMu.Unlock()
return
}
curVersion = v
enabledMap = capabilityMaps[curVersion.String()]
enableMapMu.Unlock()
plog.Infof("enabled capabilities for version %s", version.Cluster(v.String()))
}
func IsCapabilityEnabled(c Capability) bool {
enableMapMu.RLock()
defer enableMapMu.RUnlock()
if enabledMap == nil {
return false
}
return enabledMap[c]
}
func EnableCapability(c Capability) {
enableMapMu.Lock()
defer enableMapMu.Unlock()
enabledMap[c] = true
}
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// 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 api manages the capabilities and features that are exposed to clients by the etcd cluster.
package api
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -17,75 +17,14 @@ package v2http ...@@ -17,75 +17,14 @@ package v2http
import ( import (
"fmt" "fmt"
"net/http" "net/http"
"sync"
"time"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver/api"
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes" "github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
"github.com/coreos/go-semver/semver"
) )
type capability string func capabilityHandler(c api.Capability, fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
const (
authCapability capability = "auth"
)
var (
// capabilityMaps is a static map of version to capability map.
// the base capabilities is the set of capability 2.0 supports.
capabilityMaps = map[string]map[capability]bool{
"2.1.0": {authCapability: true},
"2.2.0": {authCapability: true},
"2.3.0": {authCapability: true},
}
enableMapMu sync.Mutex
// enabledMap points to a map in capabilityMaps
enabledMap map[capability]bool
)
// capabilityLoop checks the cluster version every 500ms and updates
// the enabledMap when the cluster version increased.
// capabilityLoop MUST be ran in a goroutine before checking capability
// or using capabilityHandler.
func capabilityLoop(s *etcdserver.EtcdServer) {
stopped := s.StopNotify()
var pv *semver.Version
for {
if v := s.ClusterVersion(); v != pv {
if pv == nil {
pv = v
} else if v != nil && pv.LessThan(*v) {
pv = v
}
enableMapMu.Lock()
enabledMap = capabilityMaps[pv.String()]
enableMapMu.Unlock()
plog.Infof("enabled capabilities for version %s", pv)
}
select {
case <-stopped:
return
case <-time.After(500 * time.Millisecond):
}
}
}
func isCapabilityEnabled(c capability) bool {
enableMapMu.Lock()
defer enableMapMu.Unlock()
if enabledMap == nil {
return false
}
return enabledMap[c]
}
func capabilityHandler(c capability, fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) { return func(w http.ResponseWriter, r *http.Request) {
if !isCapabilityEnabled(c) { if !api.IsCapabilityEnabled(c) {
notCapable(w, r, c) notCapable(w, r, c)
return return
} }
...@@ -93,7 +32,7 @@ func capabilityHandler(c capability, fn func(http.ResponseWriter, *http.Request) ...@@ -93,7 +32,7 @@ func capabilityHandler(c capability, fn func(http.ResponseWriter, *http.Request)
} }
} }
func notCapable(w http.ResponseWriter, r *http.Request, c capability) { func notCapable(w http.ResponseWriter, r *http.Request, c api.Capability) {
herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c)) herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c))
if err := herr.WriteTo(w); err != nil { if err := herr.WriteTo(w); err != nil {
plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr) plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr)
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -62,16 +62,15 @@ const ( ...@@ -62,16 +62,15 @@ const (
// NewClientHandler generates a muxed http.Handler with the given parameters to serve etcd client requests. // NewClientHandler generates a muxed http.Handler with the given parameters to serve etcd client requests.
func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http.Handler { func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http.Handler {
go capabilityLoop(server)
sec := auth.NewStore(server, timeout) sec := auth.NewStore(server, timeout)
kh := &keysHandler{ kh := &keysHandler{
sec: sec, sec: sec,
server: server, server: server,
cluster: server.Cluster(), cluster: server.Cluster(),
timer: server, timer: server,
timeout: timeout, timeout: timeout,
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
sh := &statsHandler{ sh := &statsHandler{
...@@ -84,6 +83,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http ...@@ -84,6 +83,7 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
cluster: server.Cluster(), cluster: server.Cluster(),
timeout: timeout, timeout: timeout,
clock: clockwork.NewRealClock(), clock: clockwork.NewRealClock(),
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
dmh := &deprecatedMachinesHandler{ dmh := &deprecatedMachinesHandler{
...@@ -91,8 +91,9 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http ...@@ -91,8 +91,9 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
} }
sech := &authHandler{ sech := &authHandler{
sec: sec, sec: sec,
cluster: server.Cluster(), cluster: server.Cluster(),
clientCertAuthEnabled: server.Cfg.ClientCertAuthEnabled,
} }
mux := http.NewServeMux() mux := http.NewServeMux()
...@@ -133,11 +134,12 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http ...@@ -133,11 +134,12 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
} }
type keysHandler struct { type keysHandler struct {
sec auth.Store sec auth.Store
server etcdserver.Server server etcdserver.Server
cluster api.Cluster cluster api.Cluster
timer etcdserver.RaftTimer timer etcdserver.RaftTimer
timeout time.Duration timeout time.Duration
clientCertAuthEnabled bool
} }
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
...@@ -157,7 +159,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { ...@@ -157,7 +159,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return return
} }
// The path must be valid at this point (we've parsed the request successfully). // The path must be valid at this point (we've parsed the request successfully).
if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive) { if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive, h.clientCertAuthEnabled) {
writeKeyNoAuth(w) writeKeyNoAuth(w)
return return
} }
...@@ -200,18 +202,19 @@ func (h *deprecatedMachinesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req ...@@ -200,18 +202,19 @@ func (h *deprecatedMachinesHandler) ServeHTTP(w http.ResponseWriter, r *http.Req
} }
type membersHandler struct { type membersHandler struct {
sec auth.Store sec auth.Store
server etcdserver.Server server etcdserver.Server
cluster api.Cluster cluster api.Cluster
timeout time.Duration timeout time.Duration
clock clockwork.Clock clock clockwork.Clock
clientCertAuthEnabled bool
} }
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") { if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
return return
} }
if !hasWriteRootAccess(h.sec, r) { if !hasWriteRootAccess(h.sec, r, h.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
...@@ -720,20 +723,19 @@ func trimEventPrefix(ev *store.Event, prefix string) *store.Event { ...@@ -720,20 +723,19 @@ func trimEventPrefix(ev *store.Event, prefix string) *store.Event {
// Since the *Event may reference one in the store history // Since the *Event may reference one in the store history
// history, we must copy it before modifying // history, we must copy it before modifying
e := ev.Clone() e := ev.Clone()
e.Node = trimNodeExternPrefix(e.Node, prefix) trimNodeExternPrefix(e.Node, prefix)
e.PrevNode = trimNodeExternPrefix(e.PrevNode, prefix) trimNodeExternPrefix(e.PrevNode, prefix)
return e return e
} }
func trimNodeExternPrefix(n *store.NodeExtern, prefix string) *store.NodeExtern { func trimNodeExternPrefix(n *store.NodeExtern, prefix string) {
if n == nil { if n == nil {
return nil return
} }
n.Key = strings.TrimPrefix(n.Key, prefix) n.Key = strings.TrimPrefix(n.Key, prefix)
for _, nn := range n.Nodes { for _, nn := range n.Nodes {
nn = trimNodeExternPrefix(nn, prefix) trimNodeExternPrefix(nn, prefix)
} }
return n
} }
func trimErrorPrefix(err error, prefix string) error { func trimErrorPrefix(err error, prefix string) error {
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -26,18 +26,56 @@ import ( ...@@ -26,18 +26,56 @@ import (
) )
type authHandler struct { type authHandler struct {
sec auth.Store sec auth.Store
cluster api.Cluster cluster api.Cluster
clientCertAuthEnabled bool
} }
func hasWriteRootAccess(sec auth.Store, r *http.Request) bool { func hasWriteRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
if r.Method == "GET" || r.Method == "HEAD" { if r.Method == "GET" || r.Method == "HEAD" {
return true return true
} }
return hasRootAccess(sec, r) return hasRootAccess(sec, r, clientCertAuthEnabled)
} }
func hasRootAccess(sec auth.Store, r *http.Request) bool { func userFromBasicAuth(sec auth.Store, r *http.Request) *auth.User {
username, password, ok := r.BasicAuth()
if !ok {
plog.Warningf("auth: malformed basic auth encoding")
return nil
}
user, err := sec.GetUser(username)
if err != nil {
return nil
}
ok = sec.CheckPassword(user, password)
if !ok {
plog.Warningf("auth: incorrect password for user: %s", username)
return nil
}
return &user
}
func userFromClientCertificate(sec auth.Store, r *http.Request) *auth.User {
if r.TLS == nil {
return nil
}
for _, chains := range r.TLS.VerifiedChains {
for _, chain := range chains {
plog.Debugf("auth: found common name %s.\n", chain.Subject.CommonName)
user, err := sec.GetUser(chain.Subject.CommonName)
if err == nil {
plog.Debugf("auth: authenticated user %s by cert common name.", user.User)
return &user
}
}
}
return nil
}
func hasRootAccess(sec auth.Store, r *http.Request, clientCertAuthEnabled bool) bool {
if sec == nil { if sec == nil {
// No store means no auth available, eg, tests. // No store means no auth available, eg, tests.
return true return true
...@@ -45,30 +83,30 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool { ...@@ -45,30 +83,30 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool {
if !sec.AuthEnabled() { if !sec.AuthEnabled() {
return true return true
} }
username, password, ok := r.BasicAuth()
if !ok {
return false
}
rootUser, err := sec.GetUser(username)
if err != nil {
return false
}
ok = sec.CheckPassword(rootUser, password) var rootUser *auth.User
if !ok { if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
plog.Warningf("auth: wrong password for user %s", username) rootUser = userFromClientCertificate(sec, r)
return false if rootUser == nil {
return false
}
} else {
rootUser = userFromBasicAuth(sec, r)
if rootUser == nil {
return false
}
} }
for _, role := range rootUser.Roles { for _, role := range rootUser.Roles {
if role == auth.RootRoleName { if role == auth.RootRoleName {
return true return true
} }
} }
plog.Warningf("auth: user %s does not have the %s role for resource %s.", username, auth.RootRoleName, r.URL.Path) plog.Warningf("auth: user %s does not have the %s role for resource %s.", rootUser.User, auth.RootRoleName, r.URL.Path)
return false return false
} }
func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive bool) bool { func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive, clientCertAuthEnabled bool) bool {
if sec == nil { if sec == nil {
// No store means no auth available, eg, tests. // No store means no auth available, eg, tests.
return true return true
...@@ -76,25 +114,21 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b ...@@ -76,25 +114,21 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
if !sec.AuthEnabled() { if !sec.AuthEnabled() {
return true return true
} }
if r.Header.Get("Authorization") == "" {
plog.Warningf("auth: no authorization provided, checking guest access") var user *auth.User
return hasGuestAccess(sec, r, key) if r.Header.Get("Authorization") == "" && clientCertAuthEnabled {
} user = userFromClientCertificate(sec, r)
username, password, ok := r.BasicAuth() if user == nil {
if !ok { plog.Warningf("auth: no authorization provided, checking guest access")
plog.Warningf("auth: malformed basic auth encoding") return hasGuestAccess(sec, r, key)
return false }
} } else {
user, err := sec.GetUser(username) user = userFromBasicAuth(sec, r)
if err != nil { if user == nil {
plog.Warningf("auth: no such user: %s.", username) return false
return false }
}
authAsUser := sec.CheckPassword(user, password)
if !authAsUser {
plog.Warningf("auth: incorrect password for user: %s.", username)
return false
} }
writeAccess := r.Method != "GET" && r.Method != "HEAD" writeAccess := r.Method != "GET" && r.Method != "HEAD"
for _, roleName := range user.Roles { for _, roleName := range user.Roles {
role, err := sec.GetRole(roleName) role, err := sec.GetRole(roleName)
...@@ -109,7 +143,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b ...@@ -109,7 +143,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
return true return true
} }
} }
plog.Warningf("auth: invalid access for user %s on key %s.", username, key) plog.Warningf("auth: invalid access for user %s on key %s.", user.User, key)
return false return false
} }
...@@ -134,18 +168,18 @@ func writeNoAuth(w http.ResponseWriter, r *http.Request) { ...@@ -134,18 +168,18 @@ func writeNoAuth(w http.ResponseWriter, r *http.Request) {
} }
func handleAuth(mux *http.ServeMux, sh *authHandler) { func handleAuth(mux *http.ServeMux, sh *authHandler) {
mux.HandleFunc(authPrefix+"/roles", capabilityHandler(authCapability, sh.baseRoles)) mux.HandleFunc(authPrefix+"/roles", capabilityHandler(api.AuthCapability, sh.baseRoles))
mux.HandleFunc(authPrefix+"/roles/", capabilityHandler(authCapability, sh.handleRoles)) mux.HandleFunc(authPrefix+"/roles/", capabilityHandler(api.AuthCapability, sh.handleRoles))
mux.HandleFunc(authPrefix+"/users", capabilityHandler(authCapability, sh.baseUsers)) mux.HandleFunc(authPrefix+"/users", capabilityHandler(api.AuthCapability, sh.baseUsers))
mux.HandleFunc(authPrefix+"/users/", capabilityHandler(authCapability, sh.handleUsers)) mux.HandleFunc(authPrefix+"/users/", capabilityHandler(api.AuthCapability, sh.handleUsers))
mux.HandleFunc(authPrefix+"/enable", capabilityHandler(authCapability, sh.enableDisable)) mux.HandleFunc(authPrefix+"/enable", capabilityHandler(api.AuthCapability, sh.enableDisable))
} }
func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) { func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") { if !allowMethod(w, r.Method, "GET") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
...@@ -209,7 +243,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri ...@@ -209,7 +243,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
...@@ -285,11 +319,15 @@ type userWithRoles struct { ...@@ -285,11 +319,15 @@ type userWithRoles struct {
Roles []auth.Role `json:"roles,omitempty"` Roles []auth.Role `json:"roles,omitempty"`
} }
type usersCollections struct {
Users []userWithRoles `json:"users"`
}
func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") { if !allowMethod(w, r.Method, "GET") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
...@@ -311,9 +349,7 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { ...@@ -311,9 +349,7 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
return return
} }
var usersCollections struct { ucs := usersCollections{}
Users []userWithRoles `json:"users"`
}
for _, userName := range users { for _, userName := range users {
var user auth.User var user auth.User
user, err = sh.sec.GetUser(userName) user, err = sh.sec.GetUser(userName)
...@@ -327,15 +363,14 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) { ...@@ -327,15 +363,14 @@ func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
var role auth.Role var role auth.Role
role, err = sh.sec.GetRole(roleName) role, err = sh.sec.GetRole(roleName)
if err != nil { if err != nil {
writeError(w, r, err) continue
return
} }
uwr.Roles = append(uwr.Roles, role) uwr.Roles = append(uwr.Roles, role)
} }
usersCollections.Users = append(usersCollections.Users, uwr) ucs.Users = append(ucs.Users, uwr)
} }
err = json.NewEncoder(w).Encode(usersCollections) err = json.NewEncoder(w).Encode(ucs)
if err != nil { if err != nil {
plog.Warningf("baseUsers error encoding on %s", r.URL) plog.Warningf("baseUsers error encoding on %s", r.URL)
...@@ -364,7 +399,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri ...@@ -364,7 +399,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasRootAccess(sh.sec, r) { if !hasRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
...@@ -477,7 +512,7 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) { ...@@ -477,7 +512,7 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") { if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
return return
} }
if !hasWriteRootAccess(sh.sec, r) { if !hasWriteRootAccess(sh.sec, r, sh.clientCertAuthEnabled) {
writeNoAuth(w, r) writeNoAuth(w, r)
return return
} }
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -48,7 +48,7 @@ var ( ...@@ -48,7 +48,7 @@ var (
prometheus.HistogramOpts{ prometheus.HistogramOpts{
Namespace: "etcd", Namespace: "etcd",
Subsystem: "http", Subsystem: "http",
Name: "successful_duration_second", Name: "successful_duration_seconds",
Help: "Bucketed histogram of processing time (s) of successfully handled requests (non-watches), by method (GET/PUT etc.).", Help: "Bucketed histogram of processing time (s) of successfully handled requests (non-watches), by method (GET/PUT etc.).",
Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13), Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13),
}, []string{"method"}) }, []string{"method"})
......
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
......
// Copyright 2016 Nippon Telegraph and Telephone Corporation. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -37,13 +37,19 @@ func (as *AuthServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) ( ...@@ -37,13 +37,19 @@ func (as *AuthServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (
} }
func (as *AuthServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) { func (as *AuthServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.AuthDisable(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) { func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.Authenticate(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
...@@ -55,23 +61,43 @@ func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*p ...@@ -55,23 +61,43 @@ func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*p
} }
func (as *AuthServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) { func (as *AuthServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.RoleDelete(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) { func (as *AuthServer) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.RoleGet(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
}
func (as *AuthServer) RoleList(ctx context.Context, r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
resp, err := as.authenticator.RoleList(ctx, r)
if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) RoleRevoke(ctx context.Context, r *pb.AuthRoleRevokeRequest) (*pb.AuthRoleRevokeResponse, error) { func (as *AuthServer) RoleRevokePermission(ctx context.Context, r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.RoleRevokePermission(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) { func (as *AuthServer) RoleGrantPermission(ctx context.Context, r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.RoleGrantPermission(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) { func (as *AuthServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
...@@ -91,18 +117,35 @@ func (as *AuthServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteReques ...@@ -91,18 +117,35 @@ func (as *AuthServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteReques
} }
func (as *AuthServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) { func (as *AuthServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.UserGet(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
}
func (as *AuthServer) UserList(ctx context.Context, r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
resp, err := as.authenticator.UserList(ctx, r)
if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) { func (as *AuthServer) UserGrantRole(ctx context.Context, r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.UserGrantRole(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.AuthUserRevokeRequest) (*pb.AuthUserRevokeResponse, error) { func (as *AuthServer) UserRevokeRole(ctx context.Context, r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
plog.Info("not implemented yet") resp, err := as.authenticator.UserRevokeRole(ctx, r)
return nil, nil if err != nil {
return nil, togRPCError(err)
}
return resp, nil
} }
func (as *AuthServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) { func (as *AuthServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
......
// 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 v3rpc
import "github.com/gogo/protobuf/proto"
type codec struct{}
func (c *codec) Marshal(v interface{}) ([]byte, error) {
b, err := proto.Marshal(v.(proto.Message))
sentBytes.Add(float64(len(b)))
return b, err
}
func (c *codec) Unmarshal(data []byte, v interface{}) error {
receivedBytes.Add(float64(len(data)))
return proto.Unmarshal(data, v.(proto.Message))
}
func (c *codec) String() string {
return "proto"
}
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -19,15 +19,24 @@ import ( ...@@ -19,15 +19,24 @@ import (
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/pkg/capnslog"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog"
) )
func init() {
grpclog.SetLogger(capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver", "v3rpc/grpc"))
}
func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server { func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server {
var opts []grpc.ServerOption var opts []grpc.ServerOption
opts = append(opts, grpc.CustomCodec(&codec{}))
if tls != nil { if tls != nil {
opts = append(opts, grpc.Creds(credentials.NewTLS(tls))) opts = append(opts, grpc.Creds(credentials.NewTLS(tls)))
} }
opts = append(opts, grpc.UnaryInterceptor(newUnaryInterceptor(s)))
opts = append(opts, grpc.StreamInterceptor(newStreamInterceptor(s)))
grpcServer := grpc.NewServer(opts...) grpcServer := grpc.NewServer(opts...)
pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s)) pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s))
...@@ -36,5 +45,6 @@ func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server { ...@@ -36,5 +45,6 @@ func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server {
pb.RegisterClusterServer(grpcServer, NewClusterServer(s)) pb.RegisterClusterServer(grpcServer, NewClusterServer(s))
pb.RegisterAuthServer(grpcServer, NewAuthServer(s)) pb.RegisterAuthServer(grpcServer, NewAuthServer(s))
pb.RegisterMaintenanceServer(grpcServer, NewMaintenanceServer(s)) pb.RegisterMaintenanceServer(grpcServer, NewMaintenanceServer(s))
return grpcServer return grpcServer
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -40,4 +40,7 @@ func (h *header) fill(rh *pb.ResponseHeader) { ...@@ -40,4 +40,7 @@ func (h *header) fill(rh *pb.ResponseHeader) {
rh.ClusterId = uint64(h.clusterID) rh.ClusterId = uint64(h.clusterID)
rh.MemberId = uint64(h.memberID) rh.MemberId = uint64(h.memberID)
rh.RaftTerm = h.raftTimer.Term() rh.RaftTerm = h.raftTimer.Term()
if rh.Revision == 0 {
rh.Revision = h.rev()
}
} }
// 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 v3rpc
import (
"strings"
"sync"
"time"
"github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/raft"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
maxNoLeaderCnt = 3
)
type streamsMap struct {
mu sync.Mutex
streams map[grpc.ServerStream]struct{}
}
func newUnaryInterceptor(s *etcdserver.EtcdServer) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
if !api.IsCapabilityEnabled(api.V3rpcCapability) {
return nil, rpctypes.ErrGRPCNotCapable
}
md, ok := metadata.FromContext(ctx)
if ok {
if ks := md[rpctypes.MetadataRequireLeaderKey]; len(ks) > 0 && ks[0] == rpctypes.MetadataHasLeader {
if s.Leader() == types.ID(raft.None) {
return nil, rpctypes.ErrGRPCNoLeader
}
}
}
return metricsUnaryInterceptor(ctx, req, info, handler)
}
}
func newStreamInterceptor(s *etcdserver.EtcdServer) grpc.StreamServerInterceptor {
smap := monitorLeader(s)
return func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
if !api.IsCapabilityEnabled(api.V3rpcCapability) {
return rpctypes.ErrGRPCNotCapable
}
md, ok := metadata.FromContext(ss.Context())
if ok {
if ks := md[rpctypes.MetadataRequireLeaderKey]; len(ks) > 0 && ks[0] == rpctypes.MetadataHasLeader {
if s.Leader() == types.ID(raft.None) {
return rpctypes.ErrGRPCNoLeader
}
cctx, cancel := context.WithCancel(ss.Context())
ss = serverStreamWithCtx{ctx: cctx, cancel: &cancel, ServerStream: ss}
smap.mu.Lock()
smap.streams[ss] = struct{}{}
smap.mu.Unlock()
defer func() {
smap.mu.Lock()
delete(smap.streams, ss)
smap.mu.Unlock()
cancel()
}()
}
}
return metricsStreamInterceptor(srv, ss, info, handler)
}
}
func metricsUnaryInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
service, method := splitMethodName(info.FullMethod)
receivedCounter.WithLabelValues(service, method).Inc()
start := time.Now()
resp, err = handler(ctx, req)
if err != nil {
failedCounter.WithLabelValues(service, method, grpc.Code(err).String()).Inc()
}
handlingDuration.WithLabelValues(service, method).Observe(time.Since(start).Seconds())
return resp, err
}
func metricsStreamInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
service, method := splitMethodName(info.FullMethod)
receivedCounter.WithLabelValues(service, method).Inc()
err := handler(srv, ss)
if err != nil {
failedCounter.WithLabelValues(service, method, grpc.Code(err).String()).Inc()
}
return err
}
func splitMethodName(fullMethodName string) (string, string) {
fullMethodName = strings.TrimPrefix(fullMethodName, "/") // remove leading slash
if i := strings.Index(fullMethodName, "/"); i >= 0 {
return fullMethodName[:i], fullMethodName[i+1:]
}
return "unknown", "unknown"
}
type serverStreamWithCtx struct {
grpc.ServerStream
ctx context.Context
cancel *context.CancelFunc
}
func (ssc serverStreamWithCtx) Context() context.Context { return ssc.ctx }
func monitorLeader(s *etcdserver.EtcdServer) *streamsMap {
smap := &streamsMap{
streams: make(map[grpc.ServerStream]struct{}),
}
go func() {
election := time.Duration(s.Cfg.TickMs) * time.Duration(s.Cfg.ElectionTicks) * time.Millisecond
noLeaderCnt := 0
for {
select {
case <-s.StopNotify():
return
case <-time.After(election):
if s.Leader() == types.ID(raft.None) {
noLeaderCnt++
} else {
noLeaderCnt = 0
}
// We are more conservative on canceling existing streams. Reconnecting streams
// cost much more than just rejecting new requests. So we wait until the member
// cannot find a leader for maxNoLeaderCnt election timeouts to cancel existing streams.
if noLeaderCnt >= maxNoLeaderCnt {
smap.mu.Lock()
for ss := range smap.streams {
if ssWithCtx, ok := ss.(serverStreamWithCtx); ok {
(*ssWithCtx.cancel)()
<-ss.Context().Done()
}
}
smap.streams = make(map[grpc.ServerStream]struct{})
smap.mu.Unlock()
}
}
}
}()
return smap
}
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -125,38 +125,38 @@ func (s *kvServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.Co ...@@ -125,38 +125,38 @@ func (s *kvServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.Co
func checkRangeRequest(r *pb.RangeRequest) error { func checkRangeRequest(r *pb.RangeRequest) error {
if len(r.Key) == 0 { if len(r.Key) == 0 {
return rpctypes.ErrEmptyKey return rpctypes.ErrGRPCEmptyKey
} }
return nil return nil
} }
func checkPutRequest(r *pb.PutRequest) error { func checkPutRequest(r *pb.PutRequest) error {
if len(r.Key) == 0 { if len(r.Key) == 0 {
return rpctypes.ErrEmptyKey return rpctypes.ErrGRPCEmptyKey
} }
return nil return nil
} }
func checkDeleteRequest(r *pb.DeleteRangeRequest) error { func checkDeleteRequest(r *pb.DeleteRangeRequest) error {
if len(r.Key) == 0 { if len(r.Key) == 0 {
return rpctypes.ErrEmptyKey return rpctypes.ErrGRPCEmptyKey
} }
return nil return nil
} }
func checkTxnRequest(r *pb.TxnRequest) error { func checkTxnRequest(r *pb.TxnRequest) error {
if len(r.Compare) > MaxOpsPerTxn || len(r.Success) > MaxOpsPerTxn || len(r.Failure) > MaxOpsPerTxn { if len(r.Compare) > MaxOpsPerTxn || len(r.Success) > MaxOpsPerTxn || len(r.Failure) > MaxOpsPerTxn {
return rpctypes.ErrTooManyOps return rpctypes.ErrGRPCTooManyOps
} }
for _, c := range r.Compare { for _, c := range r.Compare {
if len(c.Key) == 0 { if len(c.Key) == 0 {
return rpctypes.ErrEmptyKey return rpctypes.ErrGRPCEmptyKey
} }
} }
for _, u := range r.Success { for _, u := range r.Success {
if err := checkRequestUnion(u); err != nil { if err := checkRequestOp(u); err != nil {
return err return err
} }
} }
...@@ -165,23 +165,19 @@ func checkTxnRequest(r *pb.TxnRequest) error { ...@@ -165,23 +165,19 @@ func checkTxnRequest(r *pb.TxnRequest) error {
} }
for _, u := range r.Failure { for _, u := range r.Failure {
if err := checkRequestUnion(u); err != nil { if err := checkRequestOp(u); err != nil {
return err return err
} }
} }
if err := checkRequestDupKeys(r.Failure); err != nil { return checkRequestDupKeys(r.Failure)
return err
}
return nil
} }
// checkRequestDupKeys gives rpctypes.ErrDuplicateKey if the same key is modified twice // checkRequestDupKeys gives rpctypes.ErrGRPCDuplicateKey if the same key is modified twice
func checkRequestDupKeys(reqs []*pb.RequestUnion) error { func checkRequestDupKeys(reqs []*pb.RequestOp) error {
// check put overlap // check put overlap
keys := make(map[string]struct{}) keys := make(map[string]struct{})
for _, requ := range reqs { for _, requ := range reqs {
tv, ok := requ.Request.(*pb.RequestUnion_RequestPut) tv, ok := requ.Request.(*pb.RequestOp_RequestPut)
if !ok { if !ok {
continue continue
} }
...@@ -189,11 +185,10 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error { ...@@ -189,11 +185,10 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
if preq == nil { if preq == nil {
continue continue
} }
key := string(preq.Key) if _, ok := keys[string(preq.Key)]; ok {
if _, ok := keys[key]; ok { return rpctypes.ErrGRPCDuplicateKey
return rpctypes.ErrDuplicateKey
} }
keys[key] = struct{}{} keys[string(preq.Key)] = struct{}{}
} }
// no need to check deletes if no puts; delete overlaps are permitted // no need to check deletes if no puts; delete overlaps are permitted
...@@ -210,7 +205,7 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error { ...@@ -210,7 +205,7 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
// check put overlap with deletes // check put overlap with deletes
for _, requ := range reqs { for _, requ := range reqs {
tv, ok := requ.Request.(*pb.RequestUnion_RequestDeleteRange) tv, ok := requ.Request.(*pb.RequestOp_RequestDeleteRange)
if !ok { if !ok {
continue continue
} }
...@@ -218,17 +213,16 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error { ...@@ -218,17 +213,16 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
if dreq == nil { if dreq == nil {
continue continue
} }
key := string(dreq.Key)
if dreq.RangeEnd == nil { if dreq.RangeEnd == nil {
if _, found := keys[key]; found { if _, found := keys[string(dreq.Key)]; found {
return rpctypes.ErrDuplicateKey return rpctypes.ErrGRPCDuplicateKey
} }
} else { } else {
lo := sort.SearchStrings(sortedKeys, key) lo := sort.SearchStrings(sortedKeys, string(dreq.Key))
hi := sort.SearchStrings(sortedKeys, string(dreq.RangeEnd)) hi := sort.SearchStrings(sortedKeys, string(dreq.RangeEnd))
if lo != hi { if lo != hi {
// element between lo and hi => overlap // element between lo and hi => overlap
return rpctypes.ErrDuplicateKey return rpctypes.ErrGRPCDuplicateKey
} }
} }
} }
...@@ -236,23 +230,23 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error { ...@@ -236,23 +230,23 @@ func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
return nil return nil
} }
func checkRequestUnion(u *pb.RequestUnion) error { func checkRequestOp(u *pb.RequestOp) error {
// TODO: ensure only one of the field is set. // TODO: ensure only one of the field is set.
switch uv := u.Request.(type) { switch uv := u.Request.(type) {
case *pb.RequestUnion_RequestRange: case *pb.RequestOp_RequestRange:
if uv.RequestRange != nil { if uv.RequestRange != nil {
return checkRangeRequest(uv.RequestRange) return checkRangeRequest(uv.RequestRange)
} }
case *pb.RequestUnion_RequestPut: case *pb.RequestOp_RequestPut:
if uv.RequestPut != nil { if uv.RequestPut != nil {
return checkPutRequest(uv.RequestPut) return checkPutRequest(uv.RequestPut)
} }
case *pb.RequestUnion_RequestDeleteRange: case *pb.RequestOp_RequestDeleteRange:
if uv.RequestDeleteRange != nil { if uv.RequestDeleteRange != nil {
return checkDeleteRequest(uv.RequestDeleteRange) return checkDeleteRequest(uv.RequestDeleteRange)
} }
default: default:
// empty union // empty op
return nil return nil
} }
return nil return nil
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -25,27 +25,33 @@ import ( ...@@ -25,27 +25,33 @@ import (
) )
type LeaseServer struct { type LeaseServer struct {
le etcdserver.Lessor hdr header
le etcdserver.Lessor
} }
func NewLeaseServer(le etcdserver.Lessor) pb.LeaseServer { func NewLeaseServer(s *etcdserver.EtcdServer) pb.LeaseServer {
return &LeaseServer{le: le} return &LeaseServer{le: s, hdr: newHeader(s)}
} }
func (ls *LeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) { func (ls *LeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
resp, err := ls.le.LeaseGrant(ctx, cr) resp, err := ls.le.LeaseGrant(ctx, cr)
if err == lease.ErrLeaseExists { if err == lease.ErrLeaseExists {
return nil, rpctypes.ErrLeaseExist return nil, rpctypes.ErrGRPCLeaseExist
} }
if err != nil {
return nil, err
}
ls.hdr.fill(resp.Header)
return resp, err return resp, err
} }
func (ls *LeaseServer) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) { func (ls *LeaseServer) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
r, err := ls.le.LeaseRevoke(ctx, rr) resp, err := ls.le.LeaseRevoke(ctx, rr)
if err != nil { if err != nil {
return nil, rpctypes.ErrLeaseNotFound return nil, rpctypes.ErrGRPCLeaseNotFound
} }
return r, nil ls.hdr.fill(resp.Header)
return resp, nil
} }
func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error { func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
...@@ -58,16 +64,26 @@ func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro ...@@ -58,16 +64,26 @@ func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro
return err return err
} }
// Create header before we sent out the renew request.
// This can make sure that the revision is strictly smaller or equal to
// when the keepalive happened at the local server (when the local server is the leader)
// or remote leader.
// Without this, a lease might be revoked at rev 3 but client can see the keepalive succeeded
// at rev 4.
resp := &pb.LeaseKeepAliveResponse{ID: req.ID, Header: &pb.ResponseHeader{}}
ls.hdr.fill(resp.Header)
ttl, err := ls.le.LeaseRenew(lease.LeaseID(req.ID)) ttl, err := ls.le.LeaseRenew(lease.LeaseID(req.ID))
if err == lease.ErrLeaseNotFound { if err == lease.ErrLeaseNotFound {
return rpctypes.ErrLeaseNotFound err = nil
ttl = 0
} }
if err != nil && err != lease.ErrLeaseNotFound { if err != nil {
return err return err
} }
resp := &pb.LeaseKeepAliveResponse{ID: req.ID, TTL: ttl} resp.TTL = ttl
err = stream.Send(resp) err = stream.Send(resp)
if err != nil { if err != nil {
return err return err
......
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -15,13 +15,22 @@ ...@@ -15,13 +15,22 @@
package v3rpc package v3rpc
import ( import (
"crypto/sha256"
"io"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/storage/backend" "github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
type KVGetter interface {
KV() mvcc.ConsistentWatchableKV
}
type BackendGetter interface { type BackendGetter interface {
Backend() backend.Backend Backend() backend.Backend
} }
...@@ -30,33 +39,86 @@ type Alarmer interface { ...@@ -30,33 +39,86 @@ type Alarmer interface {
Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
} }
type RaftStatusGetter interface {
Index() uint64
Term() uint64
Leader() types.ID
}
type maintenanceServer struct { type maintenanceServer struct {
rg RaftStatusGetter
kg KVGetter
bg BackendGetter bg BackendGetter
a Alarmer a Alarmer
hdr header hdr header
} }
func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer { func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
return &maintenanceServer{bg: s, a: s, hdr: newHeader(s)} return &maintenanceServer{rg: s, kg: s, bg: s, a: s, hdr: newHeader(s)}
} }
func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) { func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
plog.Noticef("starting to defragment the storage backend...") plog.Noticef("starting to defragment the storage backend...")
err := ms.bg.Backend().Defrag() err := ms.bg.Backend().Defrag()
if err != nil { if err != nil {
plog.Errorf("failed to deframent the storage backend (%v)", err) plog.Errorf("failed to defragment the storage backend (%v)", err)
return nil, err return nil, err
} }
plog.Noticef("finished defragmenting the storage backend") plog.Noticef("finished defragmenting the storage backend")
return &pb.DefragmentResponse{}, nil return &pb.DefragmentResponse{}, nil
} }
func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
snap := ms.bg.Backend().Snapshot()
pr, pw := io.Pipe()
defer pr.Close()
go func() {
snap.WriteTo(pw)
if err := snap.Close(); err != nil {
plog.Errorf("error closing snapshot (%v)", err)
}
pw.Close()
}()
// send file data
h := sha256.New()
br := int64(0)
buf := make([]byte, 32*1024)
sz := snap.Size()
for br < sz {
n, err := io.ReadFull(pr, buf)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return togRPCError(err)
}
br += int64(n)
resp := &pb.SnapshotResponse{
RemainingBytes: uint64(sz - br),
Blob: buf[:n],
}
if err = srv.Send(resp); err != nil {
return togRPCError(err)
}
h.Write(buf[:n])
}
// send sha
sha := h.Sum(nil)
hresp := &pb.SnapshotResponse{RemainingBytes: 0, Blob: sha}
if err := srv.Send(hresp); err != nil {
return togRPCError(err)
}
return nil
}
func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) { func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
h, err := ms.bg.Backend().Hash() h, rev, err := ms.kg.KV().Hash()
if err != nil { if err != nil {
return nil, togRPCError(err) return nil, togRPCError(err)
} }
resp := &pb.HashResponse{Header: &pb.ResponseHeader{Revision: ms.hdr.rev()}, Hash: h} resp := &pb.HashResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h}
ms.hdr.fill(resp.Header) ms.hdr.fill(resp.Header)
return resp, nil return resp, nil
} }
...@@ -66,7 +128,14 @@ func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*p ...@@ -66,7 +128,14 @@ func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*p
} }
func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) { func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
resp := &pb.StatusResponse{Header: &pb.ResponseHeader{Revision: ms.hdr.rev()}, Version: version.Version} resp := &pb.StatusResponse{
Header: &pb.ResponseHeader{Revision: ms.hdr.rev()},
Version: version.Version,
DbSize: ms.bg.Backend().Size(),
Leader: uint64(ms.rg.Leader()),
RaftIndex: ms.rg.Index(),
RaftTerm: ms.rg.Term(),
}
ms.hdr.fill(resp.Header) ms.hdr.fill(resp.Header)
return resp, nil return resp, nil
} }
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -45,7 +45,7 @@ func NewClusterServer(s *etcdserver.EtcdServer) *ClusterServer { ...@@ -45,7 +45,7 @@ func NewClusterServer(s *etcdserver.EtcdServer) *ClusterServer {
func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) { func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
urls, err := types.NewURLs(r.PeerURLs) urls, err := types.NewURLs(r.PeerURLs)
if err != nil { if err != nil {
return nil, rpctypes.ErrMemberBadURLs return nil, rpctypes.ErrGRPCMemberBadURLs
} }
now := time.Now() now := time.Now()
...@@ -53,16 +53,16 @@ func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) ...@@ -53,16 +53,16 @@ func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest)
err = cs.server.AddMember(ctx, *m) err = cs.server.AddMember(ctx, *m)
switch { switch {
case err == membership.ErrIDExists: case err == membership.ErrIDExists:
return nil, rpctypes.ErrMemberExist return nil, rpctypes.ErrGRPCMemberExist
case err == membership.ErrPeerURLexists: case err == membership.ErrPeerURLexists:
return nil, rpctypes.ErrPeerURLExist return nil, rpctypes.ErrGRPCPeerURLExist
case err != nil: case err != nil:
return nil, grpc.Errorf(codes.Internal, err.Error()) return nil, grpc.Errorf(codes.Internal, err.Error())
} }
return &pb.MemberAddResponse{ return &pb.MemberAddResponse{
Header: cs.header(), Header: cs.header(),
Member: &pb.Member{ID: uint64(m.ID), IsLeader: m.ID == cs.server.Leader(), PeerURLs: m.PeerURLs}, Member: &pb.Member{ID: uint64(m.ID), PeerURLs: m.PeerURLs},
}, nil }, nil
} }
...@@ -72,7 +72,7 @@ func (cs *ClusterServer) MemberRemove(ctx context.Context, r *pb.MemberRemoveReq ...@@ -72,7 +72,7 @@ func (cs *ClusterServer) MemberRemove(ctx context.Context, r *pb.MemberRemoveReq
case err == membership.ErrIDRemoved: case err == membership.ErrIDRemoved:
fallthrough fallthrough
case err == membership.ErrIDNotFound: case err == membership.ErrIDNotFound:
return nil, rpctypes.ErrMemberNotFound return nil, rpctypes.ErrGRPCMemberNotFound
case err != nil: case err != nil:
return nil, grpc.Errorf(codes.Internal, err.Error()) return nil, grpc.Errorf(codes.Internal, err.Error())
} }
...@@ -88,9 +88,9 @@ func (cs *ClusterServer) MemberUpdate(ctx context.Context, r *pb.MemberUpdateReq ...@@ -88,9 +88,9 @@ func (cs *ClusterServer) MemberUpdate(ctx context.Context, r *pb.MemberUpdateReq
err := cs.server.UpdateMember(ctx, m) err := cs.server.UpdateMember(ctx, m)
switch { switch {
case err == membership.ErrPeerURLexists: case err == membership.ErrPeerURLexists:
return nil, rpctypes.ErrPeerURLExist return nil, rpctypes.ErrGRPCPeerURLExist
case err == membership.ErrIDNotFound: case err == membership.ErrIDNotFound:
return nil, rpctypes.ErrMemberNotFound return nil, rpctypes.ErrGRPCMemberNotFound
case err != nil: case err != nil:
return nil, grpc.Errorf(codes.Internal, err.Error()) return nil, grpc.Errorf(codes.Internal, err.Error())
} }
...@@ -106,7 +106,6 @@ func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest ...@@ -106,7 +106,6 @@ func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest
protoMembs[i] = &pb.Member{ protoMembs[i] = &pb.Member{
Name: membs[i].Name, Name: membs[i].Name,
ID: uint64(membs[i].ID), ID: uint64(membs[i].ID),
IsLeader: membs[i].ID == cs.server.Leader(),
PeerURLs: membs[i].PeerURLs, PeerURLs: membs[i].PeerURLs,
ClientURLs: membs[i].ClientURLs, ClientURLs: membs[i].ClientURLs,
} }
......
// 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 v3rpc
import "github.com/prometheus/client_golang/prometheus"
var (
receivedCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "grpc",
Name: "requests_total",
Help: "Counter of received requests.",
}, []string{"grpc_service", "grpc_method"})
failedCounter = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "grpc",
Name: "requests_failed_total",
Help: "Counter of failed requests.",
}, []string{"grpc_service", "grpc_method", "grpc_code"})
handlingDuration = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "etcd",
Subsystem: "grpc",
Name: "unary_requests_duration_seconds",
Help: "Bucketed histogram of processing time (s) of handled unary (non-stream) requests.",
Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13),
}, []string{"grpc_service", "grpc_method"})
sentBytes = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "network",
Name: "client_grpc_sent_bytes_total",
Help: "The total number of bytes sent to grpc clients.",
})
receivedBytes = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "network",
Name: "client_grpc_received_bytes_total",
Help: "The total number of bytes received from grpc clients.",
})
)
func init() {
prometheus.MustRegister(receivedCounter)
prometheus.MustRegister(failedCounter)
prometheus.MustRegister(handlingDuration)
prometheus.MustRegister(sentBytes)
prometheus.MustRegister(receivedBytes)
}
// Copyright 2016 CoreOS, Inc. // Copyright 2016 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -45,7 +45,7 @@ func (qa *quotaAlarmer) check(ctx context.Context, r interface{}) error { ...@@ -45,7 +45,7 @@ func (qa *quotaAlarmer) check(ctx context.Context, r interface{}) error {
Alarm: pb.AlarmType_NOSPACE, Alarm: pb.AlarmType_NOSPACE,
} }
qa.a.Alarm(ctx, req) qa.a.Alarm(ctx, req)
return rpctypes.ErrNoSpace return rpctypes.ErrGRPCNoSpace
} }
func NewQuotaKVServer(s *etcdserver.EtcdServer) pb.KVServer { func NewQuotaKVServer(s *etcdserver.EtcdServer) pb.KVServer {
......
// 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 rpctypes has types and values shared by the etcd server and client for v3 RPC interaction.
package rpctypes
// Copyright 2015 CoreOS, Inc. // Copyright 2015 The etcd Authors
// //
// Licensed under the Apache License, Version 2.0 (the "License"); // Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License. // you may not use this file except in compliance with the License.
...@@ -20,24 +20,131 @@ import ( ...@@ -20,24 +20,131 @@ import (
) )
var ( var (
ErrEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided") // server-side error
ErrTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request") ErrGRPCEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided")
ErrDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request") ErrGRPCTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request")
ErrCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision has been compacted") ErrGRPCDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request")
ErrFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision is a future revision") ErrGRPCCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted")
ErrNoSpace = grpc.Errorf(codes.ResourceExhausted, "etcdserver: storage: database space exceeded") ErrGRPCFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision is a future revision")
ErrGRPCNoSpace = grpc.Errorf(codes.ResourceExhausted, "etcdserver: mvcc: database space exceeded")
ErrLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found")
ErrLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists") ErrGRPCLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found")
ErrGRPCLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists")
ErrMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist")
ErrPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists") ErrGRPCMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist")
ErrMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid") ErrGRPCPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists")
ErrMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found") ErrGRPCMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid")
ErrGRPCMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found")
ErrRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large")
ErrGRPCRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large")
ErrUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists")
ErrUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found") ErrGRPCRootUserNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not exist")
ErrRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists") ErrGRPCRootRoleNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not have root role")
ErrGRPCUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists")
ErrGRPCUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found")
ErrGRPCRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists")
ErrGRPCRoleNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name not found")
ErrGRPCAuthFailed = grpc.Errorf(codes.InvalidArgument, "etcdserver: authentication failed, invalid user ID or password")
ErrGRPCPermissionDenied = grpc.Errorf(codes.FailedPrecondition, "etcdserver: permission denied")
ErrGRPCRoleNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role is not granted to the user")
ErrGRPCPermissionNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: permission is not granted to the role")
ErrGRPCNoLeader = grpc.Errorf(codes.Unavailable, "etcdserver: no leader")
ErrGRPCNotCapable = grpc.Errorf(codes.Unavailable, "etcdserver: not capable")
ErrGRPCStopped = grpc.Errorf(codes.Unavailable, "etcdserver: server stopped")
errStringToError = map[string]error{
grpc.ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey,
grpc.ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps,
grpc.ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey,
grpc.ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted,
grpc.ErrorDesc(ErrGRPCFutureRev): ErrGRPCFutureRev,
grpc.ErrorDesc(ErrGRPCNoSpace): ErrGRPCNoSpace,
grpc.ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound,
grpc.ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist,
grpc.ErrorDesc(ErrGRPCMemberExist): ErrGRPCMemberExist,
grpc.ErrorDesc(ErrGRPCPeerURLExist): ErrGRPCPeerURLExist,
grpc.ErrorDesc(ErrGRPCMemberBadURLs): ErrGRPCMemberBadURLs,
grpc.ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound,
grpc.ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge,
grpc.ErrorDesc(ErrGRPCRootUserNotExist): ErrGRPCRootUserNotExist,
grpc.ErrorDesc(ErrGRPCRootRoleNotExist): ErrGRPCRootRoleNotExist,
grpc.ErrorDesc(ErrGRPCUserAlreadyExist): ErrGRPCUserAlreadyExist,
grpc.ErrorDesc(ErrGRPCUserNotFound): ErrGRPCUserNotFound,
grpc.ErrorDesc(ErrGRPCRoleAlreadyExist): ErrGRPCRoleAlreadyExist,
grpc.ErrorDesc(ErrGRPCRoleNotFound): ErrGRPCRoleNotFound,
grpc.ErrorDesc(ErrGRPCAuthFailed): ErrGRPCAuthFailed,
grpc.ErrorDesc(ErrGRPCPermissionDenied): ErrGRPCPermissionDenied,
grpc.ErrorDesc(ErrGRPCRoleNotGranted): ErrGRPCRoleNotGranted,
grpc.ErrorDesc(ErrGRPCPermissionNotGranted): ErrGRPCPermissionNotGranted,
grpc.ErrorDesc(ErrGRPCNoLeader): ErrGRPCNoLeader,
grpc.ErrorDesc(ErrGRPCNotCapable): ErrGRPCNotCapable,
grpc.ErrorDesc(ErrGRPCStopped): ErrGRPCStopped,
}
// client-side error
ErrEmptyKey = Error(ErrGRPCEmptyKey)
ErrTooManyOps = Error(ErrGRPCTooManyOps)
ErrDuplicateKey = Error(ErrGRPCDuplicateKey)
ErrCompacted = Error(ErrGRPCCompacted)
ErrFutureRev = Error(ErrGRPCFutureRev)
ErrNoSpace = Error(ErrGRPCNoSpace)
ErrLeaseNotFound = Error(ErrGRPCLeaseNotFound)
ErrLeaseExist = Error(ErrGRPCLeaseExist)
ErrMemberExist = Error(ErrGRPCMemberExist)
ErrPeerURLExist = Error(ErrGRPCPeerURLExist)
ErrMemberBadURLs = Error(ErrGRPCMemberBadURLs)
ErrMemberNotFound = Error(ErrGRPCMemberNotFound)
ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge)
ErrRootUserNotExist = Error(ErrGRPCRootUserNotExist)
ErrRootRoleNotExist = Error(ErrGRPCRootRoleNotExist)
ErrUserAlreadyExist = Error(ErrGRPCUserAlreadyExist)
ErrUserNotFound = Error(ErrGRPCUserNotFound)
ErrRoleAlreadyExist = Error(ErrGRPCRoleAlreadyExist)
ErrRoleNotFound = Error(ErrGRPCRoleNotFound)
ErrAuthFailed = Error(ErrGRPCAuthFailed)
ErrPermissionDenied = Error(ErrGRPCPermissionDenied)
ErrRoleNotGranted = Error(ErrGRPCRoleNotGranted)
ErrPermissionNotGranted = Error(ErrGRPCPermissionNotGranted)
ErrNoLeader = Error(ErrGRPCNoLeader)
ErrNotCapable = Error(ErrGRPCNotCapable)
ErrStopped = Error(ErrGRPCStopped)
) )
// EtcdError defines gRPC server errors.
// (https://github.com/grpc/grpc-go/blob/master/rpc_util.go#L319-L323)
type EtcdError struct {
code codes.Code
desc string
}
// Code returns grpc/codes.Code.
// TODO: define clientv3/codes.Code.
func (e EtcdError) Code() codes.Code {
return e.code
}
func (e EtcdError) Error() string {
return e.desc
}
func Error(err error) error {
if err == nil {
return nil
}
verr, ok := errStringToError[grpc.ErrorDesc(err)]
if !ok { // not gRPC error
return err
}
return EtcdError{code: grpc.Code(verr), desc: grpc.ErrorDesc(verr)}
}
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment