vendor: upgrade etcd v3.3, Consul

Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
This commit is contained in:
Gyuho Lee 2018-01-09 07:09:25 -08:00
parent 01bb4fe475
commit 860b3283fa
472 changed files with 162215 additions and 60303 deletions

84
Gopkg.lock generated
View File

@ -18,6 +18,7 @@
"storage" "storage"
] ]
revision = "2a6493c7a9214bf56c1003bd97443d505cc7e952" revision = "2a6493c7a9214bf56c1003bd97443d505cc7e952"
source = "https://github.com/GoogleCloudPlatform/google-cloud-go"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -28,32 +29,24 @@
[[projects]] [[projects]]
name = "github.com/cheggaaa/pb" name = "github.com/cheggaaa/pb"
packages = ["."] packages = ["."]
revision = "d7e6ca3010b6f084d8056847f55d7f572f180678" revision = "18d384da9bdc1e5a08fc2a62a494c321d9ae74ea"
source = "https://github.com/cheggaaa/pb"
[[projects]] [[projects]]
name = "github.com/coreos/etcd" name = "github.com/coreos/etcd"
packages = [ packages = [
"auth/authpb", "auth/authpb",
"client",
"clientv3", "clientv3",
"etcdserver/api/v3rpc/rpctypes", "etcdserver/api/v3rpc/rpctypes",
"etcdserver/etcdserverpb", "etcdserver/etcdserverpb",
"mvcc/mvccpb", "mvcc/mvccpb",
"pkg/cpuutil", "pkg/cpuutil",
"pkg/netutil", "pkg/netutil",
"pkg/pathutil",
"pkg/report", "pkg/report",
"pkg/srv", "pkg/types"
"pkg/types",
"version"
] ]
revision = "47a8156851b5a59665421661edb7c813f8a7993e" revision = "eaf6ae02e47edbea90aa7f0fe6c4d558611e00ab"
source = "https://github.com/coreos/etcd"
[[projects]]
name = "github.com/coreos/go-semver"
packages = ["semver"]
revision = "8ab6407b697782a06568d4b7f1db25550ec2e4c6"
version = "v0.2.0"
[[projects]] [[projects]]
name = "github.com/coreos/go-systemd" name = "github.com/coreos/go-systemd"
@ -64,12 +57,14 @@
[[projects]] [[projects]]
name = "github.com/coreos/pkg" name = "github.com/coreos/pkg"
packages = ["capnslog"] packages = ["capnslog"]
revision = "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8" revision = "97fdf19511ea361ae1c100dd393cc47f8dcfa1e1"
source = "https://github.com/coreos/pkg"
[[projects]] [[projects]]
name = "github.com/dustin/go-humanize" name = "github.com/dustin/go-humanize"
packages = ["."] packages = ["."]
revision = "ef638b6c2e62b857442c6443dace9366a48c0ee2" revision = "bb3d318650d48840a39aa21a027c6630e198e626"
source = "https://github.com/dustin/go-humanize"
[[projects]] [[projects]]
name = "github.com/gogo/protobuf" name = "github.com/gogo/protobuf"
@ -79,6 +74,7 @@
"protoc-gen-gogo/descriptor" "protoc-gen-gogo/descriptor"
] ]
revision = "160de10b2537169b5ae3e7e221d28269ef40d311" revision = "160de10b2537169b5ae3e7e221d28269ef40d311"
source = "https://github.com/gogo/protobuf"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -95,9 +91,13 @@
packages = [ packages = [
"proto", "proto",
"protoc-gen-go/descriptor", "protoc-gen-go/descriptor",
"ptypes/any" "ptypes",
"ptypes/any",
"ptypes/duration",
"ptypes/timestamp"
] ]
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
source = "https://github.com/golang/protobuf"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -161,6 +161,7 @@
"vg/vgsvg" "vg/vgsvg"
] ]
revision = "51b62dc5319d7fce41240d13e780a93e640b9a38" revision = "51b62dc5319d7fce41240d13e780a93e640b9a38"
source = "https://github.com/gonum/plot"
[[projects]] [[projects]]
name = "github.com/googleapis/gax-go" name = "github.com/googleapis/gax-go"
@ -171,7 +172,8 @@
[[projects]] [[projects]]
name = "github.com/gyuho/dataframe" name = "github.com/gyuho/dataframe"
packages = ["."] packages = ["."]
revision = "73de2c550b1177c1640f3dacbbc1af00f913fedb" revision = "008fc241adc41d4bd5c54b9f6564ef16407c030e"
source = "https://github.com/gyuho/dataframe"
[[projects]] [[projects]]
name = "github.com/gyuho/linux-inspect" name = "github.com/gyuho/linux-inspect"
@ -185,11 +187,13 @@
"top" "top"
] ]
revision = "db8def6abe6a96a20aae6ae01664cc3817a27ba0" revision = "db8def6abe6a96a20aae6ae01664cc3817a27ba0"
source = "https://github.com/gyuho/linux-inspect"
[[projects]] [[projects]]
name = "github.com/hashicorp/consul" name = "github.com/hashicorp/consul"
packages = ["api"] packages = ["api"]
revision = "42f60b04bba2e4391e9a95a218c873986ebeea6b" revision = "b55059fc3d0327c92c41431e57dfd2df3f956b03"
source = "https://github.com/hashicorp/consul"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -246,17 +250,20 @@
[[projects]] [[projects]]
name = "github.com/olekukonko/tablewriter" name = "github.com/olekukonko/tablewriter"
packages = ["."] packages = ["."]
revision = "febf2d34b54a69ce7530036c7503b1c9fbfdf0bb" revision = "96aac992fc8b1a4c83841a6c3e7178d20d989625"
source = "https://github.com/olekukonko/tablewriter"
[[projects]] [[projects]]
name = "github.com/samuel/go-zookeeper" name = "github.com/samuel/go-zookeeper"
packages = ["zk"] packages = ["zk"]
revision = "1d7be4effb13d2d908342d349d71a284a7542693" revision = "471cd4e61d7a78ece1791fa5faa0345dc8c7d5a5"
source = "https://github.com/samuel/go-zookeeper"
[[projects]] [[projects]]
name = "github.com/spf13/cobra" name = "github.com/spf13/cobra"
packages = ["."] packages = ["."]
revision = "9495bc009a56819bdb0ddbc1a373e29c140bc674" revision = "b95ab734e27d33e0d8fbabf71ca990568d4e2020"
source = "https://github.com/spf13/cobra"
[[projects]] [[projects]]
name = "github.com/spf13/pflag" name = "github.com/spf13/pflag"
@ -264,12 +271,6 @@
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66" revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
version = "v1.0.0" version = "v1.0.0"
[[projects]]
branch = "master"
name = "github.com/ugorji/go"
packages = ["codec"]
revision = "ccfe18359b55b97855cee1d3f74e5efbda4869dc"
[[projects]] [[projects]]
branch = "master" branch = "master"
name = "golang.org/x/image" name = "golang.org/x/image"
@ -295,7 +296,8 @@
"lex/httplex", "lex/httplex",
"trace" "trace"
] ]
revision = "c8c74377599bd978aee1cf3b9b63a8634051cec2" revision = "434ec0c7fe3742c984919a691b2018a6e9694425"
source = "https://github.com/golang/net"
[[projects]] [[projects]]
name = "golang.org/x/oauth2" name = "golang.org/x/oauth2"
@ -307,6 +309,13 @@
"jwt" "jwt"
] ]
revision = "30785a2c434e431ef7c507b54617d6a951d5f2b4" revision = "30785a2c434e431ef7c507b54617d6a951d5f2b4"
source = "https://github.com/golang/oauth2"
[[projects]]
branch = "master"
name = "golang.org/x/sys"
packages = ["unix"]
revision = "d38bf781f16e180a1b2ad82697d2f81d7b7ecfac"
[[projects]] [[projects]]
branch = "master" branch = "master"
@ -332,7 +341,8 @@
[[projects]] [[projects]]
name = "golang.org/x/time" name = "golang.org/x/time"
packages = ["rate"] packages = ["rate"]
revision = "a4bde12657593d5e90d0533a3e4fd95e635124cb" revision = "6dc17368e09b0e8634d71cac8168d853e869a0c7"
source = "https://github.com/golang/time"
[[projects]] [[projects]]
name = "google.golang.org/api" name = "google.golang.org/api"
@ -348,6 +358,7 @@
"transport/http" "transport/http"
] ]
revision = "e5c227fa33ebccc5a430d863efae400431fc0e85" revision = "e5c227fa33ebccc5a430d863efae400431fc0e85"
source = "https://github.com/google/google-api-go-client"
[[projects]] [[projects]]
name = "google.golang.org/appengine" name = "google.golang.org/appengine"
@ -380,31 +391,36 @@
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
packages = [ packages = [
".", ".",
"balancer",
"codes", "codes",
"connectivity",
"credentials", "credentials",
"grpclb/grpc_lb_v1", "grpclb/grpc_lb_v1/messages",
"grpclog", "grpclog",
"health/grpc_health_v1",
"internal", "internal",
"keepalive", "keepalive",
"metadata", "metadata",
"naming", "naming",
"peer", "peer",
"resolver",
"stats", "stats",
"status", "status",
"tap", "tap",
"transport" "transport"
] ]
revision = "b15215fb911b24a5d61d57feec4233d610530464" revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
source = "https://github.com/grpc/grpc-go.git" source = "https://github.com/grpc/grpc-go"
[[projects]] [[projects]]
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
packages = ["."] packages = ["."]
revision = "53feefa2559fb8dfa8d81baad31be332c97d6c77" revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
source = "https://github.com/go-yaml/yaml"
[solve-meta] [solve-meta]
analyzer-name = "dep" analyzer-name = "dep"
analyzer-version = 1 analyzer-version = 1
inputs-digest = "37b0bbd62b245e28469686b68faccde5593acb6e719031c70018e2b1cf29e36e" inputs-digest = "de282f54a00d78e5cc1cdf8a7554929ec7b716676d49647b2d9a83910b24df74"
solver-name = "gps-cdcl" solver-name = "gps-cdcl"
solver-version = 1 solver-version = 1

View File

@ -1,95 +1,141 @@
################################ ################################
# Direct dependencies # Direct dependencies
# v3.3.0 RC
[[constraint]] [[constraint]]
name = "github.com/coreos/etcd" name = "github.com/coreos/etcd"
revision = "47a8156851b5a59665421661edb7c813f8a7993e" source = "https://github.com/coreos/etcd"
revision = "eaf6ae02e47edbea90aa7f0fe6c4d558611e00ab"
# v1.7.5
[[constraint]] [[constraint]]
name = "google.golang.org/grpc" name = "google.golang.org/grpc"
source = "https://github.com/grpc/grpc-go.git" source = "https://github.com/grpc/grpc-go"
revision = "b15215fb911b24a5d61d57feec4233d610530464" revision = "5b3c4e850e90a4cf6a20ebd46c8b32a0a3afcb9e"
[[constraint]] [[constraint]]
name = "github.com/gogo/protobuf" name = "github.com/gogo/protobuf"
source = "https://github.com/gogo/protobuf"
revision = "160de10b2537169b5ae3e7e221d28269ef40d311" revision = "160de10b2537169b5ae3e7e221d28269ef40d311"
[[constraint]] [[constraint]]
name = "github.com/golang/protobuf" name = "github.com/golang/protobuf"
source = "https://github.com/golang/protobuf"
revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845" revision = "1e59b77b52bf8e4b449a57e6f79f21226d571845"
[[constraint]]
name = "github.com/samuel/go-zookeeper"
revision = "1d7be4effb13d2d908342d349d71a284a7542693"
[[constraint]]
name = "github.com/hashicorp/consul"
revision = "42f60b04bba2e4391e9a95a218c873986ebeea6b"
[[constraint]] [[constraint]]
name = "golang.org/x/net" name = "golang.org/x/net"
revision = "c8c74377599bd978aee1cf3b9b63a8634051cec2" source = "https://github.com/golang/net"
revision = "434ec0c7fe3742c984919a691b2018a6e9694425"
[[constraint]] [[constraint]]
name = "golang.org/x/time" name = "golang.org/x/time"
revision = "a4bde12657593d5e90d0533a3e4fd95e635124cb" source = "https://github.com/golang/time"
revision = "6dc17368e09b0e8634d71cac8168d853e869a0c7"
[[constraint]]
name = "github.com/coreos/pkg"
revision = "fa29b1d70f0beaddd4c7021607cc3c3be8ce94b8"
[[constraint]]
name = "github.com/gyuho/dataframe"
revision = "73de2c550b1177c1640f3dacbbc1af00f913fedb"
[[constraint]]
name = "github.com/gyuho/linux-inspect"
revision = "db8def6abe6a96a20aae6ae01664cc3817a27ba0"
[[constraint]]
name = "github.com/gonum/plot"
revision = "51b62dc5319d7fce41240d13e780a93e640b9a38"
# https://github.com/golang/oauth2
[[constraint]]
name = "golang.org/x/oauth2"
revision = "30785a2c434e431ef7c507b54617d6a951d5f2b4"
# https://github.com/google/google-api-go-client
[[constraint]]
name = "google.golang.org/api"
revision = "e5c227fa33ebccc5a430d863efae400431fc0e85"
# https://github.com/golang/appengine
[[constraint]]
name = "google.golang.org/appengine"
revision = "5bee14b453b4c71be47ec1781b0fa61c2ea182db"
# https://github.com/GoogleCloudPlatform/google-cloud-go
[[constraint]]
name = "cloud.google.com/go"
revision = "2a6493c7a9214bf56c1003bd97443d505cc7e952"
[[constraint]]
name = "github.com/dustin/go-humanize"
revision = "ef638b6c2e62b857442c6443dace9366a48c0ee2"
[[constraint]]
name = "github.com/cheggaaa/pb"
revision = "d7e6ca3010b6f084d8056847f55d7f572f180678"
[[constraint]] [[constraint]]
name = "gopkg.in/yaml.v2" name = "gopkg.in/yaml.v2"
revision = "53feefa2559fb8dfa8d81baad31be332c97d6c77" source = "https://github.com/go-yaml/yaml"
revision = "d670f9405373e636a5a2765eea47fac0c9bc91a4"
[[constraint]]
name = "github.com/dustin/go-humanize"
source = "https://github.com/dustin/go-humanize"
revision = "bb3d318650d48840a39aa21a027c6630e198e626"
[[constraint]] [[constraint]]
name = "github.com/spf13/cobra" name = "github.com/spf13/cobra"
revision = "9495bc009a56819bdb0ddbc1a373e29c140bc674" source = "https://github.com/spf13/cobra"
revision = "b95ab734e27d33e0d8fbabf71ca990568d4e2020"
[[constraint]] [[constraint]]
name = "github.com/olekukonko/tablewriter" name = "github.com/olekukonko/tablewriter"
revision = "febf2d34b54a69ce7530036c7503b1c9fbfdf0bb" source = "https://github.com/olekukonko/tablewriter"
################################ revision = "96aac992fc8b1a4c83841a6c3e7178d20d989625"
[[constraint]]
name = "github.com/cheggaaa/pb"
source = "https://github.com/cheggaaa/pb"
revision = "18d384da9bdc1e5a08fc2a62a494c321d9ae74ea"
[[constraint]]
name = "github.com/samuel/go-zookeeper"
source = "https://github.com/samuel/go-zookeeper"
revision = "471cd4e61d7a78ece1791fa5faa0345dc8c7d5a5"
[[constraint]]
name = "github.com/hashicorp/consul"
source = "https://github.com/hashicorp/consul"
revision = "b55059fc3d0327c92c41431e57dfd2df3f956b03"
[[constraint]]
name = "github.com/gyuho/dataframe"
source = "https://github.com/gyuho/dataframe"
revision = "008fc241adc41d4bd5c54b9f6564ef16407c030e"
[[constraint]]
name = "github.com/gyuho/linux-inspect"
source = "https://github.com/gyuho/linux-inspect"
revision = "db8def6abe6a96a20aae6ae01664cc3817a27ba0"
[[constraint]]
name = "github.com/gonum/plot"
source = "https://github.com/gonum/plot"
revision = "51b62dc5319d7fce41240d13e780a93e640b9a38"
[[constraint]]
name = "github.com/coreos/pkg"
source = "https://github.com/coreos/pkg"
revision = "97fdf19511ea361ae1c100dd393cc47f8dcfa1e1"
[[constraint]]
name = "golang.org/x/oauth2"
source = "https://github.com/golang/oauth2"
revision = "30785a2c434e431ef7c507b54617d6a951d5f2b4"
[[constraint]]
name = "google.golang.org/api"
source = "https://github.com/google/google-api-go-client"
revision = "e5c227fa33ebccc5a430d863efae400431fc0e85"
[[constraint]]
name = "google.golang.org/appengine"
source = "https://github.com/golang/appengine"
revision = "5bee14b453b4c71be47ec1781b0fa61c2ea182db"
[[constraint]]
name = "cloud.google.com/go"
source = "https://github.com/GoogleCloudPlatform/google-cloud-go"
revision = "2a6493c7a9214bf56c1003bd97443d505cc7e952"
################################ ################################
# Transitive dependencies, but to override
################################
# Transitive dependencies, and overrides
# v1.3.1-coreos.6
[[override]]
name = "github.com/coreos/bbolt"
source = "https://github.com/coreos/bbolt"
revision = "48ea1b39c25fc1bab3506fbc712ecbaa842c4d2d"
# v1.3.0
[[override]]
name = "github.com/grpc-ecosystem/grpc-gateway"
source = "https://github.com/grpc-ecosystem/grpc-gateway"
revision = "8cc3a55af3bcf171a1c23a90c4df9cf591706104"
################################ ################################

View File

@ -2,7 +2,6 @@ package pb
import ( import (
"fmt" "fmt"
"strings"
"time" "time"
) )
@ -11,12 +10,26 @@ type Units int
const ( const (
// U_NO are default units, they represent a simple value and are not formatted at all. // U_NO are default units, they represent a simple value and are not formatted at all.
U_NO Units = iota U_NO Units = iota
// U_BYTES units are formatted in a human readable way (b, Bb, Mb, ...) // U_BYTES units are formatted in a human readable way (B, KiB, MiB, ...)
U_BYTES U_BYTES
// U_BYTES_DEC units are like U_BYTES, but base 10 (B, KB, MB, ...)
U_BYTES_DEC
// U_DURATION units are formatted in a human readable way (3h14m15s) // U_DURATION units are formatted in a human readable way (3h14m15s)
U_DURATION U_DURATION
) )
const (
KiB = 1024
MiB = 1048576
GiB = 1073741824
TiB = 1099511627776
KB = 1e3
MB = 1e6
GB = 1e9
TB = 1e12
)
func Format(i int64) *formatter { func Format(i int64) *formatter {
return &formatter{n: i} return &formatter{n: i}
} }
@ -28,11 +41,6 @@ type formatter struct {
perSec bool perSec bool
} }
func (f *formatter) Value(n int64) *formatter {
f.n = n
return f
}
func (f *formatter) To(unit Units) *formatter { func (f *formatter) To(unit Units) *formatter {
f.unit = unit f.unit = unit
return f return f
@ -52,13 +60,10 @@ func (f *formatter) String() (out string) {
switch f.unit { switch f.unit {
case U_BYTES: case U_BYTES:
out = formatBytes(f.n) out = formatBytes(f.n)
case U_BYTES_DEC:
out = formatBytesDec(f.n)
case U_DURATION: case U_DURATION:
d := time.Duration(f.n) out = formatDuration(f.n)
if d > time.Hour*24 {
out = fmt.Sprintf("%dd", d/24/time.Hour)
d -= (d / time.Hour / 24) * (time.Hour * 24)
}
out = fmt.Sprintf("%s%v", out, d)
default: default:
out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n) out = fmt.Sprintf(fmt.Sprintf("%%%dd", f.width), f.n)
} }
@ -68,20 +73,46 @@ func (f *formatter) String() (out string) {
return return
} }
// Convert bytes to human readable string. Like a 2 MB, 64.2 KB, 52 B // Convert bytes to human readable string. Like 2 MiB, 64.2 KiB, 52 B
func formatBytes(i int64) (result string) { func formatBytes(i int64) (result string) {
switch { switch {
case i > (1024 * 1024 * 1024 * 1024): case i >= TiB:
result = fmt.Sprintf("%.02f TB", float64(i)/1024/1024/1024/1024) result = fmt.Sprintf("%.02f TiB", float64(i)/TiB)
case i > (1024 * 1024 * 1024): case i >= GiB:
result = fmt.Sprintf("%.02f GB", float64(i)/1024/1024/1024) result = fmt.Sprintf("%.02f GiB", float64(i)/GiB)
case i > (1024 * 1024): case i >= MiB:
result = fmt.Sprintf("%.02f MB", float64(i)/1024/1024) result = fmt.Sprintf("%.02f MiB", float64(i)/MiB)
case i > 1024: case i >= KiB:
result = fmt.Sprintf("%.02f KB", float64(i)/1024) result = fmt.Sprintf("%.02f KiB", float64(i)/KiB)
default: default:
result = fmt.Sprintf("%d B", i) result = fmt.Sprintf("%d B", i)
} }
result = strings.Trim(result, " ") return
}
// Convert bytes to base-10 human readable string. Like 2 MB, 64.2 KB, 52 B
func formatBytesDec(i int64) (result string) {
switch {
case i >= TB:
result = fmt.Sprintf("%.02f TB", float64(i)/TB)
case i >= GB:
result = fmt.Sprintf("%.02f GB", float64(i)/GB)
case i >= MB:
result = fmt.Sprintf("%.02f MB", float64(i)/MB)
case i >= KB:
result = fmt.Sprintf("%.02f KB", float64(i)/KB)
default:
result = fmt.Sprintf("%d B", i)
}
return
}
func formatDuration(n int64) (result string) {
d := time.Duration(n)
if d > time.Hour*24 {
result = fmt.Sprintf("%dd", d/24/time.Hour)
d -= (d / time.Hour / 24) * (time.Hour * 24)
}
result = fmt.Sprintf("%s%v", result, d)
return return
} }

103
vendor/github.com/cheggaaa/pb/pb.go generated vendored
View File

@ -13,7 +13,7 @@ import (
) )
// Current version // Current version
const Version = "1.0.6" const Version = "1.0.19"
const ( const (
// Default refresh rate - 200ms // Default refresh rate - 200ms
@ -47,8 +47,6 @@ func New64(total int64) *ProgressBar {
Units: U_NO, Units: U_NO,
ManualUpdate: false, ManualUpdate: false,
finish: make(chan struct{}), finish: make(chan struct{}),
currentValue: -1,
mu: new(sync.Mutex),
} }
return pb.Format(FORMAT) return pb.Format(FORMAT)
} }
@ -67,7 +65,8 @@ func StartNew(total int) *ProgressBar {
type Callback func(out string) type Callback func(out string)
type ProgressBar struct { type ProgressBar struct {
current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278) current int64 // current must be first member of struct (https://code.google.com/p/go/issues/detail?id=5278)
previous int64
Total int64 Total int64
RefreshRate time.Duration RefreshRate time.Duration
@ -91,13 +90,14 @@ type ProgressBar struct {
finish chan struct{} finish chan struct{}
isFinish bool isFinish bool
startTime time.Time startTime time.Time
startValue int64 startValue int64
currentValue int64
changeTime time.Time
prefix, postfix string prefix, postfix string
mu *sync.Mutex mu sync.Mutex
lastPrint string lastPrint string
BarStart string BarStart string
@ -112,7 +112,7 @@ type ProgressBar struct {
// Start print // Start print
func (pb *ProgressBar) Start() *ProgressBar { func (pb *ProgressBar) Start() *ProgressBar {
pb.startTime = time.Now() pb.startTime = time.Now()
pb.startValue = pb.current pb.startValue = atomic.LoadInt64(&pb.current)
if pb.Total == 0 { if pb.Total == 0 {
pb.ShowTimeLeft = false pb.ShowTimeLeft = false
pb.ShowPercent = false pb.ShowPercent = false
@ -173,7 +173,7 @@ func (pb *ProgressBar) Postfix(postfix string) *ProgressBar {
// Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter // Example: bar.Format("[\x00=\x00>\x00-\x00]") // \x00 is the delimiter
func (pb *ProgressBar) Format(format string) *ProgressBar { func (pb *ProgressBar) Format(format string) *ProgressBar {
var formatEntries []string var formatEntries []string
if len(format) == 5 { if utf8.RuneCountInString(format) == 5 {
formatEntries = strings.Split(format, "") formatEntries = strings.Split(format, "")
} else { } else {
formatEntries = strings.Split(format, "\x00") formatEntries = strings.Split(format, "\x00")
@ -222,6 +222,8 @@ func (pb *ProgressBar) Finish() {
pb.finishOnce.Do(func() { pb.finishOnce.Do(func() {
close(pb.finish) close(pb.finish)
pb.write(atomic.LoadInt64(&pb.current)) pb.write(atomic.LoadInt64(&pb.current))
pb.mu.Lock()
defer pb.mu.Unlock()
switch { switch {
case pb.Output != nil: case pb.Output != nil:
fmt.Fprintln(pb.Output) fmt.Fprintln(pb.Output)
@ -232,6 +234,13 @@ func (pb *ProgressBar) Finish() {
}) })
} }
// IsFinished return boolean
func (pb *ProgressBar) IsFinished() bool {
pb.mu.Lock()
defer pb.mu.Unlock()
return pb.isFinish
}
// End print and write string 'str' // End print and write string 'str'
func (pb *ProgressBar) FinishPrint(str string) { func (pb *ProgressBar) FinishPrint(str string) {
pb.Finish() pb.Finish()
@ -290,8 +299,12 @@ func (pb *ProgressBar) write(current int64) {
} }
// time left // time left
fromStart := time.Now().Sub(pb.startTime) pb.mu.Lock()
currentFromStart := current - pb.startValue currentFromStart := current - pb.startValue
fromStart := time.Now().Sub(pb.startTime)
lastChangeTime := pb.changeTime
fromChange := lastChangeTime.Sub(pb.startTime)
pb.mu.Unlock()
select { select {
case <-pb.finish: case <-pb.finish:
if pb.ShowFinalTime { if pb.ShowFinalTime {
@ -301,17 +314,20 @@ func (pb *ProgressBar) write(current int64) {
} }
default: default:
if pb.ShowTimeLeft && currentFromStart > 0 { if pb.ShowTimeLeft && currentFromStart > 0 {
perEntry := fromStart / time.Duration(currentFromStart) perEntry := fromChange / time.Duration(currentFromStart)
var left time.Duration var left time.Duration
if pb.Total > 0 { if pb.Total > 0 {
left = time.Duration(pb.Total-currentFromStart) * perEntry left = time.Duration(pb.Total-currentFromStart) * perEntry
left -= time.Since(lastChangeTime)
left = (left / time.Second) * time.Second left = (left / time.Second) * time.Second
} else { } else {
left = time.Duration(currentFromStart) * perEntry left = time.Duration(currentFromStart) * perEntry
left = (left / time.Second) * time.Second left = (left / time.Second) * time.Second
} }
timeLeft := Format(int64(left)).To(U_DURATION).String() if left > 0 {
timeLeftBox = fmt.Sprintf(" %s", timeLeft) timeLeft := Format(int64(left)).To(U_DURATION).String()
timeLeftBox = fmt.Sprintf(" %s", timeLeft)
}
} }
} }
@ -332,24 +348,32 @@ func (pb *ProgressBar) write(current int64) {
size := width - barWidth size := width - barWidth
if size > 0 { if size > 0 {
if pb.Total > 0 { if pb.Total > 0 {
curCount := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size))) curSize := int(math.Ceil((float64(current) / float64(pb.Total)) * float64(size)))
emptCount := size - curCount emptySize := size - curSize
barBox = pb.BarStart barBox = pb.BarStart
if emptCount < 0 { if emptySize < 0 {
emptCount = 0 emptySize = 0
} }
if curCount > size { if curSize > size {
curCount = size curSize = size
} }
if emptCount <= 0 {
barBox += strings.Repeat(pb.Current, curCount) cursorLen := escapeAwareRuneCountInString(pb.Current)
} else if curCount > 0 { if emptySize <= 0 {
barBox += strings.Repeat(pb.Current, curCount-1) + pb.CurrentN barBox += strings.Repeat(pb.Current, curSize/cursorLen)
} else if curSize > 0 {
cursorEndLen := escapeAwareRuneCountInString(pb.CurrentN)
cursorRepetitions := (curSize - cursorEndLen) / cursorLen
barBox += strings.Repeat(pb.Current, cursorRepetitions)
barBox += pb.CurrentN
} }
barBox += strings.Repeat(pb.Empty, emptCount) + pb.BarEnd
emptyLen := escapeAwareRuneCountInString(pb.Empty)
barBox += strings.Repeat(pb.Empty, emptySize/emptyLen)
barBox += pb.BarEnd
} else { } else {
barBox = pb.BarStart
pos := size - int(current)%int(size) pos := size - int(current)%int(size)
barBox = pb.BarStart
if pos-1 > 0 { if pos-1 > 0 {
barBox += strings.Repeat(pb.Empty, pos-1) barBox += strings.Repeat(pb.Empty, pos-1)
} }
@ -364,16 +388,17 @@ func (pb *ProgressBar) write(current int64) {
// check len // check len
out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix out = pb.prefix + countersBox + barBox + percentBox + speedBox + timeLeftBox + pb.postfix
if escapeAwareRuneCountInString(out) < width { if cl := escapeAwareRuneCountInString(out); cl < width {
end = strings.Repeat(" ", width-utf8.RuneCountInString(out)) end = strings.Repeat(" ", width-cl)
} }
// and print! // and print!
pb.mu.Lock() pb.mu.Lock()
pb.lastPrint = out + end pb.lastPrint = out + end
isFinish := pb.isFinish
pb.mu.Unlock() pb.mu.Unlock()
switch { switch {
case pb.isFinish: case isFinish:
return return
case pb.Output != nil: case pb.Output != nil:
fmt.Fprint(pb.Output, "\r"+out+end) fmt.Fprint(pb.Output, "\r"+out+end)
@ -406,10 +431,14 @@ func (pb *ProgressBar) GetWidth() int {
// Write the current state of the progressbar // Write the current state of the progressbar
func (pb *ProgressBar) Update() { func (pb *ProgressBar) Update() {
c := atomic.LoadInt64(&pb.current) c := atomic.LoadInt64(&pb.current)
if pb.AlwaysUpdate || c != pb.currentValue { p := atomic.LoadInt64(&pb.previous)
pb.write(c) if p != c {
pb.currentValue = c pb.mu.Lock()
pb.changeTime = time.Now()
pb.mu.Unlock()
atomic.StoreInt64(&pb.previous, c)
} }
pb.write(c)
if pb.AutoStat { if pb.AutoStat {
if c == 0 { if c == 0 {
pb.startTime = time.Now() pb.startTime = time.Now()
@ -420,7 +449,10 @@ func (pb *ProgressBar) Update() {
} }
} }
// String return the last bar print
func (pb *ProgressBar) String() string { func (pb *ProgressBar) String() string {
pb.mu.Lock()
defer pb.mu.Unlock()
return pb.lastPrint return pb.lastPrint
} }
@ -435,10 +467,3 @@ func (pb *ProgressBar) refresher() {
} }
} }
} }
type window struct {
Row uint16
Col uint16
Xpixel uint16
Ypixel uint16
}

View File

@ -1,8 +0,0 @@
// +build linux darwin freebsd netbsd openbsd dragonfly
// +build !appengine
package pb
import "syscall"
const sysIoctl = syscall.SYS_IOCTL

View File

@ -1,6 +0,0 @@
// +build solaris
// +build !appengine
package pb
const sysIoctl = 54

View File

@ -8,25 +8,24 @@ import (
"fmt" "fmt"
"os" "os"
"os/signal" "os/signal"
"runtime"
"sync" "sync"
"syscall" "syscall"
"unsafe"
)
const ( "golang.org/x/sys/unix"
TIOCGWINSZ = 0x5413
TIOCGWINSZ_OSX = 1074295912
) )
var tty *os.File
var ErrPoolWasStarted = errors.New("Bar pool was started") var ErrPoolWasStarted = errors.New("Bar pool was started")
var echoLocked bool var (
var echoLockMutex sync.Mutex echoLockMutex sync.Mutex
origTermStatePtr *unix.Termios
tty *os.File
)
func init() { func init() {
echoLockMutex.Lock()
defer echoLockMutex.Unlock()
var err error var err error
tty, err = os.Open("/dev/tty") tty, err = os.Open("/dev/tty")
if err != nil { if err != nil {
@ -36,64 +35,63 @@ func init() {
// terminalWidth returns width of the terminal. // terminalWidth returns width of the terminal.
func terminalWidth() (int, error) { func terminalWidth() (int, error) {
w := new(window) echoLockMutex.Lock()
tio := syscall.TIOCGWINSZ defer echoLockMutex.Unlock()
if runtime.GOOS == "darwin" {
tio = TIOCGWINSZ_OSX fd := int(tty.Fd())
}
res, _, err := syscall.Syscall(sysIoctl, ws, err := unix.IoctlGetWinsize(fd, unix.TIOCGWINSZ)
tty.Fd(), if err != nil {
uintptr(tio),
uintptr(unsafe.Pointer(w)),
)
if int(res) == -1 {
return 0, err return 0, err
} }
return int(w.Col), nil
}
var oldState syscall.Termios return int(ws.Col), nil
}
func lockEcho() (quit chan int, err error) { func lockEcho() (quit chan int, err error) {
echoLockMutex.Lock() echoLockMutex.Lock()
defer echoLockMutex.Unlock() defer echoLockMutex.Unlock()
if echoLocked { if origTermStatePtr != nil {
err = ErrPoolWasStarted return quit, ErrPoolWasStarted
return
}
echoLocked = true
fd := tty.Fd()
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlReadTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 {
err = fmt.Errorf("Can't get terminal settings: %v", e)
return
} }
newState := oldState fd := int(tty.Fd())
newState.Lflag &^= syscall.ECHO
newState.Lflag |= syscall.ICANON | syscall.ISIG oldTermStatePtr, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
newState.Iflag |= syscall.ICRNL if err != nil {
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&newState)), 0, 0, 0); e != 0 { return nil, fmt.Errorf("Can't get terminal settings: %v", err)
err = fmt.Errorf("Can't set terminal settings: %v", e)
return
} }
oldTermios := *oldTermStatePtr
newTermios := oldTermios
newTermios.Lflag &^= syscall.ECHO
newTermios.Lflag |= syscall.ICANON | syscall.ISIG
newTermios.Iflag |= syscall.ICRNL
if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, &newTermios); err != nil {
return nil, fmt.Errorf("Can't set terminal settings: %v", err)
}
quit = make(chan int, 1) quit = make(chan int, 1)
go catchTerminate(quit) go catchTerminate(quit)
return return
} }
func unlockEcho() (err error) { func unlockEcho() error {
echoLockMutex.Lock() echoLockMutex.Lock()
defer echoLockMutex.Unlock() defer echoLockMutex.Unlock()
if !echoLocked { if origTermStatePtr == nil {
return return nil
} }
echoLocked = false
fd := tty.Fd() fd := int(tty.Fd())
if _, _, e := syscall.Syscall6(sysIoctl, fd, ioctlWriteTermios, uintptr(unsafe.Pointer(&oldState)), 0, 0, 0); e != 0 {
err = fmt.Errorf("Can't set terminal settings") if err := unix.IoctlSetTermios(fd, ioctlWriteTermios, origTermStatePtr); err != nil {
return fmt.Errorf("Can't set terminal settings: %v", err)
} }
return
origTermStatePtr = nil
return nil
} }
// listen exit signals and restore terminal state // listen exit signals and restore terminal state

View File

@ -3,6 +3,7 @@
package pb package pb
import ( import (
"io"
"sync" "sync"
"time" "time"
) )
@ -19,14 +20,19 @@ func StartPool(pbs ...*ProgressBar) (pool *Pool, err error) {
} }
type Pool struct { type Pool struct {
RefreshRate time.Duration Output io.Writer
bars []*ProgressBar RefreshRate time.Duration
quit chan int bars []*ProgressBar
finishOnce sync.Once lastBarsCount int
quit chan int
m sync.Mutex
finishOnce sync.Once
} }
// Add progress bars. // Add progress bars.
func (p *Pool) Add(pbs ...*ProgressBar) { func (p *Pool) Add(pbs ...*ProgressBar) {
p.m.Lock()
defer p.m.Unlock()
for _, bar := range pbs { for _, bar := range pbs {
bar.ManualUpdate = true bar.ManualUpdate = true
bar.NotPrint = true bar.NotPrint = true

View File

@ -8,13 +8,18 @@ import (
) )
func (p *Pool) print(first bool) bool { func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string var out string
if !first { if !first {
coords, err := getCursorPos() coords, err := getCursorPos()
if err != nil { if err != nil {
log.Panic(err) log.Panic(err)
} }
coords.Y -= int16(len(p.bars)) coords.Y -= int16(p.lastBarsCount)
if coords.Y < 0 {
coords.Y = 0
}
coords.X = 0 coords.X = 0
err = setCursorPos(coords) err = setCursorPos(coords)
@ -24,12 +29,17 @@ func (p *Pool) print(first bool) bool {
} }
isFinished := true isFinished := true
for _, bar := range p.bars { for _, bar := range p.bars {
if !bar.isFinish { if !bar.IsFinished() {
isFinished = false isFinished = false
} }
bar.Update() bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String()) out += fmt.Sprintf("\r%s\n", bar.String())
} }
fmt.Print(out) if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished return isFinished
} }

View File

@ -5,18 +5,25 @@ package pb
import "fmt" import "fmt"
func (p *Pool) print(first bool) bool { func (p *Pool) print(first bool) bool {
p.m.Lock()
defer p.m.Unlock()
var out string var out string
if !first { if !first {
out = fmt.Sprintf("\033[%dA", len(p.bars)) out = fmt.Sprintf("\033[%dA", p.lastBarsCount)
} }
isFinished := true isFinished := true
for _, bar := range p.bars { for _, bar := range p.bars {
if !bar.isFinish { if !bar.IsFinished() {
isFinished = false isFinished = false
} }
bar.Update() bar.Update()
out += fmt.Sprintf("\r%s\n", bar.String()) out += fmt.Sprintf("\r%s\n", bar.String())
} }
fmt.Print(out) if p.Output != nil {
fmt.Fprint(p.Output, out)
} else {
fmt.Print(out)
}
p.lastBarsCount = len(p.bars)
return isFinished return isFinished
} }

View File

@ -11,7 +11,7 @@ var ctrlFinder = regexp.MustCompile("\x1b\x5b[0-9]+\x6d")
func escapeAwareRuneCountInString(s string) int { func escapeAwareRuneCountInString(s string) int {
n := runewidth.StringWidth(s) n := runewidth.StringWidth(s)
for _, sm := range ctrlFinder.FindAllString(s, -1) { for _, sm := range ctrlFinder.FindAllString(s, -1) {
n -= len(sm) n -= runewidth.StringWidth(sm)
} }
return n return n
} }

View File

@ -1,7 +0,0 @@
// +build linux solaris
// +build !appengine
package pb
const ioctlReadTermios = 0x5401 // syscall.TCGETS
const ioctlWriteTermios = 0x5402 // syscall.TCSETS

13
vendor/github.com/cheggaaa/pb/termios_sysv.go generated vendored Normal file
View File

@ -0,0 +1,13 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build linux solaris
// +build !appengine
package pb
import "golang.org/x/sys/unix"
const ioctlReadTermios = unix.TCGETS
const ioctlWriteTermios = unix.TCSETS

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: auth.proto // source: auth.proto
// DO NOT EDIT!
/* /*
Package authpb is a generated protocol buffer package. Package authpb is a generated protocol buffer package.
@ -22,6 +21,8 @@ import (
math "math" math "math"
_ "github.com/gogo/protobuf/gogoproto"
io "io" io "io"
) )
@ -217,24 +218,6 @@ func (m *Role) MarshalTo(dAtA []byte) (int, error) {
return i, nil return i, nil
} }
func encodeFixed64Auth(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Auth(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintAuth(dAtA []byte, offset int, v uint64) int { func encodeVarintAuth(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 { for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80) dAtA[offset] = uint8(v&0x7f | 0x80)

View File

@ -15,11 +15,11 @@
package auth package auth
import ( import (
"context"
"crypto/rsa" "crypto/rsa"
"io/ioutil" "io/ioutil"
jwt "github.com/dgrijalva/jwt-go" jwt "github.com/dgrijalva/jwt-go"
"golang.org/x/net/context"
) )
type tokenJWT struct { type tokenJWT struct {

View File

@ -18,6 +18,7 @@ package auth
// JWT based mechanism will be added in the near future. // JWT based mechanism will be added in the near future.
import ( import (
"context"
"crypto/rand" "crypto/rand"
"fmt" "fmt"
"math/big" "math/big"
@ -25,8 +26,6 @@ import (
"strings" "strings"
"sync" "sync"
"time" "time"
"golang.org/x/net/context"
) )
const ( const (
@ -119,6 +118,9 @@ func (t *tokenSimple) genTokenPrefix() (string, error) {
func (t *tokenSimple) assignSimpleTokenToUser(username, token string) { func (t *tokenSimple) assignSimpleTokenToUser(username, token string) {
t.simpleTokensMu.Lock() t.simpleTokensMu.Lock()
defer t.simpleTokensMu.Unlock() defer t.simpleTokensMu.Unlock()
if t.simpleTokenKeeper == nil {
return
}
_, ok := t.simpleTokens[token] _, ok := t.simpleTokens[token]
if ok { if ok {
@ -186,9 +188,9 @@ func (t *tokenSimple) info(ctx context.Context, token string, revision uint64) (
func (t *tokenSimple) assign(ctx context.Context, username string, rev uint64) (string, error) { func (t *tokenSimple) assign(ctx context.Context, username string, rev uint64) (string, error) {
// rev isn't used in simple token, it is only used in JWT // rev isn't used in simple token, it is only used in JWT
index := ctx.Value("index").(uint64) index := ctx.Value(AuthenticateParamIndex{}).(uint64)
simpleToken := ctx.Value("simpleToken").(string) simpleTokenPrefix := ctx.Value(AuthenticateParamSimpleTokenPrefix{}).(string)
token := fmt.Sprintf("%s.%d", simpleToken, index) token := fmt.Sprintf("%s.%d", simpleTokenPrefix, index)
t.assignSimpleTokenToUser(username, token) t.assignSimpleTokenToUser(username, token)
return token, nil return token, nil

View File

@ -16,6 +16,7 @@ package auth
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"sort" "sort"
@ -26,9 +27,9 @@ import (
"github.com/coreos/etcd/auth/authpb" "github.com/coreos/etcd/auth/authpb"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/mvcc/backend" "github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
"golang.org/x/net/context"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer" "google.golang.org/grpc/peer"
@ -80,6 +81,12 @@ type AuthInfo struct {
Revision uint64 Revision uint64
} }
// AuthenticateParamIndex is used for a key of context in the parameters of Authenticate()
type AuthenticateParamIndex struct{}
// AuthenticateParamSimpleTokenPrefix is used for a key of context in the parameters of Authenticate()
type AuthenticateParamSimpleTokenPrefix struct{}
type AuthStore interface { type AuthStore interface {
// AuthEnable turns on the authentication feature // AuthEnable turns on the authentication feature
AuthEnable() error AuthEnable() error
@ -165,6 +172,9 @@ type AuthStore interface {
// WithRoot generates and installs a token that can be used as a root credential // WithRoot generates and installs a token that can be used as a root credential
WithRoot(ctx context.Context) context.Context WithRoot(ctx context.Context) context.Context
// HasRole checks that user has role
HasRole(user, role string) bool
} }
type TokenProvider interface { type TokenProvider interface {
@ -448,7 +458,7 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser
} }
user.Roles = append(user.Roles, r.Role) user.Roles = append(user.Roles, r.Role)
sort.Sort(sort.StringSlice(user.Roles)) sort.Strings(user.Roles)
putUser(tx, user) putUser(tx, user)
@ -463,14 +473,14 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser
func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) { func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
tx := as.be.BatchTx() tx := as.be.BatchTx()
tx.Lock() tx.Lock()
defer tx.Unlock()
var resp pb.AuthUserGetResponse
user := getUser(tx, r.Name) user := getUser(tx, r.Name)
tx.Unlock()
if user == nil { if user == nil {
return nil, ErrUserNotFound return nil, ErrUserNotFound
} }
var resp pb.AuthUserGetResponse
resp.Roles = append(resp.Roles, user.Roles...) resp.Roles = append(resp.Roles, user.Roles...)
return &resp, nil return &resp, nil
} }
@ -478,17 +488,14 @@ func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse,
func (as *authStore) UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) { func (as *authStore) UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
tx := as.be.BatchTx() tx := as.be.BatchTx()
tx.Lock() tx.Lock()
defer tx.Unlock()
var resp pb.AuthUserListResponse
users := getAllUsers(tx) users := getAllUsers(tx)
tx.Unlock()
for _, u := range users { resp := &pb.AuthUserListResponse{Users: make([]string, len(users))}
resp.Users = append(resp.Users, string(u.Name)) for i := range users {
resp.Users[i] = string(users[i].Name)
} }
return resp, nil
return &resp, nil
} }
func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) { func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
@ -549,17 +556,14 @@ func (as *authStore) RoleGet(r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse,
func (as *authStore) RoleList(r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) { func (as *authStore) RoleList(r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
tx := as.be.BatchTx() tx := as.be.BatchTx()
tx.Lock() tx.Lock()
defer tx.Unlock()
var resp pb.AuthRoleListResponse
roles := getAllRoles(tx) roles := getAllRoles(tx)
tx.Unlock()
for _, r := range roles { resp := &pb.AuthRoleListResponse{Roles: make([]string, len(roles))}
resp.Roles = append(resp.Roles, string(r.Name)) for i := range roles {
resp.Roles[i] = string(roles[i].Name)
} }
return resp, nil
return &resp, nil
} }
func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) { func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
@ -785,9 +789,9 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
tx := as.be.BatchTx() tx := as.be.BatchTx()
tx.Lock() tx.Lock()
defer tx.Unlock()
u := getUser(tx, authInfo.Username) u := getUser(tx, authInfo.Username)
tx.Unlock()
if u == nil { if u == nil {
return ErrUserNotFound return ErrUserNotFound
} }
@ -819,18 +823,15 @@ func getAllUsers(tx backend.BatchTx) []*authpb.User {
return nil return nil
} }
var users []*authpb.User users := make([]*authpb.User, len(vs))
for i := range vs {
for _, v := range vs {
user := &authpb.User{} user := &authpb.User{}
err := user.Unmarshal(v) err := user.Unmarshal(vs[i])
if err != nil { if err != nil {
plog.Panicf("failed to unmarshal user struct: %s", err) plog.Panicf("failed to unmarshal user struct: %s", err)
} }
users[i] = user
users = append(users, user)
} }
return users return users
} }
@ -866,18 +867,15 @@ func getAllRoles(tx backend.BatchTx) []*authpb.Role {
return nil return nil
} }
var roles []*authpb.Role roles := make([]*authpb.Role, len(vs))
for i := range vs {
for _, v := range vs {
role := &authpb.Role{} role := &authpb.Role{}
err := role.Unmarshal(v) err := role.Unmarshal(vs[i])
if err != nil { if err != nil {
plog.Panicf("failed to unmarshal role struct: %s", err) plog.Panicf("failed to unmarshal role struct: %s", err)
} }
roles[i] = role
roles = append(roles, role)
} }
return roles return roles
} }
@ -939,12 +937,9 @@ func NewAuthStore(be backend.Backend, tp TokenProvider) *authStore {
} }
func hasRootRole(u *authpb.User) bool { func hasRootRole(u *authpb.User) bool {
for _, r := range u.Roles { // u.Roles is sorted in UserGrantRole(), so we can use binary search.
if r == rootRole { idx := sort.SearchStrings(u.Roles, rootRole)
return true return idx != len(u.Roles) && u.Roles[idx] == rootRole
}
}
return false
} }
func (as *authStore) commitRevision(tx backend.BatchTx) { func (as *authStore) commitRevision(tx backend.BatchTx) {
@ -1073,13 +1068,13 @@ func (as *authStore) WithRoot(ctx context.Context) context.Context {
var ctxForAssign context.Context var ctxForAssign context.Context
if ts := as.tokenProvider.(*tokenSimple); ts != nil { if ts := as.tokenProvider.(*tokenSimple); ts != nil {
ctx1 := context.WithValue(ctx, "index", uint64(0)) ctx1 := context.WithValue(ctx, AuthenticateParamIndex{}, uint64(0))
prefix, err := ts.genTokenPrefix() prefix, err := ts.genTokenPrefix()
if err != nil { if err != nil {
plog.Errorf("failed to generate prefix of internally used token") plog.Errorf("failed to generate prefix of internally used token")
return ctx return ctx
} }
ctxForAssign = context.WithValue(ctx1, "simpleToken", prefix) ctxForAssign = context.WithValue(ctx1, AuthenticateParamSimpleTokenPrefix{}, prefix)
} else { } else {
ctxForAssign = ctx ctxForAssign = ctx
} }
@ -1095,5 +1090,27 @@ func (as *authStore) WithRoot(ctx context.Context) context.Context {
"token": token, "token": token,
} }
tokenMD := metadata.New(mdMap) tokenMD := metadata.New(mdMap)
return metadata.NewContext(ctx, tokenMD)
// use "mdIncomingKey{}" since it's called from local etcdserver
return metadata.NewIncomingContext(ctx, tokenMD)
}
func (as *authStore) HasRole(user, role string) bool {
tx := as.be.BatchTx()
tx.Lock()
u := getUser(tx, user)
tx.Unlock()
if u == nil {
plog.Warningf("tried to check user %s has role %s, but user %s doesn't exist", user, role, user)
return false
}
for _, r := range u.Roles {
if role == r {
return true
}
}
return false
} }

View File

@ -16,11 +16,10 @@ package client
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"golang.org/x/net/context"
) )
type Role struct { type Role struct {

View File

@ -16,12 +16,11 @@ package client
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"golang.org/x/net/context"
) )
var ( var (

View File

@ -15,6 +15,7 @@
package client package client
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -29,8 +30,6 @@ import (
"time" "time"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
"golang.org/x/net/context"
) )
var ( var (
@ -372,12 +371,7 @@ 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 { } else if resp.StatusCode/100 == 5 {
return nil, nil, err
}
continue
}
if resp.StatusCode/100 == 5 {
switch resp.StatusCode { switch resp.StatusCode {
case http.StatusInternalServerError, http.StatusServiceUnavailable: case http.StatusInternalServerError, http.StatusServiceUnavailable:
// TODO: make sure this is a no leader response // TODO: make sure this is a no leader response
@ -385,10 +379,16 @@ 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 { err = cerr.Errors[0]
return nil, nil, cerr.Errors[0] }
if err != nil {
if !isOneShot {
continue
} }
continue c.Lock()
c.pinned = (k + 1) % leps
c.Unlock()
return nil, nil, err
} }
if k != pinned { if k != pinned {
c.Lock() c.Lock()
@ -670,8 +670,15 @@ func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
} }
func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL { func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
p := r.Perm(len(eps)) // copied from Go 1.9<= rand.Rand.Perm
neps := make([]url.URL, len(eps)) n := len(eps)
p := make([]int, n)
for i := 0; i < n; i++ {
j := r.Intn(i + 1)
p[i] = p[j]
p[j] = i
}
neps := make([]url.URL, n)
for i, k := range p { for i, k := range p {
neps[i] = eps[k] neps[i] = eps[k]
} }

View File

@ -19,9 +19,9 @@ Create a Config and exchange it for a Client:
import ( import (
"net/http" "net/http"
"context"
"github.com/coreos/etcd/client" "github.com/coreos/etcd/client"
"golang.org/x/net/context"
) )
cfg := client.Config{ cfg := client.Config{

File diff suppressed because it is too large Load Diff

View File

@ -17,6 +17,7 @@ package client
//go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go //go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go
import ( import (
"context"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@ -28,7 +29,6 @@ import (
"github.com/coreos/etcd/pkg/pathutil" "github.com/coreos/etcd/pkg/pathutil"
"github.com/ugorji/go/codec" "github.com/ugorji/go/codec"
"golang.org/x/net/context"
) )
const ( const (
@ -653,8 +653,7 @@ func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Resp
default: default:
err = unmarshalFailedKeysResponse(body) err = unmarshalFailedKeysResponse(body)
} }
return res, err
return
} }
func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) { func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {

View File

@ -16,14 +16,13 @@ package client
import ( import (
"bytes" "bytes"
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"net/http" "net/http"
"net/url" "net/url"
"path" "path"
"golang.org/x/net/context"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
) )

View File

@ -15,12 +15,13 @@
package clientv3 package clientv3
import ( import (
"context"
"fmt" "fmt"
"strings" "strings"
"github.com/coreos/etcd/auth/authpb" "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"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -100,60 +101,65 @@ type Auth interface {
} }
type auth struct { type auth struct {
remote pb.AuthClient remote pb.AuthClient
callOpts []grpc.CallOption
} }
func NewAuth(c *Client) Auth { func NewAuth(c *Client) Auth {
return &auth{remote: pb.NewAuthClient(c.ActiveConnection())} api := &auth{remote: RetryAuthClient(c)}
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
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{}, grpc.FailFast(false)) resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}, auth.callOpts...)
return (*AuthEnableResponse)(resp), toErr(ctx, err) return (*AuthEnableResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) { func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) {
resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, grpc.FailFast(false)) resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, auth.callOpts...)
return (*AuthDisableResponse)(resp), toErr(ctx, err) 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}, auth.callOpts...)
return (*AuthUserAddResponse)(resp), toErr(ctx, 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}, auth.callOpts...)
return (*AuthUserDeleteResponse)(resp), toErr(ctx, 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}, auth.callOpts...)
return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err) return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) { 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}) resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}, auth.callOpts...)
return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err) return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) { func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, grpc.FailFast(false)) resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, auth.callOpts...)
return (*AuthUserGetResponse)(resp), toErr(ctx, err) return (*AuthUserGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) { func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) {
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, grpc.FailFast(false)) resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, auth.callOpts...)
return (*AuthUserListResponse)(resp), toErr(ctx, err) return (*AuthUserListResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) { 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}) resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}, auth.callOpts...)
return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, err) 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}, auth.callOpts...)
return (*AuthRoleAddResponse)(resp), toErr(ctx, err) return (*AuthRoleAddResponse)(resp), toErr(ctx, err)
} }
@ -163,27 +169,27 @@ func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, ran
RangeEnd: []byte(rangeEnd), RangeEnd: []byte(rangeEnd),
PermType: authpb.Permission_Type(permType), PermType: authpb.Permission_Type(permType),
} }
resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}) resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}, auth.callOpts...)
return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err) return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) { func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, grpc.FailFast(false)) resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, auth.callOpts...)
return (*AuthRoleGetResponse)(resp), toErr(ctx, err) return (*AuthRoleGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) { func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, grpc.FailFast(false)) resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, auth.callOpts...)
return (*AuthRoleListResponse)(resp), toErr(ctx, err) return (*AuthRoleListResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) { 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}) resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}, auth.callOpts...)
return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err) return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) { func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) {
resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}) resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}, auth.callOpts...)
return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err) return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err)
} }
@ -196,12 +202,13 @@ func StrToPermissionType(s string) (PermissionType, error) {
} }
type authenticator struct { type authenticator struct {
conn *grpc.ClientConn // conn in-use conn *grpc.ClientConn // conn in-use
remote pb.AuthClient remote pb.AuthClient
callOpts []grpc.CallOption
} }
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) { 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)) resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}, auth.callOpts...)
return (*AuthenticateResponse)(resp), toErr(ctx, err) return (*AuthenticateResponse)(resp), toErr(ctx, err)
} }
@ -209,14 +216,18 @@ func (auth *authenticator) close() {
auth.conn.Close() auth.conn.Close()
} }
func newAuthenticator(endpoint string, opts []grpc.DialOption) (*authenticator, error) { func newAuthenticator(endpoint string, opts []grpc.DialOption, c *Client) (*authenticator, error) {
conn, err := grpc.Dial(endpoint, opts...) conn, err := grpc.Dial(endpoint, opts...)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &authenticator{ api := &authenticator{
conn: conn, conn: conn,
remote: pb.NewAuthClient(conn), remote: pb.NewAuthClient(conn),
}, nil }
if c != nil {
api.callOpts = c.callOpts
}
return api, nil
} }

View File

@ -1,356 +0,0 @@
// 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"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
)
// ErrNoAddrAvilable is returned by Get() when the balancer does not have
// any active connection to endpoints at the time.
// This error is returned only when opts.BlockingWait is true.
var ErrNoAddrAvilable = grpc.Errorf(codes.Unavailable, "there is no address available")
// simpleBalancer does the bare minimum to expose multiple eps
// to the grpc reconnection code path
type simpleBalancer struct {
// addrs are the client's endpoints for grpc
addrs []grpc.Address
// notifyCh notifies grpc of the set of addresses for connecting
notifyCh chan []grpc.Address
// readyc closes once the first connection is up
readyc chan struct{}
readyOnce sync.Once
// mu protects upEps, pinAddr, and connectingAddr
mu sync.RWMutex
// upc closes when upEps transitions from empty to non-zero or the balancer closes.
upc chan struct{}
// downc closes when grpc calls down() on pinAddr
downc chan struct{}
// stopc is closed to signal updateNotifyLoop should stop.
stopc chan struct{}
// donec closes when all goroutines are exited
donec chan struct{}
// updateAddrsC notifies updateNotifyLoop to update addrs.
updateAddrsC chan struct{}
// grpc issues TLS cert checks using the string passed into dial so
// that string must be the host. To recover the full scheme://host URL,
// have a map from hosts to the original endpoint.
host2ep map[string]string
// pinAddr is the currently pinned address; set to the empty string on
// intialization and shutdown.
pinAddr string
closed bool
}
func newSimpleBalancer(eps []string) *simpleBalancer {
notifyCh := make(chan []grpc.Address, 1)
addrs := make([]grpc.Address, len(eps))
for i := range eps {
addrs[i].Addr = getHost(eps[i])
}
sb := &simpleBalancer{
addrs: addrs,
notifyCh: notifyCh,
readyc: make(chan struct{}),
upc: make(chan struct{}),
stopc: make(chan struct{}),
downc: make(chan struct{}),
donec: make(chan struct{}),
updateAddrsC: make(chan struct{}, 1),
host2ep: getHost2ep(eps),
}
close(sb.downc)
go sb.updateNotifyLoop()
return sb
}
func (b *simpleBalancer) Start(target string, config grpc.BalancerConfig) error { return nil }
func (b *simpleBalancer) ConnectNotify() <-chan struct{} {
b.mu.Lock()
defer b.mu.Unlock()
return b.upc
}
func (b *simpleBalancer) getEndpoint(host string) string {
b.mu.Lock()
defer b.mu.Unlock()
return b.host2ep[host]
}
func getHost2ep(eps []string) map[string]string {
hm := make(map[string]string, len(eps))
for i := range eps {
_, host, _ := parseEndpoint(eps[i])
hm[host] = eps[i]
}
return hm
}
func (b *simpleBalancer) updateAddrs(eps []string) {
np := getHost2ep(eps)
b.mu.Lock()
match := len(np) == len(b.host2ep)
for k, v := range np {
if b.host2ep[k] != v {
match = false
break
}
}
if match {
// same endpoints, so no need to update address
b.mu.Unlock()
return
}
b.host2ep = np
addrs := make([]grpc.Address, 0, len(eps))
for i := range eps {
addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])})
}
b.addrs = addrs
// updating notifyCh can trigger new connections,
// only update addrs if all connections are down
// or addrs does not include pinAddr.
update := !hasAddr(addrs, b.pinAddr)
b.mu.Unlock()
if update {
select {
case b.updateAddrsC <- struct{}{}:
case <-b.stopc:
}
}
}
func hasAddr(addrs []grpc.Address, targetAddr string) bool {
for _, addr := range addrs {
if targetAddr == addr.Addr {
return true
}
}
return false
}
func (b *simpleBalancer) updateNotifyLoop() {
defer close(b.donec)
for {
b.mu.RLock()
upc, downc, addr := b.upc, b.downc, b.pinAddr
b.mu.RUnlock()
// downc or upc should be closed
select {
case <-downc:
downc = nil
default:
}
select {
case <-upc:
upc = nil
default:
}
switch {
case downc == nil && upc == nil:
// stale
select {
case <-b.stopc:
return
default:
}
case downc == nil:
b.notifyAddrs()
select {
case <-upc:
case <-b.updateAddrsC:
b.notifyAddrs()
case <-b.stopc:
return
}
case upc == nil:
select {
// close connections that are not the pinned address
case b.notifyCh <- []grpc.Address{{Addr: addr}}:
case <-downc:
case <-b.stopc:
return
}
select {
case <-downc:
case <-b.updateAddrsC:
case <-b.stopc:
return
}
b.notifyAddrs()
}
}
}
func (b *simpleBalancer) notifyAddrs() {
b.mu.RLock()
addrs := b.addrs
b.mu.RUnlock()
select {
case b.notifyCh <- addrs:
case <-b.stopc:
}
}
func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
b.mu.Lock()
defer b.mu.Unlock()
// gRPC might call Up after it called Close. We add this check
// to "fix" it up at application layer. Or our simplerBalancer
// might panic since b.upc is closed.
if b.closed {
return func(err error) {}
}
// gRPC might call Up on a stale address.
// Prevent updating pinAddr with a stale address.
if !hasAddr(b.addrs, addr.Addr) {
return func(err error) {}
}
if b.pinAddr != "" {
return func(err error) {}
}
// notify waiting Get()s and pin first connected address
close(b.upc)
b.downc = make(chan struct{})
b.pinAddr = addr.Addr
// notify client that a connection is up
b.readyOnce.Do(func() { close(b.readyc) })
return func(err error) {
b.mu.Lock()
b.upc = make(chan struct{})
close(b.downc)
b.pinAddr = ""
b.mu.Unlock()
}
}
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
var (
addr string
closed bool
)
// If opts.BlockingWait is false (for fail-fast RPCs), it should return
// an address it has notified via Notify immediately instead of blocking.
if !opts.BlockingWait {
b.mu.RLock()
closed = b.closed
addr = b.pinAddr
b.mu.RUnlock()
if closed {
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
}
if addr == "" {
return grpc.Address{Addr: ""}, nil, ErrNoAddrAvilable
}
return grpc.Address{Addr: addr}, func() {}, nil
}
for {
b.mu.RLock()
ch := b.upc
b.mu.RUnlock()
select {
case <-ch:
case <-b.donec:
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
case <-ctx.Done():
return grpc.Address{Addr: ""}, nil, ctx.Err()
}
b.mu.RLock()
closed = b.closed
addr = b.pinAddr
b.mu.RUnlock()
// Close() which sets b.closed = true can be called before Get(), Get() must exit if balancer is closed.
if closed {
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
}
if addr != "" {
break
}
}
return grpc.Address{Addr: addr}, func() {}, nil
}
func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.notifyCh }
func (b *simpleBalancer) Close() error {
b.mu.Lock()
// In case gRPC calls close twice. TODO: remove the checking
// when we are sure that gRPC wont call close twice.
if b.closed {
b.mu.Unlock()
<-b.donec
return nil
}
b.closed = true
close(b.stopc)
b.pinAddr = ""
// In the case of following scenario:
// 1. upc is not closed; no pinned address
// 2. client issues an rpc, calling invoke(), which calls Get(), enters for loop, blocks
// 3. clientconn.Close() calls balancer.Close(); closed = true
// 4. for loop in Get() never exits since ctx is the context passed in by the client and may not be canceled
// we must close upc so Get() exits from blocking on upc
select {
case <-b.upc:
default:
// terminate all waiting Get()s
close(b.upc)
}
b.mu.Unlock()
// wait for updateNotifyLoop to finish
<-b.donec
close(b.notifyCh)
return nil
}
func getHost(ep string) string {
url, uerr := url.Parse(ep)
if uerr != nil || !strings.Contains(ep, "://") {
return ep
}
return url.Host
}

View File

@ -15,6 +15,7 @@
package clientv3 package clientv3
import ( import (
"context"
"crypto/tls" "crypto/tls"
"errors" "errors"
"fmt" "fmt"
@ -27,11 +28,12 @@ import (
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
) )
var ( var (
@ -51,21 +53,22 @@ type Client struct {
conn *grpc.ClientConn conn *grpc.ClientConn
dialerrc chan error dialerrc chan error
cfg Config cfg Config
creds *credentials.TransportCredentials creds *credentials.TransportCredentials
balancer *simpleBalancer balancer *healthBalancer
retryWrapper retryRpcFunc mu *sync.Mutex
retryAuthWrapper retryRpcFunc
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
// Username is a username for authentication // Username is a user name for authentication.
Username string Username string
// Password is a password for authentication // Password is a password for authentication.
Password string Password string
// tokenCred is an instance of WithPerRPCCredentials()'s argument // tokenCred is an instance of WithPerRPCCredentials()'s argument
tokenCred *authTokenCredential tokenCred *authTokenCredential
callOpts []grpc.CallOption
} }
// New creates a new etcdv3 client from a given configuration. // New creates a new etcdv3 client from a given configuration.
@ -116,8 +119,23 @@ func (c *Client) Endpoints() (eps []string) {
// SetEndpoints updates client's endpoints. // SetEndpoints updates client's endpoints.
func (c *Client) SetEndpoints(eps ...string) { func (c *Client) SetEndpoints(eps ...string) {
c.mu.Lock()
c.cfg.Endpoints = eps c.cfg.Endpoints = eps
c.balancer.updateAddrs(eps) c.mu.Unlock()
c.balancer.updateAddrs(eps...)
// updating notifyCh can trigger new connections,
// need update addrs if all connections are down
// or addrs does not include pinAddr.
c.balancer.mu.RLock()
update := !hasAddr(c.balancer.addrs, c.balancer.pinAddr)
c.balancer.mu.RUnlock()
if update {
select {
case c.balancer.updateAddrsC <- notifyNext:
case <-c.balancer.stopc:
}
}
} }
// Sync synchronizes client's endpoints with the known endpoints from the etcd membership. // Sync synchronizes client's endpoints with the known endpoints from the etcd membership.
@ -144,8 +162,10 @@ func (c *Client) autoSync() {
case <-c.ctx.Done(): case <-c.ctx.Done():
return return
case <-time.After(c.cfg.AutoSyncInterval): case <-time.After(c.cfg.AutoSyncInterval):
ctx, _ := context.WithTimeout(c.ctx, 5*time.Second) ctx, cancel := context.WithTimeout(c.ctx, 5*time.Second)
if err := c.Sync(ctx); err != nil && err != c.ctx.Err() { err := c.Sync(ctx)
cancel()
if err != nil && err != c.ctx.Err() {
logger.Println("Auto sync endpoints failed:", err) logger.Println("Auto sync endpoints failed:", err)
} }
} }
@ -174,7 +194,7 @@ func parseEndpoint(endpoint string) (proto string, host string, scheme string) {
host = endpoint host = endpoint
url, uerr := url.Parse(endpoint) url, uerr := url.Parse(endpoint)
if uerr != nil || !strings.Contains(endpoint, "://") { if uerr != nil || !strings.Contains(endpoint, "://") {
return return proto, host, scheme
} }
scheme = url.Scheme scheme = url.Scheme
@ -188,7 +208,7 @@ func parseEndpoint(endpoint string) (proto string, host string, scheme string) {
default: default:
proto, host = "", "" proto, host = "", ""
} }
return return proto, host, scheme
} }
func (c *Client) processCreds(scheme string) (creds *credentials.TransportCredentials) { func (c *Client) processCreds(scheme string) (creds *credentials.TransportCredentials) {
@ -207,7 +227,7 @@ func (c *Client) processCreds(scheme string) (creds *credentials.TransportCreden
default: default:
creds = nil creds = nil
} }
return return creds
} }
// dialSetupOpts gives the dial opts prior to any authentication // dialSetupOpts gives the dial opts prior to any authentication
@ -215,10 +235,17 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts
if c.cfg.DialTimeout > 0 { if c.cfg.DialTimeout > 0 {
opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)} opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)}
} }
if c.cfg.DialKeepAliveTime > 0 {
params := keepalive.ClientParameters{
Time: c.cfg.DialKeepAliveTime,
Timeout: c.cfg.DialKeepAliveTimeout,
}
opts = append(opts, grpc.WithKeepaliveParams(params))
}
opts = append(opts, dopts...) opts = append(opts, dopts...)
f := func(host string, t time.Duration) (net.Conn, error) { f := func(host string, t time.Duration) (net.Conn, error) {
proto, host, _ := parseEndpoint(c.balancer.getEndpoint(host)) proto, host, _ := parseEndpoint(c.balancer.endpoint(host))
if host == "" && endpoint != "" { if host == "" && endpoint != "" {
// dialing an endpoint not in the balancer; use // dialing an endpoint not in the balancer; use
// endpoint passed into dial // endpoint passed into dial
@ -270,7 +297,7 @@ func (c *Client) getToken(ctx context.Context) error {
endpoint := c.cfg.Endpoints[i] endpoint := c.cfg.Endpoints[i]
host := getHost(endpoint) host := getHost(endpoint)
// use dial options without dopts to avoid reusing the client balancer // use dial options without dopts to avoid reusing the client balancer
auth, err = newAuthenticator(host, c.dialSetupOpts(endpoint)) auth, err = newAuthenticator(host, c.dialSetupOpts(endpoint), c)
if err != nil { if err != nil {
continue continue
} }
@ -311,7 +338,7 @@ func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientCo
if err != nil { if err != nil {
if toErr(ctx, err) != rpctypes.ErrAuthNotEnabled { if toErr(ctx, err) != rpctypes.ErrAuthNotEnabled {
if err == ctx.Err() && ctx.Err() != c.ctx.Err() { if err == ctx.Err() && ctx.Err() != c.ctx.Err() {
err = grpc.ErrClientConnTimeout err = context.DeadlineExceeded
} }
return nil, err return nil, err
} }
@ -360,15 +387,37 @@ func newClient(cfg *Config) (*Client, error) {
creds: creds, creds: creds,
ctx: ctx, ctx: ctx,
cancel: cancel, cancel: cancel,
mu: new(sync.Mutex),
callOpts: defaultCallOpts,
} }
if cfg.Username != "" && cfg.Password != "" { if cfg.Username != "" && cfg.Password != "" {
client.Username = cfg.Username client.Username = cfg.Username
client.Password = cfg.Password client.Password = cfg.Password
} }
if cfg.MaxCallSendMsgSize > 0 || cfg.MaxCallRecvMsgSize > 0 {
if cfg.MaxCallRecvMsgSize > 0 && cfg.MaxCallSendMsgSize > cfg.MaxCallRecvMsgSize {
return nil, fmt.Errorf("gRPC message recv limit (%d bytes) must be greater than send limit (%d bytes)", cfg.MaxCallRecvMsgSize, cfg.MaxCallSendMsgSize)
}
callOpts := []grpc.CallOption{
defaultFailFast,
defaultMaxCallSendMsgSize,
defaultMaxCallRecvMsgSize,
}
if cfg.MaxCallSendMsgSize > 0 {
callOpts[1] = grpc.MaxCallSendMsgSize(cfg.MaxCallSendMsgSize)
}
if cfg.MaxCallRecvMsgSize > 0 {
callOpts[2] = grpc.MaxCallRecvMsgSize(cfg.MaxCallRecvMsgSize)
}
client.callOpts = callOpts
}
client.balancer = newHealthBalancer(cfg.Endpoints, cfg.DialTimeout, func(ep string) (bool, error) {
return grpcHealthCheck(client, ep)
})
client.balancer = newSimpleBalancer(cfg.Endpoints)
// use Endpoints[0] so that for https:// without any tls config given, then // use Endpoints[0] so that for https:// without any tls config given, then
// grpc will assume the ServerName is in the endpoint. // grpc will assume the certificate server name is the endpoint host.
conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer)) conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer))
if err != nil { if err != nil {
client.cancel() client.cancel()
@ -376,21 +425,19 @@ func newClient(cfg *Config) (*Client, error) {
return nil, err return nil, err
} }
client.conn = conn client.conn = conn
client.retryWrapper = client.newRetryWrapper()
client.retryAuthWrapper = client.newAuthRetryWrapper()
// wait for a connection // wait for a connection
if cfg.DialTimeout > 0 { if cfg.DialTimeout > 0 {
hasConn := false hasConn := false
waitc := time.After(cfg.DialTimeout) waitc := time.After(cfg.DialTimeout)
select { select {
case <-client.balancer.readyc: case <-client.balancer.ready():
hasConn = true hasConn = true
case <-ctx.Done(): case <-ctx.Done():
case <-waitc: case <-waitc:
} }
if !hasConn { if !hasConn {
err := grpc.ErrClientConnTimeout err := context.DeadlineExceeded
select { select {
case err = <-client.dialerrc: case err = <-client.dialerrc:
default: default:
@ -425,7 +472,7 @@ func (c *Client) checkVersion() (err error) {
errc := make(chan error, len(c.cfg.Endpoints)) errc := make(chan error, len(c.cfg.Endpoints))
ctx, cancel := context.WithCancel(c.ctx) ctx, cancel := context.WithCancel(c.ctx)
if c.cfg.DialTimeout > 0 { if c.cfg.DialTimeout > 0 {
ctx, _ = context.WithTimeout(ctx, c.cfg.DialTimeout) ctx, cancel = context.WithTimeout(ctx, c.cfg.DialTimeout)
} }
wg.Add(len(c.cfg.Endpoints)) wg.Add(len(c.cfg.Endpoints))
for _, ep := range c.cfg.Endpoints { for _, ep := range c.cfg.Endpoints {
@ -440,7 +487,7 @@ func (c *Client) checkVersion() (err error) {
vs := strings.Split(resp.Version, ".") vs := strings.Split(resp.Version, ".")
maj, min := 0, 0 maj, min := 0, 0
if len(vs) >= 2 { if len(vs) >= 2 {
maj, rerr = strconv.Atoi(vs[0]) maj, _ = strconv.Atoi(vs[0])
min, rerr = strconv.Atoi(vs[1]) min, rerr = strconv.Atoi(vs[1])
} }
if maj < 3 || (maj == 3 && min < 2) { if maj < 3 || (maj == 3 && min < 2) {
@ -472,14 +519,14 @@ func isHaltErr(ctx context.Context, err error) bool {
if err == nil { if err == nil {
return false return false
} }
code := grpc.Code(err) ev, _ := status.FromError(err)
// Unavailable codes mean the system will be right back. // Unavailable codes mean the system will be right back.
// (e.g., can't connect, lost leader) // (e.g., can't connect, lost leader)
// Treat Internal codes as if something failed, leaving the // Treat Internal codes as if something failed, leaving the
// system in an inconsistent state, but retrying could make progress. // system in an inconsistent state, but retrying could make progress.
// (e.g., failed in middle of send, corrupted frame) // (e.g., failed in middle of send, corrupted frame)
// TODO: are permanent Internal errors possible from grpc? // TODO: are permanent Internal errors possible from grpc?
return code != codes.Unavailable && code != codes.Internal return ev.Code() != codes.Unavailable && ev.Code() != codes.Internal
} }
func toErr(ctx context.Context, err error) error { func toErr(ctx context.Context, err error) error {
@ -490,7 +537,8 @@ func toErr(ctx context.Context, err error) error {
if _, ok := err.(rpctypes.EtcdError); ok { if _, ok := err.(rpctypes.EtcdError); ok {
return err return err
} }
code := grpc.Code(err) ev, _ := status.FromError(err)
code := ev.Code()
switch code { switch code {
case codes.DeadlineExceeded: case codes.DeadlineExceeded:
fallthrough fallthrough
@ -499,7 +547,6 @@ func toErr(ctx context.Context, err error) error {
err = ctx.Err() err = ctx.Err()
} }
case codes.Unavailable: case codes.Unavailable:
err = ErrNoAvailableEndpoints
case codes.FailedPrecondition: case codes.FailedPrecondition:
err = grpc.ErrClientConnClosing err = grpc.ErrClientConnClosing
} }

View File

@ -15,8 +15,10 @@
package clientv3 package clientv3
import ( import (
"context"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -43,20 +45,29 @@ type Cluster interface {
} }
type cluster struct { type cluster struct {
remote pb.ClusterClient remote pb.ClusterClient
callOpts []grpc.CallOption
} }
func NewCluster(c *Client) Cluster { func NewCluster(c *Client) Cluster {
return &cluster{remote: RetryClusterClient(c)} api := &cluster{remote: RetryClusterClient(c)}
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
func NewClusterFromClusterClient(remote pb.ClusterClient) Cluster { func NewClusterFromClusterClient(remote pb.ClusterClient, c *Client) Cluster {
return &cluster{remote: remote} api := &cluster{remote: remote}
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
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.remote.MemberAdd(ctx, r) resp, err := c.remote.MemberAdd(ctx, r, c.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -65,7 +76,7 @@ func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAdd
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.remote.MemberRemove(ctx, r) resp, err := c.remote.MemberRemove(ctx, r, c.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -74,27 +85,19 @@ func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveRes
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 { r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs}
r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs} resp, err := c.remote.MemberUpdate(ctx, r, c.callOpts...)
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) {
return nil, toErr(ctx, err)
}
} }
return nil, toErr(ctx, err)
} }
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 { resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, c.callOpts...)
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) {
return nil, toErr(ctx, err)
}
} }
return nil, toErr(ctx, err)
} }

View File

@ -44,10 +44,8 @@ func (op CompactOp) toRequest() *pb.CompactionRequest {
return &pb.CompactionRequest{Revision: op.revision, Physical: op.physical} return &pb.CompactionRequest{Revision: op.revision, Physical: op.physical}
} }
// WithCompactPhysical makes compact RPC call wait until // WithCompactPhysical makes Compact wait until all compacted entries are
// the compaction is physically applied to the local database // removed from the etcd server's storage.
// such that compacted entries are totally removed from the
// backend database.
func WithCompactPhysical() CompactOption { func WithCompactPhysical() CompactOption {
return func(op *CompactOp) { op.physical = true } return func(op *CompactOp) { op.physical = true }
} }

View File

@ -60,6 +60,8 @@ func Compare(cmp Cmp, result string, v interface{}) Cmp {
cmp.TargetUnion = &pb.Compare_CreateRevision{CreateRevision: mustInt64(v)} cmp.TargetUnion = &pb.Compare_CreateRevision{CreateRevision: mustInt64(v)}
case pb.Compare_MOD: case pb.Compare_MOD:
cmp.TargetUnion = &pb.Compare_ModRevision{ModRevision: mustInt64(v)} cmp.TargetUnion = &pb.Compare_ModRevision{ModRevision: mustInt64(v)}
case pb.Compare_LEASE:
cmp.TargetUnion = &pb.Compare_Lease{Lease: mustInt64orLeaseID(v)}
default: default:
panic("Unknown compare type") panic("Unknown compare type")
} }
@ -82,6 +84,12 @@ func ModRevision(key string) Cmp {
return Cmp{Key: []byte(key), Target: pb.Compare_MOD} return Cmp{Key: []byte(key), Target: pb.Compare_MOD}
} }
// LeaseValue compares a key's LeaseID to a value of your choosing. The empty
// LeaseID is 0, otherwise known as `NoLease`.
func LeaseValue(key string) Cmp {
return Cmp{Key: []byte(key), Target: pb.Compare_LEASE}
}
// KeyBytes returns the byte slice holding with the comparison key. // KeyBytes returns the byte slice holding with the comparison key.
func (cmp *Cmp) KeyBytes() []byte { return cmp.Key } func (cmp *Cmp) KeyBytes() []byte { return cmp.Key }
@ -111,6 +119,7 @@ func (cmp Cmp) WithPrefix() Cmp {
return cmp return cmp
} }
// mustInt64 panics if val isn't an int or int64. It returns an int64 otherwise.
func mustInt64(val interface{}) int64 { func mustInt64(val interface{}) int64 {
if v, ok := val.(int64); ok { if v, ok := val.(int64); ok {
return v return v
@ -120,3 +129,12 @@ func mustInt64(val interface{}) int64 {
} }
panic("bad value") panic("bad value")
} }
// mustInt64orLeaseID panics if val isn't a LeaseID, int or int64. It returns an
// int64 otherwise.
func mustInt64orLeaseID(val interface{}) int64 {
if v, ok := val.(LeaseID); ok {
return int64(v)
}
return mustInt64(val)
}

View File

@ -15,10 +15,10 @@
package clientv3 package clientv3
import ( import (
"context"
"crypto/tls" "crypto/tls"
"time" "time"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -33,10 +33,31 @@ type Config struct {
// DialTimeout is the timeout for failing to establish a connection. // DialTimeout is the timeout for failing to establish a connection.
DialTimeout time.Duration `json:"dial-timeout"` DialTimeout time.Duration `json:"dial-timeout"`
// DialKeepAliveTime is the time after which client pings the server to see if
// transport is alive.
DialKeepAliveTime time.Duration `json:"dial-keep-alive-time"`
// DialKeepAliveTimeout is the time that the client waits for a response for the
// keep-alive probe. If the response is not received in this time, the connection is closed.
DialKeepAliveTimeout time.Duration `json:"dial-keep-alive-timeout"`
// MaxCallSendMsgSize is the client-side request send limit in bytes.
// If 0, it defaults to 2.0 MiB (2 * 1024 * 1024).
// Make sure that "MaxCallSendMsgSize" < server-side default send/recv limit.
// ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes").
MaxCallSendMsgSize int
// MaxCallRecvMsgSize is the client-side response receive limit.
// If 0, it defaults to "math.MaxInt32", because range response can
// easily exceed request send limits.
// Make sure that "MaxCallRecvMsgSize" >= server-side default send/recv limit.
// ("--max-request-bytes" flag to etcd or "embed.Config.MaxRequestBytes").
MaxCallRecvMsgSize int
// TLS holds the client secure credentials, if any. // TLS holds the client secure credentials, if any.
TLS *tls.Config TLS *tls.Config
// Username is a username for authentication. // Username is a user name for authentication.
Username string `json:"username"` Username string `json:"username"`
// Password is a password for authentication. // Password is a password for authentication.

View File

@ -16,6 +16,22 @@
// //
// Create client using `clientv3.New`: // Create client using `clientv3.New`:
// //
// // expect dial time-out on ipv4 blackhole
// _, err := clientv3.New(clientv3.Config{
// Endpoints: []string{"http://254.0.0.1:12345"},
// DialTimeout: 2 * time.Second
// })
//
// // etcd clientv3 >= v3.2.10, grpc/grpc-go >= v1.7.3
// if err == context.DeadlineExceeded {
// // handle errors
// }
//
// // etcd clientv3 <= v3.2.9, grpc/grpc-go <= v1.2.1
// if err == grpc.ErrClientConnTimeout {
// // handle errors
// }
//
// cli, err := clientv3.New(clientv3.Config{ // cli, err := clientv3.New(clientv3.Config{
// Endpoints: []string{"localhost:2379", "localhost:22379", "localhost:32379"}, // Endpoints: []string{"localhost:2379", "localhost:22379", "localhost:32379"},
// DialTimeout: 5 * time.Second, // DialTimeout: 5 * time.Second,
@ -28,7 +44,7 @@
// Make sure to close the client after using it. If the client is not closed, the // Make sure to close the client after using it. If the client is not closed, the
// connection will have leaky goroutines. // connection will have leaky goroutines.
// //
// To specify client request timeout, pass context.WithTimeout to APIs: // To specify a client request timeout, wrap the context with context.WithTimeout:
// //
// ctx, cancel := context.WithTimeout(context.Background(), timeout) // ctx, cancel := context.WithTimeout(context.Background(), timeout)
// resp, err := kvc.Put(ctx, "sample_key", "sample_value") // resp, err := kvc.Put(ctx, "sample_key", "sample_value")
@ -41,10 +57,11 @@
// The Client has internal state (watchers and leases), so Clients should be reused instead of created as needed. // The Client has internal state (watchers and leases), so Clients should be reused instead of created as needed.
// Clients are safe for concurrent use by multiple goroutines. // Clients are safe for concurrent use by multiple goroutines.
// //
// etcd client returns 2 types of errors: // etcd client returns 3 types of errors:
// //
// 1. context error: canceled or deadline exceeded. // 1. context error: canceled or deadline exceeded.
// 2. gRPC error: see https://github.com/coreos/etcd/blob/master/etcdserver/api/v3rpc/rpctypes/error.go // 2. gRPC status error: e.g. when clock drifts in server-side before client's context deadline exceeded.
// 3. gRPC error: see https://github.com/coreos/etcd/blob/master/etcdserver/api/v3rpc/rpctypes/error.go
// //
// Here is the example code to handle client errors: // Here is the example code to handle client errors:
// //
@ -54,6 +71,12 @@
// // ctx is canceled by another routine // // ctx is canceled by another routine
// } else if err == context.DeadlineExceeded { // } else if err == context.DeadlineExceeded {
// // ctx is attached with a deadline and it exceeded // // ctx is attached with a deadline and it exceeded
// } else if ev, ok := status.FromError(err); ok {
// code := ev.Code()
// if code == codes.DeadlineExceeded {
// // server-side context might have timed-out first (due to clock skew)
// // while original client-side context is not timed-out yet
// }
// } else if verr, ok := err.(*v3rpc.ErrEmptyKey); ok { // } else if verr, ok := err.(*v3rpc.ErrEmptyKey); ok {
// // process (verr.Errors) // // process (verr.Errors)
// } else { // } else {
@ -61,4 +84,14 @@
// } // }
// } // }
// //
// go func() { cli.Close() }()
// _, err := kvc.Get(ctx, "a")
// if err != nil {
// if err == context.Canceled {
// // grpc balancer calls 'Get' with an inflight client.Close
// } else if err == grpc.ErrClientConnClosing {
// // grpc balancer calls 'Get' after client.Close.
// }
// }
//
package clientv3 package clientv3

46
vendor/github.com/coreos/etcd/clientv3/grpc_options.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
// Copyright 2017 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 (
"math"
"google.golang.org/grpc"
)
var (
// Disable gRPC internal retrial logic
// TODO: enable when gRPC retry is stable (FailFast=false)
// Reference:
// - https://github.com/grpc/grpc-go/issues/1532
// - https://github.com/grpc/proposal/blob/master/A6-client-retries.md
defaultFailFast = grpc.FailFast(true)
// client-side request send limit, gRPC default is math.MaxInt32
// Make sure that "client-side send limit < server-side default send/recv limit"
// Same value as "embed.DefaultMaxRequestBytes" plus gRPC overhead bytes
defaultMaxCallSendMsgSize = grpc.MaxCallSendMsgSize(2 * 1024 * 1024)
// client-side response receive limit, gRPC default is 4MB
// Make sure that "client-side receive limit >= server-side default send/recv limit"
// because range response can easily exceed request send limits
// Default to math.MaxInt32; writes exceeding server-side send limit fails anyway
defaultMaxCallRecvMsgSize = grpc.MaxCallRecvMsgSize(math.MaxInt32)
)
// defaultCallOpts defines a list of default "gRPC.CallOption".
// Some options are exposed to "clientv3.Config".
// Defaults will be overridden by the settings in "clientv3.Config".
var defaultCallOpts = []grpc.CallOption{defaultFailFast, defaultMaxCallSendMsgSize, defaultMaxCallRecvMsgSize}

View File

@ -0,0 +1,609 @@
// Copyright 2017 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 (
"context"
"errors"
"net/url"
"strings"
"sync"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/status"
)
const (
minHealthRetryDuration = 3 * time.Second
unknownService = "unknown service grpc.health.v1.Health"
)
// ErrNoAddrAvilable is returned by Get() when the balancer does not have
// any active connection to endpoints at the time.
// This error is returned only when opts.BlockingWait is true.
var ErrNoAddrAvilable = status.Error(codes.Unavailable, "there is no address available")
type healthCheckFunc func(ep string) (bool, error)
type notifyMsg int
const (
notifyReset notifyMsg = iota
notifyNext
)
// healthBalancer does the bare minimum to expose multiple eps
// to the grpc reconnection code path
type healthBalancer struct {
// addrs are the client's endpoint addresses for grpc
addrs []grpc.Address
// eps holds the raw endpoints from the client
eps []string
// notifyCh notifies grpc of the set of addresses for connecting
notifyCh chan []grpc.Address
// readyc closes once the first connection is up
readyc chan struct{}
readyOnce sync.Once
// healthCheck checks an endpoint's health.
healthCheck healthCheckFunc
healthCheckTimeout time.Duration
unhealthyMu sync.RWMutex
unhealthyHostPorts map[string]time.Time
// mu protects all fields below.
mu sync.RWMutex
// upc closes when pinAddr transitions from empty to non-empty or the balancer closes.
upc chan struct{}
// downc closes when grpc calls down() on pinAddr
downc chan struct{}
// stopc is closed to signal updateNotifyLoop should stop.
stopc chan struct{}
stopOnce sync.Once
wg sync.WaitGroup
// donec closes when all goroutines are exited
donec chan struct{}
// updateAddrsC notifies updateNotifyLoop to update addrs.
updateAddrsC chan notifyMsg
// grpc issues TLS cert checks using the string passed into dial so
// that string must be the host. To recover the full scheme://host URL,
// have a map from hosts to the original endpoint.
hostPort2ep map[string]string
// pinAddr is the currently pinned address; set to the empty string on
// initialization and shutdown.
pinAddr string
closed bool
}
func newHealthBalancer(eps []string, timeout time.Duration, hc healthCheckFunc) *healthBalancer {
notifyCh := make(chan []grpc.Address)
addrs := eps2addrs(eps)
hb := &healthBalancer{
addrs: addrs,
eps: eps,
notifyCh: notifyCh,
readyc: make(chan struct{}),
healthCheck: hc,
unhealthyHostPorts: make(map[string]time.Time),
upc: make(chan struct{}),
stopc: make(chan struct{}),
downc: make(chan struct{}),
donec: make(chan struct{}),
updateAddrsC: make(chan notifyMsg),
hostPort2ep: getHostPort2ep(eps),
}
if timeout < minHealthRetryDuration {
timeout = minHealthRetryDuration
}
hb.healthCheckTimeout = timeout
close(hb.downc)
go hb.updateNotifyLoop()
hb.wg.Add(1)
go func() {
defer hb.wg.Done()
hb.updateUnhealthy()
}()
return hb
}
func (b *healthBalancer) Start(target string, config grpc.BalancerConfig) error { return nil }
func (b *healthBalancer) ConnectNotify() <-chan struct{} {
b.mu.Lock()
defer b.mu.Unlock()
return b.upc
}
func (b *healthBalancer) ready() <-chan struct{} { return b.readyc }
func (b *healthBalancer) endpoint(hostPort string) string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.hostPort2ep[hostPort]
}
func (b *healthBalancer) pinned() string {
b.mu.RLock()
defer b.mu.RUnlock()
return b.pinAddr
}
func (b *healthBalancer) hostPortError(hostPort string, err error) {
if b.endpoint(hostPort) == "" {
logger.Lvl(4).Infof("clientv3/balancer: %q is stale (skip marking as unhealthy on %q)", hostPort, err.Error())
return
}
b.unhealthyMu.Lock()
b.unhealthyHostPorts[hostPort] = time.Now()
b.unhealthyMu.Unlock()
logger.Lvl(4).Infof("clientv3/balancer: %q is marked unhealthy (%q)", hostPort, err.Error())
}
func (b *healthBalancer) removeUnhealthy(hostPort, msg string) {
if b.endpoint(hostPort) == "" {
logger.Lvl(4).Infof("clientv3/balancer: %q was not in unhealthy (%q)", hostPort, msg)
return
}
b.unhealthyMu.Lock()
delete(b.unhealthyHostPorts, hostPort)
b.unhealthyMu.Unlock()
logger.Lvl(4).Infof("clientv3/balancer: %q is removed from unhealthy (%q)", hostPort, msg)
}
func (b *healthBalancer) countUnhealthy() (count int) {
b.unhealthyMu.RLock()
count = len(b.unhealthyHostPorts)
b.unhealthyMu.RUnlock()
return count
}
func (b *healthBalancer) isUnhealthy(hostPort string) (unhealthy bool) {
b.unhealthyMu.RLock()
_, unhealthy = b.unhealthyHostPorts[hostPort]
b.unhealthyMu.RUnlock()
return unhealthy
}
func (b *healthBalancer) cleanupUnhealthy() {
b.unhealthyMu.Lock()
for k, v := range b.unhealthyHostPorts {
if time.Since(v) > b.healthCheckTimeout {
delete(b.unhealthyHostPorts, k)
logger.Lvl(4).Infof("clientv3/balancer: removed %q from unhealthy after %v", k, b.healthCheckTimeout)
}
}
b.unhealthyMu.Unlock()
}
func (b *healthBalancer) liveAddrs() ([]grpc.Address, map[string]struct{}) {
unhealthyCnt := b.countUnhealthy()
b.mu.RLock()
defer b.mu.RUnlock()
hbAddrs := b.addrs
if len(b.addrs) == 1 || unhealthyCnt == 0 || unhealthyCnt == len(b.addrs) {
liveHostPorts := make(map[string]struct{}, len(b.hostPort2ep))
for k := range b.hostPort2ep {
liveHostPorts[k] = struct{}{}
}
return hbAddrs, liveHostPorts
}
addrs := make([]grpc.Address, 0, len(b.addrs)-unhealthyCnt)
liveHostPorts := make(map[string]struct{}, len(addrs))
for _, addr := range b.addrs {
if !b.isUnhealthy(addr.Addr) {
addrs = append(addrs, addr)
liveHostPorts[addr.Addr] = struct{}{}
}
}
return addrs, liveHostPorts
}
func (b *healthBalancer) updateUnhealthy() {
for {
select {
case <-time.After(b.healthCheckTimeout):
b.cleanupUnhealthy()
pinned := b.pinned()
if pinned == "" || b.isUnhealthy(pinned) {
select {
case b.updateAddrsC <- notifyNext:
case <-b.stopc:
return
}
}
case <-b.stopc:
return
}
}
}
func (b *healthBalancer) updateAddrs(eps ...string) {
np := getHostPort2ep(eps)
b.mu.Lock()
defer b.mu.Unlock()
match := len(np) == len(b.hostPort2ep)
if match {
for k, v := range np {
if b.hostPort2ep[k] != v {
match = false
break
}
}
}
if match {
// same endpoints, so no need to update address
return
}
b.hostPort2ep = np
b.addrs, b.eps = eps2addrs(eps), eps
b.unhealthyMu.Lock()
b.unhealthyHostPorts = make(map[string]time.Time)
b.unhealthyMu.Unlock()
}
func (b *healthBalancer) next() {
b.mu.RLock()
downc := b.downc
b.mu.RUnlock()
select {
case b.updateAddrsC <- notifyNext:
case <-b.stopc:
}
// wait until disconnect so new RPCs are not issued on old connection
select {
case <-downc:
case <-b.stopc:
}
}
func (b *healthBalancer) updateNotifyLoop() {
defer close(b.donec)
for {
b.mu.RLock()
upc, downc, addr := b.upc, b.downc, b.pinAddr
b.mu.RUnlock()
// downc or upc should be closed
select {
case <-downc:
downc = nil
default:
}
select {
case <-upc:
upc = nil
default:
}
switch {
case downc == nil && upc == nil:
// stale
select {
case <-b.stopc:
return
default:
}
case downc == nil:
b.notifyAddrs(notifyReset)
select {
case <-upc:
case msg := <-b.updateAddrsC:
b.notifyAddrs(msg)
case <-b.stopc:
return
}
case upc == nil:
select {
// close connections that are not the pinned address
case b.notifyCh <- []grpc.Address{{Addr: addr}}:
case <-downc:
case <-b.stopc:
return
}
select {
case <-downc:
b.notifyAddrs(notifyReset)
case msg := <-b.updateAddrsC:
b.notifyAddrs(msg)
case <-b.stopc:
return
}
}
}
}
func (b *healthBalancer) notifyAddrs(msg notifyMsg) {
if msg == notifyNext {
select {
case b.notifyCh <- []grpc.Address{}:
case <-b.stopc:
return
}
}
b.mu.RLock()
pinAddr := b.pinAddr
downc := b.downc
b.mu.RUnlock()
addrs, hostPorts := b.liveAddrs()
var waitDown bool
if pinAddr != "" {
_, ok := hostPorts[pinAddr]
waitDown = !ok
}
select {
case b.notifyCh <- addrs:
if waitDown {
select {
case <-downc:
case <-b.stopc:
}
}
case <-b.stopc:
}
}
func (b *healthBalancer) Up(addr grpc.Address) func(error) {
if !b.mayPin(addr) {
return func(err error) {}
}
b.mu.Lock()
defer b.mu.Unlock()
// gRPC might call Up after it called Close. We add this check
// to "fix" it up at application layer. Otherwise, will panic
// if b.upc is already closed.
if b.closed {
return func(err error) {}
}
// gRPC might call Up on a stale address.
// Prevent updating pinAddr with a stale address.
if !hasAddr(b.addrs, addr.Addr) {
return func(err error) {}
}
if b.pinAddr != "" {
logger.Lvl(4).Infof("clientv3/balancer: %q is up but not pinned (already pinned %q)", addr.Addr, b.pinAddr)
return func(err error) {}
}
// notify waiting Get()s and pin first connected address
close(b.upc)
b.downc = make(chan struct{})
b.pinAddr = addr.Addr
logger.Lvl(4).Infof("clientv3/balancer: pin %q", addr.Addr)
// notify client that a connection is up
b.readyOnce.Do(func() { close(b.readyc) })
return func(err error) {
// If connected to a black hole endpoint or a killed server, the gRPC ping
// timeout will induce a network I/O error, and retrying until success;
// finding healthy endpoint on retry could take several timeouts and redials.
// To avoid wasting retries, gray-list unhealthy endpoints.
b.hostPortError(addr.Addr, err)
b.mu.Lock()
b.upc = make(chan struct{})
close(b.downc)
b.pinAddr = ""
b.mu.Unlock()
logger.Lvl(4).Infof("clientv3/balancer: unpin %q (%q)", addr.Addr, err.Error())
}
}
func (b *healthBalancer) mayPin(addr grpc.Address) bool {
if b.endpoint(addr.Addr) == "" { // stale host:port
return false
}
b.unhealthyMu.RLock()
unhealthyCnt := len(b.unhealthyHostPorts)
failedTime, bad := b.unhealthyHostPorts[addr.Addr]
b.unhealthyMu.RUnlock()
b.mu.RLock()
skip := len(b.addrs) == 1 || unhealthyCnt == 0 || len(b.addrs) == unhealthyCnt
b.mu.RUnlock()
if skip || !bad {
return true
}
// prevent isolated member's endpoint from being infinitely retried, as follows:
// 1. keepalive pings detects GoAway with http2.ErrCodeEnhanceYourCalm
// 2. balancer 'Up' unpins with grpc: failed with network I/O error
// 3. grpc-healthcheck still SERVING, thus retry to pin
// instead, return before grpc-healthcheck if failed within healthcheck timeout
if elapsed := time.Since(failedTime); elapsed < b.healthCheckTimeout {
logger.Lvl(4).Infof("clientv3/balancer: %q is up but not pinned (failed %v ago, require minimum %v after failure)", addr.Addr, elapsed, b.healthCheckTimeout)
return false
}
if ok, _ := b.healthCheck(addr.Addr); ok {
b.removeUnhealthy(addr.Addr, "health check success")
return true
}
b.hostPortError(addr.Addr, errors.New("health check failed"))
return false
}
func (b *healthBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
var (
addr string
closed bool
)
// If opts.BlockingWait is false (for fail-fast RPCs), it should return
// an address it has notified via Notify immediately instead of blocking.
if !opts.BlockingWait {
b.mu.RLock()
closed = b.closed
addr = b.pinAddr
b.mu.RUnlock()
if closed {
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
}
if addr == "" {
return grpc.Address{Addr: ""}, nil, ErrNoAddrAvilable
}
return grpc.Address{Addr: addr}, func() {}, nil
}
for {
b.mu.RLock()
ch := b.upc
b.mu.RUnlock()
select {
case <-ch:
case <-b.donec:
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
case <-ctx.Done():
return grpc.Address{Addr: ""}, nil, ctx.Err()
}
b.mu.RLock()
closed = b.closed
addr = b.pinAddr
b.mu.RUnlock()
// Close() which sets b.closed = true can be called before Get(), Get() must exit if balancer is closed.
if closed {
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
}
if addr != "" {
break
}
}
return grpc.Address{Addr: addr}, func() {}, nil
}
func (b *healthBalancer) Notify() <-chan []grpc.Address { return b.notifyCh }
func (b *healthBalancer) Close() error {
b.mu.Lock()
// In case gRPC calls close twice. TODO: remove the checking
// when we are sure that gRPC wont call close twice.
if b.closed {
b.mu.Unlock()
<-b.donec
return nil
}
b.closed = true
b.stopOnce.Do(func() { close(b.stopc) })
b.pinAddr = ""
// In the case of following scenario:
// 1. upc is not closed; no pinned address
// 2. client issues an RPC, calling invoke(), which calls Get(), enters for loop, blocks
// 3. client.conn.Close() calls balancer.Close(); closed = true
// 4. for loop in Get() never exits since ctx is the context passed in by the client and may not be canceled
// we must close upc so Get() exits from blocking on upc
select {
case <-b.upc:
default:
// terminate all waiting Get()s
close(b.upc)
}
b.mu.Unlock()
b.wg.Wait()
// wait for updateNotifyLoop to finish
<-b.donec
close(b.notifyCh)
return nil
}
func grpcHealthCheck(client *Client, ep string) (bool, error) {
conn, err := client.dial(ep)
if err != nil {
return false, err
}
defer conn.Close()
cli := healthpb.NewHealthClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
resp, err := cli.Check(ctx, &healthpb.HealthCheckRequest{})
cancel()
if err != nil {
if s, ok := status.FromError(err); ok && s.Code() == codes.Unavailable {
if s.Message() == unknownService { // etcd < v3.3.0
return true, nil
}
}
return false, err
}
return resp.Status == healthpb.HealthCheckResponse_SERVING, nil
}
func hasAddr(addrs []grpc.Address, targetAddr string) bool {
for _, addr := range addrs {
if targetAddr == addr.Addr {
return true
}
}
return false
}
func getHost(ep string) string {
url, uerr := url.Parse(ep)
if uerr != nil || !strings.Contains(ep, "://") {
return ep
}
return url.Host
}
func eps2addrs(eps []string) []grpc.Address {
addrs := make([]grpc.Address, len(eps))
for i := range eps {
addrs[i].Addr = getHost(eps[i])
}
return addrs
}
func getHostPort2ep(eps []string) map[string]string {
hm := make(map[string]string, len(eps))
for i := range eps {
_, host, _ := parseEndpoint(eps[i])
hm[host] = eps[i]
}
return hm
}

View File

@ -15,8 +15,10 @@
package clientv3 package clientv3
import ( import (
"context"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -74,16 +76,38 @@ func (op OpResponse) Get() *GetResponse { return op.get }
func (op OpResponse) Del() *DeleteResponse { return op.del } func (op OpResponse) Del() *DeleteResponse { return op.del }
func (op OpResponse) Txn() *TxnResponse { return op.txn } func (op OpResponse) Txn() *TxnResponse { return op.txn }
func (resp *PutResponse) OpResponse() OpResponse {
return OpResponse{put: resp}
}
func (resp *GetResponse) OpResponse() OpResponse {
return OpResponse{get: resp}
}
func (resp *DeleteResponse) OpResponse() OpResponse {
return OpResponse{del: resp}
}
func (resp *TxnResponse) OpResponse() OpResponse {
return OpResponse{txn: resp}
}
type kv struct { type kv struct {
remote pb.KVClient remote pb.KVClient
callOpts []grpc.CallOption
} }
func NewKV(c *Client) KV { func NewKV(c *Client) KV {
return &kv{remote: RetryKVClient(c)} api := &kv{remote: RetryKVClient(c)}
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
func NewKVFromKVClient(remote pb.KVClient) KV { func NewKVFromKVClient(remote pb.KVClient, c *Client) KV {
return &kv{remote: remote} api := &kv{remote: remote}
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
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) {
@ -102,7 +126,7 @@ func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*Delete
} }
func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) { func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest()) resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest(), kv.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -111,59 +135,43 @@ func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*C
func (kv *kv) Txn(ctx context.Context) Txn { func (kv *kv) Txn(ctx context.Context) Txn {
return &txn{ return &txn{
kv: kv, kv: kv,
ctx: ctx, ctx: ctx,
callOpts: kv.callOpts,
} }
} }
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) { func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
for {
resp, err := kv.do(ctx, op)
if err == nil {
return resp, nil
}
if isHaltErr(ctx, err) {
return resp, toErr(ctx, err)
}
// do not retry on modifications
if op.isWrite() {
return resp, toErr(ctx, err)
}
}
}
func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
var err error var err error
switch op.t { switch op.t {
case tRange: case tRange:
var resp *pb.RangeResponse var resp *pb.RangeResponse
resp, err = kv.remote.Range(ctx, op.toRangeRequest(), grpc.FailFast(false)) resp, err = kv.remote.Range(ctx, op.toRangeRequest(), kv.callOpts...)
if err == nil { if err == nil {
return OpResponse{get: (*GetResponse)(resp)}, nil return OpResponse{get: (*GetResponse)(resp)}, nil
} }
case tPut: case tPut:
var resp *pb.PutResponse var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease} r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease}
resp, err = kv.remote.Put(ctx, r) resp, err = kv.remote.Put(ctx, r, kv.callOpts...)
if err == nil { if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil return OpResponse{put: (*PutResponse)(resp)}, nil
} }
case tDeleteRange: case tDeleteRange:
var resp *pb.DeleteRangeResponse var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV} r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
resp, err = kv.remote.DeleteRange(ctx, r) resp, err = kv.remote.DeleteRange(ctx, r, kv.callOpts...)
if err == nil { if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil return OpResponse{del: (*DeleteResponse)(resp)}, nil
} }
case tTxn: case tTxn:
var resp *pb.TxnResponse var resp *pb.TxnResponse
resp, err = kv.remote.Txn(ctx, op.toTxnRequest()) resp, err = kv.remote.Txn(ctx, op.toTxnRequest(), kv.callOpts...)
if err == nil { if err == nil {
return OpResponse{txn: (*TxnResponse)(resp)}, nil return OpResponse{txn: (*TxnResponse)(resp)}, nil
} }
default: default:
panic("Unknown op") panic("Unknown op")
} }
return OpResponse{}, err return OpResponse{}, toErr(ctx, err)
} }

View File

@ -15,12 +15,13 @@
package clientv3 package clientv3
import ( import (
"context"
"sync" "sync"
"time" "time"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )
@ -30,7 +31,7 @@ type (
LeaseID int64 LeaseID int64
) )
// LeaseGrantResponse is used to convert the protobuf grant response. // LeaseGrantResponse wraps the protobuf message LeaseGrantResponse.
type LeaseGrantResponse struct { type LeaseGrantResponse struct {
*pb.ResponseHeader *pb.ResponseHeader
ID LeaseID ID LeaseID
@ -38,19 +39,19 @@ type LeaseGrantResponse struct {
Error string Error string
} }
// LeaseKeepAliveResponse is used to convert the protobuf keepalive response. // LeaseKeepAliveResponse wraps the protobuf message LeaseKeepAliveResponse.
type LeaseKeepAliveResponse struct { type LeaseKeepAliveResponse struct {
*pb.ResponseHeader *pb.ResponseHeader
ID LeaseID ID LeaseID
TTL int64 TTL int64
} }
// LeaseTimeToLiveResponse is used to convert the protobuf lease timetolive response. // LeaseTimeToLiveResponse wraps the protobuf message LeaseTimeToLiveResponse.
type LeaseTimeToLiveResponse struct { type LeaseTimeToLiveResponse struct {
*pb.ResponseHeader *pb.ResponseHeader
ID LeaseID `json:"id"` ID LeaseID `json:"id"`
// TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds. // TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds. Expired lease will return -1.
TTL int64 `json:"ttl"` TTL int64 `json:"ttl"`
// GrantedTTL is the initial granted time in seconds upon lease creation/renewal. // GrantedTTL is the initial granted time in seconds upon lease creation/renewal.
@ -60,6 +61,18 @@ type LeaseTimeToLiveResponse struct {
Keys [][]byte `json:"keys"` Keys [][]byte `json:"keys"`
} }
// LeaseStatus represents a lease status.
type LeaseStatus struct {
ID LeaseID `json:"id"`
// TODO: TTL int64
}
// LeaseLeasesResponse wraps the protobuf message LeaseLeasesResponse.
type LeaseLeasesResponse struct {
*pb.ResponseHeader
Leases []LeaseStatus `json:"leases"`
}
const ( const (
// defaultTTL is the assumed lease TTL used for the first keepalive // defaultTTL is the assumed lease TTL used for the first keepalive
// deadline before the actual TTL is known to the client. // deadline before the actual TTL is known to the client.
@ -98,11 +111,32 @@ type Lease interface {
// TimeToLive retrieves the lease information of the given lease ID. // TimeToLive retrieves the lease information of the given lease ID.
TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error)
// KeepAlive keeps the given lease alive forever. // Leases retrieves all leases.
Leases(ctx context.Context) (*LeaseLeasesResponse, error)
// KeepAlive keeps the given lease alive forever. If the keepalive response
// posted to the channel is not consumed immediately, the lease client will
// continue sending keep alive requests to the etcd server at least every
// second until latest response is consumed.
//
// The returned "LeaseKeepAliveResponse" channel closes if underlying keep
// alive stream is interrupted in some way the client cannot handle itself;
// given context "ctx" is canceled or timed out. "LeaseKeepAliveResponse"
// from this closed channel is nil.
//
// If client keep alive loop halts with an unexpected error (e.g. "etcdserver:
// no leader") or canceled by the caller (e.g. context.Canceled), the error
// is returned. Otherwise, it retries.
//
// TODO(v4.0): post errors to last keep alive message before closing
// (see https://github.com/coreos/etcd/pull/7866)
KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)
// KeepAliveOnce renews the lease once. In most of the cases, Keepalive // KeepAliveOnce renews the lease once. The response corresponds to the
// should be used instead of KeepAliveOnce. // first message from calling KeepAlive. If the response has a recoverable
// error, KeepAliveOnce will retry the RPC with a new keep alive message.
//
// In most of the cases, Keepalive should be used instead of KeepAliveOnce.
KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error)
// Close releases all resources Lease keeps for efficient communication // Close releases all resources Lease keeps for efficient communication
@ -133,6 +167,8 @@ type lessor struct {
// firstKeepAliveOnce ensures stream starts after first KeepAlive call. // firstKeepAliveOnce ensures stream starts after first KeepAlive call.
firstKeepAliveOnce sync.Once firstKeepAliveOnce sync.Once
callOpts []grpc.CallOption
} }
// keepAlive multiplexes a keepalive for a lease over multiple channels // keepAlive multiplexes a keepalive for a lease over multiple channels
@ -148,10 +184,10 @@ type keepAlive struct {
} }
func NewLease(c *Client) Lease { func NewLease(c *Client) Lease {
return NewLeaseFromLeaseClient(RetryLeaseClient(c), c.cfg.DialTimeout+time.Second) return NewLeaseFromLeaseClient(RetryLeaseClient(c), c, c.cfg.DialTimeout+time.Second)
} }
func NewLeaseFromLeaseClient(remote pb.LeaseClient, keepAliveTimeout time.Duration) Lease { func NewLeaseFromLeaseClient(remote pb.LeaseClient, c *Client, keepAliveTimeout time.Duration) Lease {
l := &lessor{ l := &lessor{
donec: make(chan struct{}), donec: make(chan struct{}),
keepAlives: make(map[LeaseID]*keepAlive), keepAlives: make(map[LeaseID]*keepAlive),
@ -161,62 +197,64 @@ func NewLeaseFromLeaseClient(remote pb.LeaseClient, keepAliveTimeout time.Durati
if l.firstKeepAliveTimeout == time.Second { if l.firstKeepAliveTimeout == time.Second {
l.firstKeepAliveTimeout = defaultTTL l.firstKeepAliveTimeout = defaultTTL
} }
if c != nil {
l.callOpts = c.callOpts
}
reqLeaderCtx := WithRequireLeader(context.Background()) reqLeaderCtx := WithRequireLeader(context.Background())
l.stopCtx, l.stopCancel = context.WithCancel(reqLeaderCtx) l.stopCtx, l.stopCancel = context.WithCancel(reqLeaderCtx)
return l return l
} }
func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) { func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) {
for { r := &pb.LeaseGrantRequest{TTL: ttl}
r := &pb.LeaseGrantRequest{TTL: ttl} resp, err := l.remote.LeaseGrant(ctx, r, l.callOpts...)
resp, err := l.remote.LeaseGrant(ctx, r) if err == nil {
if err == nil { gresp := &LeaseGrantResponse{
gresp := &LeaseGrantResponse{ ResponseHeader: resp.GetHeader(),
ResponseHeader: resp.GetHeader(), ID: LeaseID(resp.ID),
ID: LeaseID(resp.ID), TTL: resp.TTL,
TTL: resp.TTL, Error: resp.Error,
Error: resp.Error,
}
return gresp, nil
}
if isHaltErr(ctx, err) {
return nil, toErr(ctx, err)
} }
return gresp, nil
} }
return nil, toErr(ctx, err)
} }
func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) { func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) {
for { r := &pb.LeaseRevokeRequest{ID: int64(id)}
r := &pb.LeaseRevokeRequest{ID: int64(id)} resp, err := l.remote.LeaseRevoke(ctx, r, l.callOpts...)
resp, err := l.remote.LeaseRevoke(ctx, r) if err == nil {
return (*LeaseRevokeResponse)(resp), nil
if err == nil {
return (*LeaseRevokeResponse)(resp), nil
}
if isHaltErr(ctx, err) {
return nil, toErr(ctx, err)
}
} }
return nil, toErr(ctx, err)
} }
func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) { func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) {
for { r := toLeaseTimeToLiveRequest(id, opts...)
r := toLeaseTimeToLiveRequest(id, opts...) resp, err := l.remote.LeaseTimeToLive(ctx, r, l.callOpts...)
resp, err := l.remote.LeaseTimeToLive(ctx, r, grpc.FailFast(false)) if err == nil {
if err == nil { gresp := &LeaseTimeToLiveResponse{
gresp := &LeaseTimeToLiveResponse{ ResponseHeader: resp.GetHeader(),
ResponseHeader: resp.GetHeader(), ID: LeaseID(resp.ID),
ID: LeaseID(resp.ID), TTL: resp.TTL,
TTL: resp.TTL, GrantedTTL: resp.GrantedTTL,
GrantedTTL: resp.GrantedTTL, Keys: resp.Keys,
Keys: resp.Keys,
}
return gresp, nil
}
if isHaltErr(ctx, err) {
return nil, toErr(ctx, err)
} }
return gresp, nil
} }
return nil, toErr(ctx, err)
}
func (l *lessor) Leases(ctx context.Context) (*LeaseLeasesResponse, error) {
resp, err := l.remote.LeaseLeases(ctx, &pb.LeaseLeasesRequest{}, l.callOpts...)
if err == nil {
leases := make([]LeaseStatus, len(resp.Leases))
for i := range resp.Leases {
leases[i] = LeaseStatus{ID: LeaseID(resp.Leases[i].ID)}
}
return &LeaseLeasesResponse{ResponseHeader: resp.GetHeader(), Leases: leases}, nil
}
return nil, toErr(ctx, err)
} }
func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) { func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {
@ -314,7 +352,7 @@ func (l *lessor) keepAliveCtxCloser(id LeaseID, ctx context.Context, donec <-cha
} }
} }
// closeRequireLeader scans all keep alives for ctxs that have require leader // closeRequireLeader scans keepAlives for ctxs that have require leader
// and closes the associated channels. // and closes the associated channels.
func (l *lessor) closeRequireLeader() { func (l *lessor) closeRequireLeader() {
l.mu.Lock() l.mu.Lock()
@ -357,7 +395,7 @@ func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAlive
cctx, cancel := context.WithCancel(ctx) cctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
stream, err := l.remote.LeaseKeepAlive(cctx, grpc.FailFast(false)) stream, err := l.remote.LeaseKeepAlive(cctx, l.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -401,7 +439,6 @@ func (l *lessor) recvKeepAliveLoop() (gerr error) {
} else { } else {
for { for {
resp, err := stream.Recv() resp, err := stream.Recv()
if err != nil { if err != nil {
if canceledByCaller(l.stopCtx, err) { if canceledByCaller(l.stopCtx, err) {
return err return err
@ -426,10 +463,10 @@ func (l *lessor) recvKeepAliveLoop() (gerr error) {
} }
} }
// resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests // resetRecv opens a new lease stream and starts sending keep alive requests.
func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) { func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) {
sctx, cancel := context.WithCancel(l.stopCtx) sctx, cancel := context.WithCancel(l.stopCtx)
stream, err := l.remote.LeaseKeepAlive(sctx, grpc.FailFast(false)) stream, err := l.remote.LeaseKeepAlive(sctx, l.callOpts...)
if err != nil { if err != nil {
cancel() cancel()
return nil, err return nil, err
@ -505,7 +542,7 @@ func (l *lessor) deadlineLoop() {
} }
} }
// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream // sendKeepAliveLoop sends keep alive requests for the lifetime of the given stream.
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) { func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
for { for {
var tosend []LeaseID var tosend []LeaseID

View File

@ -16,67 +16,120 @@ package clientv3
import ( import (
"io/ioutil" "io/ioutil"
"log"
"sync" "sync"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )
// Logger is the logger used by client library. // Logger is the logger used by client library.
// It implements grpclog.Logger interface. // It implements grpclog.LoggerV2 interface.
type Logger grpclog.Logger type Logger interface {
grpclog.LoggerV2
// Lvl returns logger if logger's verbosity level >= "lvl".
// Otherwise, logger that discards all logs.
Lvl(lvl int) Logger
// to satisfy capnslog
Print(args ...interface{})
Printf(format string, args ...interface{})
Println(args ...interface{})
}
var ( var (
logger settableLogger loggerMu sync.RWMutex
logger Logger
) )
type settableLogger struct { type settableLogger struct {
l grpclog.Logger l grpclog.LoggerV2
mu sync.RWMutex mu sync.RWMutex
} }
func init() { func init() {
// disable client side logs by default // disable client side logs by default
logger.mu.Lock() logger = &settableLogger{}
logger.l = log.New(ioutil.Discard, "", 0) SetLogger(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
// logger has to override the grpclog at initialization so that
// any changes to the grpclog go through logger with locking
// instead of through SetLogger
//
// now updates only happen through settableLogger.set
grpclog.SetLogger(&logger)
logger.mu.Unlock()
} }
// SetLogger sets client-side Logger. By default, logs are disabled. // SetLogger sets client-side Logger.
func SetLogger(l Logger) { func SetLogger(l grpclog.LoggerV2) {
logger.set(l) loggerMu.Lock()
logger = NewLogger(l)
// override grpclog so that any changes happen with locking
grpclog.SetLoggerV2(logger)
loggerMu.Unlock()
} }
// GetLogger returns the current logger. // GetLogger returns the current logger.
func GetLogger() Logger { func GetLogger() Logger {
return logger.get() loggerMu.RLock()
l := logger
loggerMu.RUnlock()
return l
} }
func (s *settableLogger) set(l Logger) { // NewLogger returns a new Logger with grpclog.LoggerV2.
s.mu.Lock() func NewLogger(gl grpclog.LoggerV2) Logger {
logger.l = l return &settableLogger{l: gl}
s.mu.Unlock()
} }
func (s *settableLogger) get() Logger { func (s *settableLogger) get() grpclog.LoggerV2 {
s.mu.RLock() s.mu.RLock()
l := logger.l l := s.l
s.mu.RUnlock() s.mu.RUnlock()
return l return l
} }
// implement the grpclog.Logger interface // implement the grpclog.LoggerV2 interface
func (s *settableLogger) Info(args ...interface{}) { s.get().Info(args...) }
func (s *settableLogger) Infof(format string, args ...interface{}) { s.get().Infof(format, args...) }
func (s *settableLogger) Infoln(args ...interface{}) { s.get().Infoln(args...) }
func (s *settableLogger) Warning(args ...interface{}) { s.get().Warning(args...) }
func (s *settableLogger) Warningf(format string, args ...interface{}) {
s.get().Warningf(format, args...)
}
func (s *settableLogger) Warningln(args ...interface{}) { s.get().Warningln(args...) }
func (s *settableLogger) Error(args ...interface{}) { s.get().Error(args...) }
func (s *settableLogger) Errorf(format string, args ...interface{}) {
s.get().Errorf(format, args...)
}
func (s *settableLogger) Errorln(args ...interface{}) { s.get().Errorln(args...) }
func (s *settableLogger) Fatal(args ...interface{}) { s.get().Fatal(args...) } func (s *settableLogger) Fatal(args ...interface{}) { s.get().Fatal(args...) }
func (s *settableLogger) Fatalf(format string, args ...interface{}) { s.get().Fatalf(format, args...) } func (s *settableLogger) Fatalf(format string, args ...interface{}) { s.get().Fatalf(format, args...) }
func (s *settableLogger) Fatalln(args ...interface{}) { s.get().Fatalln(args...) } func (s *settableLogger) Fatalln(args ...interface{}) { s.get().Fatalln(args...) }
func (s *settableLogger) Print(args ...interface{}) { s.get().Print(args...) } func (s *settableLogger) Print(args ...interface{}) { s.get().Info(args...) }
func (s *settableLogger) Printf(format string, args ...interface{}) { s.get().Printf(format, args...) } func (s *settableLogger) Printf(format string, args ...interface{}) { s.get().Infof(format, args...) }
func (s *settableLogger) Println(args ...interface{}) { s.get().Println(args...) } func (s *settableLogger) Println(args ...interface{}) { s.get().Infoln(args...) }
func (s *settableLogger) V(l int) bool { return s.get().V(l) }
func (s *settableLogger) Lvl(lvl int) Logger {
s.mu.RLock()
l := s.l
s.mu.RUnlock()
if l.V(lvl) {
return s
}
return &noLogger{}
}
type noLogger struct{}
func (*noLogger) Info(args ...interface{}) {}
func (*noLogger) Infof(format string, args ...interface{}) {}
func (*noLogger) Infoln(args ...interface{}) {}
func (*noLogger) Warning(args ...interface{}) {}
func (*noLogger) Warningf(format string, args ...interface{}) {}
func (*noLogger) Warningln(args ...interface{}) {}
func (*noLogger) Error(args ...interface{}) {}
func (*noLogger) Errorf(format string, args ...interface{}) {}
func (*noLogger) Errorln(args ...interface{}) {}
func (*noLogger) Fatal(args ...interface{}) {}
func (*noLogger) Fatalf(format string, args ...interface{}) {}
func (*noLogger) Fatalln(args ...interface{}) {}
func (*noLogger) Print(args ...interface{}) {}
func (*noLogger) Printf(format string, args ...interface{}) {}
func (*noLogger) Println(args ...interface{}) {}
func (*noLogger) V(l int) bool { return false }
func (ng *noLogger) Lvl(lvl int) Logger { return ng }

View File

@ -15,11 +15,11 @@
package clientv3 package clientv3
import ( import (
"context"
"io" "io"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
@ -28,6 +28,8 @@ type (
AlarmResponse pb.AlarmResponse AlarmResponse pb.AlarmResponse
AlarmMember pb.AlarmMember AlarmMember pb.AlarmMember
StatusResponse pb.StatusResponse StatusResponse pb.StatusResponse
HashKVResponse pb.HashKVResponse
MoveLeaderResponse pb.MoveLeaderResponse
) )
type Maintenance interface { type Maintenance interface {
@ -37,7 +39,7 @@ type Maintenance interface {
// AlarmDisarm disarms a given alarm. // AlarmDisarm disarms a given alarm.
AlarmDisarm(ctx context.Context, m *AlarmMember) (*AlarmResponse, error) AlarmDisarm(ctx context.Context, m *AlarmMember) (*AlarmResponse, error)
// Defragment defragments storage backend of the etcd member with given endpoint. // Defragment releases wasted space from internal fragmentation on a given etcd member.
// Defragment is only needed when deleting a large number of keys and want to reclaim // Defragment is only needed when deleting a large number of keys and want to reclaim
// the resources. // the resources.
// Defragment is an expensive operation. User should avoid defragmenting multiple members // Defragment is an expensive operation. User should avoid defragmenting multiple members
@ -49,36 +51,54 @@ type Maintenance interface {
// Status gets the status of the endpoint. // 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. // HashKV returns a hash of the KV state at the time of the RPC.
// If revision is zero, the hash is computed on all keys. If the revision
// is non-zero, the hash is computed on all keys at or below the given revision.
HashKV(ctx context.Context, endpoint string, rev int64) (*HashKVResponse, error)
// Snapshot provides a reader for a point-in-time snapshot of etcd.
Snapshot(ctx context.Context) (io.ReadCloser, error) Snapshot(ctx context.Context) (io.ReadCloser, error)
// MoveLeader requests current leader to transfer its leadership to the transferee.
// Request must be made to the leader.
MoveLeader(ctx context.Context, transfereeID uint64) (*MoveLeaderResponse, error)
} }
type maintenance struct { type maintenance struct {
dial func(endpoint string) (pb.MaintenanceClient, func(), error) dial func(endpoint string) (pb.MaintenanceClient, func(), error)
remote pb.MaintenanceClient remote pb.MaintenanceClient
callOpts []grpc.CallOption
} }
func NewMaintenance(c *Client) Maintenance { func NewMaintenance(c *Client) Maintenance {
return &maintenance{ api := &maintenance{
dial: func(endpoint string) (pb.MaintenanceClient, func(), error) { dial: func(endpoint string) (pb.MaintenanceClient, func(), error) {
conn, err := c.dial(endpoint) conn, err := c.dial(endpoint)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
cancel := func() { conn.Close() } cancel := func() { conn.Close() }
return pb.NewMaintenanceClient(conn), cancel, nil return RetryMaintenanceClient(c, conn), cancel, nil
}, },
remote: pb.NewMaintenanceClient(c.conn), remote: RetryMaintenanceClient(c, c.conn),
} }
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient) Maintenance { func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient, c *Client) Maintenance {
return &maintenance{ api := &maintenance{
dial: func(string) (pb.MaintenanceClient, func(), error) { dial: func(string) (pb.MaintenanceClient, func(), error) {
return remote, func() {}, nil return remote, func() {}, nil
}, },
remote: remote, remote: remote,
} }
if c != nil {
api.callOpts = c.callOpts
}
return api
} }
func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) { func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
@ -87,15 +107,11 @@ func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
MemberID: 0, // all MemberID: 0, // all
Alarm: pb.AlarmType_NONE, // all Alarm: pb.AlarmType_NONE, // all
} }
for { resp, err := m.remote.Alarm(ctx, req, m.callOpts...)
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 isHaltErr(ctx, err) {
return nil, toErr(ctx, err)
}
} }
return nil, toErr(ctx, err)
} }
func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmResponse, error) { func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmResponse, error) {
@ -121,7 +137,7 @@ func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmR
return &ret, nil return &ret, nil
} }
resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false)) resp, err := m.remote.Alarm(ctx, req, m.callOpts...)
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
@ -134,7 +150,7 @@ func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*Defragm
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
defer cancel() defer cancel()
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, grpc.FailFast(false)) resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, m.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -147,15 +163,28 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
defer cancel() defer cancel()
resp, err := remote.Status(ctx, &pb.StatusRequest{}, grpc.FailFast(false)) resp, err := remote.Status(ctx, &pb.StatusRequest{}, m.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
return (*StatusResponse)(resp), nil return (*StatusResponse)(resp), nil
} }
func (m *maintenance) HashKV(ctx context.Context, endpoint string, rev int64) (*HashKVResponse, error) {
remote, cancel, err := m.dial(endpoint)
if err != nil {
return nil, toErr(ctx, err)
}
defer cancel()
resp, err := remote.HashKV(ctx, &pb.HashKVRequest{Revision: rev}, m.callOpts...)
if err != nil {
return nil, toErr(ctx, err)
}
return (*HashKVResponse)(resp), nil
}
func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) { func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, grpc.FailFast(false)) ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, m.callOpts...)
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -178,5 +207,20 @@ func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
} }
pw.Close() pw.Close()
}() }()
return pr, nil return &snapshotReadCloser{ctx: ctx, ReadCloser: pr}, nil
}
type snapshotReadCloser struct {
ctx context.Context
io.ReadCloser
}
func (rc *snapshotReadCloser) Read(p []byte) (n int, err error) {
n, err = rc.ReadCloser.Read(p)
return n, toErr(rc.ctx, err)
}
func (m *maintenance) MoveLeader(ctx context.Context, transfereeID uint64) (*MoveLeaderResponse, error) {
resp, err := m.remote.MoveLeader(ctx, &pb.MoveLeaderRequest{TargetID: transfereeID}, m.callOpts...)
return (*MoveLeaderResponse)(resp), toErr(ctx, err)
} }

View File

@ -75,7 +75,7 @@ type Op struct {
elseOps []Op elseOps []Op
} }
// accesors / mutators // accessors / mutators
func (op Op) IsTxn() bool { return op.t == tTxn } func (op Op) IsTxn() bool { return op.t == tTxn }
func (op Op) Txn() ([]Cmp, []Op, []Op) { return op.cmps, op.thenOps, op.elseOps } func (op Op) Txn() ([]Cmp, []Op, []Op) { return op.cmps, op.thenOps, op.elseOps }
@ -89,6 +89,39 @@ func (op *Op) WithKeyBytes(key []byte) { op.key = key }
// RangeBytes returns the byte slice holding with the Op's range end, if any. // RangeBytes returns the byte slice holding with the Op's range end, if any.
func (op Op) RangeBytes() []byte { return op.end } func (op Op) RangeBytes() []byte { return op.end }
// Rev returns the requested revision, if any.
func (op Op) Rev() int64 { return op.rev }
// IsPut returns true iff the operation is a Put.
func (op Op) IsPut() bool { return op.t == tPut }
// IsGet returns true iff the operation is a Get.
func (op Op) IsGet() bool { return op.t == tRange }
// IsDelete returns true iff the operation is a Delete.
func (op Op) IsDelete() bool { return op.t == tDeleteRange }
// IsSerializable returns true if the serializable field is true.
func (op Op) IsSerializable() bool { return op.serializable == true }
// IsKeysOnly returns whether keysOnly is set.
func (op Op) IsKeysOnly() bool { return op.keysOnly == true }
// IsCountOnly returns whether countOnly is set.
func (op Op) IsCountOnly() bool { return op.countOnly == true }
// MinModRev returns the operation's minimum modify revision.
func (op Op) MinModRev() int64 { return op.minModRev }
// MaxModRev returns the operation's maximum modify revision.
func (op Op) MaxModRev() int64 { return op.maxModRev }
// MinCreateRev returns the operation's minimum create revision.
func (op Op) MinCreateRev() int64 { return op.minCreateRev }
// MaxCreateRev returns the operation's maximum create revision.
func (op Op) MaxCreateRev() int64 { return op.maxCreateRev }
// WithRangeBytes sets the byte slice for the Op's range end. // WithRangeBytes sets the byte slice for the Op's range end.
func (op *Op) WithRangeBytes(end []byte) { op.end = end } func (op *Op) WithRangeBytes(end []byte) { op.end = end }
@ -291,9 +324,9 @@ func WithSort(target SortTarget, order SortOrder) OpOption {
if target == SortByKey && order == SortAscend { if target == SortByKey && order == SortAscend {
// If order != SortNone, server fetches the entire key-space, // If order != SortNone, server fetches the entire key-space,
// and then applies the sort and limit, if provided. // and then applies the sort and limit, if provided.
// Since current mvcc.Range implementation returns results // Since by default the server returns results sorted by keys
// sorted by keys in lexicographically ascending order, // in lexicographically ascending order, the client should ignore
// client should ignore SortOrder if the target is SortByKey. // SortOrder if the target is SortByKey.
order = SortNone order = SortNone
} }
op.sort = &SortOption{target, order} op.sort = &SortOption{target, order}
@ -434,7 +467,7 @@ func WithPrevKV() OpOption {
} }
// WithIgnoreValue updates the key using its current value. // WithIgnoreValue updates the key using its current value.
// Empty value should be passed when ignore_value is set. // This option can not be combined with non-empty values.
// Returns an error if the key does not exist. // Returns an error if the key does not exist.
func WithIgnoreValue() OpOption { func WithIgnoreValue() OpOption {
return func(op *Op) { return func(op *Op) {
@ -443,7 +476,7 @@ func WithIgnoreValue() OpOption {
} }
// WithIgnoreLease updates the key using its current lease. // WithIgnoreLease updates the key using its current lease.
// Empty lease should be passed when ignore_lease is set. // This option can not be combined with WithLease.
// Returns an error if the key does not exist. // Returns an error if the key does not exist.
func WithIgnoreLease() OpOption { func WithIgnoreLease() OpOption {
return func(op *Op) { return func(op *Op) {
@ -468,8 +501,7 @@ func (op *LeaseOp) applyOpts(opts []LeaseOption) {
} }
} }
// WithAttachedKeys requests lease timetolive API to return // WithAttachedKeys makes TimeToLive list the keys attached to the given lease ID.
// attached keys of given lease ID.
func WithAttachedKeys() LeaseOption { func WithAttachedKeys() LeaseOption {
return func(op *LeaseOp) { op.attachedKeys = true } return func(op *LeaseOp) { op.attachedKeys = true }
} }

View File

@ -1,4 +1,4 @@
// Copyright 2013-2015 CoreOS, Inc. // Copyright 2017 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.
@ -12,27 +12,19 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
package semver package clientv3
import ( import "context"
"sort"
)
type Versions []*Version // TODO: remove this when "FailFast=false" is fixed.
// See https://github.com/grpc/grpc-go/issues/1532.
func (s Versions) Len() int { func readyWait(rpcCtx, clientCtx context.Context, ready <-chan struct{}) error {
return len(s) select {
} case <-ready:
return nil
func (s Versions) Swap(i, j int) { case <-rpcCtx.Done():
s[i], s[j] = s[j], s[i] return rpcCtx.Err()
} case <-clientCtx.Done():
return clientCtx.Err()
func (s Versions) Less(i, j int) bool { }
return s[i].LessThan(*s[j])
}
// Sort sorts the given slice of Version
func Sort(versions []*Version) {
sort.Sort(Versions(versions))
} }

View File

@ -15,279 +15,482 @@
package clientv3 package clientv3
import ( import (
"context"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type retryPolicy uint8
const (
repeatable retryPolicy = iota
nonRepeatable
) )
type rpcFunc func(ctx context.Context) error type rpcFunc func(ctx context.Context) error
type retryRpcFunc func(context.Context, rpcFunc) error type retryRPCFunc func(context.Context, rpcFunc, retryPolicy) error
type retryStopErrFunc func(error) bool
func (c *Client) newRetryWrapper() retryRpcFunc { // immutable requests (e.g. Get) should be retried unless it's
return func(rpcCtx context.Context, f rpcFunc) error { // an obvious server-side error (e.g. rpctypes.ErrRequestTooLarge).
//
// "isRepeatableStopError" returns "true" when an immutable request
// is interrupted by server-side or gRPC-side error and its status
// code is not transient (!= codes.Unavailable).
//
// Returning "true" means retry should stop, since client cannot
// handle itself even with retries.
func isRepeatableStopError(err error) bool {
eErr := rpctypes.Error(err)
// always stop retry on etcd errors
if serverErr, ok := eErr.(rpctypes.EtcdError); ok && serverErr.Code() != codes.Unavailable {
return true
}
// only retry if unavailable
ev, _ := status.FromError(err)
return ev.Code() != codes.Unavailable
}
// mutable requests (e.g. Put, Delete, Txn) should only be retried
// when the status code is codes.Unavailable when initial connection
// has not been established (no pinned endpoint).
//
// "isNonRepeatableStopError" returns "true" when a mutable request
// is interrupted by non-transient error that client cannot handle itself,
// or transient error while the connection has already been established
// (pinned endpoint exists).
//
// Returning "true" means retry should stop, otherwise it violates
// write-at-most-once semantics.
func isNonRepeatableStopError(err error) bool {
ev, _ := status.FromError(err)
if ev.Code() != codes.Unavailable {
return true
}
desc := rpctypes.ErrorDesc(err)
return desc != "there is no address available" && desc != "there is no connection available"
}
func (c *Client) newRetryWrapper() retryRPCFunc {
return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error {
var isStop retryStopErrFunc
switch rp {
case repeatable:
isStop = isRepeatableStopError
case nonRepeatable:
isStop = isNonRepeatableStopError
}
for { for {
if err := readyWait(rpcCtx, c.ctx, c.balancer.ConnectNotify()); err != nil {
return err
}
pinned := c.balancer.pinned()
err := f(rpcCtx) err := f(rpcCtx)
if err == nil { if err == nil {
return nil return nil
} }
logger.Lvl(4).Infof("clientv3/retry: error %q on pinned endpoint %q", err.Error(), pinned)
eErr := rpctypes.Error(err) if s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded || s.Code() == codes.Internal) {
// always stop retry on etcd errors // mark this before endpoint switch is triggered
if _, ok := eErr.(rpctypes.EtcdError); ok { c.balancer.hostPortError(pinned, err)
return err c.balancer.next()
logger.Lvl(4).Infof("clientv3/retry: switching from %q due to error %q", pinned, err.Error())
} }
// only retry if unavailable if isStop(err) {
if grpc.Code(err) != codes.Unavailable {
return err return err
} }
select {
case <-c.balancer.ConnectNotify():
case <-rpcCtx.Done():
return rpcCtx.Err()
case <-c.ctx.Done():
return c.ctx.Err()
}
} }
} }
} }
func (c *Client) newAuthRetryWrapper() retryRpcFunc { func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc {
return func(rpcCtx context.Context, f rpcFunc) error { return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error {
for { for {
err := f(rpcCtx) pinned := c.balancer.pinned()
err := retryf(rpcCtx, f, rp)
if err == nil { if err == nil {
return nil return nil
} }
logger.Lvl(4).Infof("clientv3/auth-retry: error %q on pinned endpoint %q", err.Error(), pinned)
// always stop retry on etcd errors other than invalid auth token // always stop retry on etcd errors other than invalid auth token
if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken {
gterr := c.getToken(rpcCtx) gterr := c.getToken(rpcCtx)
if gterr != nil { if gterr != nil {
logger.Lvl(4).Infof("clientv3/auth-retry: cannot retry due to error %q(%q) on pinned endpoint %q", err.Error(), gterr.Error(), pinned)
return err // return the original error for simplicity return err // return the original error for simplicity
} }
continue continue
} }
return err return err
} }
} }
} }
// RetryKVClient implements a KVClient that uses the client's FailFast retry policy.
func RetryKVClient(c *Client) pb.KVClient {
retryWrite := &retryWriteKVClient{pb.NewKVClient(c.conn), c.retryWrapper}
return &retryKVClient{&retryWriteKVClient{retryWrite, c.retryAuthWrapper}}
}
type retryKVClient struct { type retryKVClient struct {
*retryWriteKVClient kc pb.KVClient
retryf retryRPCFunc
} }
// RetryKVClient implements a KVClient.
func RetryKVClient(c *Client) pb.KVClient {
return &retryKVClient{
kc: pb.NewKVClient(c.conn),
retryf: c.newAuthRetryWrapper(c.newRetryWrapper()),
}
}
func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) { func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) {
err = rkv.retryf(ctx, func(rctx context.Context) error { err = rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.retryWriteKVClient.Range(rctx, in, opts...) resp, err = rkv.kc.Range(rctx, in, opts...)
return err return err
}) }, repeatable)
return resp, err return resp, err
} }
type retryWriteKVClient struct { func (rkv *retryKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) {
pb.KVClient
retryf retryRpcFunc
}
func (rkv *retryWriteKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) {
err = rkv.retryf(ctx, func(rctx context.Context) error { err = rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Put(rctx, in, opts...) resp, err = rkv.kc.Put(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rkv *retryWriteKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) { func (rkv *retryKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) {
err = rkv.retryf(ctx, func(rctx context.Context) error { err = rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.DeleteRange(rctx, in, opts...) resp, err = rkv.kc.DeleteRange(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rkv *retryWriteKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) { func (rkv *retryKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) {
// TODO: "repeatable" for read-only txn
err = rkv.retryf(ctx, func(rctx context.Context) error { err = rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Txn(rctx, in, opts...) resp, err = rkv.kc.Txn(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rkv *retryWriteKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) { func (rkv *retryKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) {
err = rkv.retryf(ctx, func(rctx context.Context) error { err = rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Compact(rctx, in, opts...) resp, err = rkv.kc.Compact(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
type retryLeaseClient struct { type retryLeaseClient struct {
pb.LeaseClient lc pb.LeaseClient
retryf retryRpcFunc retryf retryRPCFunc
} }
// RetryLeaseClient implements a LeaseClient that uses the client's FailFast retry policy. // RetryLeaseClient implements a LeaseClient.
func RetryLeaseClient(c *Client) pb.LeaseClient { func RetryLeaseClient(c *Client) pb.LeaseClient {
retry := &retryLeaseClient{pb.NewLeaseClient(c.conn), c.retryWrapper} return &retryLeaseClient{
return &retryLeaseClient{retry, c.retryAuthWrapper} lc: pb.NewLeaseClient(c.conn),
retryf: c.newAuthRetryWrapper(c.newRetryWrapper()),
}
}
func (rlc *retryLeaseClient) LeaseTimeToLive(ctx context.Context, in *pb.LeaseTimeToLiveRequest, opts ...grpc.CallOption) (resp *pb.LeaseTimeToLiveResponse, err error) {
err = rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.lc.LeaseTimeToLive(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rlc *retryLeaseClient) LeaseLeases(ctx context.Context, in *pb.LeaseLeasesRequest, opts ...grpc.CallOption) (resp *pb.LeaseLeasesResponse, err error) {
err = rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.lc.LeaseLeases(rctx, in, opts...)
return err
}, repeatable)
return resp, err
} }
func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) { func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) {
err = rlc.retryf(ctx, func(rctx context.Context) error { err = rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.LeaseClient.LeaseGrant(rctx, in, opts...) resp, err = rlc.lc.LeaseGrant(rctx, in, opts...)
return err return err
}) }, repeatable)
return resp, err return resp, err
} }
func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) { func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) {
err = rlc.retryf(ctx, func(rctx context.Context) error { err = rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.LeaseClient.LeaseRevoke(rctx, in, opts...) resp, err = rlc.lc.LeaseRevoke(rctx, in, opts...)
return err return err
}) }, repeatable)
return resp, err return resp, err
} }
type retryClusterClient struct { func (rlc *retryLeaseClient) LeaseKeepAlive(ctx context.Context, opts ...grpc.CallOption) (stream pb.Lease_LeaseKeepAliveClient, err error) {
pb.ClusterClient err = rlc.retryf(ctx, func(rctx context.Context) error {
retryf retryRpcFunc stream, err = rlc.lc.LeaseKeepAlive(rctx, opts...)
return err
}, repeatable)
return stream, err
} }
// RetryClusterClient implements a ClusterClient that uses the client's FailFast retry policy. type retryClusterClient struct {
cc pb.ClusterClient
retryf retryRPCFunc
}
// RetryClusterClient implements a ClusterClient.
func RetryClusterClient(c *Client) pb.ClusterClient { func RetryClusterClient(c *Client) pb.ClusterClient {
return &retryClusterClient{pb.NewClusterClient(c.conn), c.retryWrapper} return &retryClusterClient{
cc: pb.NewClusterClient(c.conn),
retryf: c.newRetryWrapper(),
}
}
func (rcc *retryClusterClient) MemberList(ctx context.Context, in *pb.MemberListRequest, opts ...grpc.CallOption) (resp *pb.MemberListResponse, err error) {
err = rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.cc.MemberList(rctx, in, opts...)
return err
}, repeatable)
return resp, err
} }
func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) { func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) {
err = rcc.retryf(ctx, func(rctx context.Context) error { err = rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberAdd(rctx, in, opts...) resp, err = rcc.cc.MemberAdd(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) { func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) {
err = rcc.retryf(ctx, func(rctx context.Context) error { err = rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberRemove(rctx, in, opts...) resp, err = rcc.cc.MemberRemove(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) { func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) {
err = rcc.retryf(ctx, func(rctx context.Context) error { err = rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberUpdate(rctx, in, opts...) resp, err = rcc.cc.MemberUpdate(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err
}
type retryMaintenanceClient struct {
mc pb.MaintenanceClient
retryf retryRPCFunc
}
// RetryMaintenanceClient implements a Maintenance.
func RetryMaintenanceClient(c *Client, conn *grpc.ClientConn) pb.MaintenanceClient {
return &retryMaintenanceClient{
mc: pb.NewMaintenanceClient(conn),
retryf: c.newRetryWrapper(),
}
}
func (rmc *retryMaintenanceClient) Alarm(ctx context.Context, in *pb.AlarmRequest, opts ...grpc.CallOption) (resp *pb.AlarmResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.Alarm(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rmc *retryMaintenanceClient) Status(ctx context.Context, in *pb.StatusRequest, opts ...grpc.CallOption) (resp *pb.StatusResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.Status(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rmc *retryMaintenanceClient) Hash(ctx context.Context, in *pb.HashRequest, opts ...grpc.CallOption) (resp *pb.HashResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.Hash(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rmc *retryMaintenanceClient) HashKV(ctx context.Context, in *pb.HashKVRequest, opts ...grpc.CallOption) (resp *pb.HashKVResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.HashKV(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rmc *retryMaintenanceClient) Snapshot(ctx context.Context, in *pb.SnapshotRequest, opts ...grpc.CallOption) (stream pb.Maintenance_SnapshotClient, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
stream, err = rmc.mc.Snapshot(rctx, in, opts...)
return err
}, repeatable)
return stream, err
}
func (rmc *retryMaintenanceClient) MoveLeader(ctx context.Context, in *pb.MoveLeaderRequest, opts ...grpc.CallOption) (resp *pb.MoveLeaderResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.MoveLeader(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rmc *retryMaintenanceClient) Defragment(ctx context.Context, in *pb.DefragmentRequest, opts ...grpc.CallOption) (resp *pb.DefragmentResponse, err error) {
err = rmc.retryf(ctx, func(rctx context.Context) error {
resp, err = rmc.mc.Defragment(rctx, in, opts...)
return err
}, nonRepeatable)
return resp, err return resp, err
} }
type retryAuthClient struct { type retryAuthClient struct {
pb.AuthClient ac pb.AuthClient
retryf retryRpcFunc retryf retryRPCFunc
} }
// RetryAuthClient implements a AuthClient that uses the client's FailFast retry policy. // RetryAuthClient implements a AuthClient.
func RetryAuthClient(c *Client) pb.AuthClient { func RetryAuthClient(c *Client) pb.AuthClient {
return &retryAuthClient{pb.NewAuthClient(c.conn), c.retryWrapper} return &retryAuthClient{
ac: pb.NewAuthClient(c.conn),
retryf: c.newRetryWrapper(),
}
}
func (rac *retryAuthClient) UserList(ctx context.Context, in *pb.AuthUserListRequest, opts ...grpc.CallOption) (resp *pb.AuthUserListResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.ac.UserList(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rac *retryAuthClient) UserGet(ctx context.Context, in *pb.AuthUserGetRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGetResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.ac.UserGet(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rac *retryAuthClient) RoleGet(ctx context.Context, in *pb.AuthRoleGetRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGetResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.ac.RoleGet(rctx, in, opts...)
return err
}, repeatable)
return resp, err
}
func (rac *retryAuthClient) RoleList(ctx context.Context, in *pb.AuthRoleListRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleListResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.ac.RoleList(rctx, in, opts...)
return err
}, repeatable)
return resp, err
} }
func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) { func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.AuthEnable(rctx, in, opts...) resp, err = rac.ac.AuthEnable(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) { func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.AuthDisable(rctx, in, opts...) resp, err = rac.ac.AuthDisable(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) { func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserAdd(rctx, in, opts...) resp, err = rac.ac.UserAdd(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) { func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserDelete(rctx, in, opts...) resp, err = rac.ac.UserDelete(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) { func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserChangePassword(rctx, in, opts...) resp, err = rac.ac.UserChangePassword(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) { func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserGrantRole(rctx, in, opts...) resp, err = rac.ac.UserGrantRole(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) { func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserRevokeRole(rctx, in, opts...) resp, err = rac.ac.UserRevokeRole(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) { func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleAdd(rctx, in, opts...) resp, err = rac.ac.RoleAdd(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) { func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleDelete(rctx, in, opts...) resp, err = rac.ac.RoleDelete(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) { func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleGrantPermission(rctx, in, opts...) resp, err = rac.ac.RoleGrantPermission(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err return resp, err
} }
func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) { func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error { err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleRevokePermission(rctx, in, opts...) resp, err = rac.ac.RoleRevokePermission(rctx, in, opts...)
return err return err
}) }, nonRepeatable)
return resp, err
}
func (rac *retryAuthClient) Authenticate(ctx context.Context, in *pb.AuthenticateRequest, opts ...grpc.CallOption) (resp *pb.AuthenticateResponse, err error) {
err = rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.ac.Authenticate(rctx, in, opts...)
return err
}, nonRepeatable)
return resp, err return resp, err
} }

View File

@ -15,16 +15,17 @@
package clientv3 package clientv3
import ( import (
"context"
"sync" "sync"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
// Txn is the interface that wraps mini-transactions. // Txn is the interface that wraps mini-transactions.
// //
// Tx.If( // Txn(context.TODO()).If(
// Compare(Value(k1), ">", v1), // Compare(Value(k1), ">", v1),
// Compare(Version(k1), "=", 2) // Compare(Version(k1), "=", 2)
// ).Then( // ).Then(
@ -66,6 +67,8 @@ type txn struct {
sus []*pb.RequestOp sus []*pb.RequestOp
fas []*pb.RequestOp fas []*pb.RequestOp
callOpts []grpc.CallOption
} }
func (txn *txn) If(cs ...Cmp) Txn { func (txn *txn) If(cs ...Cmp) Txn {
@ -135,30 +138,14 @@ 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()
for {
resp, err := txn.commit()
if err == nil {
return resp, err
}
if isHaltErr(txn.ctx, err) {
return nil, toErr(txn.ctx, err)
}
if txn.isWrite {
return nil, toErr(txn.ctx, err)
}
}
}
func (txn *txn) commit() (*TxnResponse, error) {
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas} r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
var opts []grpc.CallOption var resp *pb.TxnResponse
if !txn.isWrite { var err error
opts = []grpc.CallOption{grpc.FailFast(false)} resp, err = txn.kv.remote.Txn(txn.ctx, r, txn.callOpts...)
}
resp, err := txn.kv.remote.Txn(txn.ctx, r, opts...)
if err != nil { if err != nil {
return nil, err return nil, toErr(txn.ctx, err)
} }
return (*TxnResponse)(resp), nil return (*TxnResponse)(resp), nil
} }

View File

@ -15,6 +15,7 @@
package clientv3 package clientv3
import ( import (
"context"
"fmt" "fmt"
"sync" "sync"
"time" "time"
@ -22,9 +23,11 @@ import (
v3rpc "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" v3rpc "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
mvccpb "github.com/coreos/etcd/mvcc/mvccpb" mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
) )
const ( const (
@ -89,7 +92,7 @@ func (wr *WatchResponse) Err() error {
return v3rpc.ErrCompacted return v3rpc.ErrCompacted
case wr.Canceled: case wr.Canceled:
if len(wr.cancelReason) != 0 { if len(wr.cancelReason) != 0 {
return v3rpc.Error(grpc.Errorf(codes.FailedPrecondition, "%s", wr.cancelReason)) return v3rpc.Error(status.Error(codes.FailedPrecondition, wr.cancelReason))
} }
return v3rpc.ErrFutureRev return v3rpc.ErrFutureRev
} }
@ -103,7 +106,8 @@ func (wr *WatchResponse) IsProgressNotify() bool {
// watcher implements the Watcher interface // watcher implements the Watcher interface
type watcher struct { type watcher struct {
remote pb.WatchClient remote pb.WatchClient
callOpts []grpc.CallOption
// mu protects the grpc streams map // mu protects the grpc streams map
mu sync.RWMutex mu sync.RWMutex
@ -114,8 +118,9 @@ type watcher struct {
// watchGrpcStream tracks all watch resources attached to a single grpc stream. // watchGrpcStream tracks all watch resources attached to a single grpc stream.
type watchGrpcStream struct { type watchGrpcStream struct {
owner *watcher owner *watcher
remote pb.WatchClient remote pb.WatchClient
callOpts []grpc.CallOption
// ctx controls internal remote.Watch requests // ctx controls internal remote.Watch requests
ctx context.Context ctx context.Context
@ -134,7 +139,7 @@ type watchGrpcStream struct {
respc chan *pb.WatchResponse respc chan *pb.WatchResponse
// donec closes to broadcast shutdown // donec closes to broadcast shutdown
donec chan struct{} donec chan struct{}
// errc transmits errors from grpc Recv to the watch stream reconn logic // errc transmits errors from grpc Recv to the watch stream reconnect logic
errc chan error errc chan error
// closingc gets the watcherStream of closing watchers // closingc gets the watcherStream of closing watchers
closingc chan *watcherStream closingc chan *watcherStream
@ -186,14 +191,18 @@ type watcherStream struct {
} }
func NewWatcher(c *Client) Watcher { func NewWatcher(c *Client) Watcher {
return NewWatchFromWatchClient(pb.NewWatchClient(c.conn)) return NewWatchFromWatchClient(pb.NewWatchClient(c.conn), c)
} }
func NewWatchFromWatchClient(wc pb.WatchClient) Watcher { func NewWatchFromWatchClient(wc pb.WatchClient, c *Client) Watcher {
return &watcher{ w := &watcher{
remote: wc, remote: wc,
streams: make(map[string]*watchGrpcStream), streams: make(map[string]*watchGrpcStream),
} }
if c != nil {
w.callOpts = c.callOpts
}
return w
} }
// never closes // never closes
@ -212,17 +221,17 @@ func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream {
wgs := &watchGrpcStream{ wgs := &watchGrpcStream{
owner: w, owner: w,
remote: w.remote, remote: w.remote,
callOpts: w.callOpts,
ctx: ctx, ctx: ctx,
ctxKey: fmt.Sprintf("%v", inctx), ctxKey: streamKeyFromCtx(inctx),
cancel: cancel, cancel: cancel,
substreams: make(map[int64]*watcherStream), substreams: make(map[int64]*watcherStream),
respc: make(chan *pb.WatchResponse),
respc: make(chan *pb.WatchResponse), reqc: make(chan *watchRequest),
reqc: make(chan *watchRequest), donec: make(chan struct{}),
donec: make(chan struct{}), errc: make(chan error, 1),
errc: make(chan error, 1), closingc: make(chan *watcherStream),
closingc: make(chan *watcherStream), resumec: make(chan struct{}),
resumec: make(chan struct{}),
} }
go wgs.run() go wgs.run()
return wgs return wgs
@ -253,7 +262,7 @@ func (w *watcher) Watch(ctx context.Context, key string, opts ...OpOption) Watch
} }
ok := false ok := false
ctxKey := fmt.Sprintf("%v", ctx) ctxKey := streamKeyFromCtx(ctx)
// find or allocate appropriate grpc watch stream // find or allocate appropriate grpc watch stream
w.mu.Lock() w.mu.Lock()
@ -434,7 +443,7 @@ func (w *watchGrpcStream) run() {
initReq: *wreq, initReq: *wreq,
id: -1, id: -1,
outc: outc, outc: outc,
// unbufffered so resumes won't cause repeat events // unbuffered so resumes won't cause repeat events
recvc: make(chan *WatchResponse), recvc: make(chan *WatchResponse),
} }
@ -461,7 +470,7 @@ func (w *watchGrpcStream) run() {
if ws := w.nextResume(); ws != nil { if ws := w.nextResume(); ws != nil {
wc.Send(ws.initReq.toPB()) wc.Send(ws.initReq.toPB())
} }
case pbresp.Canceled: case pbresp.Canceled && pbresp.CompactRevision == 0:
delete(cancelSet, pbresp.WatchId) delete(cancelSet, pbresp.WatchId)
if ws, ok := w.substreams[pbresp.WatchId]; ok { if ws, ok := w.substreams[pbresp.WatchId]; ok {
// signal to stream goroutine to update closingc // signal to stream goroutine to update closingc
@ -486,7 +495,7 @@ func (w *watchGrpcStream) run() {
req := &pb.WatchRequest{RequestUnion: cr} req := &pb.WatchRequest{RequestUnion: cr}
wc.Send(req) wc.Send(req)
} }
// watch client failed to recv; spawn another if possible // watch client failed on Recv; spawn another if possible
case err := <-w.errc: case err := <-w.errc:
if isHaltErr(w.ctx, err) || toErr(w.ctx, err) == v3rpc.ErrNoLeader { if isHaltErr(w.ctx, err) || toErr(w.ctx, err) == v3rpc.ErrNoLeader {
closeErr = err closeErr = err
@ -748,7 +757,7 @@ func (w *watchGrpcStream) waitCancelSubstreams(stopc <-chan struct{}) <-chan str
return donec return donec
} }
// joinSubstream waits for all substream goroutines to complete // joinSubstreams waits for all substream goroutines to complete.
func (w *watchGrpcStream) joinSubstreams() { func (w *watchGrpcStream) joinSubstreams() {
for _, ws := range w.substreams { for _, ws := range w.substreams {
<-ws.donec <-ws.donec
@ -760,7 +769,9 @@ func (w *watchGrpcStream) joinSubstreams() {
} }
} }
// openWatchClient retries opening a watchclient until retryConnection fails // openWatchClient retries opening a watch client until success or halt.
// manually retry in case "ws==nil && err==nil"
// TODO: remove FailFast=false
func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error) { func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error) {
for { for {
select { select {
@ -771,7 +782,7 @@ func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error)
return nil, err return nil, err
default: default:
} }
if ws, err = w.remote.Watch(w.ctx, grpc.FailFast(false)); ws != nil && err == nil { if ws, err = w.remote.Watch(w.ctx, w.callOpts...); ws != nil && err == nil {
break break
} }
if isHaltErr(w.ctx, err) { if isHaltErr(w.ctx, err) {
@ -781,7 +792,7 @@ func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error)
return ws, nil return ws, nil
} }
// toPB converts an internal watch request structure to its protobuf messagefunc (wr *watchRequest) // toPB converts an internal watch request structure to its protobuf WatchRequest structure.
func (wr *watchRequest) toPB() *pb.WatchRequest { func (wr *watchRequest) toPB() *pb.WatchRequest {
req := &pb.WatchCreateRequest{ req := &pb.WatchCreateRequest{
StartRevision: wr.rev, StartRevision: wr.rev,
@ -794,3 +805,10 @@ func (wr *watchRequest) toPB() *pb.WatchRequest {
cr := &pb.WatchRequest_CreateRequest{CreateRequest: req} cr := &pb.WatchRequest_CreateRequest{CreateRequest: req}
return &pb.WatchRequest{RequestUnion: cr} return &pb.WatchRequest{RequestUnion: cr}
} }
func streamKeyFromCtx(ctx context.Context) string {
if md, ok := metadata.FromOutgoingContext(ctx); ok {
return fmt.Sprintf("%+v", md)
}
return ""
}

View File

@ -37,6 +37,7 @@ var (
"3.0.0": {AuthCapability: true, V3rpcCapability: true}, "3.0.0": {AuthCapability: true, V3rpcCapability: true},
"3.1.0": {AuthCapability: true, V3rpcCapability: true}, "3.1.0": {AuthCapability: true, V3rpcCapability: true},
"3.2.0": {AuthCapability: true, V3rpcCapability: true}, "3.2.0": {AuthCapability: true, V3rpcCapability: true},
"3.3.0": {AuthCapability: true, V3rpcCapability: true},
} }
enableMapMu sync.RWMutex enableMapMu sync.RWMutex

View File

@ -33,9 +33,6 @@ type Cluster interface {
// Member retrieves a particular member based on ID, or nil if the // Member retrieves a particular member based on ID, or nil if the
// member does not exist in the cluster // member does not exist in the cluster
Member(id types.ID) *membership.Member Member(id types.ID) *membership.Member
// IsIDRemoved checks whether the given ID has been removed from this
// cluster at some point in the past
IsIDRemoved(id types.ID) bool
// Version is the cluster-wide minimum major.minor version. // Version is the cluster-wide minimum major.minor version.
Version() *semver.Version Version() *semver.Version
} }

View File

@ -15,9 +15,10 @@
package v3rpc package v3rpc
import ( import (
"context"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
) )
type AuthServer struct { type AuthServer struct {

View File

@ -16,10 +16,15 @@ package v3rpc
import ( import (
"crypto/tls" "crypto/tls"
"io/ioutil"
"math"
"os"
"sync"
"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/grpc-ecosystem/go-grpc-prometheus"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
@ -27,13 +32,16 @@ import (
healthpb "google.golang.org/grpc/health/grpc_health_v1" healthpb "google.golang.org/grpc/health/grpc_health_v1"
) )
const grpcOverheadBytes = 512 * 1024 const (
grpcOverheadBytes = 512 * 1024
maxStreams = math.MaxUint32
maxSendBytes = math.MaxInt32
)
func init() { // integration tests call this multiple times, which is racey in gRPC side
grpclog.SetLogger(plog) var grpclogOnce sync.Once
}
func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server { func Server(s *etcdserver.EtcdServer, tls *tls.Config, gopts ...grpc.ServerOption) *grpc.Server {
var opts []grpc.ServerOption var opts []grpc.ServerOption
opts = append(opts, grpc.CustomCodec(&codec{})) opts = append(opts, grpc.CustomCodec(&codec{}))
if tls != nil { if tls != nil {
@ -41,8 +49,10 @@ func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server {
} }
opts = append(opts, grpc.UnaryInterceptor(newUnaryInterceptor(s))) opts = append(opts, grpc.UnaryInterceptor(newUnaryInterceptor(s)))
opts = append(opts, grpc.StreamInterceptor(newStreamInterceptor(s))) opts = append(opts, grpc.StreamInterceptor(newStreamInterceptor(s)))
opts = append(opts, grpc.MaxMsgSize(int(s.Cfg.MaxRequestBytes+grpcOverheadBytes))) opts = append(opts, grpc.MaxRecvMsgSize(int(s.Cfg.MaxRequestBytes+grpcOverheadBytes)))
grpcServer := grpc.NewServer(opts...) opts = append(opts, grpc.MaxSendMsgSize(maxSendBytes))
opts = append(opts, grpc.MaxConcurrentStreams(maxStreams))
grpcServer := grpc.NewServer(append(opts, gopts...)...)
pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s)) pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s))
pb.RegisterWatchServer(grpcServer, NewWatchServer(s)) pb.RegisterWatchServer(grpcServer, NewWatchServer(s))
@ -58,5 +68,19 @@ func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server {
hsrv.SetServingStatus("", healthpb.HealthCheckResponse_SERVING) hsrv.SetServingStatus("", healthpb.HealthCheckResponse_SERVING)
healthpb.RegisterHealthServer(grpcServer, hsrv) healthpb.RegisterHealthServer(grpcServer, hsrv)
// set zero values for metrics registered for this grpc server
grpc_prometheus.Register(grpcServer)
grpclogOnce.Do(func() {
if s.Cfg.Debug {
grpc.EnableTracing = true
// enable info, warning, error
grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
} else {
// only discard info
grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
}
})
return grpcServer return grpcServer
} }

View File

@ -15,6 +15,7 @@
package v3rpc package v3rpc
import ( import (
"context"
"sync" "sync"
"time" "time"
@ -25,7 +26,6 @@ import (
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
prometheus "github.com/grpc-ecosystem/go-grpc-prometheus" prometheus "github.com/grpc-ecosystem/go-grpc-prometheus"
"golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/metadata" "google.golang.org/grpc/metadata"
) )

View File

@ -16,12 +16,14 @@
package v3rpc package v3rpc
import ( import (
"context"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/adt" "github.com/coreos/etcd/pkg/adt"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"golang.org/x/net/context"
) )
var ( var (

View File

@ -15,13 +15,13 @@
package v3rpc package v3rpc
import ( import (
"context"
"io" "io"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/lease" "github.com/coreos/etcd/lease"
"golang.org/x/net/context"
) )
type LeaseServer struct { type LeaseServer struct {
@ -68,6 +68,21 @@ func (ls *LeaseServer) LeaseTimeToLive(ctx context.Context, rr *pb.LeaseTimeToLi
return resp, nil return resp, nil
} }
func (ls *LeaseServer) LeaseLeases(ctx context.Context, rr *pb.LeaseLeasesRequest) (*pb.LeaseLeasesResponse, error) {
resp, err := ls.le.LeaseLeases(ctx, rr)
if err != nil && err != lease.ErrLeaseNotFound {
return nil, togRPCError(err)
}
if err == lease.ErrLeaseNotFound {
resp = &pb.LeaseLeasesResponse{
Header: &pb.ResponseHeader{},
Leases: []*pb.LeaseStatus{},
}
}
ls.hdr.fill(resp.Header)
return resp, nil
}
func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) (err error) { func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) (err error) {
errc := make(chan error, 1) errc := make(chan error, 1)
go func() { go func() {
@ -92,6 +107,11 @@ func (ls *LeaseServer) leaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro
return nil return nil
} }
if err != nil { if err != nil {
if isClientCtxErr(stream.Context().Err(), err) {
plog.Debugf("failed to receive lease keepalive request from gRPC stream (%q)", err.Error())
} else {
plog.Warningf("failed to receive lease keepalive request from gRPC stream (%q)", err.Error())
}
return err return err
} }
@ -117,6 +137,11 @@ func (ls *LeaseServer) leaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro
resp.TTL = ttl resp.TTL = ttl
err = stream.Send(resp) err = stream.Send(resp)
if err != nil { if err != nil {
if isClientCtxErr(stream.Context().Err(), err) {
plog.Debugf("failed to send lease keepalive response to gRPC stream (%q)", err.Error())
} else {
plog.Warningf("failed to send lease keepalive response to gRPC stream (%q)", err.Error())
}
return err return err
} }
} }

View File

@ -15,17 +15,18 @@
package v3rpc package v3rpc
import ( import (
"context"
"crypto/sha256" "crypto/sha256"
"io" "io"
"github.com/coreos/etcd/auth" "github.com/coreos/etcd/auth"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/mvcc" "github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/mvcc/backend" "github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
"golang.org/x/net/context"
) )
type KVGetter interface { type KVGetter interface {
@ -40,9 +41,13 @@ type Alarmer interface {
Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
} }
type LeaderTransferrer interface {
MoveLeader(ctx context.Context, lead, target uint64) error
}
type RaftStatusGetter interface { type RaftStatusGetter interface {
Index() uint64 etcdserver.RaftTimer
Term() uint64 ID() types.ID
Leader() types.ID Leader() types.ID
} }
@ -56,11 +61,12 @@ type maintenanceServer struct {
kg KVGetter kg KVGetter
bg BackendGetter bg BackendGetter
a Alarmer a Alarmer
lt LeaderTransferrer
hdr header hdr header
} }
func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer { func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
srv := &maintenanceServer{rg: s, kg: s, bg: s, a: s, hdr: newHeader(s)} srv := &maintenanceServer{rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s)}
return &authMaintenanceServer{srv, s} return &authMaintenanceServer{srv, s}
} }
@ -130,6 +136,17 @@ func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.H
return resp, nil return resp, nil
} }
func (ms *maintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
h, rev, compactRev, err := ms.kg.KV().HashByRev(r.Revision)
if err != nil {
return nil, togRPCError(err)
}
resp := &pb.HashKVResponse{Header: &pb.ResponseHeader{Revision: rev}, Hash: h, CompactRevision: compactRev}
ms.hdr.fill(resp.Header)
return resp, nil
}
func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) { func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
return ms.a.Alarm(ctx, ar) return ms.a.Alarm(ctx, ar)
} }
@ -147,6 +164,17 @@ func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (
return resp, nil return resp, nil
} }
func (ms *maintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
if ms.rg.ID() != ms.rg.Leader() {
return nil, rpctypes.ErrGRPCNotLeader
}
if err := ms.lt.MoveLeader(ctx, uint64(ms.rg.Leader()), tr.TargetID); err != nil {
return nil, togRPCError(err)
}
return &pb.MoveLeaderResponse{}, nil
}
type authMaintenanceServer struct { type authMaintenanceServer struct {
*maintenanceServer *maintenanceServer
ag AuthGetter ag AuthGetter
@ -185,6 +213,17 @@ func (ams *authMaintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (
return ams.maintenanceServer.Hash(ctx, r) return ams.maintenanceServer.Hash(ctx, r)
} }
func (ams *authMaintenanceServer) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
if err := ams.isAuthenticated(ctx); err != nil {
return nil, err
}
return ams.maintenanceServer.HashKV(ctx, r)
}
func (ams *authMaintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) { func (ams *authMaintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
return ams.maintenanceServer.Status(ctx, ar) return ams.maintenanceServer.Status(ctx, ar)
} }
func (ams *authMaintenanceServer) MoveLeader(ctx context.Context, tr *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
return ams.maintenanceServer.MoveLeader(ctx, tr)
}

View File

@ -15,6 +15,7 @@
package v3rpc package v3rpc
import ( import (
"context"
"time" "time"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
@ -23,20 +24,17 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/etcdserver/membership" "github.com/coreos/etcd/etcdserver/membership"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"golang.org/x/net/context"
) )
type ClusterServer struct { type ClusterServer struct {
cluster api.Cluster cluster api.Cluster
server etcdserver.Server server etcdserver.ServerV3
raftTimer etcdserver.RaftTimer
} }
func NewClusterServer(s *etcdserver.EtcdServer) *ClusterServer { func NewClusterServer(s etcdserver.ServerV3) *ClusterServer {
return &ClusterServer{ return &ClusterServer{
cluster: s.Cluster(), cluster: s.Cluster(),
server: s, server: s,
raftTimer: s,
} }
} }
@ -86,7 +84,7 @@ func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest
} }
func (cs *ClusterServer) header() *pb.ResponseHeader { func (cs *ClusterServer) header() *pb.ResponseHeader {
return &pb.ResponseHeader{ClusterId: uint64(cs.cluster.ID()), MemberId: uint64(cs.server.ID()), RaftTerm: cs.raftTimer.Term()} return &pb.ResponseHeader{ClusterId: uint64(cs.cluster.ID()), MemberId: uint64(cs.server.ID()), RaftTerm: cs.server.Term()}
} }
func membersToProtoMembers(membs []*membership.Member) []*pb.Member { func membersToProtoMembers(membs []*membership.Member) []*pb.Member {

View File

@ -15,11 +15,12 @@
package v3rpc package v3rpc
import ( import (
"context"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"golang.org/x/net/context"
) )
type quotaKVServer struct { type quotaKVServer struct {

View File

@ -15,106 +15,112 @@
package rpctypes package rpctypes
import ( import (
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
// server-side error
var ( var (
// server-side error ErrGRPCEmptyKey = status.New(codes.InvalidArgument, "etcdserver: key is not provided").Err()
ErrGRPCEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided") ErrGRPCKeyNotFound = status.New(codes.InvalidArgument, "etcdserver: key not found").Err()
ErrGRPCKeyNotFound = grpc.Errorf(codes.InvalidArgument, "etcdserver: key not found") ErrGRPCValueProvided = status.New(codes.InvalidArgument, "etcdserver: value is provided").Err()
ErrGRPCValueProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: value is provided") ErrGRPCLeaseProvided = status.New(codes.InvalidArgument, "etcdserver: lease is provided").Err()
ErrGRPCLeaseProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: lease is provided") ErrGRPCTooManyOps = status.New(codes.InvalidArgument, "etcdserver: too many operations in txn request").Err()
ErrGRPCTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request") ErrGRPCDuplicateKey = status.New(codes.InvalidArgument, "etcdserver: duplicate key given in txn request").Err()
ErrGRPCDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request") ErrGRPCCompacted = status.New(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted").Err()
ErrGRPCCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted") ErrGRPCFutureRev = status.New(codes.OutOfRange, "etcdserver: mvcc: required revision is a future revision").Err()
ErrGRPCFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision is a future revision") ErrGRPCNoSpace = status.New(codes.ResourceExhausted, "etcdserver: mvcc: database space exceeded").Err()
ErrGRPCNoSpace = grpc.Errorf(codes.ResourceExhausted, "etcdserver: mvcc: database space exceeded")
ErrGRPCLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found") ErrGRPCLeaseNotFound = status.New(codes.NotFound, "etcdserver: requested lease not found").Err()
ErrGRPCLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists") ErrGRPCLeaseExist = status.New(codes.FailedPrecondition, "etcdserver: lease already exists").Err()
ErrGRPCMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist") ErrGRPCMemberExist = status.New(codes.FailedPrecondition, "etcdserver: member ID already exist").Err()
ErrGRPCPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists") ErrGRPCPeerURLExist = status.New(codes.FailedPrecondition, "etcdserver: Peer URLs already exists").Err()
ErrGRPCMemberNotEnoughStarted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: re-configuration failed due to not enough started members") ErrGRPCMemberNotEnoughStarted = status.New(codes.FailedPrecondition, "etcdserver: re-configuration failed due to not enough started members").Err()
ErrGRPCMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid") ErrGRPCMemberBadURLs = status.New(codes.InvalidArgument, "etcdserver: given member URLs are invalid").Err()
ErrGRPCMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found") ErrGRPCMemberNotFound = status.New(codes.NotFound, "etcdserver: member not found").Err()
ErrGRPCRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large") ErrGRPCRequestTooLarge = status.New(codes.InvalidArgument, "etcdserver: request is too large").Err()
ErrGRPCRequestTooManyRequests = grpc.Errorf(codes.ResourceExhausted, "etcdserver: too many requests") ErrGRPCRequestTooManyRequests = status.New(codes.ResourceExhausted, "etcdserver: too many requests").Err()
ErrGRPCRootUserNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not exist") ErrGRPCRootUserNotExist = status.New(codes.FailedPrecondition, "etcdserver: root user does not exist").Err()
ErrGRPCRootRoleNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not have root role") ErrGRPCRootRoleNotExist = status.New(codes.FailedPrecondition, "etcdserver: root user does not have root role").Err()
ErrGRPCUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists") ErrGRPCUserAlreadyExist = status.New(codes.FailedPrecondition, "etcdserver: user name already exists").Err()
ErrGRPCUserEmpty = grpc.Errorf(codes.InvalidArgument, "etcdserver: user name is empty") ErrGRPCUserEmpty = status.New(codes.InvalidArgument, "etcdserver: user name is empty").Err()
ErrGRPCUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found") ErrGRPCUserNotFound = status.New(codes.FailedPrecondition, "etcdserver: user name not found").Err()
ErrGRPCRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists") ErrGRPCRoleAlreadyExist = status.New(codes.FailedPrecondition, "etcdserver: role name already exists").Err()
ErrGRPCRoleNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name not found") ErrGRPCRoleNotFound = status.New(codes.FailedPrecondition, "etcdserver: role name not found").Err()
ErrGRPCAuthFailed = grpc.Errorf(codes.InvalidArgument, "etcdserver: authentication failed, invalid user ID or password") ErrGRPCAuthFailed = status.New(codes.InvalidArgument, "etcdserver: authentication failed, invalid user ID or password").Err()
ErrGRPCPermissionDenied = grpc.Errorf(codes.PermissionDenied, "etcdserver: permission denied") ErrGRPCPermissionDenied = status.New(codes.PermissionDenied, "etcdserver: permission denied").Err()
ErrGRPCRoleNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role is not granted to the user") ErrGRPCRoleNotGranted = status.New(codes.FailedPrecondition, "etcdserver: role is not granted to the user").Err()
ErrGRPCPermissionNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: permission is not granted to the role") ErrGRPCPermissionNotGranted = status.New(codes.FailedPrecondition, "etcdserver: permission is not granted to the role").Err()
ErrGRPCAuthNotEnabled = grpc.Errorf(codes.FailedPrecondition, "etcdserver: authentication is not enabled") ErrGRPCAuthNotEnabled = status.New(codes.FailedPrecondition, "etcdserver: authentication is not enabled").Err()
ErrGRPCInvalidAuthToken = grpc.Errorf(codes.Unauthenticated, "etcdserver: invalid auth token") ErrGRPCInvalidAuthToken = status.New(codes.Unauthenticated, "etcdserver: invalid auth token").Err()
ErrGRPCInvalidAuthMgmt = grpc.Errorf(codes.InvalidArgument, "etcdserver: invalid auth management") ErrGRPCInvalidAuthMgmt = status.New(codes.InvalidArgument, "etcdserver: invalid auth management").Err()
ErrGRPCNoLeader = grpc.Errorf(codes.Unavailable, "etcdserver: no leader") ErrGRPCNoLeader = status.New(codes.Unavailable, "etcdserver: no leader").Err()
ErrGRPCNotCapable = grpc.Errorf(codes.Unavailable, "etcdserver: not capable") ErrGRPCNotLeader = status.New(codes.FailedPrecondition, "etcdserver: not leader").Err()
ErrGRPCStopped = grpc.Errorf(codes.Unavailable, "etcdserver: server stopped") ErrGRPCNotCapable = status.New(codes.Unavailable, "etcdserver: not capable").Err()
ErrGRPCTimeout = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out") ErrGRPCStopped = status.New(codes.Unavailable, "etcdserver: server stopped").Err()
ErrGRPCTimeoutDueToLeaderFail = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out, possibly due to previous leader failure") ErrGRPCTimeout = status.New(codes.Unavailable, "etcdserver: request timed out").Err()
ErrGRPCTimeoutDueToConnectionLost = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out, possibly due to connection lost") ErrGRPCTimeoutDueToLeaderFail = status.New(codes.Unavailable, "etcdserver: request timed out, possibly due to previous leader failure").Err()
ErrGRPCUnhealthy = grpc.Errorf(codes.Unavailable, "etcdserver: unhealthy cluster") ErrGRPCTimeoutDueToConnectionLost = status.New(codes.Unavailable, "etcdserver: request timed out, possibly due to connection lost").Err()
ErrGRPCUnhealthy = status.New(codes.Unavailable, "etcdserver: unhealthy cluster").Err()
ErrGRPCCorrupt = status.New(codes.DataLoss, "etcdserver: corrupt cluster").Err()
errStringToError = map[string]error{ errStringToError = map[string]error{
grpc.ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey, ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey,
grpc.ErrorDesc(ErrGRPCKeyNotFound): ErrGRPCKeyNotFound, ErrorDesc(ErrGRPCKeyNotFound): ErrGRPCKeyNotFound,
grpc.ErrorDesc(ErrGRPCValueProvided): ErrGRPCValueProvided, ErrorDesc(ErrGRPCValueProvided): ErrGRPCValueProvided,
grpc.ErrorDesc(ErrGRPCLeaseProvided): ErrGRPCLeaseProvided, ErrorDesc(ErrGRPCLeaseProvided): ErrGRPCLeaseProvided,
grpc.ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps, ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps,
grpc.ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey, ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey,
grpc.ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted, ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted,
grpc.ErrorDesc(ErrGRPCFutureRev): ErrGRPCFutureRev, ErrorDesc(ErrGRPCFutureRev): ErrGRPCFutureRev,
grpc.ErrorDesc(ErrGRPCNoSpace): ErrGRPCNoSpace, ErrorDesc(ErrGRPCNoSpace): ErrGRPCNoSpace,
grpc.ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound, ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound,
grpc.ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist, ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist,
grpc.ErrorDesc(ErrGRPCMemberExist): ErrGRPCMemberExist, ErrorDesc(ErrGRPCMemberExist): ErrGRPCMemberExist,
grpc.ErrorDesc(ErrGRPCPeerURLExist): ErrGRPCPeerURLExist, ErrorDesc(ErrGRPCPeerURLExist): ErrGRPCPeerURLExist,
grpc.ErrorDesc(ErrGRPCMemberNotEnoughStarted): ErrGRPCMemberNotEnoughStarted, ErrorDesc(ErrGRPCMemberNotEnoughStarted): ErrGRPCMemberNotEnoughStarted,
grpc.ErrorDesc(ErrGRPCMemberBadURLs): ErrGRPCMemberBadURLs, ErrorDesc(ErrGRPCMemberBadURLs): ErrGRPCMemberBadURLs,
grpc.ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound, ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound,
grpc.ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge, ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge,
grpc.ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests, ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests,
grpc.ErrorDesc(ErrGRPCRootUserNotExist): ErrGRPCRootUserNotExist, ErrorDesc(ErrGRPCRootUserNotExist): ErrGRPCRootUserNotExist,
grpc.ErrorDesc(ErrGRPCRootRoleNotExist): ErrGRPCRootRoleNotExist, ErrorDesc(ErrGRPCRootRoleNotExist): ErrGRPCRootRoleNotExist,
grpc.ErrorDesc(ErrGRPCUserAlreadyExist): ErrGRPCUserAlreadyExist, ErrorDesc(ErrGRPCUserAlreadyExist): ErrGRPCUserAlreadyExist,
grpc.ErrorDesc(ErrGRPCUserEmpty): ErrGRPCUserEmpty, ErrorDesc(ErrGRPCUserEmpty): ErrGRPCUserEmpty,
grpc.ErrorDesc(ErrGRPCUserNotFound): ErrGRPCUserNotFound, ErrorDesc(ErrGRPCUserNotFound): ErrGRPCUserNotFound,
grpc.ErrorDesc(ErrGRPCRoleAlreadyExist): ErrGRPCRoleAlreadyExist, ErrorDesc(ErrGRPCRoleAlreadyExist): ErrGRPCRoleAlreadyExist,
grpc.ErrorDesc(ErrGRPCRoleNotFound): ErrGRPCRoleNotFound, ErrorDesc(ErrGRPCRoleNotFound): ErrGRPCRoleNotFound,
grpc.ErrorDesc(ErrGRPCAuthFailed): ErrGRPCAuthFailed, ErrorDesc(ErrGRPCAuthFailed): ErrGRPCAuthFailed,
grpc.ErrorDesc(ErrGRPCPermissionDenied): ErrGRPCPermissionDenied, ErrorDesc(ErrGRPCPermissionDenied): ErrGRPCPermissionDenied,
grpc.ErrorDesc(ErrGRPCRoleNotGranted): ErrGRPCRoleNotGranted, ErrorDesc(ErrGRPCRoleNotGranted): ErrGRPCRoleNotGranted,
grpc.ErrorDesc(ErrGRPCPermissionNotGranted): ErrGRPCPermissionNotGranted, ErrorDesc(ErrGRPCPermissionNotGranted): ErrGRPCPermissionNotGranted,
grpc.ErrorDesc(ErrGRPCAuthNotEnabled): ErrGRPCAuthNotEnabled, ErrorDesc(ErrGRPCAuthNotEnabled): ErrGRPCAuthNotEnabled,
grpc.ErrorDesc(ErrGRPCInvalidAuthToken): ErrGRPCInvalidAuthToken, ErrorDesc(ErrGRPCInvalidAuthToken): ErrGRPCInvalidAuthToken,
grpc.ErrorDesc(ErrGRPCInvalidAuthMgmt): ErrGRPCInvalidAuthMgmt, ErrorDesc(ErrGRPCInvalidAuthMgmt): ErrGRPCInvalidAuthMgmt,
grpc.ErrorDesc(ErrGRPCNoLeader): ErrGRPCNoLeader, ErrorDesc(ErrGRPCNoLeader): ErrGRPCNoLeader,
grpc.ErrorDesc(ErrGRPCNotCapable): ErrGRPCNotCapable, ErrorDesc(ErrGRPCNotLeader): ErrGRPCNotLeader,
grpc.ErrorDesc(ErrGRPCStopped): ErrGRPCStopped, ErrorDesc(ErrGRPCNotCapable): ErrGRPCNotCapable,
grpc.ErrorDesc(ErrGRPCTimeout): ErrGRPCTimeout, ErrorDesc(ErrGRPCStopped): ErrGRPCStopped,
grpc.ErrorDesc(ErrGRPCTimeoutDueToLeaderFail): ErrGRPCTimeoutDueToLeaderFail, ErrorDesc(ErrGRPCTimeout): ErrGRPCTimeout,
grpc.ErrorDesc(ErrGRPCTimeoutDueToConnectionLost): ErrGRPCTimeoutDueToConnectionLost, ErrorDesc(ErrGRPCTimeoutDueToLeaderFail): ErrGRPCTimeoutDueToLeaderFail,
grpc.ErrorDesc(ErrGRPCUnhealthy): ErrGRPCUnhealthy, ErrorDesc(ErrGRPCTimeoutDueToConnectionLost): ErrGRPCTimeoutDueToConnectionLost,
ErrorDesc(ErrGRPCUnhealthy): ErrGRPCUnhealthy,
ErrorDesc(ErrGRPCCorrupt): ErrGRPCCorrupt,
} }
)
// client-side error // client-side error
var (
ErrEmptyKey = Error(ErrGRPCEmptyKey) ErrEmptyKey = Error(ErrGRPCEmptyKey)
ErrKeyNotFound = Error(ErrGRPCKeyNotFound) ErrKeyNotFound = Error(ErrGRPCKeyNotFound)
ErrValueProvided = Error(ErrGRPCValueProvided) ErrValueProvided = Error(ErrGRPCValueProvided)
@ -153,12 +159,14 @@ var (
ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt) ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt)
ErrNoLeader = Error(ErrGRPCNoLeader) ErrNoLeader = Error(ErrGRPCNoLeader)
ErrNotLeader = Error(ErrGRPCNotLeader)
ErrNotCapable = Error(ErrGRPCNotCapable) ErrNotCapable = Error(ErrGRPCNotCapable)
ErrStopped = Error(ErrGRPCStopped) ErrStopped = Error(ErrGRPCStopped)
ErrTimeout = Error(ErrGRPCTimeout) ErrTimeout = Error(ErrGRPCTimeout)
ErrTimeoutDueToLeaderFail = Error(ErrGRPCTimeoutDueToLeaderFail) ErrTimeoutDueToLeaderFail = Error(ErrGRPCTimeoutDueToLeaderFail)
ErrTimeoutDueToConnectionLost = Error(ErrGRPCTimeoutDueToConnectionLost) ErrTimeoutDueToConnectionLost = Error(ErrGRPCTimeoutDueToConnectionLost)
ErrUnhealthy = Error(ErrGRPCUnhealthy) ErrUnhealthy = Error(ErrGRPCUnhealthy)
ErrCorrupt = Error(ErrGRPCCorrupt)
) )
// EtcdError defines gRPC server errors. // EtcdError defines gRPC server errors.
@ -182,9 +190,23 @@ func Error(err error) error {
if err == nil { if err == nil {
return nil return nil
} }
verr, ok := errStringToError[grpc.ErrorDesc(err)] verr, ok := errStringToError[ErrorDesc(err)]
if !ok { // not gRPC error if !ok { // not gRPC error
return err return err
} }
return EtcdError{code: grpc.Code(verr), desc: grpc.ErrorDesc(verr)} ev, ok := status.FromError(verr)
var desc string
if ok {
desc = ev.Message()
} else {
desc = verr.Error()
}
return EtcdError{code: ev.Code(), desc: desc}
}
func ErrorDesc(err error) string {
if s, ok := status.FromError(err); ok {
return s.Message()
}
return err.Error()
} }

View File

@ -15,14 +15,17 @@
package v3rpc package v3rpc
import ( import (
"context"
"github.com/coreos/etcd/auth" "github.com/coreos/etcd/auth"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
"github.com/coreos/etcd/etcdserver/membership" "github.com/coreos/etcd/etcdserver/membership"
"github.com/coreos/etcd/lease" "github.com/coreos/etcd/lease"
"github.com/coreos/etcd/mvcc" "github.com/coreos/etcd/mvcc"
"google.golang.org/grpc"
"google.golang.org/grpc/codes" "google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
) )
var toGRPCErrorMap = map[error]error{ var toGRPCErrorMap = map[error]error{
@ -39,12 +42,14 @@ var toGRPCErrorMap = map[error]error{
etcdserver.ErrTooManyRequests: rpctypes.ErrTooManyRequests, etcdserver.ErrTooManyRequests: rpctypes.ErrTooManyRequests,
etcdserver.ErrNoLeader: rpctypes.ErrGRPCNoLeader, etcdserver.ErrNoLeader: rpctypes.ErrGRPCNoLeader,
etcdserver.ErrNotLeader: rpctypes.ErrGRPCNotLeader,
etcdserver.ErrStopped: rpctypes.ErrGRPCStopped, etcdserver.ErrStopped: rpctypes.ErrGRPCStopped,
etcdserver.ErrTimeout: rpctypes.ErrGRPCTimeout, etcdserver.ErrTimeout: rpctypes.ErrGRPCTimeout,
etcdserver.ErrTimeoutDueToLeaderFail: rpctypes.ErrGRPCTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToLeaderFail: rpctypes.ErrGRPCTimeoutDueToLeaderFail,
etcdserver.ErrTimeoutDueToConnectionLost: rpctypes.ErrGRPCTimeoutDueToConnectionLost, etcdserver.ErrTimeoutDueToConnectionLost: rpctypes.ErrGRPCTimeoutDueToConnectionLost,
etcdserver.ErrUnhealthy: rpctypes.ErrGRPCUnhealthy, etcdserver.ErrUnhealthy: rpctypes.ErrGRPCUnhealthy,
etcdserver.ErrKeyNotFound: rpctypes.ErrGRPCKeyNotFound, etcdserver.ErrKeyNotFound: rpctypes.ErrGRPCKeyNotFound,
etcdserver.ErrCorrupt: rpctypes.ErrGRPCCorrupt,
lease.ErrLeaseNotFound: rpctypes.ErrGRPCLeaseNotFound, lease.ErrLeaseNotFound: rpctypes.ErrGRPCLeaseNotFound,
lease.ErrLeaseExists: rpctypes.ErrGRPCLeaseExist, lease.ErrLeaseExists: rpctypes.ErrGRPCLeaseExist,
@ -66,9 +71,26 @@ var toGRPCErrorMap = map[error]error{
} }
func togRPCError(err error) error { func togRPCError(err error) error {
// let gRPC server convert to codes.Canceled, codes.DeadlineExceeded
if err == context.Canceled || err == context.DeadlineExceeded {
return err
}
grpcErr, ok := toGRPCErrorMap[err] grpcErr, ok := toGRPCErrorMap[err]
if !ok { if !ok {
return grpc.Errorf(codes.Unknown, err.Error()) return status.Error(codes.Unknown, err.Error())
} }
return grpcErr return grpcErr
} }
func isClientCtxErr(ctxErr error, err error) bool {
if ctxErr != nil {
return true
}
ev, ok := status.FromError(err)
if !ok {
return false
}
code := ev.Code()
return code == codes.Canceled || code == codes.DeadlineExceeded
}

View File

@ -15,12 +15,11 @@
package v3rpc package v3rpc
import ( import (
"context"
"io" "io"
"sync" "sync"
"time" "time"
"golang.org/x/net/context"
"github.com/coreos/etcd/auth" "github.com/coreos/etcd/auth"
"github.com/coreos/etcd/etcdserver" "github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
@ -141,6 +140,11 @@ func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
// deadlock when calling sws.close(). // deadlock when calling sws.close().
go func() { go func() {
if rerr := sws.recvLoop(); rerr != nil { if rerr := sws.recvLoop(); rerr != nil {
if isClientCtxErr(stream.Context().Err(), rerr) {
plog.Debugf("failed to receive watch request from gRPC stream (%q)", rerr.Error())
} else {
plog.Warningf("failed to receive watch request from gRPC stream (%q)", rerr.Error())
}
errc <- rerr errc <- rerr
} }
}() }()
@ -321,11 +325,13 @@ func (sws *serverWatchStream) sendLoop() {
} }
} }
canceled := wresp.CompactRevision != 0
wr := &pb.WatchResponse{ wr := &pb.WatchResponse{
Header: sws.newResponseHeader(wresp.Revision), Header: sws.newResponseHeader(wresp.Revision),
WatchId: int64(wresp.WatchID), WatchId: int64(wresp.WatchID),
Events: events, Events: events,
CompactRevision: wresp.CompactRevision, CompactRevision: wresp.CompactRevision,
Canceled: canceled,
} }
if _, hasId := ids[wresp.WatchID]; !hasId { if _, hasId := ids[wresp.WatchID]; !hasId {
@ -337,6 +343,11 @@ func (sws *serverWatchStream) sendLoop() {
mvcc.ReportEventReceived(len(evs)) mvcc.ReportEventReceived(len(evs))
if err := sws.gRPCStream.Send(wr); err != nil { if err := sws.gRPCStream.Send(wr); err != nil {
if isClientCtxErr(sws.gRPCStream.Context().Err(), err) {
plog.Debugf("failed to send watch response to gRPC stream (%q)", err.Error())
} else {
plog.Warningf("failed to send watch response to gRPC stream (%q)", err.Error())
}
return return
} }
@ -353,6 +364,11 @@ func (sws *serverWatchStream) sendLoop() {
} }
if err := sws.gRPCStream.Send(c); err != nil { if err := sws.gRPCStream.Send(c); err != nil {
if isClientCtxErr(sws.gRPCStream.Context().Err(), err) {
plog.Debugf("failed to send watch control response to gRPC stream (%q)", err.Error())
} else {
plog.Warningf("failed to send watch control response to gRPC stream (%q)", err.Error())
}
return return
} }
@ -368,6 +384,11 @@ func (sws *serverWatchStream) sendLoop() {
for _, v := range pending[wid] { for _, v := range pending[wid] {
mvcc.ReportEventReceived(len(v.Events)) mvcc.ReportEventReceived(len(v.Events))
if err := sws.gRPCStream.Send(v); err != nil { if err := sws.gRPCStream.Send(v); err != nil {
if isClientCtxErr(sws.gRPCStream.Context().Err(), err) {
plog.Debugf("failed to send pending watch response to gRPC stream (%q)", err.Error())
} else {
plog.Warningf("failed to send pending watch response to gRPC stream (%q)", err.Error())
}
return return
} }
} }

View File

@ -16,16 +16,18 @@ package etcdserver
import ( import (
"bytes" "bytes"
"context"
"sort" "sort"
"time" "time"
"github.com/coreos/etcd/auth"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/lease" "github.com/coreos/etcd/lease"
"github.com/coreos/etcd/mvcc" "github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/mvcc/mvccpb" "github.com/coreos/etcd/mvcc/mvccpb"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
"github.com/gogo/protobuf/proto" "github.com/gogo/protobuf/proto"
"golang.org/x/net/context"
) )
const ( const (
@ -223,8 +225,9 @@ func (a *applierV3backend) DeleteRange(txn mvcc.TxnWrite, dr *pb.DeleteRangeRequ
return nil, err return nil, err
} }
if rr != nil { if rr != nil {
resp.PrevKvs = make([]*mvccpb.KeyValue, len(rr.KVs))
for i := range rr.KVs { for i := range rr.KVs {
resp.PrevKvs = append(resp.PrevKvs, &rr.KVs[i]) resp.PrevKvs[i] = &rr.KVs[i]
} }
} }
} }
@ -318,11 +321,12 @@ func (a *applierV3backend) Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.Rang
resp.Header.Revision = rr.Rev resp.Header.Revision = rr.Rev
resp.Count = int64(rr.Count) resp.Count = int64(rr.Count)
resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs))
for i := range rr.KVs { for i := range rr.KVs {
if r.KeysOnly { if r.KeysOnly {
rr.KVs[i].Value = nil rr.KVs[i].Value = nil
} }
resp.Kvs = append(resp.Kvs, &rr.KVs[i]) resp.Kvs[i] = &rr.KVs[i]
} }
return resp, nil return resp, nil
} }
@ -423,7 +427,7 @@ func applyCompares(rv mvcc.ReadView, cmps []*pb.Compare) bool {
// applyCompare applies the compare request. // applyCompare applies the compare request.
// If the comparison succeeds, it returns true. Otherwise, returns false. // If the comparison succeeds, it returns true. Otherwise, returns false.
func applyCompare(rv mvcc.ReadView, c *pb.Compare) bool { func applyCompare(rv mvcc.ReadView, c *pb.Compare) bool {
// TOOD: possible optimizations // TODO: possible optimizations
// * chunk reads for large ranges to conserve memory // * chunk reads for large ranges to conserve memory
// * rewrite rules for common patterns: // * rewrite rules for common patterns:
// ex. "[a, b) createrev > 0" => "limit 1 /\ kvs > 0" // ex. "[a, b) createrev > 0" => "limit 1 /\ kvs > 0"
@ -473,6 +477,11 @@ func compareKV(c *pb.Compare, ckv mvccpb.KeyValue) bool {
rev = tv.Version rev = tv.Version
} }
result = compareInt64(ckv.Version, rev) result = compareInt64(ckv.Version, rev)
case pb.Compare_LEASE:
if tv, _ := c.TargetUnion.(*pb.Compare_Lease); tv != nil {
rev = tv.Lease
}
result = compareInt64(ckv.Lease, rev)
} }
switch c.Result { switch c.Result {
case pb.Compare_EQUAL: case pb.Compare_EQUAL:
@ -572,9 +581,11 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
break break
} }
plog.Warningf("alarm %v raised by peer %s", m.Alarm, types.ID(m.MemberID))
switch m.Alarm { switch m.Alarm {
case pb.AlarmType_CORRUPT:
a.s.applyV3 = newApplierV3Corrupt(a)
case pb.AlarmType_NOSPACE: case pb.AlarmType_NOSPACE:
plog.Warningf("alarm raised %+v", m)
a.s.applyV3 = newApplierV3Capped(a) a.s.applyV3 = newApplierV3Capped(a)
default: default:
plog.Errorf("unimplemented alarm activation (%+v)", m) plog.Errorf("unimplemented alarm activation (%+v)", m)
@ -591,7 +602,8 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
} }
switch m.Alarm { switch m.Alarm {
case pb.AlarmType_NOSPACE: case pb.AlarmType_NOSPACE, pb.AlarmType_CORRUPT:
// TODO: check kv hash before deactivating CORRUPT?
plog.Infof("alarm disarmed %+v", ar) plog.Infof("alarm disarmed %+v", ar)
a.s.applyV3 = a.s.newApplierV3() a.s.applyV3 = a.s.newApplierV3()
default: default:
@ -641,7 +653,7 @@ func (a *applierV3backend) AuthDisable() (*pb.AuthDisableResponse, error) {
} }
func (a *applierV3backend) Authenticate(r *pb.InternalAuthenticateRequest) (*pb.AuthenticateResponse, error) { func (a *applierV3backend) Authenticate(r *pb.InternalAuthenticateRequest) (*pb.AuthenticateResponse, error) {
ctx := context.WithValue(context.WithValue(a.s.ctx, "index", a.s.consistIndex.ConsistentIndex()), "simpleToken", r.SimpleToken) ctx := context.WithValue(context.WithValue(a.s.ctx, auth.AuthenticateParamIndex{}, a.s.consistIndex.ConsistentIndex()), auth.AuthenticateParamSimpleTokenPrefix{}, r.SimpleToken)
resp, err := a.s.AuthStore().Authenticate(ctx, r.Name, r.Password) resp, err := a.s.AuthStore().Authenticate(ctx, r.Name, r.Password)
if resp != nil { if resp != nil {
resp.Header = newHeader(a.s) resp.Header = newHeader(a.s)

View File

@ -189,6 +189,28 @@ func (aa *authApplierV3) checkLeasePuts(leaseID lease.LeaseID) error {
return nil return nil
} }
func (aa *authApplierV3) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
err := aa.as.IsAdminPermitted(&aa.authInfo)
if err != nil && r.Name != aa.authInfo.Username {
aa.authInfo.Username = ""
aa.authInfo.Revision = 0
return &pb.AuthUserGetResponse{}, err
}
return aa.applierV3.UserGet(r)
}
func (aa *authApplierV3) RoleGet(r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
err := aa.as.IsAdminPermitted(&aa.authInfo)
if err != nil && !aa.as.HasRole(aa.authInfo.Username, r.Role) {
aa.authInfo.Username = ""
aa.authInfo.Revision = 0
return &pb.AuthRoleGetResponse{}, err
}
return aa.applierV3.RoleGet(r)
}
func needAdminPermission(r *pb.InternalRaftRequest) bool { func needAdminPermission(r *pb.InternalRaftRequest) bool {
switch { switch {
case r.AuthEnable != nil: case r.AuthEnable != nil:
@ -203,16 +225,12 @@ func needAdminPermission(r *pb.InternalRaftRequest) bool {
return true return true
case r.AuthUserGrantRole != nil: case r.AuthUserGrantRole != nil:
return true return true
case r.AuthUserGet != nil:
return true
case r.AuthUserRevokeRole != nil: case r.AuthUserRevokeRole != nil:
return true return true
case r.AuthRoleAdd != nil: case r.AuthRoleAdd != nil:
return true return true
case r.AuthRoleGrantPermission != nil: case r.AuthRoleGrantPermission != nil:
return true return true
case r.AuthRoleGet != nil:
return true
case r.AuthRoleRevokePermission != nil: case r.AuthRoleRevokePermission != nil:
return true return true
case r.AuthRoleDelete != nil: case r.AuthRoleDelete != nil:

View File

@ -20,7 +20,6 @@ import (
"time" "time"
"github.com/coreos/etcd/etcdserver/api" "github.com/coreos/etcd/etcdserver/api"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/etcdserver/membership" "github.com/coreos/etcd/etcdserver/membership"
"github.com/coreos/etcd/pkg/pbutil" "github.com/coreos/etcd/pkg/pbutil"
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
@ -29,11 +28,11 @@ import (
// ApplierV2 is the interface for processing V2 raft messages // ApplierV2 is the interface for processing V2 raft messages
type ApplierV2 interface { type ApplierV2 interface {
Delete(r *pb.Request) Response Delete(r *RequestV2) Response
Post(r *pb.Request) Response Post(r *RequestV2) Response
Put(r *pb.Request) Response Put(r *RequestV2) Response
QGet(r *pb.Request) Response QGet(r *RequestV2) Response
Sync(r *pb.Request) Response Sync(r *RequestV2) Response
} }
func NewApplierV2(s store.Store, c *membership.RaftCluster) ApplierV2 { func NewApplierV2(s store.Store, c *membership.RaftCluster) ApplierV2 {
@ -45,7 +44,7 @@ type applierV2store struct {
cluster *membership.RaftCluster cluster *membership.RaftCluster
} }
func (a *applierV2store) Delete(r *pb.Request) Response { func (a *applierV2store) Delete(r *RequestV2) Response {
switch { switch {
case r.PrevIndex > 0 || r.PrevValue != "": case r.PrevIndex > 0 || r.PrevValue != "":
return toResponse(a.store.CompareAndDelete(r.Path, r.PrevValue, r.PrevIndex)) return toResponse(a.store.CompareAndDelete(r.Path, r.PrevValue, r.PrevIndex))
@ -54,12 +53,12 @@ func (a *applierV2store) Delete(r *pb.Request) Response {
} }
} }
func (a *applierV2store) Post(r *pb.Request) Response { func (a *applierV2store) Post(r *RequestV2) Response {
return toResponse(a.store.Create(r.Path, r.Dir, r.Val, true, toTTLOptions(r))) return toResponse(a.store.Create(r.Path, r.Dir, r.Val, true, r.TTLOptions()))
} }
func (a *applierV2store) Put(r *pb.Request) Response { func (a *applierV2store) Put(r *RequestV2) Response {
ttlOptions := toTTLOptions(r) ttlOptions := r.TTLOptions()
exists, existsSet := pbutil.GetBool(r.PrevExist) exists, existsSet := pbutil.GetBool(r.PrevExist)
switch { switch {
case existsSet: case existsSet:
@ -96,19 +95,18 @@ func (a *applierV2store) Put(r *pb.Request) Response {
} }
} }
func (a *applierV2store) QGet(r *pb.Request) Response { func (a *applierV2store) QGet(r *RequestV2) Response {
return toResponse(a.store.Get(r.Path, r.Recursive, r.Sorted)) return toResponse(a.store.Get(r.Path, r.Recursive, r.Sorted))
} }
func (a *applierV2store) Sync(r *pb.Request) Response { func (a *applierV2store) Sync(r *RequestV2) Response {
a.store.DeleteExpiredKeys(time.Unix(0, r.Time)) a.store.DeleteExpiredKeys(time.Unix(0, r.Time))
return Response{} return Response{}
} }
// applyV2Request interprets r as a call to store.X and returns a Response interpreted // applyV2Request interprets r as a call to store.X and returns a Response interpreted
// from store.Event // from store.Event
func (s *EtcdServer) applyV2Request(r *pb.Request) Response { func (s *EtcdServer) applyV2Request(r *RequestV2) Response {
toTTLOptions(r)
switch r.Method { switch r.Method {
case "POST": case "POST":
return s.applyV2.Post(r) return s.applyV2.Post(r)
@ -122,11 +120,11 @@ func (s *EtcdServer) applyV2Request(r *pb.Request) Response {
return s.applyV2.Sync(r) return s.applyV2.Sync(r)
default: default:
// This should never be reached, but just in case: // This should never be reached, but just in case:
return Response{err: ErrUnknownMethod} return Response{Err: ErrUnknownMethod}
} }
} }
func toTTLOptions(r *pb.Request) store.TTLOptionSet { func (r *RequestV2) TTLOptions() store.TTLOptionSet {
refresh, _ := pbutil.GetBool(r.Refresh) refresh, _ := pbutil.GetBool(r.Refresh)
ttlOptions := store.TTLOptionSet{Refresh: refresh} ttlOptions := store.TTLOptionSet{Refresh: refresh}
if r.Expiration != 0 { if r.Expiration != 0 {
@ -136,5 +134,5 @@ func toTTLOptions(r *pb.Request) store.TTLOptionSet {
} }
func toResponse(ev *store.Event, err error) Response { func toResponse(ev *store.Event, err error) Response {
return Response{Event: ev, err: err} return Response{Event: ev, Err: err}
} }

View File

@ -15,14 +15,13 @@
package etcdserver package etcdserver
import ( import (
"context"
"fmt" "fmt"
"path/filepath" "path/filepath"
"sort" "sort"
"strings" "strings"
"time" "time"
"golang.org/x/net/context"
"github.com/coreos/etcd/pkg/netutil" "github.com/coreos/etcd/pkg/netutil"
"github.com/coreos/etcd/pkg/transport" "github.com/coreos/etcd/pkg/transport"
"github.com/coreos/etcd/pkg/types" "github.com/coreos/etcd/pkg/types"
@ -52,7 +51,7 @@ type ServerConfig struct {
ElectionTicks int ElectionTicks int
BootstrapTimeout time.Duration BootstrapTimeout time.Duration
AutoCompactionRetention int AutoCompactionRetention time.Duration
AutoCompactionMode string AutoCompactionMode string
QuotaBackendBytes int64 QuotaBackendBytes int64
MaxTxnOps uint MaxTxnOps uint
@ -66,6 +65,13 @@ type ServerConfig struct {
ClientCertAuthEnabled bool ClientCertAuthEnabled bool
AuthToken string AuthToken string
// InitialCorruptCheck is true to check data corruption on boot
// before serving any peer/client traffic.
InitialCorruptCheck bool
CorruptCheckTime time.Duration
Debug bool
} }
// VerifyBootstrap sanity-checks the initial config for bootstrap case // VerifyBootstrap sanity-checks the initial config for bootstrap case
@ -172,17 +178,16 @@ func (c *ServerConfig) ShouldDiscover() bool { return c.DiscoveryURL != "" }
func (c *ServerConfig) ReqTimeout() time.Duration { func (c *ServerConfig) ReqTimeout() time.Duration {
// 5s for queue waiting, computation and disk IO delay // 5s for queue waiting, computation and disk IO delay
// + 2 * election timeout for possible leader election // + 2 * election timeout for possible leader election
return 5*time.Second + 2*time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond return 5*time.Second + 2*time.Duration(c.ElectionTicks*int(c.TickMs))*time.Millisecond
} }
func (c *ServerConfig) electionTimeout() time.Duration { func (c *ServerConfig) electionTimeout() time.Duration {
return time.Duration(c.ElectionTicks) * time.Duration(c.TickMs) * time.Millisecond return time.Duration(c.ElectionTicks*int(c.TickMs)) * time.Millisecond
} }
func (c *ServerConfig) peerDialTimeout() time.Duration { func (c *ServerConfig) peerDialTimeout() time.Duration {
// 1s for queue wait and system delay // 1s for queue wait and election timeout
// + one RTT, which is smaller than 1/5 election timeout return time.Second + time.Duration(c.ElectionTicks*int(c.TickMs))*time.Millisecond
return time.Second + time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond/5
} }
func (c *ServerConfig) PrintWithInitial() { c.print(true) } func (c *ServerConfig) PrintWithInitial() { c.print(true) }

262
vendor/github.com/coreos/etcd/etcdserver/corrupt.go generated vendored Normal file
View File

@ -0,0 +1,262 @@
// Copyright 2017 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 etcdserver
import (
"context"
"fmt"
"time"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/pkg/types"
)
// CheckInitialHashKV compares initial hash values with its peers
// before serving any peer/client traffic. Only mismatch when hashes
// are different at requested revision, with same compact revision.
func (s *EtcdServer) CheckInitialHashKV() error {
if !s.Cfg.InitialCorruptCheck {
return nil
}
plog.Infof("%s starting initial corruption check with timeout %v...", s.ID(), s.Cfg.ReqTimeout())
h, rev, crev, err := s.kv.HashByRev(0)
if err != nil {
return fmt.Errorf("%s failed to fetch hash (%v)", s.ID(), err)
}
peers := s.getPeerHashKVs(rev)
mismatch := 0
for _, p := range peers {
if p.resp != nil {
peerID := types.ID(p.resp.Header.MemberId)
if h != p.resp.Hash {
if crev == p.resp.CompactRevision {
plog.Errorf("%s's hash %d != %s's hash %d (revision %d, peer revision %d, compact revision %d)", s.ID(), h, peerID, p.resp.Hash, rev, p.resp.Header.Revision, crev)
mismatch++
} else {
plog.Warningf("%s cannot check hash of peer(%s): peer has a different compact revision %d (revision:%d)", s.ID(), peerID, p.resp.CompactRevision, rev)
}
}
continue
}
if p.err != nil {
switch p.err {
case rpctypes.ErrFutureRev:
plog.Warningf("%s cannot check the hash of peer(%q) at revision %d: peer is lagging behind(%q)", s.ID(), p.eps, rev, p.err.Error())
case rpctypes.ErrCompacted:
plog.Warningf("%s cannot check the hash of peer(%q) at revision %d: local node is lagging behind(%q)", s.ID(), p.eps, rev, p.err.Error())
}
}
}
if mismatch > 0 {
return fmt.Errorf("%s found data inconsistency with peers", s.ID())
}
plog.Infof("%s succeeded on initial corruption checking: no corruption", s.ID())
return nil
}
func (s *EtcdServer) monitorKVHash() {
t := s.Cfg.CorruptCheckTime
if t == 0 {
return
}
plog.Infof("enabled corruption checking with %s interval", t)
for {
select {
case <-s.stopping:
return
case <-time.After(t):
}
if !s.isLeader() {
continue
}
if err := s.checkHashKV(); err != nil {
plog.Debugf("check hash kv failed %v", err)
}
}
}
func (s *EtcdServer) checkHashKV() error {
h, rev, crev, err := s.kv.HashByRev(0)
if err != nil {
plog.Fatalf("failed to hash kv store (%v)", err)
}
peers := s.getPeerHashKVs(rev)
ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
err = s.linearizableReadNotify(ctx)
cancel()
if err != nil {
return err
}
h2, rev2, crev2, err := s.kv.HashByRev(0)
if err != nil {
plog.Warningf("failed to hash kv store (%v)", err)
return err
}
alarmed := false
mismatch := func(id uint64) {
if alarmed {
return
}
alarmed = true
a := &pb.AlarmRequest{
MemberID: uint64(id),
Action: pb.AlarmRequest_ACTIVATE,
Alarm: pb.AlarmType_CORRUPT,
}
s.goAttach(func() {
s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
})
}
if h2 != h && rev2 == rev && crev == crev2 {
plog.Warningf("mismatched hashes %d and %d for revision %d", h, h2, rev)
mismatch(uint64(s.ID()))
}
for _, p := range peers {
if p.resp == nil {
continue
}
id := p.resp.Header.MemberId
// leader expects follower's latest revision less than or equal to leader's
if p.resp.Header.Revision > rev2 {
plog.Warningf(
"revision %d from member %v, expected at most %d",
p.resp.Header.Revision,
types.ID(id),
rev2)
mismatch(id)
}
// leader expects follower's latest compact revision less than or equal to leader's
if p.resp.CompactRevision > crev2 {
plog.Warningf(
"compact revision %d from member %v, expected at most %d",
p.resp.CompactRevision,
types.ID(id),
crev2,
)
mismatch(id)
}
// follower's compact revision is leader's old one, then hashes must match
if p.resp.CompactRevision == crev && p.resp.Hash != h {
plog.Warningf(
"hash %d at revision %d from member %v, expected hash %d",
p.resp.Hash,
rev,
types.ID(id),
h,
)
mismatch(id)
}
}
return nil
}
type peerHashKVResp struct {
resp *clientv3.HashKVResponse
err error
eps []string
}
func (s *EtcdServer) getPeerHashKVs(rev int64) (resps []*peerHashKVResp) {
// TODO: handle the case when "s.cluster.Members" have not
// been populated (e.g. no snapshot to load from disk)
mbs := s.cluster.Members()
pURLs := make([][]string, len(mbs))
for _, m := range mbs {
if m.ID == s.ID() {
continue
}
pURLs = append(pURLs, m.PeerURLs)
}
for _, purls := range pURLs {
if len(purls) == 0 {
continue
}
cli, cerr := clientv3.New(clientv3.Config{
DialTimeout: s.Cfg.ReqTimeout(),
Endpoints: purls,
})
if cerr != nil {
plog.Warningf("%s failed to create client to peer %q for hash checking (%q)", s.ID(), purls, cerr.Error())
continue
}
respsLen := len(resps)
for _, c := range cli.Endpoints() {
ctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
var resp *clientv3.HashKVResponse
resp, cerr = cli.HashKV(ctx, c, rev)
cancel()
if cerr == nil {
resps = append(resps, &peerHashKVResp{resp: resp})
break
}
plog.Warningf("%s hash-kv error %q on peer %q with revision %d", s.ID(), cerr.Error(), c, rev)
}
cli.Close()
if respsLen == len(resps) {
resps = append(resps, &peerHashKVResp{err: cerr, eps: purls})
}
}
return resps
}
type applierV3Corrupt struct {
applierV3
}
func newApplierV3Corrupt(a applierV3) *applierV3Corrupt { return &applierV3Corrupt{a} }
func (a *applierV3Corrupt) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
return nil, ErrCorrupt
}
func (a *applierV3Corrupt) Range(txn mvcc.TxnRead, p *pb.RangeRequest) (*pb.RangeResponse, error) {
return nil, ErrCorrupt
}
func (a *applierV3Corrupt) DeleteRange(txn mvcc.TxnWrite, p *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
return nil, ErrCorrupt
}
func (a *applierV3Corrupt) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
return nil, ErrCorrupt
}
func (a *applierV3Corrupt) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error) {
return nil, nil, ErrCorrupt
}
func (a *applierV3Corrupt) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
return nil, ErrCorrupt
}
func (a *applierV3Corrupt) LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
return nil, ErrCorrupt
}

View File

@ -29,11 +29,13 @@ var (
ErrTimeoutLeaderTransfer = errors.New("etcdserver: request timed out, leader transfer took too long") ErrTimeoutLeaderTransfer = errors.New("etcdserver: request timed out, leader transfer took too long")
ErrNotEnoughStartedMembers = errors.New("etcdserver: re-configuration failed due to not enough started members") ErrNotEnoughStartedMembers = errors.New("etcdserver: re-configuration failed due to not enough started members")
ErrNoLeader = errors.New("etcdserver: no leader") ErrNoLeader = errors.New("etcdserver: no leader")
ErrNotLeader = errors.New("etcdserver: not leader")
ErrRequestTooLarge = errors.New("etcdserver: request is too large") ErrRequestTooLarge = errors.New("etcdserver: request is too large")
ErrNoSpace = errors.New("etcdserver: no space") ErrNoSpace = errors.New("etcdserver: no space")
ErrTooManyRequests = errors.New("etcdserver: too many requests") ErrTooManyRequests = errors.New("etcdserver: too many requests")
ErrUnhealthy = errors.New("etcdserver: unhealthy cluster") ErrUnhealthy = errors.New("etcdserver: unhealthy cluster")
ErrKeyNotFound = errors.New("etcdserver: key not found") ErrKeyNotFound = errors.New("etcdserver: key not found")
ErrCorrupt = errors.New("etcdserver: corrupt cluster")
) )
type DiscoveryError struct { type DiscoveryError struct {

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: etcdserver.proto // source: etcdserver.proto
// DO NOT EDIT!
/* /*
Package etcdserverpb is a generated protocol buffer package. Package etcdserverpb is a generated protocol buffer package.
@ -32,6 +31,8 @@
CompactionRequest CompactionRequest
CompactionResponse CompactionResponse
HashRequest HashRequest
HashKVRequest
HashKVResponse
HashResponse HashResponse
SnapshotRequest SnapshotRequest
SnapshotResponse SnapshotResponse
@ -47,6 +48,9 @@
LeaseKeepAliveResponse LeaseKeepAliveResponse
LeaseTimeToLiveRequest LeaseTimeToLiveRequest
LeaseTimeToLiveResponse LeaseTimeToLiveResponse
LeaseLeasesRequest
LeaseStatus
LeaseLeasesResponse
Member Member
MemberAddRequest MemberAddRequest
MemberAddResponse MemberAddResponse
@ -58,6 +62,8 @@
MemberListResponse MemberListResponse
DefragmentRequest DefragmentRequest
DefragmentResponse DefragmentResponse
MoveLeaderRequest
MoveLeaderResponse
AlarmRequest AlarmRequest
AlarmMember AlarmMember
AlarmResponse AlarmResponse
@ -105,6 +111,8 @@ import (
math "math" math "math"
_ "github.com/gogo/protobuf/gogoproto"
io "io" io "io"
) )
@ -311,24 +319,6 @@ func (m *Metadata) MarshalTo(dAtA []byte) (int, error) {
return i, nil return i, nil
} }
func encodeFixed64Etcdserver(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Etcdserver(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintEtcdserver(dAtA []byte, offset int, v uint64) int { func encodeVarintEtcdserver(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 { for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80) dAtA[offset] = uint8(v&0x7f | 0x80)

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: raft_internal.proto // source: raft_internal.proto
// DO NOT EDIT!
package etcdserverpb package etcdserverpb
@ -11,6 +10,8 @@ import (
math "math" math "math"
_ "github.com/gogo/protobuf/gogoproto"
io "io" io "io"
) )
@ -505,24 +506,6 @@ func (m *InternalAuthenticateRequest) MarshalTo(dAtA []byte) (int, error) {
return i, nil return i, nil
} }
func encodeFixed64RaftInternal(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32RaftInternal(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintRaftInternal(dAtA []byte, offset int, v uint64) int { func encodeVarintRaftInternal(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 { for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80) dAtA[offset] = uint8(v&0x7f | 0x80)

File diff suppressed because it is too large Load Diff

View File

@ -18,6 +18,7 @@ import (
"time" "time"
"github.com/coreos/etcd/pkg/runtime" "github.com/coreos/etcd/pkg/runtime"
"github.com/coreos/etcd/version"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
) )
@ -64,6 +65,13 @@ var (
Name: "lease_expired_total", Name: "lease_expired_total",
Help: "The total number of expired leases.", Help: "The total number of expired leases.",
}) })
currentVersion = prometheus.NewGaugeVec(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "server",
Name: "version",
Help: "Which version is running. 1 for 'server_version' label with current version.",
},
[]string{"server_version"})
) )
func init() { func init() {
@ -74,6 +82,11 @@ func init() {
prometheus.MustRegister(proposalsPending) prometheus.MustRegister(proposalsPending)
prometheus.MustRegister(proposalsFailed) prometheus.MustRegister(proposalsFailed)
prometheus.MustRegister(leaseExpired) prometheus.MustRegister(leaseExpired)
prometheus.MustRegister(currentVersion)
currentVersion.With(prometheus.Labels{
"server_version": version.Version,
}).Set(1)
} }
func monitorFileDescriptor(done <-chan struct{}) { func monitorFileDescriptor(done <-chan struct{}) {

View File

@ -416,7 +416,7 @@ func startNode(cfg ServerConfig, cl *membership.RaftCluster, ids []types.ID) (id
raftStatus = n.Status raftStatus = n.Status
raftStatusMu.Unlock() raftStatusMu.Unlock()
advanceTicksForElection(n, c.ElectionTick) advanceTicksForElection(n, c.ElectionTick)
return return id, n, s, w
} }
func restartNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *membership.RaftCluster, raft.Node, *raft.MemoryStorage, *wal.WAL) { func restartNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *membership.RaftCluster, raft.Node, *raft.MemoryStorage, *wal.WAL) {

View File

@ -15,6 +15,7 @@
package etcdserver package etcdserver
import ( import (
"context"
"encoding/json" "encoding/json"
"expvar" "expvar"
"fmt" "fmt"
@ -38,6 +39,7 @@ import (
"github.com/coreos/etcd/etcdserver/membership" "github.com/coreos/etcd/etcdserver/membership"
"github.com/coreos/etcd/etcdserver/stats" "github.com/coreos/etcd/etcdserver/stats"
"github.com/coreos/etcd/lease" "github.com/coreos/etcd/lease"
"github.com/coreos/etcd/lease/leasehttp"
"github.com/coreos/etcd/mvcc" "github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/mvcc/backend" "github.com/coreos/etcd/mvcc/backend"
"github.com/coreos/etcd/pkg/fileutil" "github.com/coreos/etcd/pkg/fileutil"
@ -54,9 +56,9 @@ import (
"github.com/coreos/etcd/store" "github.com/coreos/etcd/store"
"github.com/coreos/etcd/version" "github.com/coreos/etcd/version"
"github.com/coreos/etcd/wal" "github.com/coreos/etcd/wal"
"github.com/coreos/go-semver/semver" "github.com/coreos/go-semver/semver"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"golang.org/x/net/context"
) )
const ( const (
@ -108,29 +110,33 @@ func init() {
} }
type Response struct { type Response struct {
Term uint64
Index uint64
Event *store.Event Event *store.Event
Watcher store.Watcher Watcher store.Watcher
err error Err error
} }
type Server interface { type ServerV2 interface {
// Start performs any initialization of the Server necessary for it to Server
// begin serving requests. It must be called before Do or Process. // Do takes a V2 request and attempts to fulfill it, returning a Response.
// Start must be non-blocking; any long-running server functionality Do(ctx context.Context, r pb.Request) (Response, error)
// should be implemented in goroutines. stats.Stats
Start() ClientCertAuthEnabled() bool
// Stop terminates the Server and performs any necessary finalization. }
// Do and Process cannot be called after Stop has been invoked.
Stop() type ServerV3 interface {
// ID returns the ID of the Server. Server
ID() types.ID ID() types.ID
RaftTimer
}
func (s *EtcdServer) ClientCertAuthEnabled() bool { return s.Cfg.ClientCertAuthEnabled }
type Server interface {
// Leader returns the ID of the leader Server. // Leader returns the ID of the leader Server.
Leader() types.ID Leader() types.ID
// Do takes a request and attempts to fulfill it, returning a Response.
Do(ctx context.Context, r pb.Request) (Response, error)
// Process takes a raft message and applies it to the server's raft state
// machine, respecting any timeout of the given context.
Process(ctx context.Context, m raftpb.Message) error
// AddMember attempts to add a member into the cluster. It will return // AddMember attempts to add a member into the cluster. It will return
// ErrIDRemoved if member ID is removed from the cluster, or return // ErrIDRemoved if member ID is removed from the cluster, or return
// ErrIDExists if member ID exists in the cluster. // ErrIDExists if member ID exists in the cluster.
@ -139,7 +145,6 @@ type Server interface {
// return ErrIDRemoved if member ID is removed from the cluster, or return // return ErrIDRemoved if member ID is removed from the cluster, or return
// ErrIDNotFound if member ID is not in the cluster. // ErrIDNotFound if member ID is not in the cluster.
RemoveMember(ctx context.Context, id uint64) ([]*membership.Member, error) RemoveMember(ctx context.Context, id uint64) ([]*membership.Member, error)
// UpdateMember attempts to update an existing member in the cluster. It will // UpdateMember attempts to update an existing member in the cluster. It will
// return ErrIDNotFound if the member ID does not exist. // return ErrIDNotFound if the member ID does not exist.
UpdateMember(ctx context.Context, updateMemb membership.Member) ([]*membership.Member, error) UpdateMember(ctx context.Context, updateMemb membership.Member) ([]*membership.Member, error)
@ -159,6 +164,8 @@ type Server interface {
// the leader is etcd 2.0. etcd 2.0 leader will not update clusterVersion since // the leader is etcd 2.0. etcd 2.0 leader will not update clusterVersion since
// this feature is introduced post 2.0. // this feature is introduced post 2.0.
ClusterVersion() *semver.Version ClusterVersion() *semver.Version
Cluster() api.Cluster
Alarms() []*pb.AlarmMember
} }
// EtcdServer is the production implementation of the Server interface // EtcdServer is the production implementation of the Server interface
@ -514,9 +521,10 @@ func NewServer(cfg ServerConfig) (srv *EtcdServer, err error) {
return srv, nil return srv, nil
} }
// Start prepares and starts server in a new goroutine. It is no longer safe to // Start performs any initialization of the Server necessary for it to
// modify a server's fields after it has been sent to Start. // begin serving requests. It must be called before Do or Process.
// It also starts a goroutine to publish its server information. // Start must be non-blocking; any long-running server functionality
// should be implemented in goroutines.
func (s *EtcdServer) Start() { func (s *EtcdServer) Start() {
s.start() s.start()
s.goAttach(func() { s.publish(s.Cfg.ReqTimeout()) }) s.goAttach(func() { s.publish(s.Cfg.ReqTimeout()) })
@ -524,6 +532,7 @@ func (s *EtcdServer) Start() {
s.goAttach(func() { monitorFileDescriptor(s.stopping) }) s.goAttach(func() { monitorFileDescriptor(s.stopping) })
s.goAttach(s.monitorVersions) s.goAttach(s.monitorVersions)
s.goAttach(s.linearizableReadLoop) s.goAttach(s.linearizableReadLoop)
s.goAttach(s.monitorKVHash)
} }
// start prepares and starts server in a new goroutine. It is no longer safe to // start prepares and starts server in a new goroutine. It is no longer safe to
@ -575,14 +584,27 @@ func (s *EtcdServer) purgeFile() {
func (s *EtcdServer) ID() types.ID { return s.id } func (s *EtcdServer) ID() types.ID { return s.id }
func (s *EtcdServer) Cluster() *membership.RaftCluster { return s.cluster } func (s *EtcdServer) Cluster() api.Cluster { return s.cluster }
func (s *EtcdServer) RaftHandler() http.Handler { return s.r.transport.Handler() }
func (s *EtcdServer) Lessor() lease.Lessor { return s.lessor }
func (s *EtcdServer) ApplyWait() <-chan struct{} { return s.applyWait.Wait(s.getCommittedIndex()) } func (s *EtcdServer) ApplyWait() <-chan struct{} { return s.applyWait.Wait(s.getCommittedIndex()) }
type ServerPeer interface {
ServerV2
RaftHandler() http.Handler
LeaseHandler() http.Handler
}
func (s *EtcdServer) LeaseHandler() http.Handler {
if s.lessor == nil {
return nil
}
return leasehttp.NewHandler(s.lessor, s.ApplyWait)
}
func (s *EtcdServer) RaftHandler() http.Handler { return s.r.transport.Handler() }
// Process takes a raft message and applies it to the server's raft state
// machine, respecting any timeout of the given context.
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error { func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error {
if s.cluster.IsIDRemoved(types.ID(m.From)) { if s.cluster.IsIDRemoved(types.ID(m.From)) {
plog.Warningf("reject message from removed member %s", types.ID(m.From).String()) plog.Warningf("reject message from removed member %s", types.ID(m.From).String())
@ -748,7 +770,9 @@ func (s *EtcdServer) run() {
lid := lease.ID lid := lease.ID
s.goAttach(func() { s.goAttach(func() {
ctx := s.authStore.WithRoot(s.ctx) ctx := s.authStore.WithRoot(s.ctx)
s.LeaseRevoke(ctx, &pb.LeaseRevokeRequest{ID: int64(lid)}) if _, lerr := s.LeaseRevoke(ctx, &pb.LeaseRevokeRequest{ID: int64(lid)}); lerr != nil {
plog.Warningf("failed to revoke %016x (%q)", lid, lerr.Error())
}
leaseExpired.Inc() leaseExpired.Inc()
<-c <-c
}) })
@ -932,9 +956,8 @@ func (s *EtcdServer) isLeader() bool {
return uint64(s.ID()) == s.Lead() return uint64(s.ID()) == s.Lead()
} }
// transferLeadership transfers the leader to the given transferee. // MoveLeader transfers the leader to the given transferee.
// TODO: maybe expose to client? func (s *EtcdServer) MoveLeader(ctx context.Context, lead, transferee uint64) error {
func (s *EtcdServer) transferLeadership(ctx context.Context, lead, transferee uint64) error {
now := time.Now() now := time.Now()
interval := time.Duration(s.Cfg.TickMs) * time.Millisecond interval := time.Duration(s.Cfg.TickMs) * time.Millisecond
@ -973,7 +996,7 @@ func (s *EtcdServer) TransferLeadership() error {
tm := s.Cfg.ReqTimeout() tm := s.Cfg.ReqTimeout()
ctx, cancel := context.WithTimeout(s.ctx, tm) ctx, cancel := context.WithTimeout(s.ctx, tm)
err := s.transferLeadership(ctx, s.Lead(), uint64(transferee)) err := s.MoveLeader(ctx, s.Lead(), uint64(transferee))
cancel() cancel()
return err return err
} }
@ -992,6 +1015,8 @@ func (s *EtcdServer) HardStop() {
// Stop should be called after a Start(s), otherwise it will block forever. // Stop should be called after a Start(s), otherwise it will block forever.
// When stopping leader, Stop transfers its leadership to one of its peers // When stopping leader, Stop transfers its leadership to one of its peers
// before stopping the server. // before stopping the server.
// Stop terminates the Server and performs any necessary finalization.
// Do and Process cannot be called after Stop has been invoked.
func (s *EtcdServer) Stop() { func (s *EtcdServer) Stop() {
if err := s.TransferLeadership(); err != nil { if err := s.TransferLeadership(); err != nil {
plog.Warningf("%s failed to transfer leadership (%v)", s.ID(), err) plog.Warningf("%s failed to transfer leadership (%v)", s.ID(), err)
@ -1322,12 +1347,13 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {
var raftReq pb.InternalRaftRequest var raftReq pb.InternalRaftRequest
if !pbutil.MaybeUnmarshal(&raftReq, e.Data) { // backward compatible if !pbutil.MaybeUnmarshal(&raftReq, e.Data) { // backward compatible
var r pb.Request var r pb.Request
pbutil.MustUnmarshal(&r, e.Data) rp := &r
s.w.Trigger(r.ID, s.applyV2Request(&r)) pbutil.MustUnmarshal(rp, e.Data)
s.w.Trigger(r.ID, s.applyV2Request((*RequestV2)(rp)))
return return
} }
if raftReq.V2 != nil { if raftReq.V2 != nil {
req := raftReq.V2 req := (*RequestV2)(raftReq.V2)
s.w.Trigger(req.ID, s.applyV2Request(req)) s.w.Trigger(req.ID, s.applyV2Request(req))
return return
} }
@ -1367,8 +1393,7 @@ func (s *EtcdServer) applyEntryNormal(e *raftpb.Entry) {
Action: pb.AlarmRequest_ACTIVATE, Action: pb.AlarmRequest_ACTIVATE,
Alarm: pb.AlarmType_NOSPACE, Alarm: pb.AlarmType_NOSPACE,
} }
r := pb.InternalRaftRequest{Alarm: a} s.raftRequest(s.ctx, pb.InternalRaftRequest{Alarm: a})
s.processInternalRaftRequest(s.ctx, r)
s.w.Trigger(id, ar) s.w.Trigger(id, ar)
}) })
} }
@ -1630,6 +1655,9 @@ func (s *EtcdServer) restoreAlarms() error {
if len(as.Get(pb.AlarmType_NOSPACE)) > 0 { if len(as.Get(pb.AlarmType_NOSPACE)) > 0 {
s.applyV3 = newApplierV3Capped(s.applyV3) s.applyV3 = newApplierV3Capped(s.applyV3)
} }
if len(as.Get(pb.AlarmType_CORRUPT)) > 0 {
s.applyV3 = newApplierV3Corrupt(s.applyV3)
}
return nil return nil
} }
@ -1668,3 +1696,7 @@ func (s *EtcdServer) goAttach(f func()) {
f() f()
}() }()
} }
func (s *EtcdServer) Alarms() []*pb.AlarmMember {
return s.alarmStore.Get(pb.AlarmType_NONE)
}

View File

@ -94,5 +94,5 @@ func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID,
pbutil.MustUnmarshal(&metadata, wmetadata) pbutil.MustUnmarshal(&metadata, wmetadata)
id = types.ID(metadata.NodeID) id = types.ID(metadata.NodeID)
cid = types.ID(metadata.ClusterID) cid = types.ID(metadata.ClusterID)
return return w, id, cid, st, ents
} }

View File

@ -15,41 +15,86 @@
package etcdserver package etcdserver
import ( import (
"context"
"time" "time"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "github.com/coreos/etcd/store"
) )
type v2API interface { type RequestV2 pb.Request
Post(ctx context.Context, r *pb.Request) (Response, error)
Put(ctx context.Context, r *pb.Request) (Response, error) type RequestV2Handler interface {
Delete(ctx context.Context, r *pb.Request) (Response, error) Post(ctx context.Context, r *RequestV2) (Response, error)
QGet(ctx context.Context, r *pb.Request) (Response, error) Put(ctx context.Context, r *RequestV2) (Response, error)
Get(ctx context.Context, r *pb.Request) (Response, error) Delete(ctx context.Context, r *RequestV2) (Response, error)
Head(ctx context.Context, r *pb.Request) (Response, error) QGet(ctx context.Context, r *RequestV2) (Response, error)
Get(ctx context.Context, r *RequestV2) (Response, error)
Head(ctx context.Context, r *RequestV2) (Response, error)
} }
type v2apiStore struct{ s *EtcdServer } type reqV2HandlerEtcdServer struct {
reqV2HandlerStore
s *EtcdServer
}
func (a *v2apiStore) Post(ctx context.Context, r *pb.Request) (Response, error) { type reqV2HandlerStore struct {
store store.Store
applier ApplierV2
}
func NewStoreRequestV2Handler(s store.Store, applier ApplierV2) RequestV2Handler {
return &reqV2HandlerStore{s, applier}
}
func (a *reqV2HandlerStore) Post(ctx context.Context, r *RequestV2) (Response, error) {
return a.applier.Post(r), nil
}
func (a *reqV2HandlerStore) Put(ctx context.Context, r *RequestV2) (Response, error) {
return a.applier.Put(r), nil
}
func (a *reqV2HandlerStore) Delete(ctx context.Context, r *RequestV2) (Response, error) {
return a.applier.Delete(r), nil
}
func (a *reqV2HandlerStore) QGet(ctx context.Context, r *RequestV2) (Response, error) {
return a.applier.QGet(r), nil
}
func (a *reqV2HandlerStore) Get(ctx context.Context, r *RequestV2) (Response, error) {
if r.Wait {
wc, err := a.store.Watch(r.Path, r.Recursive, r.Stream, r.Since)
return Response{Watcher: wc}, err
}
ev, err := a.store.Get(r.Path, r.Recursive, r.Sorted)
return Response{Event: ev}, err
}
func (a *reqV2HandlerStore) Head(ctx context.Context, r *RequestV2) (Response, error) {
ev, err := a.store.Get(r.Path, r.Recursive, r.Sorted)
return Response{Event: ev}, err
}
func (a *reqV2HandlerEtcdServer) Post(ctx context.Context, r *RequestV2) (Response, error) {
return a.processRaftRequest(ctx, r) return a.processRaftRequest(ctx, r)
} }
func (a *v2apiStore) Put(ctx context.Context, r *pb.Request) (Response, error) { func (a *reqV2HandlerEtcdServer) Put(ctx context.Context, r *RequestV2) (Response, error) {
return a.processRaftRequest(ctx, r) return a.processRaftRequest(ctx, r)
} }
func (a *v2apiStore) Delete(ctx context.Context, r *pb.Request) (Response, error) { func (a *reqV2HandlerEtcdServer) Delete(ctx context.Context, r *RequestV2) (Response, error) {
return a.processRaftRequest(ctx, r) return a.processRaftRequest(ctx, r)
} }
func (a *v2apiStore) QGet(ctx context.Context, r *pb.Request) (Response, error) { func (a *reqV2HandlerEtcdServer) QGet(ctx context.Context, r *RequestV2) (Response, error) {
return a.processRaftRequest(ctx, r) return a.processRaftRequest(ctx, r)
} }
func (a *v2apiStore) processRaftRequest(ctx context.Context, r *pb.Request) (Response, error) { func (a *reqV2HandlerEtcdServer) processRaftRequest(ctx context.Context, r *RequestV2) (Response, error) {
data, err := r.Marshal() data, err := ((*pb.Request)(r)).Marshal()
if err != nil { if err != nil {
return Response{}, err return Response{}, err
} }
@ -63,7 +108,7 @@ func (a *v2apiStore) processRaftRequest(ctx context.Context, r *pb.Request) (Res
select { select {
case x := <-ch: case x := <-ch:
resp := x.(Response) resp := x.(Response)
return resp, resp.err return resp, resp.Err
case <-ctx.Done(): case <-ctx.Done():
proposalsFailed.Inc() proposalsFailed.Inc()
a.s.w.Trigger(r.ID, nil) // GC wait a.s.w.Trigger(r.ID, nil) // GC wait
@ -73,53 +118,43 @@ func (a *v2apiStore) processRaftRequest(ctx context.Context, r *pb.Request) (Res
return Response{}, ErrStopped return Response{}, ErrStopped
} }
func (a *v2apiStore) Get(ctx context.Context, r *pb.Request) (Response, error) { func (s *EtcdServer) Do(ctx context.Context, r pb.Request) (Response, error) {
if r.Wait { r.ID = s.reqIDGen.Next()
wc, err := a.s.store.Watch(r.Path, r.Recursive, r.Stream, r.Since) h := &reqV2HandlerEtcdServer{
if err != nil { reqV2HandlerStore: reqV2HandlerStore{
return Response{}, err store: s.store,
} applier: s.applyV2,
return Response{Watcher: wc}, nil },
s: s,
} }
ev, err := a.s.store.Get(r.Path, r.Recursive, r.Sorted) rp := &r
if err != nil { resp, err := ((*RequestV2)(rp)).Handle(ctx, h)
return Response{}, err resp.Term, resp.Index = s.Term(), s.Index()
} return resp, err
return Response{Event: ev}, nil
} }
func (a *v2apiStore) Head(ctx context.Context, r *pb.Request) (Response, error) { // Handle interprets r and performs an operation on s.store according to r.Method
ev, err := a.s.store.Get(r.Path, r.Recursive, r.Sorted)
if err != nil {
return Response{}, err
}
return Response{Event: ev}, nil
}
// Do interprets r and performs an operation on s.store according to r.Method
// and other fields. If r.Method is "POST", "PUT", "DELETE", or a "GET" with // and other fields. If r.Method is "POST", "PUT", "DELETE", or a "GET" with
// Quorum == true, r will be sent through consensus before performing its // Quorum == true, r will be sent through consensus before performing its
// respective operation. Do will block until an action is performed or there is // respective operation. Do will block until an action is performed or there is
// an error. // an error.
func (s *EtcdServer) Do(ctx context.Context, r pb.Request) (Response, error) { func (r *RequestV2) Handle(ctx context.Context, v2api RequestV2Handler) (Response, error) {
r.ID = s.reqIDGen.Next()
if r.Method == "GET" && r.Quorum { if r.Method == "GET" && r.Quorum {
r.Method = "QGET" r.Method = "QGET"
} }
v2api := (v2API)(&v2apiStore{s})
switch r.Method { switch r.Method {
case "POST": case "POST":
return v2api.Post(ctx, &r) return v2api.Post(ctx, r)
case "PUT": case "PUT":
return v2api.Put(ctx, &r) return v2api.Put(ctx, r)
case "DELETE": case "DELETE":
return v2api.Delete(ctx, &r) return v2api.Delete(ctx, r)
case "QGET": case "QGET":
return v2api.QGet(ctx, &r) return v2api.QGet(ctx, r)
case "GET": case "GET":
return v2api.Get(ctx, &r) return v2api.Get(ctx, r)
case "HEAD": case "HEAD":
return v2api.Head(ctx, &r) return v2api.Head(ctx, r)
} }
return Response{}, ErrUnknownMethod return Response{}, ErrUnknownMethod
} }

View File

@ -16,6 +16,7 @@ package etcdserver
import ( import (
"bytes" "bytes"
"context"
"encoding/binary" "encoding/binary"
"time" "time"
@ -27,7 +28,7 @@ import (
"github.com/coreos/etcd/mvcc" "github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/raft" "github.com/coreos/etcd/raft"
"golang.org/x/net/context" "github.com/gogo/protobuf/proto"
) )
const ( const (
@ -58,6 +59,9 @@ type Lessor interface {
// LeaseTimeToLive retrieves lease information. // LeaseTimeToLive retrieves lease information.
LeaseTimeToLive(ctx context.Context, r *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) LeaseTimeToLive(ctx context.Context, r *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error)
// LeaseLeases lists all leases.
LeaseLeases(ctx context.Context, r *pb.LeaseLeasesRequest) (*pb.LeaseLeasesResponse, error)
} }
type Authenticator interface { type Authenticator interface {
@ -99,25 +103,19 @@ func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeRe
} }
func (s *EtcdServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) { func (s *EtcdServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Put: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{Put: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.PutResponse), nil
return nil, result.err
}
return result.resp.(*pb.PutResponse), nil
} }
func (s *EtcdServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) { func (s *EtcdServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{DeleteRange: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{DeleteRange: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.DeleteRangeResponse), nil
return nil, result.err
}
return result.resp.(*pb.DeleteRangeResponse), nil
} }
func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) { func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
@ -139,14 +137,11 @@ func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse
} }
return resp, err return resp, err
} }
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Txn: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{Txn: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.TxnResponse), nil
return nil, result.err
}
return result.resp.(*pb.TxnResponse), nil
} }
func isTxnSerializable(r *pb.TxnRequest) bool { func isTxnSerializable(r *pb.TxnRequest) bool {
@ -211,25 +206,19 @@ func (s *EtcdServer) LeaseGrant(ctx context.Context, r *pb.LeaseGrantRequest) (*
// only use positive int64 id's // only use positive int64 id's
r.ID = int64(s.reqIDGen.Next() & ((1 << 63) - 1)) r.ID = int64(s.reqIDGen.Next() & ((1 << 63) - 1))
} }
result, err := s.processInternalRaftRequestOnce(ctx, pb.InternalRaftRequest{LeaseGrant: r}) resp, err := s.raftRequestOnce(ctx, pb.InternalRaftRequest{LeaseGrant: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.LeaseGrantResponse), nil
return nil, result.err
}
return result.resp.(*pb.LeaseGrantResponse), nil
} }
func (s *EtcdServer) LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) { func (s *EtcdServer) LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
result, err := s.processInternalRaftRequestOnce(ctx, pb.InternalRaftRequest{LeaseRevoke: r}) resp, err := s.raftRequestOnce(ctx, pb.InternalRaftRequest{LeaseRevoke: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.LeaseRevokeResponse), nil
return nil, result.err
}
return result.resp.(*pb.LeaseRevokeResponse), nil
} }
func (s *EtcdServer) LeaseRenew(ctx context.Context, id lease.LeaseID) (int64, error) { func (s *EtcdServer) LeaseRenew(ctx context.Context, id lease.LeaseID) (int64, error) {
@ -304,6 +293,15 @@ func (s *EtcdServer) LeaseTimeToLive(ctx context.Context, r *pb.LeaseTimeToLiveR
return nil, ErrTimeout return nil, ErrTimeout
} }
func (s *EtcdServer) LeaseLeases(ctx context.Context, r *pb.LeaseLeasesRequest) (*pb.LeaseLeasesResponse, error) {
ls := s.lessor.Leases()
lss := make([]*pb.LeaseStatus, len(ls))
for i := range ls {
lss[i] = &pb.LeaseStatus{ID: int64(ls[i].ID)}
}
return &pb.LeaseLeasesResponse{Header: newHeader(s), Leases: lss}, nil
}
func (s *EtcdServer) waitLeader(ctx context.Context) (*membership.Member, error) { func (s *EtcdServer) waitLeader(ctx context.Context) (*membership.Member, error) {
leader := s.cluster.Member(s.Leader()) leader := s.cluster.Member(s.Leader())
for leader == nil { for leader == nil {
@ -325,46 +323,35 @@ func (s *EtcdServer) waitLeader(ctx context.Context) (*membership.Member, error)
} }
func (s *EtcdServer) Alarm(ctx context.Context, r *pb.AlarmRequest) (*pb.AlarmResponse, error) { func (s *EtcdServer) Alarm(ctx context.Context, r *pb.AlarmRequest) (*pb.AlarmResponse, error) {
result, err := s.processInternalRaftRequestOnce(ctx, pb.InternalRaftRequest{Alarm: r}) resp, err := s.raftRequestOnce(ctx, pb.InternalRaftRequest{Alarm: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AlarmResponse), nil
return nil, result.err
}
return result.resp.(*pb.AlarmResponse), nil
} }
func (s *EtcdServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) { func (s *EtcdServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
result, err := s.processInternalRaftRequestOnce(ctx, pb.InternalRaftRequest{AuthEnable: r}) resp, err := s.raftRequestOnce(ctx, pb.InternalRaftRequest{AuthEnable: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthEnableResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthEnableResponse), nil
} }
func (s *EtcdServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) { func (s *EtcdServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthDisable: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthDisable: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthDisableResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthDisableResponse), nil
} }
func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) { func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
var result *applyResult if err := s.linearizableReadNotify(ctx); err != nil {
err := s.linearizableReadNotify(ctx)
if err != nil {
return nil, err return nil, err
} }
var resp proto.Message
for { for {
checkedRevision, err := s.AuthStore().CheckPassword(r.Name, r.Password) checkedRevision, err := s.AuthStore().CheckPassword(r.Name, r.Password)
if err != nil { if err != nil {
@ -385,166 +372,141 @@ func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest
SimpleToken: st, SimpleToken: st,
} }
result, err = s.processInternalRaftRequestOnce(ctx, pb.InternalRaftRequest{Authenticate: internalReq}) resp, err = s.raftRequestOnce(ctx, pb.InternalRaftRequest{Authenticate: internalReq})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { if checkedRevision == s.AuthStore().Revision() {
return nil, result.err break
} }
plog.Infof("revision when password checked is obsolete, retrying")
if checkedRevision != s.AuthStore().Revision() {
plog.Infof("revision when password checked is obsolete, retrying")
continue
}
break
} }
return result.resp.(*pb.AuthenticateResponse), nil return resp.(*pb.AuthenticateResponse), nil
} }
func (s *EtcdServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) { func (s *EtcdServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserAdd: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserAdd: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserAddResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserAddResponse), nil
} }
func (s *EtcdServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) { func (s *EtcdServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserDelete: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserDelete: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserDeleteResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserDeleteResponse), nil
} }
func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) { func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserChangePassword: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserChangePassword: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserChangePasswordResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserChangePasswordResponse), nil
} }
func (s *EtcdServer) UserGrantRole(ctx context.Context, r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) { func (s *EtcdServer) UserGrantRole(ctx context.Context, r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserGrantRole: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserGrantRole: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserGrantRoleResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserGrantRoleResponse), nil
} }
func (s *EtcdServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) { func (s *EtcdServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserGet: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserGet: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserGetResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserGetResponse), nil
} }
func (s *EtcdServer) UserList(ctx context.Context, r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) { func (s *EtcdServer) UserList(ctx context.Context, r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserList: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserList: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserListResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserListResponse), nil
} }
func (s *EtcdServer) UserRevokeRole(ctx context.Context, r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) { func (s *EtcdServer) UserRevokeRole(ctx context.Context, r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserRevokeRole: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthUserRevokeRole: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthUserRevokeRoleResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthUserRevokeRoleResponse), nil
} }
func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) { func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthRoleAddResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthRoleAddResponse), nil
} }
func (s *EtcdServer) RoleGrantPermission(ctx context.Context, r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) { func (s *EtcdServer) RoleGrantPermission(ctx context.Context, r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleGrantPermission: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleGrantPermission: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthRoleGrantPermissionResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthRoleGrantPermissionResponse), nil
} }
func (s *EtcdServer) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) { func (s *EtcdServer) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleGet: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleGet: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthRoleGetResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthRoleGetResponse), nil
} }
func (s *EtcdServer) RoleList(ctx context.Context, r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) { func (s *EtcdServer) RoleList(ctx context.Context, r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleList: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleList: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthRoleListResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthRoleListResponse), nil
} }
func (s *EtcdServer) RoleRevokePermission(ctx context.Context, r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) { func (s *EtcdServer) RoleRevokePermission(ctx context.Context, r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleRevokePermission: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleRevokePermission: r})
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { return resp.(*pb.AuthRoleRevokePermissionResponse), nil
return nil, result.err
}
return result.resp.(*pb.AuthRoleRevokePermissionResponse), nil
} }
func (s *EtcdServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) { func (s *EtcdServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleDelete: r}) resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{AuthRoleDelete: r})
if err != nil {
return nil, err
}
return resp.(*pb.AuthRoleDeleteResponse), nil
}
func (s *EtcdServer) raftRequestOnce(ctx context.Context, r pb.InternalRaftRequest) (proto.Message, error) {
result, err := s.processInternalRaftRequestOnce(ctx, r)
if err != nil { if err != nil {
return nil, err return nil, err
} }
if result.err != nil { if result.err != nil {
return nil, result.err return nil, result.err
} }
return result.resp.(*pb.AuthRoleDeleteResponse), nil return result.resp, nil
}
func (s *EtcdServer) raftRequest(ctx context.Context, r pb.InternalRaftRequest) (proto.Message, error) {
for {
resp, err := s.raftRequestOnce(ctx, r)
if err != auth.ErrAuthOldRevision {
return resp, err
}
}
} }
// doSerialize handles the auth logic, with permissions checked by "chk", for a serialized request "get". Returns a non-nil error on authentication failure. // doSerialize handles the auth logic, with permissions checked by "chk", for a serialized request "get". Returns a non-nil error on authentication failure.
@ -629,19 +591,6 @@ func (s *EtcdServer) processInternalRaftRequestOnce(ctx context.Context, r pb.In
} }
} }
func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
var result *applyResult
var err error
for {
result, err = s.processInternalRaftRequestOnce(ctx, r)
if err != auth.ErrAuthOldRevision {
break
}
}
return result, err
}
// Watchable returns a watchable interface attached to the etcdserver. // Watchable returns a watchable interface attached to the etcdserver.
func (s *EtcdServer) Watchable() mvcc.WatchableKV { return s.KV() } func (s *EtcdServer) Watchable() mvcc.WatchableKV { return s.KV() }
@ -737,12 +686,14 @@ func (s *EtcdServer) linearizableReadNotify(ctx context.Context) error {
} }
func (s *EtcdServer) AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error) { func (s *EtcdServer) AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error) {
if s.Cfg.ClientCertAuthEnabled { authInfo, err := s.AuthStore().AuthInfoFromCtx(ctx)
authInfo := s.AuthStore().AuthInfoFromTLS(ctx) if authInfo != nil || err != nil {
if authInfo != nil { return authInfo, err
return authInfo, nil
}
} }
if !s.Cfg.ClientCertAuthEnabled {
return nil, nil
}
authInfo = s.AuthStore().AuthInfoFromTLS(ctx)
return authInfo, nil
return s.AuthStore().AuthInfoFromCtx(ctx)
} }

View File

@ -24,10 +24,12 @@ import (
type index interface { type index interface {
Get(key []byte, atRev int64) (rev, created revision, ver int64, err error) Get(key []byte, atRev int64) (rev, created revision, ver int64, err error)
Range(key, end []byte, atRev int64) ([][]byte, []revision) Range(key, end []byte, atRev int64) ([][]byte, []revision)
Revisions(key, end []byte, atRev int64) []revision
Put(key []byte, rev revision) Put(key []byte, rev revision)
Tombstone(key []byte, rev revision) error Tombstone(key []byte, rev revision) error
RangeSince(key, end []byte, rev int64) []revision RangeSince(key, end []byte, rev int64) []revision
Compact(rev int64) map[revision]struct{} Compact(rev int64) map[revision]struct{}
Keep(rev int64) map[revision]struct{}
Equal(b index) bool Equal(b index) bool
Insert(ki *keyIndex) Insert(ki *keyIndex)
@ -83,17 +85,8 @@ func (ti *treeIndex) keyIndex(keyi *keyIndex) *keyIndex {
return nil return nil
} }
func (ti *treeIndex) Range(key, end []byte, atRev int64) (keys [][]byte, revs []revision) { func (ti *treeIndex) visit(key, end []byte, f func(ki *keyIndex)) {
if end == nil { keyi, endi := &keyIndex{key: key}, &keyIndex{key: end}
rev, _, _, err := ti.Get(key, atRev)
if err != nil {
return nil, nil
}
return [][]byte{key}, []revision{rev}
}
keyi := &keyIndex{key: key}
endi := &keyIndex{key: end}
ti.RLock() ti.RLock()
defer ti.RUnlock() defer ti.RUnlock()
@ -102,16 +95,41 @@ func (ti *treeIndex) Range(key, end []byte, atRev int64) (keys [][]byte, revs []
if len(endi.key) > 0 && !item.Less(endi) { if len(endi.key) > 0 && !item.Less(endi) {
return false return false
} }
curKeyi := item.(*keyIndex) f(item.(*keyIndex))
rev, _, _, err := curKeyi.get(atRev)
if err != nil {
return true
}
revs = append(revs, rev)
keys = append(keys, curKeyi.key)
return true return true
}) })
}
func (ti *treeIndex) Revisions(key, end []byte, atRev int64) (revs []revision) {
if end == nil {
rev, _, _, err := ti.Get(key, atRev)
if err != nil {
return nil
}
return []revision{rev}
}
ti.visit(key, end, func(ki *keyIndex) {
if rev, _, _, err := ki.get(atRev); err == nil {
revs = append(revs, rev)
}
})
return revs
}
func (ti *treeIndex) Range(key, end []byte, atRev int64) (keys [][]byte, revs []revision) {
if end == nil {
rev, _, _, err := ti.Get(key, atRev)
if err != nil {
return nil, nil
}
return [][]byte{key}, []revision{rev}
}
ti.visit(key, end, func(ki *keyIndex) {
if rev, _, _, err := ki.get(atRev); err == nil {
revs = append(revs, rev)
keys = append(keys, ki.key)
}
})
return keys, revs return keys, revs
} }
@ -133,10 +151,11 @@ func (ti *treeIndex) Tombstone(key []byte, rev revision) error {
// at or after the given rev. The returned slice is sorted in the order // at or after the given rev. The returned slice is sorted in the order
// of revision. // of revision.
func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision { func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision {
keyi := &keyIndex{key: key}
ti.RLock() ti.RLock()
defer ti.RUnlock() defer ti.RUnlock()
keyi := &keyIndex{key: key}
if end == nil { if end == nil {
item := ti.tree.Get(keyi) item := ti.tree.Get(keyi)
if item == nil { if item == nil {
@ -179,6 +198,19 @@ func (ti *treeIndex) Compact(rev int64) map[revision]struct{} {
return available return available
} }
// Keep finds all revisions to be kept for a Compaction at the given rev.
func (ti *treeIndex) Keep(rev int64) map[revision]struct{} {
available := make(map[revision]struct{})
ti.RLock()
defer ti.RUnlock()
ti.tree.Ascend(func(i btree.Item) bool {
keyi := i.(*keyIndex)
keyi.keep(rev, available)
return true
})
return available
}
func compactIndex(rev int64, available map[revision]struct{}, emptyki *[]*keyIndex) func(i btree.Item) bool { func compactIndex(rev int64, available map[revision]struct{}, emptyki *[]*keyIndex) func(i btree.Item) bool {
return func(i btree.Item) bool { return func(i btree.Item) bool {
keyi := i.(*keyIndex) keyi := i.(*keyIndex)
@ -190,16 +222,16 @@ func compactIndex(rev int64, available map[revision]struct{}, emptyki *[]*keyInd
} }
} }
func (a *treeIndex) Equal(bi index) bool { func (ti *treeIndex) Equal(bi index) bool {
b := bi.(*treeIndex) b := bi.(*treeIndex)
if a.tree.Len() != b.tree.Len() { if ti.tree.Len() != b.tree.Len() {
return false return false
} }
equal := true equal := true
a.tree.Ascend(func(item btree.Item) bool { ti.tree.Ascend(func(item btree.Item) bool {
aki := item.(*keyIndex) aki := item.(*keyIndex)
bki := b.tree.Get(item).(*keyIndex) bki := b.tree.Get(item).(*keyIndex)
if !aki.equal(bki) { if !aki.equal(bki) {

View File

@ -46,7 +46,7 @@ var (
// rev except the largest one. If the generation becomes empty // rev except the largest one. If the generation becomes empty
// during compaction, it will be removed. if all the generations get // during compaction, it will be removed. if all the generations get
// removed, the keyIndex should be removed. // removed, the keyIndex should be removed.
//
// For example: // For example:
// compact(2) on the previous example // compact(2) on the previous example
// generations: // generations:
@ -187,9 +187,44 @@ func (ki *keyIndex) compact(atRev int64, available map[revision]struct{}) {
plog.Panicf("store.keyindex: unexpected compact on empty keyIndex %s", string(ki.key)) plog.Panicf("store.keyindex: unexpected compact on empty keyIndex %s", string(ki.key))
} }
// walk until reaching the first revision that has an revision smaller or equal to genIdx, revIndex := ki.doCompact(atRev, available)
// the atRev.
// add it to the available map g := &ki.generations[genIdx]
if !g.isEmpty() {
// remove the previous contents.
if revIndex != -1 {
g.revs = g.revs[revIndex:]
}
// remove any tombstone
if len(g.revs) == 1 && genIdx != len(ki.generations)-1 {
delete(available, g.revs[0])
genIdx++
}
}
// remove the previous generations.
ki.generations = ki.generations[genIdx:]
}
// keep finds the revision to be kept if compact is called at given atRev.
func (ki *keyIndex) keep(atRev int64, available map[revision]struct{}) {
if ki.isEmpty() {
return
}
genIdx, revIndex := ki.doCompact(atRev, available)
g := &ki.generations[genIdx]
if !g.isEmpty() {
// remove any tombstone
if revIndex == len(g.revs)-1 && genIdx != len(ki.generations)-1 {
delete(available, g.revs[revIndex])
}
}
}
func (ki *keyIndex) doCompact(atRev int64, available map[revision]struct{}) (genIdx int, revIndex int) {
// walk until reaching the first revision smaller or equal to "atRev",
// and add the revision to the available map
f := func(rev revision) bool { f := func(rev revision) bool {
if rev.main <= atRev { if rev.main <= atRev {
available[rev] = struct{}{} available[rev] = struct{}{}
@ -198,30 +233,19 @@ func (ki *keyIndex) compact(atRev int64, available map[revision]struct{}) {
return true return true
} }
i, g := 0, &ki.generations[0] genIdx, g := 0, &ki.generations[0]
// find first generation includes atRev or created after atRev // find first generation includes atRev or created after atRev
for i < len(ki.generations)-1 { for genIdx < len(ki.generations)-1 {
if tomb := g.revs[len(g.revs)-1].main; tomb > atRev { if tomb := g.revs[len(g.revs)-1].main; tomb > atRev {
break break
} }
i++ genIdx++
g = &ki.generations[i] g = &ki.generations[genIdx]
} }
if !g.isEmpty() { revIndex = g.walk(f)
n := g.walk(f)
// remove the previous contents. return genIdx, revIndex
if n != -1 {
g.revs = g.revs[n:]
}
// remove any tombstone
if len(g.revs) == 1 && i != len(ki.generations)-1 {
delete(available, g.revs[0])
i++
}
}
// remove the previous generations.
ki.generations = ki.generations[i:]
} }
func (ki *keyIndex) isEmpty() bool { func (ki *keyIndex) isEmpty() bool {

View File

@ -107,10 +107,12 @@ type KV interface {
// Write creates a write transaction. // Write creates a write transaction.
Write() TxnWrite Write() TxnWrite
// Hash retrieves the hash of KV state and revision. // Hash computes the hash of the KV's backend.
// This method is designed for consistency checking purposes.
Hash() (hash uint32, revision int64, err error) Hash() (hash uint32, revision int64, err error)
// HashByRev computes the hash of all MVCC revisions up to a given revision.
HashByRev(rev int64) (hash uint32, revision int64, compactRev int64, err error)
// Compact frees all superseded keys with revisions less than rev. // Compact frees all superseded keys with revisions less than rev.
Compact(rev int64) (<-chan struct{}, error) Compact(rev int64) (<-chan struct{}, error)

View File

@ -15,8 +15,10 @@
package mvcc package mvcc
import ( import (
"context"
"encoding/binary" "encoding/binary"
"errors" "errors"
"hash/crc32"
"math" "math"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -27,7 +29,6 @@ import (
"github.com/coreos/etcd/mvcc/mvccpb" "github.com/coreos/etcd/mvcc/mvccpb"
"github.com/coreos/etcd/pkg/schedule" "github.com/coreos/etcd/pkg/schedule"
"github.com/coreos/pkg/capnslog" "github.com/coreos/pkg/capnslog"
"golang.org/x/net/context"
) )
var ( var (
@ -160,6 +161,54 @@ func (s *store) Hash() (hash uint32, revision int64, err error) {
return h, s.currentRev, err return h, s.currentRev, err
} }
func (s *store) HashByRev(rev int64) (hash uint32, currentRev int64, compactRev int64, err error) {
s.mu.RLock()
s.revMu.RLock()
compactRev, currentRev = s.compactMainRev, s.currentRev
s.revMu.RUnlock()
if rev > 0 && rev <= compactRev {
s.mu.RUnlock()
return 0, 0, compactRev, ErrCompacted
} else if rev > 0 && rev > currentRev {
s.mu.RUnlock()
return 0, currentRev, 0, ErrFutureRev
}
if rev == 0 {
rev = currentRev
}
keep := s.kvindex.Keep(rev)
tx := s.b.ReadTx()
tx.Lock()
defer tx.Unlock()
s.mu.RUnlock()
upper := revision{main: rev + 1}
lower := revision{main: compactRev + 1}
h := crc32.New(crc32.MakeTable(crc32.Castagnoli))
h.Write(keyBucketName)
err = tx.UnsafeForEach(keyBucketName, func(k, v []byte) error {
kr := bytesToRev(k)
if !upper.GreaterThan(kr) {
return nil
}
// skip revisions that are scheduled for deletion
// due to compacting; don't skip if there isn't one.
if lower.GreaterThan(kr) && len(keep) > 0 {
if _, ok := keep[kr]; !ok {
return nil
}
}
h.Write(k)
h.Write(v)
return nil
})
return h.Sum32(), currentRev, compactRev, err
}
func (s *store) Compact(rev int64) (<-chan struct{}, error) { func (s *store) Compact(rev int64) (<-chan struct{}, error) {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
@ -278,6 +327,7 @@ func (s *store) restore() error {
} }
// index keys concurrently as they're loaded in from tx // index keys concurrently as they're loaded in from tx
keysGauge.Set(0)
rkvc, revc := restoreIntoIndex(s.kvindex) rkvc, revc := restoreIntoIndex(s.kvindex)
for { for {
keys, vals := tx.UnsafeRange(keyBucketName, min, max, int64(restoreChunkKeys)) keys, vals := tx.UnsafeRange(keyBucketName, min, max, int64(restoreChunkKeys))
@ -445,16 +495,3 @@ func appendMarkTombstone(b []byte) []byte {
func isTombstone(b []byte) bool { func isTombstone(b []byte) bool {
return len(b) == markedRevBytesLen && b[markBytePosition] == markTombstone return len(b) == markedRevBytesLen && b[markBytePosition] == markTombstone
} }
// revBytesRange returns the range of revision bytes at
// the given revision.
func revBytesRange(rev revision) (start, end []byte) {
start = newRevBytes()
revToBytes(rev, start)
end = newRevBytes()
endRev := revision{main: rev.main, sub: rev.sub + 1}
revToBytes(endRev, end)
return start, end
}

View File

@ -22,6 +22,8 @@ import (
func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struct{}) bool { func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struct{}) bool {
totalStart := time.Now() totalStart := time.Now()
defer dbCompactionTotalDurations.Observe(float64(time.Since(totalStart) / time.Millisecond)) defer dbCompactionTotalDurations.Observe(float64(time.Since(totalStart) / time.Millisecond))
keyCompactions := 0
defer func() { dbCompactionKeysCounter.Add(float64(keyCompactions)) }()
end := make([]byte, 8) end := make([]byte, 8)
binary.BigEndian.PutUint64(end, uint64(compactMainRev+1)) binary.BigEndian.PutUint64(end, uint64(compactMainRev+1))
@ -40,6 +42,7 @@ func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struc
rev = bytesToRev(key) rev = bytesToRev(key)
if _, ok := keep[rev]; !ok { if _, ok := keep[rev]; !ok {
tx.UnsafeDelete(keyBucketName, key) tx.UnsafeDelete(keyBucketName, key)
keyCompactions++
} }
} }

View File

@ -120,7 +120,7 @@ func (tr *storeTxnRead) rangeKeys(key, end []byte, curRev int64, ro RangeOptions
return &RangeResult{KVs: nil, Count: -1, Rev: 0}, ErrCompacted return &RangeResult{KVs: nil, Count: -1, Rev: 0}, ErrCompacted
} }
_, revpairs := tr.s.kvindex.Range(key, end, int64(rev)) revpairs := tr.s.kvindex.Revisions(key, end, int64(rev))
if len(revpairs) == 0 { if len(revpairs) == 0 {
return &RangeResult{KVs: nil, Count: 0, Rev: curRev}, nil return &RangeResult{KVs: nil, Count: 0, Rev: curRev}, nil
} }
@ -128,22 +128,22 @@ func (tr *storeTxnRead) rangeKeys(key, end []byte, curRev int64, ro RangeOptions
return &RangeResult{KVs: nil, Count: len(revpairs), Rev: curRev}, nil return &RangeResult{KVs: nil, Count: len(revpairs), Rev: curRev}, nil
} }
var kvs []mvccpb.KeyValue limit := int(ro.Limit)
for _, revpair := range revpairs { if limit <= 0 || limit > len(revpairs) {
start, end := revBytesRange(revpair) limit = len(revpairs)
_, vs := tr.tx.UnsafeRange(keyBucketName, start, end, 0) }
kvs := make([]mvccpb.KeyValue, limit)
revBytes := newRevBytes()
for i, revpair := range revpairs[:len(kvs)] {
revToBytes(revpair, revBytes)
_, vs := tr.tx.UnsafeRange(keyBucketName, revBytes, nil, 0)
if len(vs) != 1 { if len(vs) != 1 {
plog.Fatalf("range cannot find rev (%d,%d)", revpair.main, revpair.sub) plog.Fatalf("range cannot find rev (%d,%d)", revpair.main, revpair.sub)
} }
if err := kvs[i].Unmarshal(vs[0]); err != nil {
var kv mvccpb.KeyValue
if err := kv.Unmarshal(vs[0]); err != nil {
plog.Fatalf("cannot unmarshal event: %v", err) plog.Fatalf("cannot unmarshal event: %v", err)
} }
kvs = append(kvs, kv)
if ro.Limit > 0 && len(kvs) >= int(ro.Limit) {
break
}
} }
return &RangeResult{KVs: kvs, Count: len(revpairs), Rev: curRev}, nil return &RangeResult{KVs: kvs, Count: len(revpairs), Rev: curRev}, nil
} }

View File

@ -131,6 +131,14 @@ var (
Buckets: prometheus.ExponentialBuckets(100, 2, 14), Buckets: prometheus.ExponentialBuckets(100, 2, 14),
}) })
dbCompactionKeysCounter = prometheus.NewCounter(
prometheus.CounterOpts{
Namespace: "etcd_debugging",
Subsystem: "mvcc",
Name: "db_compaction_keys_total",
Help: "Total number of db keys compacted.",
})
dbTotalSize = prometheus.NewGaugeFunc(prometheus.GaugeOpts{ dbTotalSize = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: "etcd_debugging", Namespace: "etcd_debugging",
Subsystem: "mvcc", Subsystem: "mvcc",
@ -162,6 +170,7 @@ func init() {
prometheus.MustRegister(indexCompactionPauseDurations) prometheus.MustRegister(indexCompactionPauseDurations)
prometheus.MustRegister(dbCompactionPauseDurations) prometheus.MustRegister(dbCompactionPauseDurations)
prometheus.MustRegister(dbCompactionTotalDurations) prometheus.MustRegister(dbCompactionTotalDurations)
prometheus.MustRegister(dbCompactionKeysCounter)
prometheus.MustRegister(dbTotalSize) prometheus.MustRegister(dbTotalSize)
} }

View File

@ -1,6 +1,5 @@
// Code generated by protoc-gen-gogo. // Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: kv.proto // source: kv.proto
// DO NOT EDIT!
/* /*
Package mvccpb is a generated protocol buffer package. Package mvccpb is a generated protocol buffer package.
@ -21,6 +20,8 @@ import (
math "math" math "math"
_ "github.com/gogo/protobuf/gogoproto"
io "io" io "io"
) )
@ -198,24 +199,6 @@ func (m *Event) MarshalTo(dAtA []byte) (int, error) {
return i, nil return i, nil
} }
func encodeFixed64Kv(dAtA []byte, offset int, v uint64) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
dAtA[offset+4] = uint8(v >> 32)
dAtA[offset+5] = uint8(v >> 40)
dAtA[offset+6] = uint8(v >> 48)
dAtA[offset+7] = uint8(v >> 56)
return offset + 8
}
func encodeFixed32Kv(dAtA []byte, offset int, v uint32) int {
dAtA[offset] = uint8(v)
dAtA[offset+1] = uint8(v >> 8)
dAtA[offset+2] = uint8(v >> 16)
dAtA[offset+3] = uint8(v >> 24)
return offset + 4
}
func encodeVarintKv(dAtA []byte, offset int, v uint64) int { func encodeVarintKv(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 { for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80) dAtA[offset] = uint8(v&0x7f | 0x80)

View File

@ -144,7 +144,6 @@ func (s *watchableStore) watch(key, end []byte, startRev int64, id WatchID, ch c
func (s *watchableStore) cancelWatcher(wa *watcher) { func (s *watchableStore) cancelWatcher(wa *watcher) {
for { for {
s.mu.Lock() s.mu.Lock()
if s.unsynced.delete(wa) { if s.unsynced.delete(wa) {
slowWatcherGauge.Dec() slowWatcherGauge.Dec()
break break
@ -152,6 +151,9 @@ func (s *watchableStore) cancelWatcher(wa *watcher) {
break break
} else if wa.compacted { } else if wa.compacted {
break break
} else if wa.ch == nil {
// already canceled (e.g., cancel/close race)
break
} }
if !wa.victim { if !wa.victim {
@ -177,9 +179,25 @@ func (s *watchableStore) cancelWatcher(wa *watcher) {
} }
watcherGauge.Dec() watcherGauge.Dec()
wa.ch = nil
s.mu.Unlock() s.mu.Unlock()
} }
func (s *watchableStore) Restore(b backend.Backend) error {
s.mu.Lock()
defer s.mu.Unlock()
err := s.store.Restore(b)
if err != nil {
return err
}
for wa := range s.synced.watchers {
s.unsynced.watchers.add(wa)
}
s.synced = newWatcherGroup()
return nil
}
// syncWatchersLoop syncs the watcher in the unsynced map every 100ms. // syncWatchersLoop syncs the watcher in the unsynced map every 100ms.
func (s *watchableStore) syncWatchersLoop() { func (s *watchableStore) syncWatchersLoop() {
defer s.wg.Done() defer s.wg.Done()
@ -410,7 +428,6 @@ func (s *watchableStore) notify(rev int64, evs []mvccpb.Event) {
if eb.revs != 1 { if eb.revs != 1 {
plog.Panicf("unexpected multiple revisions in notification") plog.Panicf("unexpected multiple revisions in notification")
} }
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) { if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) {
pendingEventsGauge.Add(float64(len(eb.evs))) pendingEventsGauge.Add(float64(len(eb.evs)))
} else { } else {

View File

@ -129,16 +129,25 @@ func (ws *watchStream) Chan() <-chan WatchResponse {
func (ws *watchStream) Cancel(id WatchID) error { func (ws *watchStream) Cancel(id WatchID) error {
ws.mu.Lock() ws.mu.Lock()
cancel, ok := ws.cancels[id] cancel, ok := ws.cancels[id]
w := ws.watchers[id]
ok = ok && !ws.closed ok = ok && !ws.closed
if ok {
delete(ws.cancels, id)
delete(ws.watchers, id)
}
ws.mu.Unlock() ws.mu.Unlock()
if !ok { if !ok {
return ErrWatcherNotExist return ErrWatcherNotExist
} }
cancel() cancel()
ws.mu.Lock()
// The watch isn't removed until cancel so that if Close() is called,
// it will wait for the cancel. Otherwise, Close() could close the
// watch channel while the store is still posting events.
if ww := ws.watchers[id]; ww == w {
delete(ws.cancels, id)
delete(ws.watchers, id)
}
ws.mu.Unlock()
return nil return nil
} }

View File

@ -92,15 +92,19 @@ func resolveTCPAddrs(ctx context.Context, urls [][]url.URL) ([][]url.URL, error)
} }
func resolveURL(ctx context.Context, u url.URL) (string, error) { func resolveURL(ctx context.Context, u url.URL) (string, error) {
if u.Scheme == "unix" || u.Scheme == "unixs" {
// unix sockets don't resolve over TCP
return "", nil
}
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
plog.Errorf("could not parse url %s during tcp resolving", u.Host)
return "", err
}
if host == "localhost" || net.ParseIP(host) != nil {
return "", nil
}
for ctx.Err() == nil { for ctx.Err() == nil {
host, _, err := net.SplitHostPort(u.Host)
if err != nil {
plog.Errorf("could not parse url %s during tcp resolving", u.Host)
return "", err
}
if host == "localhost" || net.ParseIP(host) != nil {
return "", nil
}
tcpAddr, err := resolveTCPAddr(ctx, u.Host) tcpAddr, err := resolveTCPAddr(ctx, u.Host)
if err == nil { if err == nil {
plog.Infof("resolving %s to %s", u.Host, tcpAddr.String()) plog.Infof("resolving %s to %s", u.Host, tcpAddr.String())

View File

@ -246,5 +246,5 @@ func parsePREFSRC(m *syscall.NetlinkMessage) (host string, oif uint32, err error
if oif == 0 { if oif == 0 {
err = errNoDefaultRoute err = errNoDefaultRoute
} }
return return host, oif, err
} }

View File

@ -1,31 +0,0 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// Package pathutil implements utility functions for handling slash-separated
// paths.
package pathutil
import "path"
// CanonicalURLPath returns the canonical url path for p, which follows the rules:
// 1. the path always starts with "/"
// 2. replace multiple slashes with a single slash
// 3. replace each '.' '..' path name element with equivalent one
// 4. keep the trailing slash
// The function is borrowed from stdlib http.cleanPath in server.go.
func CanonicalURLPath(p string) string {
if p == "" {
return "/"
}
if p[0] != '/' {
p = "/" + p
}
np := path.Clean(p)
// path.Clean removes trailing slash except for root,
// put the trailing slash back if necessary.
if p[len(p)-1] == '/' && np != "/" {
np += "/"
}
return np
}

View File

@ -127,13 +127,13 @@ func copyMap(m map[string]int) (c map[string]int) {
for k, v := range m { for k, v := range m {
c[k] = v c[k] = v
} }
return return c
} }
func copyFloats(s []float64) (c []float64) { func copyFloats(s []float64) (c []float64) {
c = make([]float64, len(s)) c = make([]float64, len(s))
copy(c, s) copy(c, s)
return return c
} }
func (r *report) String() (s string) { func (r *report) String() (s string) {
@ -221,7 +221,7 @@ func percentiles(nums []float64) (data []float64) {
j++ j++
} }
} }
return return data
} }
func (r *report) sprintLatencies() string { func (r *report) sprintLatencies() string {

View File

@ -118,20 +118,20 @@ func (sp *secondPoints) getTimeSeries() TimeSeries {
return tslice return tslice
} }
func (ts TimeSeries) String() string { func (t TimeSeries) String() string {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
wr := csv.NewWriter(buf) wr := csv.NewWriter(buf)
if err := wr.Write([]string{"UNIX-SECOND", "MIN-LATENCY-MS", "AVG-LATENCY-MS", "MAX-LATENCY-MS", "AVG-THROUGHPUT"}); err != nil { if err := wr.Write([]string{"UNIX-SECOND", "MIN-LATENCY-MS", "AVG-LATENCY-MS", "MAX-LATENCY-MS", "AVG-THROUGHPUT"}); err != nil {
log.Fatal(err) log.Fatal(err)
} }
rows := [][]string{} rows := [][]string{}
for i := range ts { for i := range t {
row := []string{ row := []string{
fmt.Sprintf("%d", ts[i].Timestamp), fmt.Sprintf("%d", t[i].Timestamp),
ts[i].MinLatency.String(), t[i].MinLatency.String(),
ts[i].AvgLatency.String(), t[i].AvgLatency.String(),
ts[i].MaxLatency.String(), t[i].MaxLatency.String(),
fmt.Sprintf("%d", ts[i].ThroughPut), fmt.Sprintf("%d", t[i].ThroughPut),
} }
rows = append(rows, row) rows = append(rows, row)
} }

View File

@ -1,140 +0,0 @@
// 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 srv looks up DNS SRV records.
package srv
import (
"fmt"
"net"
"net/url"
"strings"
"github.com/coreos/etcd/pkg/types"
)
var (
// indirection for testing
lookupSRV = net.LookupSRV // net.DefaultResolver.LookupSRV when ctxs don't conflict
resolveTCPAddr = net.ResolveTCPAddr
)
// GetCluster gets the cluster information via DNS discovery.
// Also sees each entry as a separate instance.
func GetCluster(service, name, dns string, apurls types.URLs) ([]string, error) {
tempName := int(0)
tcp2ap := make(map[string]url.URL)
// First, resolve the apurls
for _, url := range apurls {
tcpAddr, err := resolveTCPAddr("tcp", url.Host)
if err != nil {
return nil, err
}
tcp2ap[tcpAddr.String()] = url
}
stringParts := []string{}
updateNodeMap := func(service, scheme string) error {
_, addrs, err := lookupSRV(service, "tcp", dns)
if err != nil {
return err
}
for _, srv := range addrs {
port := fmt.Sprintf("%d", srv.Port)
host := net.JoinHostPort(srv.Target, port)
tcpAddr, terr := resolveTCPAddr("tcp", host)
if terr != nil {
err = terr
continue
}
n := ""
url, ok := tcp2ap[tcpAddr.String()]
if ok {
n = name
}
if n == "" {
n = fmt.Sprintf("%d", tempName)
tempName++
}
// SRV records have a trailing dot but URL shouldn't.
shortHost := strings.TrimSuffix(srv.Target, ".")
urlHost := net.JoinHostPort(shortHost, port)
stringParts = append(stringParts, fmt.Sprintf("%s=%s://%s", n, scheme, urlHost))
if ok && url.Scheme != scheme {
err = fmt.Errorf("bootstrap at %s from DNS for %s has scheme mismatch with expected peer %s", scheme+"://"+urlHost, service, url.String())
}
}
if len(stringParts) == 0 {
return err
}
return nil
}
failCount := 0
err := updateNodeMap(service+"-ssl", "https")
srvErr := make([]string, 2)
if err != nil {
srvErr[0] = fmt.Sprintf("error querying DNS SRV records for _%s-ssl %s", service, err)
failCount++
}
err = updateNodeMap(service, "http")
if err != nil {
srvErr[1] = fmt.Sprintf("error querying DNS SRV records for _%s %s", service, err)
failCount++
}
if failCount == 2 {
return nil, fmt.Errorf("srv: too many errors querying DNS SRV records (%q, %q)", srvErr[0], srvErr[1])
}
return stringParts, nil
}
type SRVClients struct {
Endpoints []string
SRVs []*net.SRV
}
// GetClient looks up the client endpoints for a service and domain.
func GetClient(service, domain string) (*SRVClients, error) {
var urls []*url.URL
var srvs []*net.SRV
updateURLs := func(service, scheme string) error {
_, addrs, err := lookupSRV(service, "tcp", domain)
if err != nil {
return err
}
for _, srv := range addrs {
urls = append(urls, &url.URL{
Scheme: scheme,
Host: net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)),
})
}
srvs = append(srvs, addrs...)
return nil
}
errHTTPS := updateURLs(service+"-ssl", "https")
errHTTP := updateURLs(service, "http")
if errHTTPS != nil && errHTTP != nil {
return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
}
endpoints := make([]string, len(urls))
for i := range urls {
endpoints[i] = urls[i].String()
}
return &SRVClients{Endpoints: endpoints, SRVs: srvs}, nil
}

View File

@ -61,7 +61,7 @@ func (us *unsafeSet) Remove(value string) {
// Contains returns whether the set contains the given value // Contains returns whether the set contains the given value
func (us *unsafeSet) Contains(value string) (exists bool) { func (us *unsafeSet) Contains(value string) (exists bool) {
_, exists = us.d[value] _, exists = us.d[value]
return return exists
} }
// ContainsAll returns whether the set contains all given values // ContainsAll returns whether the set contains all given values
@ -94,7 +94,7 @@ func (us *unsafeSet) Values() (values []string) {
for val := range us.d { for val := range us.d {
values = append(values, val) values = append(values, val)
} }
return return values
} }
// Copy creates a new Set containing the values of the first // Copy creates a new Set containing the values of the first

View File

@ -1,56 +0,0 @@
// 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 version implements etcd version parsing and contains latest version
// information.
package version
import (
"fmt"
"strings"
"github.com/coreos/go-semver/semver"
)
var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "3.0.0"
Version = "3.2.0-rc.1+git"
APIVersion = "unknown"
// Git SHA Value will be set during build
GitSHA = "Not provided (use ./build instead of go build)"
)
func init() {
ver, err := semver.NewVersion(Version)
if err == nil {
APIVersion = fmt.Sprintf("%d.%d", ver.Major, ver.Minor)
}
}
type Versions struct {
Server string `json:"etcdserver"`
Cluster string `json:"etcdcluster"`
// TODO: raft state machine version
}
// Cluster only keeps the major.minor.
func Cluster(v string) string {
vs := strings.Split(v, ".")
if len(vs) <= 2 {
return v
}
return fmt.Sprintf("%s.%s", vs[0], vs[1])
}

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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.

View File

@ -1,20 +0,0 @@
package main
import (
"fmt"
"github.com/coreos/go-semver/semver"
"os"
)
func main() {
vA, err := semver.NewVersion(os.Args[1])
if err != nil {
fmt.Println(err.Error())
}
vB, err := semver.NewVersion(os.Args[2])
if err != nil {
fmt.Println(err.Error())
}
fmt.Printf("%s < %s == %t\n", vA, vB, vA.LessThan(*vB))
}

View File

@ -1,268 +0,0 @@
// Copyright 2013-2015 CoreOS, Inc.
//
// 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.
// Semantic Versions http://semver.org
package semver
import (
"bytes"
"errors"
"fmt"
"strconv"
"strings"
)
type Version struct {
Major int64
Minor int64
Patch int64
PreRelease PreRelease
Metadata string
}
type PreRelease string
func splitOff(input *string, delim string) (val string) {
parts := strings.SplitN(*input, delim, 2)
if len(parts) == 2 {
*input = parts[0]
val = parts[1]
}
return val
}
func New(version string) *Version {
return Must(NewVersion(version))
}
func NewVersion(version string) (*Version, error) {
v := Version{}
if err := v.Set(version); err != nil {
return nil, err
}
return &v, nil
}
// Must is a helper for wrapping NewVersion and will panic if err is not nil.
func Must(v *Version, err error) *Version {
if err != nil {
panic(err)
}
return v
}
// Set parses and updates v from the given version string. Implements flag.Value
func (v *Version) Set(version string) error {
metadata := splitOff(&version, "+")
preRelease := PreRelease(splitOff(&version, "-"))
dotParts := strings.SplitN(version, ".", 3)
if len(dotParts) != 3 {
return fmt.Errorf("%s is not in dotted-tri format", version)
}
parsed := make([]int64, 3, 3)
for i, v := range dotParts[:3] {
val, err := strconv.ParseInt(v, 10, 64)
parsed[i] = val
if err != nil {
return err
}
}
v.Metadata = metadata
v.PreRelease = preRelease
v.Major = parsed[0]
v.Minor = parsed[1]
v.Patch = parsed[2]
return nil
}
func (v Version) String() string {
var buffer bytes.Buffer
fmt.Fprintf(&buffer, "%d.%d.%d", v.Major, v.Minor, v.Patch)
if v.PreRelease != "" {
fmt.Fprintf(&buffer, "-%s", v.PreRelease)
}
if v.Metadata != "" {
fmt.Fprintf(&buffer, "+%s", v.Metadata)
}
return buffer.String()
}
func (v *Version) UnmarshalYAML(unmarshal func(interface{}) error) error {
var data string
if err := unmarshal(&data); err != nil {
return err
}
return v.Set(data)
}
func (v Version) MarshalJSON() ([]byte, error) {
return []byte(`"` + v.String() + `"`), nil
}
func (v *Version) UnmarshalJSON(data []byte) error {
l := len(data)
if l == 0 || string(data) == `""` {
return nil
}
if l < 2 || data[0] != '"' || data[l-1] != '"' {
return errors.New("invalid semver string")
}
return v.Set(string(data[1 : l-1]))
}
// Compare tests if v is less than, equal to, or greater than versionB,
// returning -1, 0, or +1 respectively.
func (v Version) Compare(versionB Version) int {
if cmp := recursiveCompare(v.Slice(), versionB.Slice()); cmp != 0 {
return cmp
}
return preReleaseCompare(v, versionB)
}
// Equal tests if v is equal to versionB.
func (v Version) Equal(versionB Version) bool {
return v.Compare(versionB) == 0
}
// LessThan tests if v is less than versionB.
func (v Version) LessThan(versionB Version) bool {
return v.Compare(versionB) < 0
}
// Slice converts the comparable parts of the semver into a slice of integers.
func (v Version) Slice() []int64 {
return []int64{v.Major, v.Minor, v.Patch}
}
func (p PreRelease) Slice() []string {
preRelease := string(p)
return strings.Split(preRelease, ".")
}
func preReleaseCompare(versionA Version, versionB Version) int {
a := versionA.PreRelease
b := versionB.PreRelease
/* Handle the case where if two versions are otherwise equal it is the
* one without a PreRelease that is greater */
if len(a) == 0 && (len(b) > 0) {
return 1
} else if len(b) == 0 && (len(a) > 0) {
return -1
}
// If there is a prerelease, check and compare each part.
return recursivePreReleaseCompare(a.Slice(), b.Slice())
}
func recursiveCompare(versionA []int64, versionB []int64) int {
if len(versionA) == 0 {
return 0
}
a := versionA[0]
b := versionB[0]
if a > b {
return 1
} else if a < b {
return -1
}
return recursiveCompare(versionA[1:], versionB[1:])
}
func recursivePreReleaseCompare(versionA []string, versionB []string) int {
// A larger set of pre-release fields has a higher precedence than a smaller set,
// if all of the preceding identifiers are equal.
if len(versionA) == 0 {
if len(versionB) > 0 {
return -1
}
return 0
} else if len(versionB) == 0 {
// We're longer than versionB so return 1.
return 1
}
a := versionA[0]
b := versionB[0]
aInt := false
bInt := false
aI, err := strconv.Atoi(versionA[0])
if err == nil {
aInt = true
}
bI, err := strconv.Atoi(versionB[0])
if err == nil {
bInt = true
}
// Handle Integer Comparison
if aInt && bInt {
if aI > bI {
return 1
} else if aI < bI {
return -1
}
}
// Handle String Comparison
if a > b {
return 1
} else if a < b {
return -1
}
return recursivePreReleaseCompare(versionA[1:], versionB[1:])
}
// BumpMajor increments the Major field by 1 and resets all other fields to their default values
func (v *Version) BumpMajor() {
v.Major += 1
v.Minor = 0
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpMinor increments the Minor field by 1 and resets all other fields to their default values
func (v *Version) BumpMinor() {
v.Minor += 1
v.Patch = 0
v.PreRelease = PreRelease("")
v.Metadata = ""
}
// BumpPatch increments the Patch field by 1 and resets all other fields to their default values
func (v *Version) BumpPatch() {
v.Patch += 1
v.PreRelease = PreRelease("")
v.Metadata = ""
}

View File

@ -95,6 +95,11 @@ func (l *LogLevel) Set(s string) error {
return nil return nil
} }
// Returns an empty string, only here to fulfill the pflag.Value interface.
func (l *LogLevel) Type() string {
return ""
}
// ParseLevel translates some potential loglevel strings into their corresponding levels. // ParseLevel translates some potential loglevel strings into their corresponding levels.
func ParseLevel(s string) (LogLevel, error) { func ParseLevel(s string) (LogLevel, error) {
switch s { switch s {

View File

@ -37,6 +37,14 @@ func (p *PackageLogger) internalLog(depth int, inLevel LogLevel, entries ...inte
} }
} }
// SetLevel allows users to change the current logging level.
func (p *PackageLogger) SetLevel(l LogLevel) {
logger.Lock()
defer logger.Unlock()
p.level = l
}
// LevelAt checks if the given log level will be outputted under current setting.
func (p *PackageLogger) LevelAt(l LogLevel) bool { func (p *PackageLogger) LevelAt(l LogLevel) bool {
logger.Lock() logger.Lock()
defer logger.Unlock() defer logger.Unlock()
@ -81,6 +89,12 @@ func (p *PackageLogger) Panic(args ...interface{}) {
panic(s) panic(s)
} }
func (p *PackageLogger) Panicln(args ...interface{}) {
s := fmt.Sprintln(args...)
p.internalLog(calldepth, CRITICAL, s)
panic(s)
}
func (p *PackageLogger) Fatalf(format string, args ...interface{}) { func (p *PackageLogger) Fatalf(format string, args ...interface{}) {
p.Logf(CRITICAL, format, args...) p.Logf(CRITICAL, format, args...)
os.Exit(1) os.Exit(1)

View File

@ -113,7 +113,7 @@ func humanateBigBytes(s, base *big.Int, sizes []string) string {
// //
// See also: ParseBigBytes. // See also: ParseBigBytes.
// //
// BigBytes(82854982) -> 83MB // BigBytes(82854982) -> 83 MB
func BigBytes(s *big.Int) string { func BigBytes(s *big.Int) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"} sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"}
return humanateBigBytes(s, bigSIExp, sizes) return humanateBigBytes(s, bigSIExp, sizes)
@ -123,7 +123,7 @@ func BigBytes(s *big.Int) string {
// //
// See also: ParseBigBytes. // See also: ParseBigBytes.
// //
// BigIBytes(82854982) -> 79MiB // BigIBytes(82854982) -> 79 MiB
func BigIBytes(s *big.Int) string { func BigIBytes(s *big.Int) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"} sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB", "ZiB", "YiB"}
return humanateBigBytes(s, bigIECExp, sizes) return humanateBigBytes(s, bigIECExp, sizes)
@ -134,8 +134,8 @@ func BigIBytes(s *big.Int) string {
// //
// See also: BigBytes, BigIBytes. // See also: BigBytes, BigIBytes.
// //
// ParseBigBytes("42MB") -> 42000000, nil // ParseBigBytes("42 MB") -> 42000000, nil
// ParseBigBytes("42mib") -> 44040192, nil // ParseBigBytes("42 mib") -> 44040192, nil
func ParseBigBytes(s string) (*big.Int, error) { func ParseBigBytes(s string) (*big.Int, error) {
lastDigit := 0 lastDigit := 0
hasComma := false hasComma := false

View File

@ -84,7 +84,7 @@ func humanateBytes(s uint64, base float64, sizes []string) string {
// //
// See also: ParseBytes. // See also: ParseBytes.
// //
// Bytes(82854982) -> 83MB // Bytes(82854982) -> 83 MB
func Bytes(s uint64) string { func Bytes(s uint64) string {
sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"} sizes := []string{"B", "kB", "MB", "GB", "TB", "PB", "EB"}
return humanateBytes(s, 1000, sizes) return humanateBytes(s, 1000, sizes)
@ -94,7 +94,7 @@ func Bytes(s uint64) string {
// //
// See also: ParseBytes. // See also: ParseBytes.
// //
// IBytes(82854982) -> 79MiB // IBytes(82854982) -> 79 MiB
func IBytes(s uint64) string { func IBytes(s uint64) string {
sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"} sizes := []string{"B", "KiB", "MiB", "GiB", "TiB", "PiB", "EiB"}
return humanateBytes(s, 1024, sizes) return humanateBytes(s, 1024, sizes)
@ -105,8 +105,8 @@ func IBytes(s uint64) string {
// //
// See Also: Bytes, IBytes. // See Also: Bytes, IBytes.
// //
// ParseBytes("42MB") -> 42000000, nil // ParseBytes("42 MB") -> 42000000, nil
// ParseBytes("42mib") -> 44040192, nil // ParseBytes("42 mib") -> 44040192, nil
func ParseBytes(s string) (uint64, error) { func ParseBytes(s string) (uint64, error) {
lastDigit := 0 lastDigit := 0
hasComma := false hasComma := false

View File

@ -2,6 +2,7 @@ package humanize
import ( import (
"bytes" "bytes"
"math"
"math/big" "math/big"
"strconv" "strconv"
"strings" "strings"
@ -13,6 +14,12 @@ import (
// e.g. Comma(834142) -> 834,142 // e.g. Comma(834142) -> 834,142
func Comma(v int64) string { func Comma(v int64) string {
sign := "" sign := ""
// Min int64 can't be negated to a usable value, so it has to be special cased.
if v == math.MinInt64 {
return "-9,223,372,036,854,775,808"
}
if v < 0 { if v < 0 {
sign = "-" sign = "-"
v = 0 - v v = 0 - v

View File

@ -2,7 +2,7 @@
Package humanize converts boring ugly numbers to human-friendly strings and back. Package humanize converts boring ugly numbers to human-friendly strings and back.
Durations can be turned into strings such as "3 days ago", numbers Durations can be turned into strings such as "3 days ago", numbers
representing sizes like 82854982 into useful strings like, "83MB" or representing sizes like 82854982 into useful strings like, "83 MB" or
"79MiB" (whichever you prefer). "79 MiB" (whichever you prefer).
*/ */
package humanize package humanize

View File

@ -160,7 +160,7 @@ func FormatFloat(format string, n float64) string {
intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision]) intf, fracf := math.Modf(n + renderFloatPrecisionRounders[precision])
// generate integer part string // generate integer part string
intStr := strconv.Itoa(int(intf)) intStr := strconv.FormatInt(int64(intf), 10)
// add thousand separator if required // add thousand separator if required
if len(thousandStr) > 0 { if len(thousandStr) > 0 {

View File

@ -68,7 +68,7 @@ func ComputeSI(input float64) (float64, string) {
value := mag / math.Pow(10, exponent) value := mag / math.Pow(10, exponent)
// Handle special case where value is exactly 1000.0 // Handle special case where value is exactly 1000.0
// Should return 1M instead of 1000k // Should return 1 M instead of 1000 k
if value == 1000.0 { if value == 1000.0 {
exponent += 3 exponent += 3
value = mag / math.Pow(10, exponent) value = mag / math.Pow(10, exponent)
@ -86,8 +86,8 @@ func ComputeSI(input float64) (float64, string) {
// //
// See also: ComputeSI, ParseSI. // See also: ComputeSI, ParseSI.
// //
// e.g. SI(1000000, B) -> 1MB // e.g. SI(1000000, "B") -> 1 MB
// e.g. SI(2.2345e-12, "F") -> 2.2345pF // e.g. SI(2.2345e-12, "F") -> 2.2345 pF
func SI(input float64, unit string) string { func SI(input float64, unit string) string {
value, prefix := ComputeSI(input) value, prefix := ComputeSI(input)
return Ftoa(value) + " " + prefix + unit return Ftoa(value) + " " + prefix + unit
@ -99,7 +99,7 @@ var errInvalid = errors.New("invalid input")
// //
// See also: SI, ComputeSI. // See also: SI, ComputeSI.
// //
// e.g. ParseSI(2.2345pF) -> (2.2345e-12, "F", nil) // e.g. ParseSI("2.2345 pF") -> (2.2345e-12, "F", nil)
func ParseSI(input string) (float64, string, error) { func ParseSI(input string) (float64, string, error) {
found := riParseRegex.FindStringSubmatch(input) found := riParseRegex.FindStringSubmatch(input)
if len(found) != 4 { if len(found) != 4 {

Some files were not shown because too many files have changed in this diff Show More