mirror of https://github.com/etcd-io/dbtester.git
vendor: use latest "etcd'"
Signed-off-by: Gyuho Lee <gyuhox@gmail.com>
This commit is contained in:
parent
6d153bb3bd
commit
1a1c2079dc
|
|
@ -35,17 +35,21 @@
|
|||
[[projects]]
|
||||
name = "github.com/coreos/etcd"
|
||||
packages = [
|
||||
"auth/authpb",
|
||||
"clientv3",
|
||||
"clientv3/balancer",
|
||||
"etcdserver/api/v3rpc/rpctypes",
|
||||
"etcdserver/etcdserverpb",
|
||||
"internal/auth/authpb",
|
||||
"internal/mvcc/mvccpb",
|
||||
"mvcc/mvccpb",
|
||||
"pkg/cpuutil",
|
||||
"pkg/logutil",
|
||||
"pkg/netutil",
|
||||
"pkg/report",
|
||||
"pkg/types"
|
||||
"pkg/types",
|
||||
"raft",
|
||||
"raft/raftpb"
|
||||
]
|
||||
revision = "1d99d3886f6cb5fb7ef13100c9587cc01820d38e"
|
||||
revision = "67b1ff6724637f0a00f693471ddb17b5adde38cf"
|
||||
source = "https://github.com/coreos/etcd"
|
||||
|
||||
[[projects]]
|
||||
|
|
@ -206,6 +210,31 @@
|
|||
revision = "e57e3eeb33f795204c1ca35f56c44f83227c6e66"
|
||||
version = "v1.0.0"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/atomic"
|
||||
packages = ["."]
|
||||
revision = "1ea20fb1cbb1cc08cbd0d913a96dead89aa18289"
|
||||
version = "v1.3.2"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/multierr"
|
||||
packages = ["."]
|
||||
revision = "3c4937480c32f4c13a875a1829af76c98ca3d40a"
|
||||
version = "v1.1.0"
|
||||
|
||||
[[projects]]
|
||||
name = "go.uber.org/zap"
|
||||
packages = [
|
||||
".",
|
||||
"buffer",
|
||||
"internal/bufferpool",
|
||||
"internal/color",
|
||||
"internal/exit",
|
||||
"zapcore"
|
||||
]
|
||||
revision = "eeedf312bc6c57391d84767a4cd413f02a917974"
|
||||
version = "v1.8.0"
|
||||
|
||||
[[projects]]
|
||||
branch = "master"
|
||||
name = "golang.org/x/image"
|
||||
|
|
@ -394,6 +423,6 @@
|
|||
[solve-meta]
|
||||
analyzer-name = "dep"
|
||||
analyzer-version = 1
|
||||
inputs-digest = "69cdc0d9c700a95cfecff8f95f783cc922720cd16666ffe7279cb702a812fa58"
|
||||
inputs-digest = "be4c2e85b5fe6d0b1af6016e555dcbaa6c76b9f4d5744747a1e2984512298b3a"
|
||||
solver-name = "gps-cdcl"
|
||||
solver-version = 1
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
[[constraint]]
|
||||
name = "github.com/coreos/etcd"
|
||||
source = "https://github.com/coreos/etcd"
|
||||
revision = "1d99d3886f6cb5fb7ef13100c9587cc01820d38e"
|
||||
revision = "67b1ff6724637f0a00f693471ddb17b5adde38cf"
|
||||
|
||||
# v1.7.5
|
||||
[[constraint]]
|
||||
|
|
|
|||
|
|
@ -0,0 +1,230 @@
|
|||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type tokenJWT struct {
|
||||
lg *zap.Logger
|
||||
signMethod string
|
||||
signKey *rsa.PrivateKey
|
||||
verifyKey *rsa.PublicKey
|
||||
ttl time.Duration
|
||||
}
|
||||
|
||||
func (t *tokenJWT) enable() {}
|
||||
func (t *tokenJWT) disable() {}
|
||||
func (t *tokenJWT) invalidateUser(string) {}
|
||||
func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
|
||||
|
||||
func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
|
||||
// rev isn't used in JWT, it is only used in simple token
|
||||
var (
|
||||
username string
|
||||
revision uint64
|
||||
)
|
||||
|
||||
parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
return t.verifyKey, nil
|
||||
})
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
if !parsed.Valid {
|
||||
if t.lg != nil {
|
||||
t.lg.Warn("invalid JWT token", zap.String("token", token))
|
||||
} else {
|
||||
plog.Warningf("invalid jwt token: %s", token)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
claims := parsed.Claims.(jwt.MapClaims)
|
||||
|
||||
username = claims["username"].(string)
|
||||
revision = uint64(claims["revision"].(float64))
|
||||
default:
|
||||
if t.lg != nil {
|
||||
t.lg.Warn(
|
||||
"failed to parse a JWT token",
|
||||
zap.String("token", token),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to parse jwt token: %s", err)
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &AuthInfo{Username: username, Revision: revision}, true
|
||||
}
|
||||
|
||||
func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
|
||||
// Future work: let a jwt token include permission information would be useful for
|
||||
// permission checking in proxy side.
|
||||
tk := jwt.NewWithClaims(jwt.GetSigningMethod(t.signMethod),
|
||||
jwt.MapClaims{
|
||||
"username": username,
|
||||
"revision": revision,
|
||||
"exp": time.Now().Add(t.ttl).Unix(),
|
||||
})
|
||||
|
||||
token, err := tk.SignedString(t.signKey)
|
||||
if err != nil {
|
||||
if t.lg != nil {
|
||||
t.lg.Warn(
|
||||
"failed to sign a JWT token",
|
||||
zap.String("user-name", username),
|
||||
zap.Uint64("revision", revision),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Debugf("failed to sign jwt token: %s", err)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
if t.lg != nil {
|
||||
t.lg.Info(
|
||||
"created/assigned a new JWT token",
|
||||
zap.String("user-name", username),
|
||||
zap.Uint64("revision", revision),
|
||||
zap.String("token", token),
|
||||
)
|
||||
} else {
|
||||
plog.Debugf("jwt token: %s", token)
|
||||
}
|
||||
return token, err
|
||||
}
|
||||
|
||||
func prepareOpts(lg *zap.Logger, opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, ttl time.Duration, err error) {
|
||||
for k, v := range opts {
|
||||
switch k {
|
||||
case "sign-method":
|
||||
jwtSignMethod = v
|
||||
case "pub-key":
|
||||
jwtPubKeyPath = v
|
||||
case "priv-key":
|
||||
jwtPrivKeyPath = v
|
||||
case "ttl":
|
||||
ttl, err = time.ParseDuration(v)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse JWT TTL option",
|
||||
zap.String("ttl-value", v),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to parse ttl option (%s)", err)
|
||||
}
|
||||
return "", "", "", 0, ErrInvalidAuthOpts
|
||||
}
|
||||
default:
|
||||
if lg != nil {
|
||||
lg.Warn("unknown JWT token option", zap.String("option", k))
|
||||
} else {
|
||||
plog.Errorf("unknown token specific option: %s", k)
|
||||
}
|
||||
return "", "", "", 0, ErrInvalidAuthOpts
|
||||
}
|
||||
}
|
||||
if len(jwtSignMethod) == 0 {
|
||||
return "", "", "", 0, ErrInvalidAuthOpts
|
||||
}
|
||||
return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, nil
|
||||
}
|
||||
|
||||
func newTokenProviderJWT(lg *zap.Logger, opts map[string]string) (*tokenJWT, error) {
|
||||
jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, ttl, err := prepareOpts(lg, opts)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidAuthOpts
|
||||
}
|
||||
|
||||
if ttl == 0 {
|
||||
ttl = 5 * time.Minute
|
||||
}
|
||||
|
||||
t := &tokenJWT{
|
||||
lg: lg,
|
||||
ttl: ttl,
|
||||
}
|
||||
|
||||
t.signMethod = jwtSignMethod
|
||||
|
||||
verifyBytes, err := ioutil.ReadFile(jwtPubKeyPath)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to read JWT public key",
|
||||
zap.String("public-key-path", jwtPubKeyPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to read public key (%s) for jwt: %s", jwtPubKeyPath, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t.verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse JWT public key",
|
||||
zap.String("public-key-path", jwtPubKeyPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to parse public key (%s): %s", jwtPubKeyPath, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signBytes, err := ioutil.ReadFile(jwtPrivKeyPath)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to read JWT private key",
|
||||
zap.String("private-key-path", jwtPrivKeyPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to read private key (%s) for jwt: %s", jwtPrivKeyPath, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
t.signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
|
||||
if err != nil {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse JWT private key",
|
||||
zap.String("private-key-path", jwtPrivKeyPath),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to parse private key (%s): %s", jwtPrivKeyPath, err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
// Copyright 2018 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
)
|
||||
|
||||
type tokenNop struct{}
|
||||
|
||||
func (t *tokenNop) enable() {}
|
||||
func (t *tokenNop) disable() {}
|
||||
func (t *tokenNop) invalidateUser(string) {}
|
||||
func (t *tokenNop) genTokenPrefix() (string, error) { return "", nil }
|
||||
func (t *tokenNop) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
|
||||
return nil, false
|
||||
}
|
||||
func (t *tokenNop) assign(ctx context.Context, username string, revision uint64) (string, error) {
|
||||
return "", ErrAuthFailed
|
||||
}
|
||||
func newTokenProviderNop() (*tokenNop, error) {
|
||||
return &tokenNop{}, nil
|
||||
}
|
||||
|
|
@ -15,15 +15,16 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/internal/auth/authpb"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/auth/authpb"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/pkg/adt"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermissions {
|
||||
user := getUser(tx, userName)
|
||||
func getMergedPerms(lg *zap.Logger, tx backend.BatchTx, userName string) *unifiedRangePermissions {
|
||||
user := getUser(lg, tx, userName)
|
||||
if user == nil {
|
||||
plog.Errorf("invalid user name %s", userName)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -70,7 +71,11 @@ func getMergedPerms(tx backend.BatchTx, userName string) *unifiedRangePermission
|
|||
}
|
||||
}
|
||||
|
||||
func checkKeyInterval(cachedPerms *unifiedRangePermissions, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
|
||||
func checkKeyInterval(
|
||||
lg *zap.Logger,
|
||||
cachedPerms *unifiedRangePermissions,
|
||||
key, rangeEnd []byte,
|
||||
permtyp authpb.Permission_Type) bool {
|
||||
if len(rangeEnd) == 1 && rangeEnd[0] == 0 {
|
||||
rangeEnd = nil
|
||||
}
|
||||
|
|
@ -82,12 +87,16 @@ func checkKeyInterval(cachedPerms *unifiedRangePermissions, key, rangeEnd []byte
|
|||
case authpb.WRITE:
|
||||
return cachedPerms.writePerms.Contains(ivl)
|
||||
default:
|
||||
plog.Panicf("unknown auth type: %v", permtyp)
|
||||
if lg != nil {
|
||||
lg.Panic("unknown auth type", zap.String("auth-type", permtyp.String()))
|
||||
} else {
|
||||
plog.Panicf("unknown auth type: %v", permtyp)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkKeyPoint(cachedPerms *unifiedRangePermissions, key []byte, permtyp authpb.Permission_Type) bool {
|
||||
func checkKeyPoint(lg *zap.Logger, cachedPerms *unifiedRangePermissions, key []byte, permtyp authpb.Permission_Type) bool {
|
||||
pt := adt.NewBytesAffinePoint(key)
|
||||
switch permtyp {
|
||||
case authpb.READ:
|
||||
|
|
@ -95,7 +104,11 @@ func checkKeyPoint(cachedPerms *unifiedRangePermissions, key []byte, permtyp aut
|
|||
case authpb.WRITE:
|
||||
return cachedPerms.writePerms.Intersects(pt)
|
||||
default:
|
||||
plog.Panicf("unknown auth type: %v", permtyp)
|
||||
if lg != nil {
|
||||
lg.Panic("unknown auth type", zap.String("auth-type", permtyp.String()))
|
||||
} else {
|
||||
plog.Panicf("unknown auth type: %v", permtyp)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
@ -104,19 +117,26 @@ func (as *authStore) isRangeOpPermitted(tx backend.BatchTx, userName string, key
|
|||
// assumption: tx is Lock()ed
|
||||
_, ok := as.rangePermCache[userName]
|
||||
if !ok {
|
||||
perms := getMergedPerms(tx, userName)
|
||||
perms := getMergedPerms(as.lg, tx, userName)
|
||||
if perms == nil {
|
||||
plog.Errorf("failed to create a unified permission of user %s", userName)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"failed to create a merged permission",
|
||||
zap.String("user-name", userName),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to create a unified permission of user %s", userName)
|
||||
}
|
||||
return false
|
||||
}
|
||||
as.rangePermCache[userName] = perms
|
||||
}
|
||||
|
||||
if len(rangeEnd) == 0 {
|
||||
return checkKeyPoint(as.rangePermCache[userName], key, permtyp)
|
||||
return checkKeyPoint(as.lg, as.rangePermCache[userName], key, permtyp)
|
||||
}
|
||||
|
||||
return checkKeyInterval(as.rangePermCache[userName], key, rangeEnd, permtyp)
|
||||
return checkKeyInterval(as.lg, as.rangePermCache[userName], key, rangeEnd, permtyp)
|
||||
}
|
||||
|
||||
func (as *authStore) clearCachedPerm() {
|
||||
|
|
@ -26,6 +26,8 @@ import (
|
|||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -94,6 +96,7 @@ func (tm *simpleTokenTTLKeeper) run() {
|
|||
}
|
||||
|
||||
type tokenSimple struct {
|
||||
lg *zap.Logger
|
||||
indexWaiter func(uint64) <-chan struct{}
|
||||
simpleTokenKeeper *simpleTokenTTLKeeper
|
||||
simpleTokensMu sync.Mutex
|
||||
|
|
@ -124,7 +127,15 @@ func (t *tokenSimple) assignSimpleTokenToUser(username, token string) {
|
|||
|
||||
_, ok := t.simpleTokens[token]
|
||||
if ok {
|
||||
plog.Panicf("token %s is alredy used", token)
|
||||
if t.lg != nil {
|
||||
t.lg.Panic(
|
||||
"failed to assign already-used simple token to a user",
|
||||
zap.String("user-name", username),
|
||||
zap.String("token", token),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("token %s is alredy used", token)
|
||||
}
|
||||
}
|
||||
|
||||
t.simpleTokens[token] = username
|
||||
|
|
@ -137,7 +148,7 @@ func (t *tokenSimple) invalidateUser(username string) {
|
|||
}
|
||||
t.simpleTokensMu.Lock()
|
||||
for token, name := range t.simpleTokens {
|
||||
if strings.Compare(name, username) == 0 {
|
||||
if name == username {
|
||||
delete(t.simpleTokens, token)
|
||||
t.simpleTokenKeeper.deleteSimpleToken(token)
|
||||
}
|
||||
|
|
@ -148,7 +159,15 @@ func (t *tokenSimple) invalidateUser(username string) {
|
|||
func (t *tokenSimple) enable() {
|
||||
delf := func(tk string) {
|
||||
if username, ok := t.simpleTokens[tk]; ok {
|
||||
plog.Infof("deleting token %s for user %s", tk, username)
|
||||
if t.lg != nil {
|
||||
t.lg.Info(
|
||||
"deleted a simple token",
|
||||
zap.String("user-name", username),
|
||||
zap.String("token", tk),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("deleting token %s for user %s", tk, username)
|
||||
}
|
||||
delete(t.simpleTokens, tk)
|
||||
}
|
||||
}
|
||||
|
|
@ -215,8 +234,9 @@ func (t *tokenSimple) isValidSimpleToken(ctx context.Context, token string) bool
|
|||
return false
|
||||
}
|
||||
|
||||
func newTokenProviderSimple(indexWaiter func(uint64) <-chan struct{}) *tokenSimple {
|
||||
func newTokenProviderSimple(lg *zap.Logger, indexWaiter func(uint64) <-chan struct{}) *tokenSimple {
|
||||
return &tokenSimple{
|
||||
lg: lg,
|
||||
simpleTokens: make(map[string]string),
|
||||
indexWaiter: indexWaiter,
|
||||
}
|
||||
|
|
@ -24,11 +24,13 @@ import (
|
|||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/coreos/etcd/auth/authpb"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth/authpb"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"google.golang.org/grpc/credentials"
|
||||
"google.golang.org/grpc/metadata"
|
||||
|
|
@ -64,9 +66,6 @@ var (
|
|||
ErrInvalidAuthToken = errors.New("auth: invalid auth token")
|
||||
ErrInvalidAuthOpts = errors.New("auth: invalid auth options")
|
||||
ErrInvalidAuthMgmt = errors.New("auth: invalid auth management")
|
||||
|
||||
// BcryptCost is the algorithm cost / strength for hashing auth passwords
|
||||
BcryptCost = bcrypt.DefaultCost
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -87,6 +86,7 @@ type AuthenticateParamIndex struct{}
|
|||
// AuthenticateParamSimpleTokenPrefix is used for a key of context in the parameters of Authenticate()
|
||||
type AuthenticateParamSimpleTokenPrefix struct{}
|
||||
|
||||
// AuthStore defines auth storage interface.
|
||||
type AuthStore interface {
|
||||
// AuthEnable turns on the authentication feature
|
||||
AuthEnable() error
|
||||
|
|
@ -94,6 +94,9 @@ type AuthStore interface {
|
|||
// AuthDisable turns off the authentication feature
|
||||
AuthDisable()
|
||||
|
||||
// IsAuthEnabled returns true if the authentication feature is enabled.
|
||||
IsAuthEnabled() bool
|
||||
|
||||
// Authenticate does authentication based on given user name and password
|
||||
Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error)
|
||||
|
||||
|
|
@ -191,6 +194,7 @@ type authStore struct {
|
|||
// atomic operations; need 64-bit align, or 32-bit tests will crash
|
||||
revision uint64
|
||||
|
||||
lg *zap.Logger
|
||||
be backend.Backend
|
||||
enabled bool
|
||||
enabledMu sync.RWMutex
|
||||
|
|
@ -198,13 +202,18 @@ type authStore struct {
|
|||
rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
|
||||
|
||||
tokenProvider TokenProvider
|
||||
bcryptCost int // the algorithm cost / strength for hashing auth passwords
|
||||
}
|
||||
|
||||
func (as *authStore) AuthEnable() error {
|
||||
as.enabledMu.Lock()
|
||||
defer as.enabledMu.Unlock()
|
||||
if as.enabled {
|
||||
plog.Noticef("Authentication already enabled")
|
||||
if as.lg != nil {
|
||||
as.lg.Info("authentication is already enabled; ignored auth enable request")
|
||||
} else {
|
||||
plog.Noticef("Authentication already enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
b := as.be
|
||||
|
|
@ -215,7 +224,7 @@ func (as *authStore) AuthEnable() error {
|
|||
b.ForceCommit()
|
||||
}()
|
||||
|
||||
u := getUser(tx, rootUser)
|
||||
u := getUser(as.lg, tx, rootUser)
|
||||
if u == nil {
|
||||
return ErrRootUserNotExist
|
||||
}
|
||||
|
|
@ -233,8 +242,11 @@ func (as *authStore) AuthEnable() error {
|
|||
|
||||
as.setRevision(getRevision(tx))
|
||||
|
||||
plog.Noticef("Authentication enabled")
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info("enabled authentication")
|
||||
} else {
|
||||
plog.Noticef("Authentication enabled")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -255,7 +267,11 @@ func (as *authStore) AuthDisable() {
|
|||
as.enabled = false
|
||||
as.tokenProvider.disable()
|
||||
|
||||
plog.Noticef("Authentication disabled")
|
||||
if as.lg != nil {
|
||||
as.lg.Info("disabled authentication")
|
||||
} else {
|
||||
plog.Noticef("Authentication disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func (as *authStore) Close() error {
|
||||
|
|
@ -269,7 +285,7 @@ func (as *authStore) Close() error {
|
|||
}
|
||||
|
||||
func (as *authStore) Authenticate(ctx context.Context, username, password string) (*pb.AuthenticateResponse, error) {
|
||||
if !as.isAuthEnabled() {
|
||||
if !as.IsAuthEnabled() {
|
||||
return nil, ErrAuthNotEnabled
|
||||
}
|
||||
|
||||
|
|
@ -277,7 +293,7 @@ func (as *authStore) Authenticate(ctx context.Context, username, password string
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, username)
|
||||
user := getUser(as.lg, tx, username)
|
||||
if user == nil {
|
||||
return nil, ErrAuthFailed
|
||||
}
|
||||
|
|
@ -290,12 +306,20 @@ func (as *authStore) Authenticate(ctx context.Context, username, password string
|
|||
return nil, err
|
||||
}
|
||||
|
||||
plog.Debugf("authorized %s, token is %s", username, token)
|
||||
if as.lg != nil {
|
||||
as.lg.Debug(
|
||||
"authenticated a user",
|
||||
zap.String("user-name", username),
|
||||
zap.String("token", token),
|
||||
)
|
||||
} else {
|
||||
plog.Debugf("authorized %s, token is %s", username, token)
|
||||
}
|
||||
return &pb.AuthenticateResponse{Token: token}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) CheckPassword(username, password string) (uint64, error) {
|
||||
if !as.isAuthEnabled() {
|
||||
if !as.IsAuthEnabled() {
|
||||
return 0, ErrAuthNotEnabled
|
||||
}
|
||||
|
||||
|
|
@ -303,16 +327,19 @@ func (as *authStore) CheckPassword(username, password string) (uint64, error) {
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, username)
|
||||
user := getUser(as.lg, tx, username)
|
||||
if user == nil {
|
||||
return 0, ErrAuthFailed
|
||||
}
|
||||
|
||||
if bcrypt.CompareHashAndPassword(user.Password, []byte(password)) != nil {
|
||||
plog.Noticef("authentication failed, invalid password for user %s", username)
|
||||
if as.lg != nil {
|
||||
as.lg.Info("invalid password", zap.String("user-name", username))
|
||||
} else {
|
||||
plog.Noticef("authentication failed, invalid password for user %s", username)
|
||||
}
|
||||
return 0, ErrAuthFailed
|
||||
}
|
||||
|
||||
return getRevision(tx), nil
|
||||
}
|
||||
|
||||
|
|
@ -342,9 +369,17 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
|
|||
return nil, ErrUserEmpty
|
||||
}
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), BcryptCost)
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"failed to bcrypt hash password",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -352,7 +387,7 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, r.Name)
|
||||
user := getUser(as.lg, tx, r.Name)
|
||||
if user != nil {
|
||||
return nil, ErrUserAlreadyExist
|
||||
}
|
||||
|
|
@ -362,18 +397,25 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
|
|||
Password: hashed,
|
||||
}
|
||||
|
||||
putUser(tx, newUser)
|
||||
putUser(as.lg, tx, newUser)
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("added a new user: %s", r.Name)
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info("added a user", zap.String("user-name", r.Name))
|
||||
} else {
|
||||
plog.Noticef("added a new user: %s", r.Name)
|
||||
}
|
||||
return &pb.AuthUserAddResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
|
||||
if as.enabled && strings.Compare(r.Name, rootUser) == 0 {
|
||||
plog.Errorf("the user root must not be deleted")
|
||||
if as.enabled && r.Name == rootUser {
|
||||
if as.lg != nil {
|
||||
as.lg.Warn("cannot delete 'root' user", zap.String("user-name", r.Name))
|
||||
} else {
|
||||
plog.Errorf("the user root must not be deleted")
|
||||
}
|
||||
return nil, ErrInvalidAuthMgmt
|
||||
}
|
||||
|
||||
|
|
@ -381,7 +423,7 @@ func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDelete
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, r.Name)
|
||||
user := getUser(as.lg, tx, r.Name)
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
|
@ -393,17 +435,32 @@ func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDelete
|
|||
as.invalidateCachedPerm(r.Name)
|
||||
as.tokenProvider.invalidateUser(r.Name)
|
||||
|
||||
plog.Noticef("deleted a user: %s", r.Name)
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"deleted a user",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.Strings("user-roles", user.Roles),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("deleted a user: %s", r.Name)
|
||||
}
|
||||
return &pb.AuthUserDeleteResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||
// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
|
||||
// If the cost is too high, we should move the encryption to outside of the raft
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), BcryptCost)
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"failed to bcrypt hash password",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
|
|
@ -411,7 +468,7 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, r.Name)
|
||||
user := getUser(as.lg, tx, r.Name)
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
|
@ -422,15 +479,22 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p
|
|||
Password: hashed,
|
||||
}
|
||||
|
||||
putUser(tx, updatedUser)
|
||||
putUser(as.lg, tx, updatedUser)
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
as.invalidateCachedPerm(r.Name)
|
||||
as.tokenProvider.invalidateUser(r.Name)
|
||||
|
||||
plog.Noticef("changed a password of a user: %s", r.Name)
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"changed a password of a user",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.Strings("user-roles", user.Roles),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("changed a password of a user: %s", r.Name)
|
||||
}
|
||||
return &pb.AuthUserChangePasswordResponse{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -439,7 +503,7 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, r.User)
|
||||
user := getUser(as.lg, tx, r.User)
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
|
@ -452,28 +516,46 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser
|
|||
}
|
||||
|
||||
idx := sort.SearchStrings(user.Roles, r.Role)
|
||||
if idx < len(user.Roles) && strings.Compare(user.Roles[idx], r.Role) == 0 {
|
||||
plog.Warningf("user %s is already granted role %s", r.User, r.Role)
|
||||
if idx < len(user.Roles) && user.Roles[idx] == r.Role {
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"ignored grant role request to a user",
|
||||
zap.String("user-name", r.User),
|
||||
zap.Strings("user-roles", user.Roles),
|
||||
zap.String("duplicate-role-name", r.Role),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("user %s is already granted role %s", r.User, r.Role)
|
||||
}
|
||||
return &pb.AuthUserGrantRoleResponse{}, nil
|
||||
}
|
||||
|
||||
user.Roles = append(user.Roles, r.Role)
|
||||
sort.Strings(user.Roles)
|
||||
|
||||
putUser(tx, user)
|
||||
putUser(as.lg, tx, user)
|
||||
|
||||
as.invalidateCachedPerm(r.User)
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("granted role %s to user %s", r.Role, r.User)
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"granted a role to a user",
|
||||
zap.String("user-name", r.User),
|
||||
zap.Strings("user-roles", user.Roles),
|
||||
zap.String("added-role-name", r.Role),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("granted role %s to user %s", r.Role, r.User)
|
||||
}
|
||||
return &pb.AuthUserGrantRoleResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
user := getUser(tx, r.Name)
|
||||
user := getUser(as.lg, tx, r.Name)
|
||||
tx.Unlock()
|
||||
|
||||
if user == nil {
|
||||
|
|
@ -488,7 +570,7 @@ func (as *authStore) UserGet(r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse,
|
|||
func (as *authStore) UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
users := getAllUsers(tx)
|
||||
users := getAllUsers(as.lg, tx)
|
||||
tx.Unlock()
|
||||
|
||||
resp := &pb.AuthUserListResponse{Users: make([]string, len(users))}
|
||||
|
|
@ -499,8 +581,16 @@ func (as *authStore) UserList(r *pb.AuthUserListRequest) (*pb.AuthUserListRespon
|
|||
}
|
||||
|
||||
func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
|
||||
if as.enabled && strings.Compare(r.Name, rootUser) == 0 && strings.Compare(r.Role, rootRole) == 0 {
|
||||
plog.Errorf("the role root must not be revoked from the user root")
|
||||
if as.enabled && r.Name == rootUser && r.Role == rootRole {
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"'root' user cannot revoke 'root' role",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.String("role-name", r.Role),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("the role root must not be revoked from the user root")
|
||||
}
|
||||
return nil, ErrInvalidAuthMgmt
|
||||
}
|
||||
|
||||
|
|
@ -508,7 +598,7 @@ func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUs
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, r.Name)
|
||||
user := getUser(as.lg, tx, r.Name)
|
||||
if user == nil {
|
||||
return nil, ErrUserNotFound
|
||||
}
|
||||
|
|
@ -519,7 +609,7 @@ func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUs
|
|||
}
|
||||
|
||||
for _, role := range user.Roles {
|
||||
if strings.Compare(role, r.Role) != 0 {
|
||||
if role != r.Role {
|
||||
updatedUser.Roles = append(updatedUser.Roles, role)
|
||||
}
|
||||
}
|
||||
|
|
@ -528,13 +618,23 @@ func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUs
|
|||
return nil, ErrRoleNotGranted
|
||||
}
|
||||
|
||||
putUser(tx, updatedUser)
|
||||
putUser(as.lg, tx, updatedUser)
|
||||
|
||||
as.invalidateCachedPerm(r.Name)
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("revoked role %s from user %s", r.Role, r.Name)
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"revoked a role from a user",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.Strings("old-user-roles", user.Roles),
|
||||
zap.Strings("new-user-roles", updatedUser.Roles),
|
||||
zap.String("revoked-role-name", r.Role),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("revoked role %s from user %s", r.Role, r.Name)
|
||||
}
|
||||
return &pb.AuthUserRevokeRoleResponse{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -556,7 +656,7 @@ func (as *authStore) RoleGet(r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse,
|
|||
func (as *authStore) RoleList(r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
roles := getAllRoles(tx)
|
||||
roles := getAllRoles(as.lg, tx)
|
||||
tx.Unlock()
|
||||
|
||||
resp := &pb.AuthRoleListResponse{Roles: make([]string, len(roles))}
|
||||
|
|
@ -581,7 +681,7 @@ func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest)
|
|||
}
|
||||
|
||||
for _, perm := range role.KeyPermission {
|
||||
if !bytes.Equal(perm.Key, []byte(r.Key)) || !bytes.Equal(perm.RangeEnd, []byte(r.RangeEnd)) {
|
||||
if !bytes.Equal(perm.Key, r.Key) || !bytes.Equal(perm.RangeEnd, r.RangeEnd) {
|
||||
updatedRole.KeyPermission = append(updatedRole.KeyPermission, perm)
|
||||
}
|
||||
}
|
||||
|
|
@ -590,7 +690,7 @@ func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest)
|
|||
return nil, ErrPermissionNotGranted
|
||||
}
|
||||
|
||||
putRole(tx, updatedRole)
|
||||
putRole(as.lg, tx, updatedRole)
|
||||
|
||||
// TODO(mitake): currently single role update invalidates every cache
|
||||
// It should be optimized.
|
||||
|
|
@ -598,13 +698,26 @@ func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest)
|
|||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("revoked key %s from role %s", r.Key, r.Role)
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"revoked a permission on range",
|
||||
zap.String("role-name", r.Role),
|
||||
zap.String("key", string(r.Key)),
|
||||
zap.String("range-end", string(r.RangeEnd)),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("revoked key %s from role %s", r.Key, r.Role)
|
||||
}
|
||||
return &pb.AuthRoleRevokePermissionResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
|
||||
if as.enabled && strings.Compare(r.Role, rootRole) == 0 {
|
||||
plog.Errorf("the role root must not be deleted")
|
||||
if as.enabled && r.Role == rootRole {
|
||||
if as.lg != nil {
|
||||
as.lg.Warn("cannot delete 'root' role", zap.String("role-name", r.Role))
|
||||
} else {
|
||||
plog.Errorf("the role root must not be deleted")
|
||||
}
|
||||
return nil, ErrInvalidAuthMgmt
|
||||
}
|
||||
|
||||
|
|
@ -619,7 +732,7 @@ func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDelete
|
|||
|
||||
delRole(tx, r.Role)
|
||||
|
||||
users := getAllUsers(tx)
|
||||
users := getAllUsers(as.lg, tx)
|
||||
for _, user := range users {
|
||||
updatedUser := &authpb.User{
|
||||
Name: user.Name,
|
||||
|
|
@ -627,7 +740,7 @@ func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDelete
|
|||
}
|
||||
|
||||
for _, role := range user.Roles {
|
||||
if strings.Compare(role, r.Role) != 0 {
|
||||
if role != r.Role {
|
||||
updatedUser.Roles = append(updatedUser.Roles, role)
|
||||
}
|
||||
}
|
||||
|
|
@ -636,14 +749,18 @@ func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDelete
|
|||
continue
|
||||
}
|
||||
|
||||
putUser(tx, updatedUser)
|
||||
putUser(as.lg, tx, updatedUser)
|
||||
|
||||
as.invalidateCachedPerm(string(user.Name))
|
||||
}
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("deleted role %s", r.Role)
|
||||
if as.lg != nil {
|
||||
as.lg.Info("deleted a role", zap.String("role-name", r.Role))
|
||||
} else {
|
||||
plog.Noticef("deleted role %s", r.Role)
|
||||
}
|
||||
return &pb.AuthRoleDeleteResponse{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -661,12 +778,15 @@ func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse,
|
|||
Name: []byte(r.Name),
|
||||
}
|
||||
|
||||
putRole(tx, newRole)
|
||||
putRole(as.lg, tx, newRole)
|
||||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("Role %s is created", r.Name)
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info("created a role", zap.String("role-name", r.Name))
|
||||
} else {
|
||||
plog.Noticef("Role %s is created", r.Name)
|
||||
}
|
||||
return &pb.AuthRoleAddResponse{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -699,7 +819,7 @@ func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (
|
|||
}
|
||||
|
||||
idx := sort.Search(len(role.KeyPermission), func(i int) bool {
|
||||
return bytes.Compare(role.KeyPermission[i].Key, []byte(r.Perm.Key)) >= 0
|
||||
return bytes.Compare(role.KeyPermission[i].Key, r.Perm.Key) >= 0
|
||||
})
|
||||
|
||||
if idx < len(role.KeyPermission) && bytes.Equal(role.KeyPermission[idx].Key, r.Perm.Key) && bytes.Equal(role.KeyPermission[idx].RangeEnd, r.Perm.RangeEnd) {
|
||||
|
|
@ -708,8 +828,8 @@ func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (
|
|||
} else {
|
||||
// append new permission to the role
|
||||
newPerm := &authpb.Permission{
|
||||
Key: []byte(r.Perm.Key),
|
||||
RangeEnd: []byte(r.Perm.RangeEnd),
|
||||
Key: r.Perm.Key,
|
||||
RangeEnd: r.Perm.RangeEnd,
|
||||
PermType: r.Perm.PermType,
|
||||
}
|
||||
|
||||
|
|
@ -717,7 +837,7 @@ func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (
|
|||
sort.Sort(permSlice(role.KeyPermission))
|
||||
}
|
||||
|
||||
putRole(tx, role)
|
||||
putRole(as.lg, tx, role)
|
||||
|
||||
// TODO(mitake): currently single role update invalidates every cache
|
||||
// It should be optimized.
|
||||
|
|
@ -725,14 +845,21 @@ func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (
|
|||
|
||||
as.commitRevision(tx)
|
||||
|
||||
plog.Noticef("role %s's permission of key %s is updated as %s", r.Name, r.Perm.Key, authpb.Permission_Type_name[int32(r.Perm.PermType)])
|
||||
|
||||
if as.lg != nil {
|
||||
as.lg.Info(
|
||||
"granted/updated a permission to a user",
|
||||
zap.String("user-name", r.Name),
|
||||
zap.String("permission-name", authpb.Permission_Type_name[int32(r.Perm.PermType)]),
|
||||
)
|
||||
} else {
|
||||
plog.Noticef("role %s's permission of key %s is updated as %s", r.Name, r.Perm.Key, authpb.Permission_Type_name[int32(r.Perm.PermType)])
|
||||
}
|
||||
return &pb.AuthRoleGrantPermissionResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeEnd []byte, permTyp authpb.Permission_Type) error {
|
||||
// TODO(mitake): this function would be costly so we need a caching mechanism
|
||||
if !as.isAuthEnabled() {
|
||||
if !as.IsAuthEnabled() {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -749,9 +876,13 @@ func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeE
|
|||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
user := getUser(tx, userName)
|
||||
user := getUser(as.lg, tx, userName)
|
||||
if user == nil {
|
||||
plog.Errorf("invalid user name %s for permission checking", userName)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn("cannot find a user for permission check", zap.String("user-name", userName))
|
||||
} else {
|
||||
plog.Errorf("invalid user name %s for permission checking", userName)
|
||||
}
|
||||
return ErrPermissionDenied
|
||||
}
|
||||
|
||||
|
|
@ -780,7 +911,7 @@ func (as *authStore) IsDeleteRangePermitted(authInfo *AuthInfo, key, rangeEnd []
|
|||
}
|
||||
|
||||
func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
|
||||
if !as.isAuthEnabled() {
|
||||
if !as.IsAuthEnabled() {
|
||||
return nil
|
||||
}
|
||||
if authInfo == nil {
|
||||
|
|
@ -789,7 +920,7 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
|
|||
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
u := getUser(tx, authInfo.Username)
|
||||
u := getUser(as.lg, tx, authInfo.Username)
|
||||
tx.Unlock()
|
||||
|
||||
if u == nil {
|
||||
|
|
@ -803,7 +934,7 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func getUser(tx backend.BatchTx, username string) *authpb.User {
|
||||
func getUser(lg *zap.Logger, tx backend.BatchTx, username string) *authpb.User {
|
||||
_, vs := tx.UnsafeRange(authUsersBucketName, []byte(username), nil, 0)
|
||||
if len(vs) == 0 {
|
||||
return nil
|
||||
|
|
@ -812,12 +943,20 @@ func getUser(tx backend.BatchTx, username string) *authpb.User {
|
|||
user := &authpb.User{}
|
||||
err := user.Unmarshal(vs[0])
|
||||
if err != nil {
|
||||
plog.Panicf("failed to unmarshal user struct (name: %s): %s", username, err)
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"failed to unmarshal 'authpb.User'",
|
||||
zap.String("user-name", username),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("failed to unmarshal user struct (name: %s): %s", username, err)
|
||||
}
|
||||
}
|
||||
return user
|
||||
}
|
||||
|
||||
func getAllUsers(tx backend.BatchTx) []*authpb.User {
|
||||
func getAllUsers(lg *zap.Logger, tx backend.BatchTx) []*authpb.User {
|
||||
_, vs := tx.UnsafeRange(authUsersBucketName, []byte{0}, []byte{0xff}, -1)
|
||||
if len(vs) == 0 {
|
||||
return nil
|
||||
|
|
@ -828,17 +967,25 @@ func getAllUsers(tx backend.BatchTx) []*authpb.User {
|
|||
user := &authpb.User{}
|
||||
err := user.Unmarshal(vs[i])
|
||||
if err != nil {
|
||||
plog.Panicf("failed to unmarshal user struct: %s", err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to unmarshal 'authpb.User'", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("failed to unmarshal user struct: %s", err)
|
||||
}
|
||||
}
|
||||
users[i] = user
|
||||
}
|
||||
return users
|
||||
}
|
||||
|
||||
func putUser(tx backend.BatchTx, user *authpb.User) {
|
||||
func putUser(lg *zap.Logger, tx backend.BatchTx, user *authpb.User) {
|
||||
b, err := user.Marshal()
|
||||
if err != nil {
|
||||
plog.Panicf("failed to marshal user struct (name: %s): %s", user.Name, err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to unmarshal 'authpb.User'", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("failed to marshal user struct (name: %s): %s", user.Name, err)
|
||||
}
|
||||
}
|
||||
tx.UnsafePut(authUsersBucketName, user.Name, b)
|
||||
}
|
||||
|
|
@ -861,7 +1008,7 @@ func getRole(tx backend.BatchTx, rolename string) *authpb.Role {
|
|||
return role
|
||||
}
|
||||
|
||||
func getAllRoles(tx backend.BatchTx) []*authpb.Role {
|
||||
func getAllRoles(lg *zap.Logger, tx backend.BatchTx) []*authpb.Role {
|
||||
_, vs := tx.UnsafeRange(authRolesBucketName, []byte{0}, []byte{0xff}, -1)
|
||||
if len(vs) == 0 {
|
||||
return nil
|
||||
|
|
@ -872,33 +1019,62 @@ func getAllRoles(tx backend.BatchTx) []*authpb.Role {
|
|||
role := &authpb.Role{}
|
||||
err := role.Unmarshal(vs[i])
|
||||
if err != nil {
|
||||
plog.Panicf("failed to unmarshal role struct: %s", err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to unmarshal 'authpb.Role'", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("failed to unmarshal role struct: %s", err)
|
||||
}
|
||||
}
|
||||
roles[i] = role
|
||||
}
|
||||
return roles
|
||||
}
|
||||
|
||||
func putRole(tx backend.BatchTx, role *authpb.Role) {
|
||||
func putRole(lg *zap.Logger, tx backend.BatchTx, role *authpb.Role) {
|
||||
b, err := role.Marshal()
|
||||
if err != nil {
|
||||
plog.Panicf("failed to marshal role struct (name: %s): %s", role.Name, err)
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"failed to marshal 'authpb.Role'",
|
||||
zap.String("role-name", string(role.Name)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("failed to marshal role struct (name: %s): %s", role.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
tx.UnsafePut(authRolesBucketName, []byte(role.Name), b)
|
||||
tx.UnsafePut(authRolesBucketName, role.Name, b)
|
||||
}
|
||||
|
||||
func delRole(tx backend.BatchTx, rolename string) {
|
||||
tx.UnsafeDelete(authRolesBucketName, []byte(rolename))
|
||||
}
|
||||
|
||||
func (as *authStore) isAuthEnabled() bool {
|
||||
func (as *authStore) IsAuthEnabled() bool {
|
||||
as.enabledMu.RLock()
|
||||
defer as.enabledMu.RUnlock()
|
||||
return as.enabled
|
||||
}
|
||||
|
||||
func NewAuthStore(be backend.Backend, tp TokenProvider) *authStore {
|
||||
// NewAuthStore creates a new AuthStore.
|
||||
func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider, bcryptCost int) *authStore {
|
||||
if bcryptCost < bcrypt.MinCost || bcryptCost > bcrypt.MaxCost {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"use default bcrypt cost instead of the invalid given cost",
|
||||
zap.Int("min-cost", bcrypt.MinCost),
|
||||
zap.Int("max-cost", bcrypt.MaxCost),
|
||||
zap.Int("default-cost", bcrypt.DefaultCost),
|
||||
zap.Int("given-cost", bcryptCost))
|
||||
} else {
|
||||
plog.Warningf("Use default bcrypt-cost %d instead of the invalid value %d",
|
||||
bcrypt.DefaultCost, bcryptCost)
|
||||
}
|
||||
|
||||
bcryptCost = bcrypt.DefaultCost
|
||||
}
|
||||
|
||||
tx := be.BatchTx()
|
||||
tx.Lock()
|
||||
|
||||
|
|
@ -915,11 +1091,13 @@ func NewAuthStore(be backend.Backend, tp TokenProvider) *authStore {
|
|||
}
|
||||
|
||||
as := &authStore{
|
||||
be: be,
|
||||
revision: getRevision(tx),
|
||||
lg: lg,
|
||||
be: be,
|
||||
enabled: enabled,
|
||||
rangePermCache: make(map[string]*unifiedRangePermissions),
|
||||
tokenProvider: tp,
|
||||
bcryptCost: bcryptCost,
|
||||
}
|
||||
|
||||
if enabled {
|
||||
|
|
@ -950,12 +1128,11 @@ func (as *authStore) commitRevision(tx backend.BatchTx) {
|
|||
}
|
||||
|
||||
func getRevision(tx backend.BatchTx) uint64 {
|
||||
_, vs := tx.UnsafeRange(authBucketName, []byte(revisionKey), nil, 0)
|
||||
_, vs := tx.UnsafeRange(authBucketName, revisionKey, nil, 0)
|
||||
if len(vs) != 1 {
|
||||
// this can happen in the initialization phase
|
||||
return 0
|
||||
}
|
||||
|
||||
return binary.BigEndian.Uint64(vs[0])
|
||||
}
|
||||
|
||||
|
|
@ -967,7 +1144,7 @@ func (as *authStore) Revision() uint64 {
|
|||
return atomic.LoadUint64(&as.revision)
|
||||
}
|
||||
|
||||
func (as *authStore) AuthInfoFromTLS(ctx context.Context) *AuthInfo {
|
||||
func (as *authStore) AuthInfoFromTLS(ctx context.Context) (ai *AuthInfo) {
|
||||
peer, ok := peer.FromContext(ctx)
|
||||
if !ok || peer == nil || peer.AuthInfo == nil {
|
||||
return nil
|
||||
|
|
@ -975,18 +1152,26 @@ func (as *authStore) AuthInfoFromTLS(ctx context.Context) *AuthInfo {
|
|||
|
||||
tlsInfo := peer.AuthInfo.(credentials.TLSInfo)
|
||||
for _, chains := range tlsInfo.State.VerifiedChains {
|
||||
for _, chain := range chains {
|
||||
cn := chain.Subject.CommonName
|
||||
plog.Debugf("found common name %s", cn)
|
||||
|
||||
return &AuthInfo{
|
||||
Username: cn,
|
||||
Revision: as.Revision(),
|
||||
}
|
||||
if len(chains) < 1 {
|
||||
continue
|
||||
}
|
||||
ai = &AuthInfo{
|
||||
Username: chains[0].Subject.CommonName,
|
||||
Revision: as.Revision(),
|
||||
}
|
||||
if as.lg != nil {
|
||||
as.lg.Debug(
|
||||
"found command name",
|
||||
zap.String("common-name", ai.Username),
|
||||
zap.String("user-name", ai.Username),
|
||||
zap.Uint64("revision", ai.Revision),
|
||||
)
|
||||
} else {
|
||||
plog.Debugf("found common name %s", ai.Username)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
return ai
|
||||
}
|
||||
|
||||
func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
|
||||
|
|
@ -996,9 +1181,9 @@ func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
|
|||
}
|
||||
|
||||
//TODO(mitake|hexfusion) review unifying key names
|
||||
ts, ok := md["token"]
|
||||
ts, ok := md[rpctypes.TokenFieldNameGRPC]
|
||||
if !ok {
|
||||
ts, ok = md["authorization"]
|
||||
ts, ok = md[rpctypes.TokenFieldNameSwagger]
|
||||
}
|
||||
if !ok {
|
||||
return nil, nil
|
||||
|
|
@ -1007,7 +1192,11 @@ func (as *authStore) AuthInfoFromCtx(ctx context.Context) (*AuthInfo, error) {
|
|||
token := ts[0]
|
||||
authInfo, uok := as.authInfoFromToken(ctx, token)
|
||||
if !uok {
|
||||
plog.Warningf("invalid auth token: %s", token)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn("invalid auth token", zap.String("token", token))
|
||||
} else {
|
||||
plog.Warningf("invalid auth token: %s", token)
|
||||
}
|
||||
return nil, ErrInvalidAuthToken
|
||||
}
|
||||
|
||||
|
|
@ -1018,7 +1207,7 @@ func (as *authStore) GenTokenPrefix() (string, error) {
|
|||
return as.tokenProvider.genTokenPrefix()
|
||||
}
|
||||
|
||||
func decomposeOpts(optstr string) (string, map[string]string, error) {
|
||||
func decomposeOpts(lg *zap.Logger, optstr string) (string, map[string]string, error) {
|
||||
opts := strings.Split(optstr, ",")
|
||||
tokenType := opts[0]
|
||||
|
||||
|
|
@ -1027,12 +1216,24 @@ func decomposeOpts(optstr string) (string, map[string]string, error) {
|
|||
pair := strings.Split(opts[i], "=")
|
||||
|
||||
if len(pair) != 2 {
|
||||
plog.Errorf("invalid token specific option: %s", optstr)
|
||||
if lg != nil {
|
||||
lg.Warn("invalid token option", zap.String("option", optstr))
|
||||
} else {
|
||||
plog.Errorf("invalid token specific option: %s", optstr)
|
||||
}
|
||||
return "", nil, ErrInvalidAuthOpts
|
||||
}
|
||||
|
||||
if _, ok := typeSpecificOpts[pair[0]]; ok {
|
||||
plog.Errorf("invalid token specific option, duplicated parameters (%s): %s", pair[0], optstr)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"invalid token option",
|
||||
zap.String("option", optstr),
|
||||
zap.String("duplicate-parameter", pair[0]),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("invalid token specific option, duplicated parameters (%s): %s", pair[0], optstr)
|
||||
}
|
||||
return "", nil, ErrInvalidAuthOpts
|
||||
}
|
||||
|
||||
|
|
@ -1043,26 +1244,47 @@ func decomposeOpts(optstr string) (string, map[string]string, error) {
|
|||
|
||||
}
|
||||
|
||||
func NewTokenProvider(tokenOpts string, indexWaiter func(uint64) <-chan struct{}) (TokenProvider, error) {
|
||||
tokenType, typeSpecificOpts, err := decomposeOpts(tokenOpts)
|
||||
// NewTokenProvider creates a new token provider.
|
||||
func NewTokenProvider(
|
||||
lg *zap.Logger,
|
||||
tokenOpts string,
|
||||
indexWaiter func(uint64) <-chan struct{}) (TokenProvider, error) {
|
||||
tokenType, typeSpecificOpts, err := decomposeOpts(lg, tokenOpts)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidAuthOpts
|
||||
}
|
||||
|
||||
switch tokenType {
|
||||
case "simple":
|
||||
plog.Warningf("simple token is not cryptographically signed")
|
||||
return newTokenProviderSimple(indexWaiter), nil
|
||||
if lg != nil {
|
||||
lg.Warn("simple token is not cryptographically signed")
|
||||
} else {
|
||||
plog.Warningf("simple token is not cryptographically signed")
|
||||
}
|
||||
return newTokenProviderSimple(lg, indexWaiter), nil
|
||||
|
||||
case "jwt":
|
||||
return newTokenProviderJWT(typeSpecificOpts)
|
||||
return newTokenProviderJWT(lg, typeSpecificOpts)
|
||||
|
||||
case "":
|
||||
return newTokenProviderNop()
|
||||
|
||||
default:
|
||||
plog.Errorf("unknown token type: %s", tokenType)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"unknown token type",
|
||||
zap.String("type", tokenType),
|
||||
zap.Error(ErrInvalidAuthOpts),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("unknown token type: %s", tokenType)
|
||||
}
|
||||
return nil, ErrInvalidAuthOpts
|
||||
}
|
||||
}
|
||||
|
||||
func (as *authStore) WithRoot(ctx context.Context) context.Context {
|
||||
if !as.isAuthEnabled() {
|
||||
if !as.IsAuthEnabled() {
|
||||
return ctx
|
||||
}
|
||||
|
||||
|
|
@ -1071,7 +1293,14 @@ func (as *authStore) WithRoot(ctx context.Context) context.Context {
|
|||
ctx1 := context.WithValue(ctx, AuthenticateParamIndex{}, uint64(0))
|
||||
prefix, err := ts.genTokenPrefix()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to generate prefix of internally used token")
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"failed to generate prefix of internally used token",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to generate prefix of internally used token")
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
ctxForAssign = context.WithValue(ctx1, AuthenticateParamSimpleTokenPrefix{}, prefix)
|
||||
|
|
@ -1082,12 +1311,19 @@ func (as *authStore) WithRoot(ctx context.Context) context.Context {
|
|||
token, err := as.tokenProvider.assign(ctxForAssign, "root", as.Revision())
|
||||
if err != nil {
|
||||
// this must not happen
|
||||
plog.Errorf("failed to assign token for lease revoking: %s", err)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"failed to assign token for lease revoking",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("failed to assign token for lease revoking: %s", err)
|
||||
}
|
||||
return ctx
|
||||
}
|
||||
|
||||
mdMap := map[string]string{
|
||||
"token": token,
|
||||
rpctypes.TokenFieldNameGRPC: token,
|
||||
}
|
||||
tokenMD := metadata.New(mdMap)
|
||||
|
||||
|
|
@ -1098,11 +1334,19 @@ func (as *authStore) WithRoot(ctx context.Context) context.Context {
|
|||
func (as *authStore) HasRole(user, role string) bool {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
u := getUser(tx, user)
|
||||
u := getUser(as.lg, 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)
|
||||
if as.lg != nil {
|
||||
as.lg.Warn(
|
||||
"'has-role' requested for non-existing user",
|
||||
zap.String("user-name", user),
|
||||
zap.String("role-name", role),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("tried to check user %s has role %s, but user %s doesn't exist", user, role, user)
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -1111,6 +1355,9 @@ func (as *authStore) HasRole(user, role string) bool {
|
|||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (as *authStore) BcryptCost() int {
|
||||
return as.bcryptCost
|
||||
}
|
||||
|
|
@ -29,7 +29,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/internal/version"
|
||||
"github.com/coreos/etcd/version"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -19,8 +19,8 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/auth/authpb"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth/authpb"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
|
@ -100,70 +100,70 @@ type Auth interface {
|
|||
RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error)
|
||||
}
|
||||
|
||||
type auth struct {
|
||||
type authClient struct {
|
||||
remote pb.AuthClient
|
||||
callOpts []grpc.CallOption
|
||||
}
|
||||
|
||||
func NewAuth(c *Client) Auth {
|
||||
api := &auth{remote: RetryAuthClient(c)}
|
||||
api := &authClient{remote: RetryAuthClient(c)}
|
||||
if c != nil {
|
||||
api.callOpts = c.callOpts
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
|
||||
func (auth *authClient) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
|
||||
resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}, auth.callOpts...)
|
||||
return (*AuthEnableResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) {
|
||||
func (auth *authClient) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) {
|
||||
resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, auth.callOpts...)
|
||||
return (*AuthDisableResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
|
||||
func (auth *authClient) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
|
||||
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password}, auth.callOpts...)
|
||||
return (*AuthUserAddResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
|
||||
func (auth *authClient) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
|
||||
resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name}, auth.callOpts...)
|
||||
return (*AuthUserDeleteResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
|
||||
func (auth *authClient) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
|
||||
resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password}, auth.callOpts...)
|
||||
return (*AuthUserChangePasswordResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) {
|
||||
func (auth *authClient) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) {
|
||||
resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}, auth.callOpts...)
|
||||
return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
|
||||
func (auth *authClient) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
|
||||
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, auth.callOpts...)
|
||||
return (*AuthUserGetResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) {
|
||||
func (auth *authClient) UserList(ctx context.Context) (*AuthUserListResponse, error) {
|
||||
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, auth.callOpts...)
|
||||
return (*AuthUserListResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) {
|
||||
func (auth *authClient) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) {
|
||||
resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}, auth.callOpts...)
|
||||
return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
|
||||
func (auth *authClient) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
|
||||
resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name}, auth.callOpts...)
|
||||
return (*AuthRoleAddResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error) {
|
||||
func (auth *authClient) RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error) {
|
||||
perm := &authpb.Permission{
|
||||
Key: []byte(key),
|
||||
RangeEnd: []byte(rangeEnd),
|
||||
|
|
@ -173,22 +173,22 @@ func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, ran
|
|||
return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
|
||||
func (auth *authClient) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
|
||||
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, auth.callOpts...)
|
||||
return (*AuthRoleGetResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
|
||||
func (auth *authClient) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
|
||||
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, auth.callOpts...)
|
||||
return (*AuthRoleListResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) {
|
||||
resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}, auth.callOpts...)
|
||||
func (auth *authClient) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) {
|
||||
resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: []byte(key), RangeEnd: []byte(rangeEnd)}, auth.callOpts...)
|
||||
return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
||||
func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) {
|
||||
func (auth *authClient) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) {
|
||||
resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}, auth.callOpts...)
|
||||
return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2018 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 balancer implements client balancer.
|
||||
package balancer
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
// Copyright 2017 The etcd Authors
|
||||
// Copyright 2018 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.
|
||||
|
|
@ -12,11 +12,12 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package clientv3
|
||||
package balancer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
|
|
@ -24,10 +25,14 @@ import (
|
|||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
healthpb "google.golang.org/grpc/health/grpc_health_v1"
|
||||
"google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// TODO: replace with something better
|
||||
var lg = grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard)
|
||||
|
||||
const (
|
||||
minHealthRetryDuration = 3 * time.Second
|
||||
unknownService = "unknown service grpc.health.v1.Health"
|
||||
|
|
@ -38,18 +43,16 @@ const (
|
|||
// 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
|
||||
type NotifyMsg int
|
||||
|
||||
const (
|
||||
notifyReset notifyMsg = iota
|
||||
notifyNext
|
||||
NotifyReset NotifyMsg = iota
|
||||
NotifyNext
|
||||
)
|
||||
|
||||
// healthBalancer does the bare minimum to expose multiple eps
|
||||
// GRPC17Health does the bare minimum to expose multiple eps
|
||||
// to the grpc reconnection code path
|
||||
type healthBalancer struct {
|
||||
type GRPC17Health struct {
|
||||
// addrs are the client's endpoint addresses for grpc
|
||||
addrs []grpc.Address
|
||||
|
||||
|
|
@ -64,7 +67,7 @@ type healthBalancer struct {
|
|||
readyOnce sync.Once
|
||||
|
||||
// healthCheck checks an endpoint's health.
|
||||
healthCheck healthCheckFunc
|
||||
healthCheck func(ep string) (bool, error)
|
||||
healthCheckTimeout time.Duration
|
||||
|
||||
unhealthyMu sync.RWMutex
|
||||
|
|
@ -88,7 +91,7 @@ type healthBalancer struct {
|
|||
donec chan struct{}
|
||||
|
||||
// updateAddrsC notifies updateNotifyLoop to update addrs.
|
||||
updateAddrsC chan notifyMsg
|
||||
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,
|
||||
|
|
@ -102,21 +105,29 @@ type healthBalancer struct {
|
|||
closed bool
|
||||
}
|
||||
|
||||
func newHealthBalancer(eps []string, timeout time.Duration, hc healthCheckFunc) *healthBalancer {
|
||||
// DialFunc defines gRPC dial function.
|
||||
type DialFunc func(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error)
|
||||
|
||||
// NewGRPC17Health returns a new health balancer with gRPC v1.7.
|
||||
func NewGRPC17Health(
|
||||
eps []string,
|
||||
timeout time.Duration,
|
||||
dialFunc DialFunc,
|
||||
) *GRPC17Health {
|
||||
notifyCh := make(chan []grpc.Address)
|
||||
addrs := eps2addrs(eps)
|
||||
hb := &healthBalancer{
|
||||
hb := &GRPC17Health{
|
||||
addrs: addrs,
|
||||
eps: eps,
|
||||
notifyCh: notifyCh,
|
||||
readyc: make(chan struct{}),
|
||||
healthCheck: hc,
|
||||
healthCheck: func(ep string) (bool, error) { return grpcHealthCheck(ep, dialFunc) },
|
||||
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),
|
||||
updateAddrsC: make(chan NotifyMsg),
|
||||
hostPort2ep: getHostPort2ep(eps),
|
||||
}
|
||||
if timeout < minHealthRetryDuration {
|
||||
|
|
@ -134,78 +145,81 @@ func newHealthBalancer(eps []string, timeout time.Duration, hc healthCheckFunc)
|
|||
return hb
|
||||
}
|
||||
|
||||
func (b *healthBalancer) Start(target string, config grpc.BalancerConfig) error { return nil }
|
||||
func (b *GRPC17Health) Start(target string, config grpc.BalancerConfig) error { return nil }
|
||||
|
||||
func (b *healthBalancer) ConnectNotify() <-chan struct{} {
|
||||
func (b *GRPC17Health) ConnectNotify() <-chan struct{} {
|
||||
b.mu.Lock()
|
||||
defer b.mu.Unlock()
|
||||
return b.upc
|
||||
}
|
||||
|
||||
func (b *healthBalancer) ready() <-chan struct{} { return b.readyc }
|
||||
func (b *GRPC17Health) UpdateAddrsC() chan NotifyMsg { return b.updateAddrsC }
|
||||
func (b *GRPC17Health) StopC() chan struct{} { return b.stopc }
|
||||
|
||||
func (b *healthBalancer) endpoint(hostPort string) string {
|
||||
func (b *GRPC17Health) Ready() <-chan struct{} { return b.readyc }
|
||||
|
||||
func (b *GRPC17Health) Endpoint(hostPort string) string {
|
||||
b.mu.RLock()
|
||||
defer b.mu.RUnlock()
|
||||
return b.hostPort2ep[hostPort]
|
||||
}
|
||||
|
||||
func (b *healthBalancer) pinned() string {
|
||||
func (b *GRPC17Health) 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())
|
||||
func (b *GRPC17Health) HostPortError(hostPort string, err error) {
|
||||
if b.Endpoint(hostPort) == "" {
|
||||
lg.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())
|
||||
lg.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)
|
||||
func (b *GRPC17Health) removeUnhealthy(hostPort, msg string) {
|
||||
if b.Endpoint(hostPort) == "" {
|
||||
lg.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)
|
||||
lg.Infof("clientv3/balancer: %q is removed from unhealthy (%q)", hostPort, msg)
|
||||
}
|
||||
|
||||
func (b *healthBalancer) countUnhealthy() (count int) {
|
||||
func (b *GRPC17Health) countUnhealthy() (count int) {
|
||||
b.unhealthyMu.RLock()
|
||||
count = len(b.unhealthyHostPorts)
|
||||
b.unhealthyMu.RUnlock()
|
||||
return count
|
||||
}
|
||||
|
||||
func (b *healthBalancer) isUnhealthy(hostPort string) (unhealthy bool) {
|
||||
func (b *GRPC17Health) isUnhealthy(hostPort string) (unhealthy bool) {
|
||||
b.unhealthyMu.RLock()
|
||||
_, unhealthy = b.unhealthyHostPorts[hostPort]
|
||||
b.unhealthyMu.RUnlock()
|
||||
return unhealthy
|
||||
}
|
||||
|
||||
func (b *healthBalancer) cleanupUnhealthy() {
|
||||
func (b *GRPC17Health) 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)
|
||||
lg.Infof("clientv3/balancer: removed %q from unhealthy after %v", k, b.healthCheckTimeout)
|
||||
}
|
||||
}
|
||||
b.unhealthyMu.Unlock()
|
||||
}
|
||||
|
||||
func (b *healthBalancer) liveAddrs() ([]grpc.Address, map[string]struct{}) {
|
||||
func (b *GRPC17Health) liveAddrs() ([]grpc.Address, map[string]struct{}) {
|
||||
unhealthyCnt := b.countUnhealthy()
|
||||
|
||||
b.mu.RLock()
|
||||
|
|
@ -231,15 +245,15 @@ func (b *healthBalancer) liveAddrs() ([]grpc.Address, map[string]struct{}) {
|
|||
return addrs, liveHostPorts
|
||||
}
|
||||
|
||||
func (b *healthBalancer) updateUnhealthy() {
|
||||
func (b *GRPC17Health) updateUnhealthy() {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(b.healthCheckTimeout):
|
||||
b.cleanupUnhealthy()
|
||||
pinned := b.pinned()
|
||||
pinned := b.Pinned()
|
||||
if pinned == "" || b.isUnhealthy(pinned) {
|
||||
select {
|
||||
case b.updateAddrsC <- notifyNext:
|
||||
case b.updateAddrsC <- NotifyNext:
|
||||
case <-b.stopc:
|
||||
return
|
||||
}
|
||||
|
|
@ -250,7 +264,19 @@ func (b *healthBalancer) updateUnhealthy() {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *healthBalancer) updateAddrs(eps ...string) {
|
||||
// NeedUpdate returns true if all connections are down or
|
||||
// addresses do not include current pinned address.
|
||||
func (b *GRPC17Health) NeedUpdate() bool {
|
||||
// updating notifyCh can trigger new connections,
|
||||
// need update addrs if all connections are down
|
||||
// or addrs does not include pinAddr.
|
||||
b.mu.RLock()
|
||||
update := !hasAddr(b.addrs, b.pinAddr)
|
||||
b.mu.RUnlock()
|
||||
return update
|
||||
}
|
||||
|
||||
func (b *GRPC17Health) UpdateAddrs(eps ...string) {
|
||||
np := getHostPort2ep(eps)
|
||||
|
||||
b.mu.Lock()
|
||||
|
|
@ -278,12 +304,12 @@ func (b *healthBalancer) updateAddrs(eps ...string) {
|
|||
b.unhealthyMu.Unlock()
|
||||
}
|
||||
|
||||
func (b *healthBalancer) next() {
|
||||
func (b *GRPC17Health) Next() {
|
||||
b.mu.RLock()
|
||||
downc := b.downc
|
||||
b.mu.RUnlock()
|
||||
select {
|
||||
case b.updateAddrsC <- notifyNext:
|
||||
case b.updateAddrsC <- NotifyNext:
|
||||
case <-b.stopc:
|
||||
}
|
||||
// wait until disconnect so new RPCs are not issued on old connection
|
||||
|
|
@ -293,7 +319,7 @@ func (b *healthBalancer) next() {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *healthBalancer) updateNotifyLoop() {
|
||||
func (b *GRPC17Health) updateNotifyLoop() {
|
||||
defer close(b.donec)
|
||||
|
||||
for {
|
||||
|
|
@ -320,7 +346,7 @@ func (b *healthBalancer) updateNotifyLoop() {
|
|||
default:
|
||||
}
|
||||
case downc == nil:
|
||||
b.notifyAddrs(notifyReset)
|
||||
b.notifyAddrs(NotifyReset)
|
||||
select {
|
||||
case <-upc:
|
||||
case msg := <-b.updateAddrsC:
|
||||
|
|
@ -338,7 +364,7 @@ func (b *healthBalancer) updateNotifyLoop() {
|
|||
}
|
||||
select {
|
||||
case <-downc:
|
||||
b.notifyAddrs(notifyReset)
|
||||
b.notifyAddrs(NotifyReset)
|
||||
case msg := <-b.updateAddrsC:
|
||||
b.notifyAddrs(msg)
|
||||
case <-b.stopc:
|
||||
|
|
@ -348,8 +374,8 @@ func (b *healthBalancer) updateNotifyLoop() {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *healthBalancer) notifyAddrs(msg notifyMsg) {
|
||||
if msg == notifyNext {
|
||||
func (b *GRPC17Health) notifyAddrs(msg NotifyMsg) {
|
||||
if msg == NotifyNext {
|
||||
select {
|
||||
case b.notifyCh <- []grpc.Address{}:
|
||||
case <-b.stopc:
|
||||
|
|
@ -380,7 +406,7 @@ func (b *healthBalancer) notifyAddrs(msg notifyMsg) {
|
|||
}
|
||||
}
|
||||
|
||||
func (b *healthBalancer) Up(addr grpc.Address) func(error) {
|
||||
func (b *GRPC17Health) Up(addr grpc.Address) func(error) {
|
||||
if !b.mayPin(addr) {
|
||||
return func(err error) {}
|
||||
}
|
||||
|
|
@ -402,7 +428,7 @@ func (b *healthBalancer) Up(addr grpc.Address) func(error) {
|
|||
}
|
||||
|
||||
if b.pinAddr != "" {
|
||||
logger.Lvl(4).Infof("clientv3/balancer: %q is up but not pinned (already pinned %q)", addr.Addr, b.pinAddr)
|
||||
lg.Infof("clientv3/balancer: %q is up but not pinned (already pinned %q)", addr.Addr, b.pinAddr)
|
||||
return func(err error) {}
|
||||
}
|
||||
|
||||
|
|
@ -410,7 +436,7 @@ func (b *healthBalancer) Up(addr grpc.Address) func(error) {
|
|||
close(b.upc)
|
||||
b.downc = make(chan struct{})
|
||||
b.pinAddr = addr.Addr
|
||||
logger.Lvl(4).Infof("clientv3/balancer: pin %q", addr.Addr)
|
||||
lg.Infof("clientv3/balancer: pin %q", addr.Addr)
|
||||
|
||||
// notify client that a connection is up
|
||||
b.readyOnce.Do(func() { close(b.readyc) })
|
||||
|
|
@ -420,19 +446,19 @@ func (b *healthBalancer) Up(addr grpc.Address) func(error) {
|
|||
// 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.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())
|
||||
lg.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
|
||||
func (b *GRPC17Health) mayPin(addr grpc.Address) bool {
|
||||
if b.Endpoint(addr.Addr) == "" { // stale host:port
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -454,7 +480,7 @@ func (b *healthBalancer) mayPin(addr grpc.Address) bool {
|
|||
// 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)
|
||||
lg.Infof("clientv3/balancer: %q is up but not pinned (failed %v ago, require minimum %v after failure)", addr.Addr, elapsed, b.healthCheckTimeout)
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -463,11 +489,11 @@ func (b *healthBalancer) mayPin(addr grpc.Address) bool {
|
|||
return true
|
||||
}
|
||||
|
||||
b.hostPortError(addr.Addr, errors.New("health check failed"))
|
||||
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) {
|
||||
func (b *GRPC17Health) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
|
||||
var (
|
||||
addr string
|
||||
closed bool
|
||||
|
|
@ -515,9 +541,9 @@ func (b *healthBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions)
|
|||
return grpc.Address{Addr: addr}, func() {}, nil
|
||||
}
|
||||
|
||||
func (b *healthBalancer) Notify() <-chan []grpc.Address { return b.notifyCh }
|
||||
func (b *GRPC17Health) Notify() <-chan []grpc.Address { return b.notifyCh }
|
||||
|
||||
func (b *healthBalancer) Close() error {
|
||||
func (b *GRPC17Health) 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.
|
||||
|
|
@ -553,8 +579,8 @@ func (b *healthBalancer) Close() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func grpcHealthCheck(client *Client, ep string) (bool, error) {
|
||||
conn, err := client.dial(ep)
|
||||
func grpcHealthCheck(ep string, dialFunc func(ep string, dopts ...grpc.DialOption) (*grpc.ClientConn, error)) (bool, error) {
|
||||
conn, err := dialFunc(ep)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -607,3 +633,25 @@ func getHostPort2ep(eps []string) map[string]string {
|
|||
}
|
||||
return hm
|
||||
}
|
||||
|
||||
func parseEndpoint(endpoint string) (proto string, host string, scheme string) {
|
||||
proto = "tcp"
|
||||
host = endpoint
|
||||
url, uerr := url.Parse(endpoint)
|
||||
if uerr != nil || !strings.Contains(endpoint, "://") {
|
||||
return proto, host, scheme
|
||||
}
|
||||
scheme = url.Scheme
|
||||
|
||||
// strip scheme:// prefix since grpc dials by host
|
||||
host = url.Host
|
||||
switch url.Scheme {
|
||||
case "http", "https":
|
||||
case "unix", "unixs":
|
||||
proto = "unix"
|
||||
host = url.Host + url.Path
|
||||
default:
|
||||
proto, host = "", ""
|
||||
}
|
||||
return proto, host, scheme
|
||||
}
|
||||
|
|
@ -26,6 +26,7 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/clientv3/balancer"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
|
|
@ -55,7 +56,7 @@ type Client struct {
|
|||
|
||||
cfg Config
|
||||
creds *credentials.TransportCredentials
|
||||
balancer *healthBalancer
|
||||
balancer *balancer.GRPC17Health
|
||||
mu *sync.Mutex
|
||||
|
||||
ctx context.Context
|
||||
|
|
@ -93,6 +94,11 @@ func NewFromURL(url string) (*Client, error) {
|
|||
return New(Config{Endpoints: []string{url}})
|
||||
}
|
||||
|
||||
// NewFromURLs creates a new etcdv3 client from URLs.
|
||||
func NewFromURLs(urls []string) (*Client, error) {
|
||||
return New(Config{Endpoints: urls})
|
||||
}
|
||||
|
||||
// Close shuts down the client's etcd connections.
|
||||
func (c *Client) Close() error {
|
||||
c.cancel()
|
||||
|
|
@ -122,18 +128,12 @@ func (c *Client) SetEndpoints(eps ...string) {
|
|||
c.mu.Lock()
|
||||
c.cfg.Endpoints = eps
|
||||
c.mu.Unlock()
|
||||
c.balancer.updateAddrs(eps...)
|
||||
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 {
|
||||
if c.balancer.NeedUpdate() {
|
||||
select {
|
||||
case c.balancer.updateAddrsC <- notifyNext:
|
||||
case <-c.balancer.stopc:
|
||||
case c.balancer.UpdateAddrsC() <- balancer.NotifyNext:
|
||||
case <-c.balancer.StopC():
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -166,7 +166,7 @@ func (c *Client) autoSync() {
|
|||
err := c.Sync(ctx)
|
||||
cancel()
|
||||
if err != nil && err != c.ctx.Err() {
|
||||
logger.Println("Auto sync endpoints failed:", err)
|
||||
lg.Lvl(4).Infof("Auto sync endpoints failed: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -185,7 +185,7 @@ func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...str
|
|||
cred.tokenMu.RLock()
|
||||
defer cred.tokenMu.RUnlock()
|
||||
return map[string]string{
|
||||
"token": cred.token,
|
||||
rpctypes.TokenFieldNameGRPC: cred.token,
|
||||
}, nil
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +245,7 @@ func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts
|
|||
opts = append(opts, dopts...)
|
||||
|
||||
f := func(host string, t time.Duration) (net.Conn, error) {
|
||||
proto, host, _ := parseEndpoint(c.balancer.endpoint(host))
|
||||
proto, host, _ := parseEndpoint(c.balancer.Endpoint(host))
|
||||
if host == "" && endpoint != "" {
|
||||
// dialing an endpoint not in the balancer; use
|
||||
// endpoint passed into dial
|
||||
|
|
@ -412,9 +412,7 @@ func newClient(cfg *Config) (*Client, error) {
|
|||
client.callOpts = callOpts
|
||||
}
|
||||
|
||||
client.balancer = newHealthBalancer(cfg.Endpoints, cfg.DialTimeout, func(ep string) (bool, error) {
|
||||
return grpcHealthCheck(client, ep)
|
||||
})
|
||||
client.balancer = balancer.NewGRPC17Health(cfg.Endpoints, cfg.DialTimeout, client.dial)
|
||||
|
||||
// use Endpoints[0] so that for https:// without any tls config given, then
|
||||
// grpc will assume the certificate server name is the endpoint host.
|
||||
|
|
@ -431,7 +429,7 @@ func newClient(cfg *Config) (*Client, error) {
|
|||
hasConn := false
|
||||
waitc := time.After(cfg.DialTimeout)
|
||||
select {
|
||||
case <-client.balancer.ready():
|
||||
case <-client.balancer.Ready():
|
||||
hasConn = true
|
||||
case <-ctx.Done():
|
||||
case <-waitc:
|
||||
|
|
@ -561,3 +559,11 @@ func canceledByCaller(stopCtx context.Context, err error) bool {
|
|||
|
||||
return err == context.Canceled || err == context.DeadlineExceeded
|
||||
}
|
||||
|
||||
func getHost(ep string) string {
|
||||
url, uerr := url.Parse(ep)
|
||||
if uerr != nil || !strings.Contains(ep, "://") {
|
||||
return ep
|
||||
}
|
||||
return url.Host
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,28 +18,14 @@ import (
|
|||
"io/ioutil"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/pkg/logutil"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// Logger is the logger used by client library.
|
||||
// It implements grpclog.LoggerV2 interface.
|
||||
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 (
|
||||
loggerMu sync.RWMutex
|
||||
logger Logger
|
||||
lgMu sync.RWMutex
|
||||
lg logutil.Logger
|
||||
)
|
||||
|
||||
type settableLogger struct {
|
||||
|
|
@ -49,29 +35,29 @@ type settableLogger struct {
|
|||
|
||||
func init() {
|
||||
// disable client side logs by default
|
||||
logger = &settableLogger{}
|
||||
lg = &settableLogger{}
|
||||
SetLogger(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
|
||||
}
|
||||
|
||||
// SetLogger sets client-side Logger.
|
||||
func SetLogger(l grpclog.LoggerV2) {
|
||||
loggerMu.Lock()
|
||||
logger = NewLogger(l)
|
||||
lgMu.Lock()
|
||||
lg = logutil.NewLogger(l)
|
||||
// override grpclog so that any changes happen with locking
|
||||
grpclog.SetLoggerV2(logger)
|
||||
loggerMu.Unlock()
|
||||
grpclog.SetLoggerV2(lg)
|
||||
lgMu.Unlock()
|
||||
}
|
||||
|
||||
// GetLogger returns the current logger.
|
||||
func GetLogger() Logger {
|
||||
loggerMu.RLock()
|
||||
l := logger
|
||||
loggerMu.RUnlock()
|
||||
// GetLogger returns the current logutil.Logger.
|
||||
func GetLogger() logutil.Logger {
|
||||
lgMu.RLock()
|
||||
l := lg
|
||||
lgMu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// NewLogger returns a new Logger with grpclog.LoggerV2.
|
||||
func NewLogger(gl grpclog.LoggerV2) Logger {
|
||||
// NewLogger returns a new Logger with logutil.Logger.
|
||||
func NewLogger(gl grpclog.LoggerV2) logutil.Logger {
|
||||
return &settableLogger{l: gl}
|
||||
}
|
||||
|
||||
|
|
@ -104,32 +90,12 @@ func (s *settableLogger) Print(args ...interface{}) { s.get().In
|
|||
func (s *settableLogger) Printf(format string, args ...interface{}) { s.get().Infof(format, 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 {
|
||||
func (s *settableLogger) Lvl(lvl int) grpclog.LoggerV2 {
|
||||
s.mu.RLock()
|
||||
l := s.l
|
||||
s.mu.RUnlock()
|
||||
if l.V(lvl) {
|
||||
return s
|
||||
}
|
||||
return &noLogger{}
|
||||
return logutil.NewDiscardLogger()
|
||||
}
|
||||
|
||||
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 }
|
||||
|
|
|
|||
|
|
@ -44,3 +44,6 @@ var (
|
|||
// Some options are exposed to "clientv3.Config".
|
||||
// Defaults will be overridden by the settings in "clientv3.Config".
|
||||
var defaultCallOpts = []grpc.CallOption{defaultFailFast, defaultMaxCallSendMsgSize, defaultMaxCallRecvMsgSize}
|
||||
|
||||
// MaxLeaseTTL is the maximum lease TTL value
|
||||
const MaxLeaseTTL = 9000000000
|
||||
|
|
@ -91,18 +91,18 @@ func (c *Client) newRetryWrapper() retryRPCFunc {
|
|||
if err := readyWait(rpcCtx, c.ctx, c.balancer.ConnectNotify()); err != nil {
|
||||
return err
|
||||
}
|
||||
pinned := c.balancer.pinned()
|
||||
pinned := c.balancer.Pinned()
|
||||
err := f(rpcCtx)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logger.Lvl(4).Infof("clientv3/retry: error %q on pinned endpoint %q", err.Error(), pinned)
|
||||
lg.Lvl(4).Infof("clientv3/retry: error %q on pinned endpoint %q", err.Error(), pinned)
|
||||
|
||||
if s, ok := status.FromError(err); ok && (s.Code() == codes.Unavailable || s.Code() == codes.DeadlineExceeded || s.Code() == codes.Internal) {
|
||||
// mark this before endpoint switch is triggered
|
||||
c.balancer.hostPortError(pinned, err)
|
||||
c.balancer.next()
|
||||
logger.Lvl(4).Infof("clientv3/retry: switching from %q due to error %q", pinned, err.Error())
|
||||
c.balancer.HostPortError(pinned, err)
|
||||
c.balancer.Next()
|
||||
lg.Lvl(4).Infof("clientv3/retry: switching from %q due to error %q", pinned, err.Error())
|
||||
}
|
||||
|
||||
if isStop(err) {
|
||||
|
|
@ -115,17 +115,17 @@ func (c *Client) newRetryWrapper() retryRPCFunc {
|
|||
func (c *Client) newAuthRetryWrapper(retryf retryRPCFunc) retryRPCFunc {
|
||||
return func(rpcCtx context.Context, f rpcFunc, rp retryPolicy) error {
|
||||
for {
|
||||
pinned := c.balancer.pinned()
|
||||
pinned := c.balancer.Pinned()
|
||||
err := retryf(rpcCtx, f, rp)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
logger.Lvl(4).Infof("clientv3/auth-retry: error %q on pinned endpoint %q", err.Error(), pinned)
|
||||
lg.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
|
||||
if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken {
|
||||
gterr := c.getToken(rpcCtx)
|
||||
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)
|
||||
lg.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
|
||||
}
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
|
||||
v3rpc "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
mvccpb "github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ package api
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/internal/version"
|
||||
"github.com/coreos/etcd/version"
|
||||
"go.uber.org/zap"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
|
@ -56,7 +57,7 @@ func init() {
|
|||
}
|
||||
|
||||
// UpdateCapability updates the enabledMap when the cluster version increases.
|
||||
func UpdateCapability(v *semver.Version) {
|
||||
func UpdateCapability(lg *zap.Logger, v *semver.Version) {
|
||||
if v == nil {
|
||||
// if recovered but version was never set by cluster
|
||||
return
|
||||
|
|
@ -69,7 +70,15 @@ func UpdateCapability(v *semver.Version) {
|
|||
curVersion = v
|
||||
enabledMap = capabilityMaps[curVersion.String()]
|
||||
enableMapMu.Unlock()
|
||||
plog.Infof("enabled capabilities for version %s", version.Cluster(v.String()))
|
||||
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"enabled capabilities for version",
|
||||
zap.String("cluster-version", version.Cluster(v.String())),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("enabled capabilities for version %s", version.Cluster(v.String()))
|
||||
}
|
||||
}
|
||||
|
||||
func IsCapabilityEnabled(c Capability) bool {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
type header struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
sg etcdserver.RaftStatusGetter
|
||||
rev func() int64
|
||||
}
|
||||
|
||||
|
|
@ -30,7 +30,7 @@ func newHeader(s *etcdserver.EtcdServer) header {
|
|||
return header{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
sg: s,
|
||||
rev: func() int64 { return s.KV().Rev() },
|
||||
}
|
||||
}
|
||||
|
|
@ -42,7 +42,7 @@ func (h *header) fill(rh *pb.ResponseHeader) {
|
|||
}
|
||||
rh.ClusterId = uint64(h.clusterID)
|
||||
rh.MemberId = uint64(h.memberID)
|
||||
rh.RaftTerm = h.raftTimer.Term()
|
||||
rh.RaftTerm = h.sg.Term()
|
||||
if rh.Revision == 0 {
|
||||
rh.Revision = h.rev()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,16 +21,19 @@ import (
|
|||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/lease"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type LeaseServer struct {
|
||||
lg *zap.Logger
|
||||
hdr header
|
||||
le etcdserver.Lessor
|
||||
}
|
||||
|
||||
func NewLeaseServer(s *etcdserver.EtcdServer) pb.LeaseServer {
|
||||
return &LeaseServer{le: s, hdr: newHeader(s)}
|
||||
return &LeaseServer{lg: s.Cfg.Logger, le: s, hdr: newHeader(s)}
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
|
|
@ -108,9 +111,17 @@ func (ls *LeaseServer) leaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro
|
|||
}
|
||||
if err != nil {
|
||||
if isClientCtxErr(stream.Context().Err(), err) {
|
||||
plog.Debugf("failed to receive lease keepalive request from gRPC stream (%q)", err.Error())
|
||||
if ls.lg != nil {
|
||||
ls.lg.Debug("failed to receive lease keepalive request from gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
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())
|
||||
if ls.lg != nil {
|
||||
ls.lg.Warn("failed to receive lease keepalive request from gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to receive lease keepalive request from gRPC stream (%q)", err.Error())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
@ -138,9 +149,17 @@ func (ls *LeaseServer) leaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) erro
|
|||
err = stream.Send(resp)
|
||||
if err != nil {
|
||||
if isClientCtxErr(stream.Context().Err(), err) {
|
||||
plog.Debugf("failed to send lease keepalive response to gRPC stream (%q)", err.Error())
|
||||
if ls.lg != nil {
|
||||
ls.lg.Debug("failed to send lease keepalive response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
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())
|
||||
if ls.lg != nil {
|
||||
ls.lg.Warn("failed to send lease keepalive response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to send lease keepalive response to gRPC stream (%q)", err.Error())
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,15 +19,16 @@ import (
|
|||
"crypto/sha256"
|
||||
"io"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/version"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/raft"
|
||||
"github.com/coreos/etcd/version"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type KVGetter interface {
|
||||
|
|
@ -49,19 +50,14 @@ type LeaderTransferrer interface {
|
|||
MoveLeader(ctx context.Context, lead, target uint64) error
|
||||
}
|
||||
|
||||
type RaftStatusGetter interface {
|
||||
etcdserver.RaftTimer
|
||||
ID() types.ID
|
||||
Leader() types.ID
|
||||
}
|
||||
|
||||
type AuthGetter interface {
|
||||
AuthInfoFromCtx(ctx context.Context) (*auth.AuthInfo, error)
|
||||
AuthStore() auth.AuthStore
|
||||
}
|
||||
|
||||
type maintenanceServer struct {
|
||||
rg RaftStatusGetter
|
||||
lg *zap.Logger
|
||||
rg etcdserver.RaftStatusGetter
|
||||
kg KVGetter
|
||||
bg BackendGetter
|
||||
a Alarmer
|
||||
|
|
@ -70,18 +66,30 @@ type maintenanceServer struct {
|
|||
}
|
||||
|
||||
func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
|
||||
srv := &maintenanceServer{rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s)}
|
||||
srv := &maintenanceServer{lg: s.Cfg.Logger, rg: s, kg: s, bg: s, a: s, lt: s, hdr: newHeader(s)}
|
||||
return &authMaintenanceServer{srv, s}
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
|
||||
plog.Noticef("starting to defragment the storage backend...")
|
||||
if ms.lg != nil {
|
||||
ms.lg.Info("starting defragment")
|
||||
} else {
|
||||
plog.Noticef("starting to defragment the storage backend...")
|
||||
}
|
||||
err := ms.bg.Backend().Defrag()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to defragment the storage backend (%v)", err)
|
||||
if ms.lg != nil {
|
||||
ms.lg.Warn("failed to defragment", zap.Error(err))
|
||||
} else {
|
||||
plog.Errorf("failed to defragment the storage backend (%v)", err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
plog.Noticef("finished defragmenting the storage backend")
|
||||
if ms.lg != nil {
|
||||
ms.lg.Info("finished defragment")
|
||||
} else {
|
||||
plog.Noticef("finished defragmenting the storage backend")
|
||||
}
|
||||
return &pb.DefragmentResponse{}, nil
|
||||
}
|
||||
|
||||
|
|
@ -94,7 +102,11 @@ func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance
|
|||
go func() {
|
||||
snap.WriteTo(pw)
|
||||
if err := snap.Close(); err != nil {
|
||||
plog.Errorf("error closing snapshot (%v)", err)
|
||||
if ms.lg != nil {
|
||||
ms.lg.Warn("failed to close snapshot", zap.Error(err))
|
||||
} else {
|
||||
plog.Errorf("error closing snapshot (%v)", err)
|
||||
}
|
||||
}
|
||||
pw.Close()
|
||||
}()
|
||||
|
|
@ -156,25 +168,24 @@ func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*p
|
|||
}
|
||||
|
||||
func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
|
||||
hdr := &pb.ResponseHeader{}
|
||||
ms.hdr.fill(hdr)
|
||||
resp := &pb.StatusResponse{
|
||||
Header: &pb.ResponseHeader{Revision: ms.hdr.rev()},
|
||||
Header: hdr,
|
||||
Version: version.Version,
|
||||
DbSize: ms.bg.Backend().Size(),
|
||||
Leader: uint64(ms.rg.Leader()),
|
||||
RaftIndex: ms.rg.Index(),
|
||||
RaftTerm: ms.rg.Term(),
|
||||
RaftIndex: ms.rg.CommittedIndex(),
|
||||
RaftAppliedIndex: ms.rg.AppliedIndex(),
|
||||
RaftTerm: ms.rg.Term(),
|
||||
DbSize: ms.bg.Backend().Size(),
|
||||
DbSizeInUse: ms.bg.Backend().SizeInUse(),
|
||||
}
|
||||
if uint64(ms.rg.Leader()) == raft.None {
|
||||
if resp.Leader == raft.None {
|
||||
resp.Errors = append(resp.Errors, etcdserver.ErrNoLeader.Error())
|
||||
}
|
||||
alarms := ms.a.Alarms()
|
||||
if len(alarms) > 0 {
|
||||
for _, alarm := range alarms {
|
||||
resp.Errors = append(resp.Errors, alarm.String())
|
||||
}
|
||||
for _, a := range ms.a.Alarms() {
|
||||
resp.Errors = append(resp.Errors, a.String())
|
||||
}
|
||||
ms.hdr.fill(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ func (qa *quotaAlarmer) check(ctx context.Context, r interface{}) error {
|
|||
func NewQuotaKVServer(s *etcdserver.EtcdServer) pb.KVServer {
|
||||
return "aKVServer{
|
||||
NewKVServer(s),
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s), s, s.ID()},
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s, "kv"), s, s.ID()},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -85,6 +85,6 @@ func (s *quotaLeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequ
|
|||
func NewQuotaLeaseServer(s *etcdserver.EtcdServer) pb.LeaseServer {
|
||||
return "aLeaseServer{
|
||||
NewLeaseServer(s),
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s), s, s.ID()},
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s, "lease"), s, s.ID()},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,8 +31,9 @@ var (
|
|||
ErrGRPCFutureRev = status.New(codes.OutOfRange, "etcdserver: mvcc: required revision is a future revision").Err()
|
||||
ErrGRPCNoSpace = status.New(codes.ResourceExhausted, "etcdserver: mvcc: database space exceeded").Err()
|
||||
|
||||
ErrGRPCLeaseNotFound = status.New(codes.NotFound, "etcdserver: requested lease not found").Err()
|
||||
ErrGRPCLeaseExist = status.New(codes.FailedPrecondition, "etcdserver: lease already exists").Err()
|
||||
ErrGRPCLeaseNotFound = status.New(codes.NotFound, "etcdserver: requested lease not found").Err()
|
||||
ErrGRPCLeaseExist = status.New(codes.FailedPrecondition, "etcdserver: lease already exists").Err()
|
||||
ErrGRPCLeaseTTLTooLarge = status.New(codes.OutOfRange, "etcdserver: too large lease TTL").Err()
|
||||
|
||||
ErrGRPCMemberExist = status.New(codes.FailedPrecondition, "etcdserver: member ID already exist").Err()
|
||||
ErrGRPCPeerURLExist = status.New(codes.FailedPrecondition, "etcdserver: Peer URLs already exists").Err()
|
||||
|
|
@ -80,8 +81,9 @@ var (
|
|||
ErrorDesc(ErrGRPCFutureRev): ErrGRPCFutureRev,
|
||||
ErrorDesc(ErrGRPCNoSpace): ErrGRPCNoSpace,
|
||||
|
||||
ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound,
|
||||
ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist,
|
||||
ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound,
|
||||
ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist,
|
||||
ErrorDesc(ErrGRPCLeaseTTLTooLarge): ErrGRPCLeaseTTLTooLarge,
|
||||
|
||||
ErrorDesc(ErrGRPCMemberExist): ErrGRPCMemberExist,
|
||||
ErrorDesc(ErrGRPCPeerURLExist): ErrGRPCPeerURLExist,
|
||||
|
|
@ -131,8 +133,9 @@ var (
|
|||
ErrFutureRev = Error(ErrGRPCFutureRev)
|
||||
ErrNoSpace = Error(ErrGRPCNoSpace)
|
||||
|
||||
ErrLeaseNotFound = Error(ErrGRPCLeaseNotFound)
|
||||
ErrLeaseExist = Error(ErrGRPCLeaseExist)
|
||||
ErrLeaseNotFound = Error(ErrGRPCLeaseNotFound)
|
||||
ErrLeaseExist = Error(ErrGRPCLeaseExist)
|
||||
ErrLeaseTTLTooLarge = Error(ErrGRPCLeaseTTLTooLarge)
|
||||
|
||||
ErrMemberExist = Error(ErrGRPCMemberExist)
|
||||
ErrPeerURLExist = Error(ErrGRPCPeerURLExist)
|
||||
|
|
|
|||
20
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/metadatafields.go
generated
vendored
Normal file
20
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/metadatafields.go
generated
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
// Copyright 2018 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpctypes
|
||||
|
||||
var (
|
||||
TokenFieldNameGRPC = "token"
|
||||
TokenFieldNameSwagger = "authorization"
|
||||
)
|
||||
|
|
@ -18,12 +18,12 @@ import (
|
|||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
|
@ -52,8 +52,9 @@ var toGRPCErrorMap = map[error]error{
|
|||
etcdserver.ErrKeyNotFound: rpctypes.ErrGRPCKeyNotFound,
|
||||
etcdserver.ErrCorrupt: rpctypes.ErrGRPCCorrupt,
|
||||
|
||||
lease.ErrLeaseNotFound: rpctypes.ErrGRPCLeaseNotFound,
|
||||
lease.ErrLeaseExists: rpctypes.ErrGRPCLeaseExist,
|
||||
lease.ErrLeaseNotFound: rpctypes.ErrGRPCLeaseNotFound,
|
||||
lease.ErrLeaseExists: rpctypes.ErrGRPCLeaseExist,
|
||||
lease.ErrLeaseTTLTooLarge: rpctypes.ErrGRPCLeaseTTLTooLarge,
|
||||
|
||||
auth.ErrRootUserNotExist: rpctypes.ErrGRPCRootUserNotExist,
|
||||
auth.ErrRootRoleNotExist: rpctypes.ErrGRPCRootRoleNotExist,
|
||||
|
|
|
|||
|
|
@ -17,33 +17,39 @@ package v3rpc
|
|||
import (
|
||||
"context"
|
||||
"io"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type watchServer struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
sg etcdserver.RaftStatusGetter
|
||||
watchable mvcc.WatchableKV
|
||||
|
||||
ag AuthGetter
|
||||
|
||||
lg *zap.Logger
|
||||
}
|
||||
|
||||
func NewWatchServer(s *etcdserver.EtcdServer) pb.WatchServer {
|
||||
return &watchServer{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
sg: s,
|
||||
watchable: s.Watchable(),
|
||||
ag: s,
|
||||
lg: s.Cfg.Logger,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -57,8 +63,15 @@ var (
|
|||
|
||||
func GetProgressReportInterval() time.Duration {
|
||||
progressReportIntervalMu.RLock()
|
||||
defer progressReportIntervalMu.RUnlock()
|
||||
return progressReportInterval
|
||||
interval := progressReportInterval
|
||||
progressReportIntervalMu.RUnlock()
|
||||
|
||||
// add rand(1/10*progressReportInterval) as jitter so that etcdserver will not
|
||||
// send progress notifications to watchers around the same time even when watchers
|
||||
// are created around the same time (which is common when a client restarts itself).
|
||||
jitter := time.Duration(rand.Int63n(int64(interval) / 10))
|
||||
|
||||
return interval + jitter
|
||||
}
|
||||
|
||||
func SetProgressReportInterval(newTimeout time.Duration) {
|
||||
|
|
@ -83,7 +96,7 @@ const (
|
|||
type serverWatchStream struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
sg etcdserver.RaftStatusGetter
|
||||
|
||||
watchable mvcc.WatchableKV
|
||||
|
||||
|
|
@ -106,13 +119,15 @@ type serverWatchStream struct {
|
|||
wg sync.WaitGroup
|
||||
|
||||
ag AuthGetter
|
||||
|
||||
lg *zap.Logger
|
||||
}
|
||||
|
||||
func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
|
||||
sws := serverWatchStream{
|
||||
clusterID: ws.clusterID,
|
||||
memberID: ws.memberID,
|
||||
raftTimer: ws.raftTimer,
|
||||
sg: ws.sg,
|
||||
|
||||
watchable: ws.watchable,
|
||||
|
||||
|
|
@ -125,6 +140,8 @@ func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
|
|||
closec: make(chan struct{}),
|
||||
|
||||
ag: ws.ag,
|
||||
|
||||
lg: ws.lg,
|
||||
}
|
||||
|
||||
sws.wg.Add(1)
|
||||
|
|
@ -141,9 +158,17 @@ func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
|
|||
go func() {
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Debug("failed to receive watch request from gRPC stream", zap.Error(rerr))
|
||||
} else {
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Warn("failed to receive watch request from gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to receive watch request from gRPC stream (%q)", rerr.Error())
|
||||
}
|
||||
}
|
||||
errc <- rerr
|
||||
}
|
||||
|
|
@ -347,9 +372,17 @@ func (sws *serverWatchStream) sendLoop() {
|
|||
mvcc.ReportEventReceived(len(evs))
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Debug("failed to send watch response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Warn("failed to send watch response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to send watch response to gRPC stream (%q)", err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -368,9 +401,17 @@ func (sws *serverWatchStream) sendLoop() {
|
|||
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Debug("failed to send watch control response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Warn("failed to send watch control response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to send watch control response to gRPC stream (%q)", err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -388,9 +429,17 @@ func (sws *serverWatchStream) sendLoop() {
|
|||
mvcc.ReportEventReceived(len(v.Events))
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Debug("failed to send pending watch response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
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())
|
||||
if sws.lg != nil {
|
||||
sws.lg.Warn("failed to send pending watch response to gRPC stream", zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("failed to send pending watch response to gRPC stream (%q)", err.Error())
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
@ -423,7 +472,7 @@ func (sws *serverWatchStream) newResponseHeader(rev int64) *pb.ResponseHeader {
|
|||
ClusterId: uint64(sws.clusterID),
|
||||
MemberId: uint64(sws.memberID),
|
||||
Revision: rev,
|
||||
RaftTerm: sws.raftTimer.Term(),
|
||||
RaftTerm: sws.sg.Term(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,17 +17,19 @@ package etcdserver
|
|||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -107,6 +109,8 @@ func (s *EtcdServer) newApplierV3() applierV3 {
|
|||
}
|
||||
|
||||
func (a *applierV3backend) Apply(r *pb.InternalRaftRequest) *applyResult {
|
||||
defer warnOfExpensiveRequest(a.s.getLogger(), time.Now(), &pb.InternalRaftStringer{Request: r})
|
||||
|
||||
ar := &applyResult{}
|
||||
|
||||
// call into a.s.applyV3.F instead of a.F so upper appliers can check individual calls
|
||||
|
|
@ -501,25 +505,39 @@ func (a *applierV3backend) applyTxn(txn mvcc.TxnWrite, rt *pb.TxnRequest, txnPat
|
|||
if !txnPath[0] {
|
||||
reqs = rt.Failure
|
||||
}
|
||||
|
||||
lg := a.s.getLogger()
|
||||
for i, req := range reqs {
|
||||
respi := tresp.Responses[i].Response
|
||||
switch tv := req.Request.(type) {
|
||||
case *pb.RequestOp_RequestRange:
|
||||
resp, err := a.Range(txn, tv.RequestRange)
|
||||
if err != nil {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("unexpected error during txn", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
}
|
||||
}
|
||||
respi.(*pb.ResponseOp_ResponseRange).ResponseRange = resp
|
||||
case *pb.RequestOp_RequestPut:
|
||||
resp, err := a.Put(txn, tv.RequestPut)
|
||||
if err != nil {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("unexpected error during txn", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
}
|
||||
}
|
||||
respi.(*pb.ResponseOp_ResponsePut).ResponsePut = resp
|
||||
case *pb.RequestOp_RequestDeleteRange:
|
||||
resp, err := a.DeleteRange(txn, tv.RequestDeleteRange)
|
||||
if err != nil {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("unexpected error during txn", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("unexpected error during txn: %v", err)
|
||||
}
|
||||
}
|
||||
respi.(*pb.ResponseOp_ResponseDeleteRange).ResponseDeleteRange = resp
|
||||
case *pb.RequestOp_RequestTxn:
|
||||
|
|
@ -567,6 +585,7 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
|
|||
resp := &pb.AlarmResponse{}
|
||||
oldCount := len(a.s.alarmStore.Get(ar.Alarm))
|
||||
|
||||
lg := a.s.getLogger()
|
||||
switch ar.Action {
|
||||
case pb.AlarmRequest_GET:
|
||||
resp.Alarms = a.s.alarmStore.Get(ar.Alarm)
|
||||
|
|
@ -581,14 +600,22 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
|
|||
break
|
||||
}
|
||||
|
||||
plog.Warningf("alarm %v raised by peer %s", m.Alarm, types.ID(m.MemberID))
|
||||
if lg != nil {
|
||||
lg.Warn("alarm raised", zap.String("alarm", m.Alarm.String()), zap.String("from", types.ID(m.MemberID).String()))
|
||||
} else {
|
||||
plog.Warningf("alarm %v raised by peer %s", m.Alarm, types.ID(m.MemberID))
|
||||
}
|
||||
switch m.Alarm {
|
||||
case pb.AlarmType_CORRUPT:
|
||||
a.s.applyV3 = newApplierV3Corrupt(a)
|
||||
case pb.AlarmType_NOSPACE:
|
||||
a.s.applyV3 = newApplierV3Capped(a)
|
||||
default:
|
||||
plog.Errorf("unimplemented alarm activation (%+v)", m)
|
||||
if lg != nil {
|
||||
lg.Warn("unimplemented alarm activation", zap.String("alarm", fmt.Sprintf("%+v", m)))
|
||||
} else {
|
||||
plog.Errorf("unimplemented alarm activation (%+v)", m)
|
||||
}
|
||||
}
|
||||
case pb.AlarmRequest_DEACTIVATE:
|
||||
m := a.s.alarmStore.Deactivate(types.ID(ar.MemberID), ar.Alarm)
|
||||
|
|
@ -604,10 +631,18 @@ func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
|
|||
switch m.Alarm {
|
||||
case pb.AlarmType_NOSPACE, pb.AlarmType_CORRUPT:
|
||||
// TODO: check kv hash before deactivating CORRUPT?
|
||||
plog.Infof("alarm disarmed %+v", ar)
|
||||
if lg != nil {
|
||||
lg.Warn("alarm disarmed", zap.String("alarm", m.Alarm.String()), zap.String("from", types.ID(m.MemberID).String()))
|
||||
} else {
|
||||
plog.Infof("alarm disarmed %+v", ar)
|
||||
}
|
||||
a.s.applyV3 = a.s.newApplierV3()
|
||||
default:
|
||||
plog.Errorf("unimplemented alarm deactivation (%+v)", m)
|
||||
if lg != nil {
|
||||
lg.Warn("unimplemented alarm deactivation", zap.String("alarm", fmt.Sprintf("%+v", m)))
|
||||
} else {
|
||||
plog.Errorf("unimplemented alarm deactivation (%+v)", m)
|
||||
}
|
||||
}
|
||||
default:
|
||||
return nil, nil
|
||||
|
|
@ -771,7 +806,7 @@ type quotaApplierV3 struct {
|
|||
}
|
||||
|
||||
func newQuotaApplierV3(s *EtcdServer, app applierV3) applierV3 {
|
||||
return "aApplierV3{app, NewBackendQuota(s)}
|
||||
return "aApplierV3{app, NewBackendQuota(s, "v3-applier")}
|
||||
}
|
||||
|
||||
func (a *quotaApplierV3) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
|
|
|
|||
|
|
@ -17,10 +17,10 @@ package etcdserver
|
|||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
)
|
||||
|
||||
type authApplierV3 struct {
|
||||
|
|
|
|||
|
|
@ -21,9 +21,11 @@ import (
|
|||
|
||||
"github.com/coreos/etcd/etcdserver/api"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/internal/store"
|
||||
"github.com/coreos/etcd/etcdserver/v2store"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// ApplierV2 is the interface for processing V2 raft messages
|
||||
|
|
@ -35,12 +37,13 @@ type ApplierV2 interface {
|
|||
Sync(r *RequestV2) Response
|
||||
}
|
||||
|
||||
func NewApplierV2(s store.Store, c *membership.RaftCluster) ApplierV2 {
|
||||
func NewApplierV2(lg *zap.Logger, s v2store.Store, c *membership.RaftCluster) ApplierV2 {
|
||||
return &applierV2store{store: s, cluster: c}
|
||||
}
|
||||
|
||||
type applierV2store struct {
|
||||
store store.Store
|
||||
lg *zap.Logger
|
||||
store v2store.Store
|
||||
cluster *membership.RaftCluster
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +79,11 @@ func (a *applierV2store) Put(r *RequestV2) Response {
|
|||
id := membership.MustParseMemberIDFromKey(path.Dir(r.Path))
|
||||
var attr membership.Attributes
|
||||
if err := json.Unmarshal([]byte(r.Val), &attr); err != nil {
|
||||
plog.Panicf("unmarshal %s should never fail: %v", r.Val, err)
|
||||
if a.lg != nil {
|
||||
a.lg.Panic("failed to unmarshal", zap.String("value", r.Val), zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("unmarshal %s should never fail: %v", r.Val, err)
|
||||
}
|
||||
}
|
||||
if a.cluster != nil {
|
||||
a.cluster.UpdateAttributes(id, attr)
|
||||
|
|
@ -104,9 +111,11 @@ func (a *applierV2store) Sync(r *RequestV2) Response {
|
|||
return Response{}
|
||||
}
|
||||
|
||||
// applyV2Request interprets r as a call to store.X and returns a Response interpreted
|
||||
// from store.Event
|
||||
// applyV2Request interprets r as a call to v2store.X
|
||||
// and returns a Response interpreted from v2store.Event
|
||||
func (s *EtcdServer) applyV2Request(r *RequestV2) Response {
|
||||
defer warnOfExpensiveRequest(s.getLogger(), time.Now(), r)
|
||||
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
return s.applyV2.Post(r)
|
||||
|
|
@ -124,15 +133,15 @@ func (s *EtcdServer) applyV2Request(r *RequestV2) Response {
|
|||
}
|
||||
}
|
||||
|
||||
func (r *RequestV2) TTLOptions() store.TTLOptionSet {
|
||||
func (r *RequestV2) TTLOptions() v2store.TTLOptionSet {
|
||||
refresh, _ := pbutil.GetBool(r.Refresh)
|
||||
ttlOptions := store.TTLOptionSet{Refresh: refresh}
|
||||
ttlOptions := v2store.TTLOptionSet{Refresh: refresh}
|
||||
if r.Expiration != 0 {
|
||||
ttlOptions.ExpireTime = time.Unix(0, r.Expiration)
|
||||
}
|
||||
return ttlOptions
|
||||
}
|
||||
|
||||
func toResponse(ev *store.Event, err error) Response {
|
||||
func toResponse(ev *v2store.Event, err error) Response {
|
||||
return Response{Event: ev, Err: err}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,16 +19,19 @@ import (
|
|||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/raftsnap"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/raftsnap"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func newBackend(cfg ServerConfig) backend.Backend {
|
||||
bcfg := backend.DefaultBackendConfig()
|
||||
bcfg.Path = cfg.backendPath()
|
||||
bcfg.Logger = cfg.Logger
|
||||
if cfg.QuotaBackendBytes > 0 && cfg.QuotaBackendBytes != DefaultQuotaBytes {
|
||||
// permit 10% excess over quota for disarm
|
||||
bcfg.MmapSize = uint64(cfg.QuotaBackendBytes + cfg.QuotaBackendBytes/10)
|
||||
|
|
@ -40,10 +43,10 @@ func newBackend(cfg ServerConfig) backend.Backend {
|
|||
func openSnapshotBackend(cfg ServerConfig, ss *raftsnap.Snapshotter, snapshot raftpb.Snapshot) (backend.Backend, error) {
|
||||
snapPath, err := ss.DBFilePath(snapshot.Metadata.Index)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("database snapshot file path error: %v", err)
|
||||
return nil, fmt.Errorf("failed to find database snapshot file (%v)", err)
|
||||
}
|
||||
if err := os.Rename(snapPath, cfg.backendPath()); err != nil {
|
||||
return nil, fmt.Errorf("rename snapshot file error: %v", err)
|
||||
return nil, fmt.Errorf("failed to rename database snapshot file (%v)", err)
|
||||
}
|
||||
return openBackend(cfg), nil
|
||||
}
|
||||
|
|
@ -51,17 +54,32 @@ func openSnapshotBackend(cfg ServerConfig, ss *raftsnap.Snapshotter, snapshot ra
|
|||
// openBackend returns a backend using the current etcd db.
|
||||
func openBackend(cfg ServerConfig) backend.Backend {
|
||||
fn := cfg.backendPath()
|
||||
beOpened := make(chan backend.Backend)
|
||||
|
||||
now, beOpened := time.Now(), make(chan backend.Backend)
|
||||
go func() {
|
||||
beOpened <- newBackend(cfg)
|
||||
}()
|
||||
|
||||
select {
|
||||
case be := <-beOpened:
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info("opened backend db", zap.String("path", fn), zap.Duration("took", time.Since(now)))
|
||||
}
|
||||
return be
|
||||
|
||||
case <-time.After(10 * time.Second):
|
||||
plog.Warningf("another etcd process is using %q and holds the file lock, or loading backend file is taking >10 seconds", fn)
|
||||
plog.Warningf("waiting for it to exit before starting...")
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info(
|
||||
"db file is flocked by another process, or taking too long",
|
||||
zap.String("path", fn),
|
||||
zap.Duration("took", time.Since(now)),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("another etcd process is using %q and holds the file lock, or loading backend file is taking >10 seconds", fn)
|
||||
plog.Warningf("waiting for it to exit before starting...")
|
||||
}
|
||||
}
|
||||
|
||||
return <-beOpened
|
||||
}
|
||||
|
||||
|
|
@ -71,11 +89,11 @@ func openBackend(cfg ServerConfig) backend.Backend {
|
|||
// case, replace the db with the snapshot db sent by the leader.
|
||||
func recoverSnapshotBackend(cfg ServerConfig, oldbe backend.Backend, snapshot raftpb.Snapshot) (backend.Backend, error) {
|
||||
var cIndex consistentIndex
|
||||
kv := mvcc.New(oldbe, &lease.FakeLessor{}, &cIndex)
|
||||
kv := mvcc.New(cfg.Logger, oldbe, &lease.FakeLessor{}, &cIndex)
|
||||
defer kv.Close()
|
||||
if snapshot.Metadata.Index <= kv.ConsistentIndex() {
|
||||
return oldbe, nil
|
||||
}
|
||||
oldbe.Close()
|
||||
return openSnapshotBackend(cfg, raftsnap.New(cfg.SnapDir()), snapshot)
|
||||
return openSnapshotBackend(cfg, raftsnap.New(cfg.Logger, cfg.SnapDir()), snapshot)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,16 +23,17 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/internal/version"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/version"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// isMemberBootstrapped tries to check if the given member has been bootstrapped
|
||||
// in the given cluster.
|
||||
func isMemberBootstrapped(cl *membership.RaftCluster, member string, rt http.RoundTripper, timeout time.Duration) bool {
|
||||
rcl, err := getClusterFromRemotePeers(getRemotePeerURLs(cl, member), timeout, false, rt)
|
||||
func isMemberBootstrapped(lg *zap.Logger, cl *membership.RaftCluster, member string, rt http.RoundTripper, timeout time.Duration) bool {
|
||||
rcl, err := getClusterFromRemotePeers(lg, getRemotePeerURLs(cl, member), timeout, false, rt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
|
@ -54,21 +55,26 @@ func isMemberBootstrapped(cl *membership.RaftCluster, member string, rt http.Rou
|
|||
// response, an error is returned.
|
||||
// Each request has a 10-second timeout. Because the upper limit of TTL is 5s,
|
||||
// 10 second is enough for building connection and finishing request.
|
||||
func GetClusterFromRemotePeers(urls []string, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
return getClusterFromRemotePeers(urls, 10*time.Second, true, rt)
|
||||
func GetClusterFromRemotePeers(lg *zap.Logger, urls []string, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
return getClusterFromRemotePeers(lg, urls, 10*time.Second, true, rt)
|
||||
}
|
||||
|
||||
// If logerr is true, it prints out more error messages.
|
||||
func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
func getClusterFromRemotePeers(lg *zap.Logger, urls []string, timeout time.Duration, logerr bool, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
cc := &http.Client{
|
||||
Transport: rt,
|
||||
Timeout: timeout,
|
||||
}
|
||||
for _, u := range urls {
|
||||
resp, err := cc.Get(u + "/members")
|
||||
addr := u + "/members"
|
||||
resp, err := cc.Get(addr)
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not get cluster response from %s: %v", u, err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to get cluster response", zap.String("address", addr), zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("could not get cluster response from %s: %v", u, err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
@ -76,21 +82,38 @@ func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool
|
|||
resp.Body.Close()
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not read the body of cluster response: %v", err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to read body of cluster response", zap.String("address", addr), zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("could not read the body of cluster response: %v", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
var membs []*membership.Member
|
||||
if err = json.Unmarshal(b, &membs); err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not unmarshal cluster response: %v", err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to unmarshal cluster response", zap.String("address", addr), zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("could not unmarshal cluster response: %v", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
id, err := types.IDFromString(resp.Header.Get("X-Etcd-Cluster-ID"))
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not parse the cluster ID from cluster res: %v", err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse cluster ID",
|
||||
zap.String("address", addr),
|
||||
zap.String("header", resp.Header.Get("X-Etcd-Cluster-ID")),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("could not parse the cluster ID from cluster res: %v", err)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
|
@ -100,12 +123,11 @@ func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool
|
|||
// if membership members are not present then the raft cluster formed will be
|
||||
// an invalid empty cluster hence return failed to get raft cluster member(s) from the given urls error
|
||||
if len(membs) > 0 {
|
||||
return membership.NewClusterFromMembers("", id, membs), nil
|
||||
return membership.NewClusterFromMembers(lg, "", id, membs), nil
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("failed to get raft cluster member(s) from the given urls.")
|
||||
return nil, fmt.Errorf("failed to get raft cluster member(s) from the given URLs")
|
||||
}
|
||||
return nil, fmt.Errorf("could not retrieve cluster information from the given urls")
|
||||
return nil, fmt.Errorf("could not retrieve cluster information from the given URLs")
|
||||
}
|
||||
|
||||
// getRemotePeerURLs returns peer urls of remote members in the cluster. The
|
||||
|
|
@ -126,7 +148,7 @@ func getRemotePeerURLs(cl *membership.RaftCluster, local string) []string {
|
|||
// The key of the returned map is the member's ID. The value of the returned map
|
||||
// is the semver versions string, including server and cluster.
|
||||
// If it fails to get the version of a member, the key will be nil.
|
||||
func getVersions(cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) map[string]*version.Versions {
|
||||
func getVersions(lg *zap.Logger, cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) map[string]*version.Versions {
|
||||
members := cl.Members()
|
||||
vers := make(map[string]*version.Versions)
|
||||
for _, m := range members {
|
||||
|
|
@ -138,9 +160,13 @@ func getVersions(cl *membership.RaftCluster, local types.ID, rt http.RoundTrippe
|
|||
vers[m.ID.String()] = &version.Versions{Server: version.Version, Cluster: cv}
|
||||
continue
|
||||
}
|
||||
ver, err := getVersion(m, rt)
|
||||
ver, err := getVersion(lg, m, rt)
|
||||
if err != nil {
|
||||
plog.Warningf("cannot get the version of member %s (%v)", m.ID, err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to get version", zap.String("remote-peer-id", m.ID.String()), zap.Error(err))
|
||||
} else {
|
||||
plog.Warningf("cannot get the version of member %s (%v)", m.ID, err)
|
||||
}
|
||||
vers[m.ID.String()] = nil
|
||||
} else {
|
||||
vers[m.ID.String()] = ver
|
||||
|
|
@ -152,7 +178,7 @@ func getVersions(cl *membership.RaftCluster, local types.ID, rt http.RoundTrippe
|
|||
// decideClusterVersion decides the cluster version based on the versions map.
|
||||
// The returned version is the min server version in the map, or nil if the min
|
||||
// version in unknown.
|
||||
func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
||||
func decideClusterVersion(lg *zap.Logger, vers map[string]*version.Versions) *semver.Version {
|
||||
var cv *semver.Version
|
||||
lv := semver.Must(semver.NewVersion(version.Version))
|
||||
|
||||
|
|
@ -162,12 +188,30 @@ func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
|||
}
|
||||
v, err := semver.NewVersion(ver.Server)
|
||||
if err != nil {
|
||||
plog.Errorf("cannot understand the version of member %s (%v)", mid, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse server version of remote member",
|
||||
zap.String("remote-peer-id", mid),
|
||||
zap.String("remote-peer-version", ver.Server),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("cannot understand the version of member %s (%v)", mid, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if lv.LessThan(*v) {
|
||||
plog.Warningf("the local etcd version %s is not up-to-date", lv.String())
|
||||
plog.Warningf("member %s has a higher version %s", mid, ver.Server)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"local etcd version is not up-to-date",
|
||||
zap.String("local-member-version", lv.String()),
|
||||
zap.String("remote-peer-id", mid),
|
||||
zap.String("remote-peer-version", ver.Server),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("the local etcd version %s is not up-to-date", lv.String())
|
||||
plog.Warningf("member %s has a higher version %s", mid, ver.Server)
|
||||
}
|
||||
}
|
||||
if cv == nil {
|
||||
cv = v
|
||||
|
|
@ -184,19 +228,18 @@ func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
|||
// cluster version in the range of [MinClusterVersion, Version] and no known members has a cluster version
|
||||
// out of the range.
|
||||
// We set this rule since when the local member joins, another member might be offline.
|
||||
func isCompatibleWithCluster(cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) bool {
|
||||
vers := getVersions(cl, local, rt)
|
||||
func isCompatibleWithCluster(lg *zap.Logger, cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) bool {
|
||||
vers := getVersions(lg, cl, local, rt)
|
||||
minV := semver.Must(semver.NewVersion(version.MinClusterVersion))
|
||||
maxV := semver.Must(semver.NewVersion(version.Version))
|
||||
maxV = &semver.Version{
|
||||
Major: maxV.Major,
|
||||
Minor: maxV.Minor,
|
||||
}
|
||||
|
||||
return isCompatibleWithVers(vers, local, minV, maxV)
|
||||
return isCompatibleWithVers(lg, vers, local, minV, maxV)
|
||||
}
|
||||
|
||||
func isCompatibleWithVers(vers map[string]*version.Versions, local types.ID, minV, maxV *semver.Version) bool {
|
||||
func isCompatibleWithVers(lg *zap.Logger, vers map[string]*version.Versions, local types.ID, minV, maxV *semver.Version) bool {
|
||||
var ok bool
|
||||
for id, v := range vers {
|
||||
// ignore comparison with local version
|
||||
|
|
@ -208,15 +251,42 @@ func isCompatibleWithVers(vers map[string]*version.Versions, local types.ID, min
|
|||
}
|
||||
clusterv, err := semver.NewVersion(v.Cluster)
|
||||
if err != nil {
|
||||
plog.Errorf("cannot understand the cluster version of member %s (%v)", id, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to parse cluster version of remote member",
|
||||
zap.String("remote-peer-id", id),
|
||||
zap.String("remote-peer-cluster-version", v.Cluster),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("cannot understand the cluster version of member %s (%v)", id, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if clusterv.LessThan(*minV) {
|
||||
plog.Warningf("the running cluster version(%v) is lower than the minimal cluster version(%v) supported", clusterv.String(), minV.String())
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"cluster version of remote member is not compatible; too low",
|
||||
zap.String("remote-peer-id", id),
|
||||
zap.String("remote-peer-cluster-version", clusterv.String()),
|
||||
zap.String("minimum-cluster-version-supported", minV.String()),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("the running cluster version(%v) is lower than the minimal cluster version(%v) supported", clusterv.String(), minV.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
if maxV.LessThan(*clusterv) {
|
||||
plog.Warningf("the running cluster version(%v) is higher than the maximum cluster version(%v) supported", clusterv.String(), maxV.String())
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"cluster version of remote member is not compatible; too high",
|
||||
zap.String("remote-peer-id", id),
|
||||
zap.String("remote-peer-cluster-version", clusterv.String()),
|
||||
zap.String("minimum-cluster-version-supported", minV.String()),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("the running cluster version(%v) is higher than the maximum cluster version(%v) supported", clusterv.String(), maxV.String())
|
||||
}
|
||||
return false
|
||||
}
|
||||
ok = true
|
||||
|
|
@ -226,7 +296,7 @@ func isCompatibleWithVers(vers map[string]*version.Versions, local types.ID, min
|
|||
|
||||
// getVersion returns the Versions of the given member via its
|
||||
// peerURLs. Returns the last error if it fails to get the version.
|
||||
func getVersion(m *membership.Member, rt http.RoundTripper) (*version.Versions, error) {
|
||||
func getVersion(lg *zap.Logger, m *membership.Member, rt http.RoundTripper) (*version.Versions, error) {
|
||||
cc := &http.Client{
|
||||
Transport: rt,
|
||||
}
|
||||
|
|
@ -236,21 +306,49 @@ func getVersion(m *membership.Member, rt http.RoundTripper) (*version.Versions,
|
|||
)
|
||||
|
||||
for _, u := range m.PeerURLs {
|
||||
resp, err = cc.Get(u + "/version")
|
||||
addr := u + "/version"
|
||||
resp, err = cc.Get(addr)
|
||||
if err != nil {
|
||||
plog.Warningf("failed to reach the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to reach the peer URL",
|
||||
zap.String("address", addr),
|
||||
zap.String("remote-peer-id", m.ID.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to reach the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var b []byte
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
plog.Warningf("failed to read out the response body from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to read body of response",
|
||||
zap.String("address", addr),
|
||||
zap.String("remote-peer-id", m.ID.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to read out the response body from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var vers version.Versions
|
||||
if err = json.Unmarshal(b, &vers); err != nil {
|
||||
plog.Warningf("failed to unmarshal the response body got from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to unmarshal response",
|
||||
zap.String("address", addr),
|
||||
zap.String("remote-peer-id", m.ID.String()),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to unmarshal the response body got from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return &vers, nil
|
||||
|
|
|
|||
|
|
@ -25,6 +25,9 @@ import (
|
|||
"github.com/coreos/etcd/pkg/netutil"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// ServerConfig holds the configuration of etcd as taken from the command line or discovery.
|
||||
|
|
@ -44,11 +47,47 @@ type ServerConfig struct {
|
|||
InitialPeerURLsMap types.URLsMap
|
||||
InitialClusterToken string
|
||||
NewCluster bool
|
||||
ForceNewCluster bool
|
||||
PeerTLSInfo transport.TLSInfo
|
||||
|
||||
TickMs uint
|
||||
ElectionTicks int
|
||||
CORS map[string]struct{}
|
||||
|
||||
// HostWhitelist lists acceptable hostnames from client requests.
|
||||
// If server is insecure (no TLS), server only accepts requests
|
||||
// whose Host header value exists in this white list.
|
||||
HostWhitelist map[string]struct{}
|
||||
|
||||
TickMs uint
|
||||
ElectionTicks int
|
||||
|
||||
// InitialElectionTickAdvance is true, then local member fast-forwards
|
||||
// election ticks to speed up "initial" leader election trigger. This
|
||||
// benefits the case of larger election ticks. For instance, cross
|
||||
// datacenter deployment may require longer election timeout of 10-second.
|
||||
// If true, local node does not need wait up to 10-second. Instead,
|
||||
// forwards its election ticks to 8-second, and have only 2-second left
|
||||
// before leader election.
|
||||
//
|
||||
// Major assumptions are that:
|
||||
// - cluster has no active leader thus advancing ticks enables faster
|
||||
// leader election, or
|
||||
// - cluster already has an established leader, and rejoining follower
|
||||
// is likely to receive heartbeats from the leader after tick advance
|
||||
// and before election timeout.
|
||||
//
|
||||
// However, when network from leader to rejoining follower is congested,
|
||||
// and the follower does not receive leader heartbeat within left election
|
||||
// ticks, disruptive election has to happen thus affecting cluster
|
||||
// availabilities.
|
||||
//
|
||||
// Disabling this would slow down initial bootstrap process for cross
|
||||
// datacenter deployments. Make your own tradeoffs by configuring
|
||||
// --initial-election-tick-advance at the cost of slow initial bootstrap.
|
||||
//
|
||||
// If single-node, it advances ticks regardless.
|
||||
//
|
||||
// See https://github.com/coreos/etcd/issues/9333 for more detail.
|
||||
InitialElectionTickAdvance bool
|
||||
|
||||
BootstrapTimeout time.Duration
|
||||
|
||||
AutoCompactionRetention time.Duration
|
||||
|
|
@ -64,14 +103,32 @@ type ServerConfig struct {
|
|||
// ClientCertAuthEnabled is true when cert has been signed by the client CA.
|
||||
ClientCertAuthEnabled bool
|
||||
|
||||
AuthToken string
|
||||
AuthToken string
|
||||
BcryptCost uint
|
||||
|
||||
// InitialCorruptCheck is true to check data corruption on boot
|
||||
// before serving any peer/client traffic.
|
||||
InitialCorruptCheck bool
|
||||
CorruptCheckTime time.Duration
|
||||
|
||||
// PreVote is true to enable Raft Pre-Vote.
|
||||
PreVote bool
|
||||
|
||||
// Logger logs server-side operations.
|
||||
// If not nil, it disables "capnslog" and uses the given logger.
|
||||
Logger *zap.Logger
|
||||
|
||||
// LoggerConfig is server logger configuration for Raft logger.
|
||||
// Must be either: "LoggerConfig != nil" or "LoggerCore != nil && LoggerWriteSyncer != nil".
|
||||
LoggerConfig *zap.Config
|
||||
// LoggerCore is "zapcore.Core" for raft logger.
|
||||
// Must be either: "LoggerConfig != nil" or "LoggerCore != nil && LoggerWriteSyncer != nil".
|
||||
LoggerCore zapcore.Core
|
||||
LoggerWriteSyncer zapcore.WriteSyncer
|
||||
|
||||
Debug bool
|
||||
|
||||
ForceNewCluster bool
|
||||
}
|
||||
|
||||
// VerifyBootstrap sanity-checks the initial config for bootstrap case
|
||||
|
|
@ -124,7 +181,7 @@ func (c *ServerConfig) advertiseMatchesCluster() error {
|
|||
sort.Strings(apurls)
|
||||
ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
|
||||
defer cancel()
|
||||
ok, err := netutil.URLStringsEqual(ctx, apurls, urls.StringSlice())
|
||||
ok, err := netutil.URLStringsEqual(ctx, c.Logger, apurls, urls.StringSlice())
|
||||
if ok {
|
||||
return nil
|
||||
}
|
||||
|
|
@ -203,28 +260,64 @@ func (c *ServerConfig) PrintWithInitial() { c.print(true) }
|
|||
func (c *ServerConfig) Print() { c.print(false) }
|
||||
|
||||
func (c *ServerConfig) print(initial bool) {
|
||||
plog.Infof("name = %s", c.Name)
|
||||
if c.ForceNewCluster {
|
||||
plog.Infof("force new cluster")
|
||||
}
|
||||
plog.Infof("data dir = %s", c.DataDir)
|
||||
plog.Infof("member dir = %s", c.MemberDir())
|
||||
if c.DedicatedWALDir != "" {
|
||||
plog.Infof("dedicated WAL dir = %s", c.DedicatedWALDir)
|
||||
}
|
||||
plog.Infof("heartbeat = %dms", c.TickMs)
|
||||
plog.Infof("election = %dms", c.ElectionTicks*int(c.TickMs))
|
||||
plog.Infof("snapshot count = %d", c.SnapCount)
|
||||
if len(c.DiscoveryURL) != 0 {
|
||||
plog.Infof("discovery URL= %s", c.DiscoveryURL)
|
||||
if len(c.DiscoveryProxy) != 0 {
|
||||
plog.Infof("discovery proxy = %s", c.DiscoveryProxy)
|
||||
// TODO: remove this after dropping "capnslog"
|
||||
if c.Logger == nil {
|
||||
plog.Infof("name = %s", c.Name)
|
||||
if c.ForceNewCluster {
|
||||
plog.Infof("force new cluster")
|
||||
}
|
||||
}
|
||||
plog.Infof("advertise client URLs = %s", c.ClientURLs)
|
||||
if initial {
|
||||
plog.Infof("initial advertise peer URLs = %s", c.PeerURLs)
|
||||
plog.Infof("initial cluster = %s", c.InitialPeerURLsMap)
|
||||
plog.Infof("data dir = %s", c.DataDir)
|
||||
plog.Infof("member dir = %s", c.MemberDir())
|
||||
if c.DedicatedWALDir != "" {
|
||||
plog.Infof("dedicated WAL dir = %s", c.DedicatedWALDir)
|
||||
}
|
||||
plog.Infof("heartbeat = %dms", c.TickMs)
|
||||
plog.Infof("election = %dms", c.ElectionTicks*int(c.TickMs))
|
||||
plog.Infof("snapshot count = %d", c.SnapCount)
|
||||
if len(c.DiscoveryURL) != 0 {
|
||||
plog.Infof("discovery URL= %s", c.DiscoveryURL)
|
||||
if len(c.DiscoveryProxy) != 0 {
|
||||
plog.Infof("discovery proxy = %s", c.DiscoveryProxy)
|
||||
}
|
||||
}
|
||||
plog.Infof("advertise client URLs = %s", c.ClientURLs)
|
||||
if initial {
|
||||
plog.Infof("initial advertise peer URLs = %s", c.PeerURLs)
|
||||
plog.Infof("initial cluster = %s", c.InitialPeerURLsMap)
|
||||
}
|
||||
} else {
|
||||
state := "new"
|
||||
if !c.NewCluster {
|
||||
state = "existing"
|
||||
}
|
||||
c.Logger.Info(
|
||||
"server configuration",
|
||||
zap.String("name", c.Name),
|
||||
zap.String("data-dir", c.DataDir),
|
||||
zap.String("member-dir", c.MemberDir()),
|
||||
zap.String("dedicated-wal-dir", c.DedicatedWALDir),
|
||||
zap.Bool("force-new-cluster", c.ForceNewCluster),
|
||||
zap.Uint("heartbeat-tick-ms", c.TickMs),
|
||||
zap.String("heartbeat-interval", fmt.Sprintf("%v", time.Duration(c.TickMs)*time.Millisecond)),
|
||||
zap.Int("election-tick-ms", c.ElectionTicks),
|
||||
zap.String("election-timeout", fmt.Sprintf("%v", time.Duration(c.ElectionTicks*int(c.TickMs))*time.Millisecond)),
|
||||
zap.Bool("initial-election-tick-advance", c.InitialElectionTickAdvance),
|
||||
zap.Uint64("snapshot-count", c.SnapCount),
|
||||
zap.Strings("advertise-client-urls", c.getACURLs()),
|
||||
zap.Strings("initial-advertise-peer-urls", c.getAPURLs()),
|
||||
zap.Bool("initial", initial),
|
||||
zap.String("initial-cluster", c.InitialPeerURLsMap.String()),
|
||||
zap.String("initial-cluster-state", state),
|
||||
zap.String("initial-cluster-token", c.InitialClusterToken),
|
||||
zap.Bool("pre-vote", c.PreVote),
|
||||
zap.Bool("initial-corrupt-check", c.InitialCorruptCheck),
|
||||
zap.String("corrupt-check-time-interval", c.CorruptCheckTime.String()),
|
||||
zap.String("auto-compaction-mode", c.AutoCompactionMode),
|
||||
zap.Duration("auto-compaction-retention", c.AutoCompactionRetention),
|
||||
zap.String("auto-compaction-interval", c.AutoCompactionRetention.String()),
|
||||
zap.String("discovery-url", c.DiscoveryURL),
|
||||
zap.String("discovery-proxy", c.DiscoveryProxy),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,3 +343,19 @@ func (c *ServerConfig) bootstrapTimeout() time.Duration {
|
|||
}
|
||||
|
||||
func (c *ServerConfig) backendPath() string { return filepath.Join(c.SnapDir(), "db") }
|
||||
|
||||
func (c *ServerConfig) getAPURLs() (ss []string) {
|
||||
ss = make([]string, len(c.PeerURLs))
|
||||
for i := range c.PeerURLs {
|
||||
ss[i] = c.PeerURLs[i].String()
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
||||
func (c *ServerConfig) getACURLs() (ss []string) {
|
||||
ss = make([]string, len(c.ClientURLs))
|
||||
for i := range c.ClientURLs {
|
||||
ss[i] = c.ClientURLs[i].String()
|
||||
}
|
||||
return ss
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,8 +22,10 @@ import (
|
|||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// CheckInitialHashKV compares initial hash values with its peers
|
||||
|
|
@ -34,7 +36,18 @@ func (s *EtcdServer) CheckInitialHashKV() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
plog.Infof("%s starting initial corruption check with timeout %v...", s.ID(), s.Cfg.ReqTimeout())
|
||||
lg := s.getLogger()
|
||||
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"starting initial corruption check",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Duration("timeout", s.Cfg.ReqTimeout()),
|
||||
)
|
||||
} else {
|
||||
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)
|
||||
|
|
@ -44,22 +57,70 @@ func (s *EtcdServer) CheckInitialHashKV() error {
|
|||
for _, p := range peers {
|
||||
if p.resp != nil {
|
||||
peerID := types.ID(p.resp.Header.MemberId)
|
||||
fields := []zap.Field{
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Int64("local-member-revision", rev),
|
||||
zap.Int64("local-member-compact-revision", crev),
|
||||
zap.Uint32("local-member-hash", h),
|
||||
zap.String("remote-peer-id", peerID.String()),
|
||||
zap.Strings("remote-peer-endpoints", p.eps),
|
||||
zap.Int64("remote-peer-revision", p.resp.Header.Revision),
|
||||
zap.Int64("remote-peer-compact-revision", p.resp.CompactRevision),
|
||||
zap.Uint32("remote-peer-hash", p.resp.Hash),
|
||||
}
|
||||
|
||||
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)
|
||||
if lg != nil {
|
||||
lg.Warn("found different hash values from remote peer", fields...)
|
||||
} else {
|
||||
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)
|
||||
if lg != nil {
|
||||
lg.Warn("found different compact revision values from remote peer", fields...)
|
||||
} 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())
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"cannot fetch hash from slow remote peer",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Int64("local-member-revision", rev),
|
||||
zap.Int64("local-member-compact-revision", crev),
|
||||
zap.Uint32("local-member-hash", h),
|
||||
zap.String("remote-peer-id", p.id.String()),
|
||||
zap.Strings("remote-peer-endpoints", p.eps),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
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 lg != nil {
|
||||
lg.Warn(
|
||||
"cannot fetch hash from remote peer; local member is behind",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Int64("local-member-revision", rev),
|
||||
zap.Int64("local-member-compact-revision", crev),
|
||||
zap.Uint32("local-member-hash", h),
|
||||
zap.String("remote-peer-id", p.id.String()),
|
||||
zap.Strings("remote-peer-endpoints", p.eps),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
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())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -67,7 +128,14 @@ func (s *EtcdServer) CheckInitialHashKV() error {
|
|||
return fmt.Errorf("%s found data inconsistency with peers", s.ID())
|
||||
}
|
||||
|
||||
plog.Infof("%s succeeded on initial corruption checking: no corruption", s.ID())
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"initial corruption checking passed; no corruption",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("%s succeeded on initial corruption checking: no corruption", s.ID())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
@ -76,7 +144,18 @@ func (s *EtcdServer) monitorKVHash() {
|
|||
if t == 0 {
|
||||
return
|
||||
}
|
||||
plog.Infof("enabled corruption checking with %s interval", t)
|
||||
|
||||
lg := s.getLogger()
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"enabled corruption checking",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Duration("interval", t),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("enabled corruption checking with %s interval", t)
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.stopping:
|
||||
|
|
@ -87,15 +166,21 @@ func (s *EtcdServer) monitorKVHash() {
|
|||
continue
|
||||
}
|
||||
if err := s.checkHashKV(); err != nil {
|
||||
plog.Debugf("check hash kv failed %v", err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to check hash KV", zap.Error(err))
|
||||
} else {
|
||||
plog.Debugf("check hash kv failed %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EtcdServer) checkHashKV() error {
|
||||
lg := s.getLogger()
|
||||
|
||||
h, rev, crev, err := s.kv.HashByRev(0)
|
||||
if err != nil {
|
||||
plog.Fatalf("failed to hash kv store (%v)", err)
|
||||
return err
|
||||
}
|
||||
peers := s.getPeerHashKVs(rev)
|
||||
|
||||
|
|
@ -108,7 +193,6 @@ func (s *EtcdServer) checkHashKV() error {
|
|||
|
||||
h2, rev2, crev2, err := s.kv.HashByRev(0)
|
||||
if err != nil {
|
||||
plog.Warningf("failed to hash kv store (%v)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
@ -119,7 +203,7 @@ func (s *EtcdServer) checkHashKV() error {
|
|||
}
|
||||
alarmed = true
|
||||
a := &pb.AlarmRequest{
|
||||
MemberID: uint64(id),
|
||||
MemberID: id,
|
||||
Action: pb.AlarmRequest_ACTIVATE,
|
||||
Alarm: pb.AlarmType_CORRUPT,
|
||||
}
|
||||
|
|
@ -129,7 +213,19 @@ func (s *EtcdServer) checkHashKV() error {
|
|||
}
|
||||
|
||||
if h2 != h && rev2 == rev && crev == crev2 {
|
||||
plog.Warningf("mismatched hashes %d and %d for revision %d", h, h2, rev)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"found hash mismatch",
|
||||
zap.Int64("revision-1", rev),
|
||||
zap.Int64("compact-revision-1", crev),
|
||||
zap.Uint32("hash-1", h),
|
||||
zap.Int64("revision-2", rev2),
|
||||
zap.Int64("compact-revision-2", crev2),
|
||||
zap.Uint32("hash-2", h2),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("mismatched hashes %d and %d for revision %d", h, h2, rev)
|
||||
}
|
||||
mismatch(uint64(s.ID()))
|
||||
}
|
||||
|
||||
|
|
@ -141,34 +237,63 @@ func (s *EtcdServer) checkHashKV() error {
|
|||
|
||||
// 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)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"revision from follower must be less than or equal to leader's",
|
||||
zap.Int64("leader-revision", rev2),
|
||||
zap.Int64("follower-revision", p.resp.Header.Revision),
|
||||
zap.String("follower-peer-id", types.ID(id).String()),
|
||||
)
|
||||
} else {
|
||||
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,
|
||||
)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"compact revision from follower must be less than or equal to leader's",
|
||||
zap.Int64("leader-compact-revision", crev2),
|
||||
zap.Int64("follower-compact-revision", p.resp.CompactRevision),
|
||||
zap.String("follower-peer-id", types.ID(id).String()),
|
||||
)
|
||||
} else {
|
||||
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,
|
||||
)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"same compact revision then hashes must match",
|
||||
zap.Int64("leader-compact-revision", crev2),
|
||||
zap.Uint32("leader-hash", h),
|
||||
zap.Int64("follower-compact-revision", p.resp.CompactRevision),
|
||||
zap.Uint32("follower-hash", p.resp.Hash),
|
||||
zap.String("follower-peer-id", types.ID(id).String()),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf(
|
||||
"hash %d at revision %d from member %v, expected hash %d",
|
||||
p.resp.Hash,
|
||||
rev,
|
||||
types.ID(id),
|
||||
h,
|
||||
)
|
||||
}
|
||||
mismatch(id)
|
||||
}
|
||||
}
|
||||
|
|
@ -176,33 +301,47 @@ func (s *EtcdServer) checkHashKV() error {
|
|||
}
|
||||
|
||||
type peerHashKVResp struct {
|
||||
id types.ID
|
||||
eps []string
|
||||
|
||||
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))
|
||||
pss := make([]peerHashKVResp, len(mbs))
|
||||
for _, m := range mbs {
|
||||
if m.ID == s.ID() {
|
||||
continue
|
||||
}
|
||||
pURLs = append(pURLs, m.PeerURLs)
|
||||
pss = append(pss, peerHashKVResp{id: m.ID, eps: m.PeerURLs})
|
||||
}
|
||||
|
||||
for _, purls := range pURLs {
|
||||
if len(purls) == 0 {
|
||||
lg := s.getLogger()
|
||||
|
||||
for _, p := range pss {
|
||||
if len(p.eps) == 0 {
|
||||
continue
|
||||
}
|
||||
cli, cerr := clientv3.New(clientv3.Config{
|
||||
DialTimeout: s.Cfg.ReqTimeout(),
|
||||
Endpoints: purls,
|
||||
Endpoints: p.eps,
|
||||
})
|
||||
if cerr != nil {
|
||||
plog.Warningf("%s failed to create client to peer %q for hash checking (%q)", s.ID(), purls, cerr.Error())
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to create client to peer URL",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.String("remote-peer-id", p.id.String()),
|
||||
zap.Strings("remote-peer-endpoints", p.eps),
|
||||
zap.Error(cerr),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("%s failed to create client to peer %q for hash checking (%q)", s.ID(), p.eps, cerr.Error())
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
|
@ -213,15 +352,25 @@ func (s *EtcdServer) getPeerHashKVs(rev int64) (resps []*peerHashKVResp) {
|
|||
resp, cerr = cli.HashKV(ctx, c, rev)
|
||||
cancel()
|
||||
if cerr == nil {
|
||||
resps = append(resps, &peerHashKVResp{resp: resp})
|
||||
resps = append(resps, &peerHashKVResp{id: p.id, eps: p.eps, resp: resp, err: nil})
|
||||
break
|
||||
}
|
||||
plog.Warningf("%s hash-kv error %q on peer %q with revision %d", s.ID(), cerr.Error(), c, rev)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed hash kv request",
|
||||
zap.String("local-member-id", s.ID().String()),
|
||||
zap.Int64("requested-revision", rev),
|
||||
zap.String("remote-peer-endpoint", c),
|
||||
zap.Error(cerr),
|
||||
)
|
||||
} else {
|
||||
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})
|
||||
resps = append(resps, &peerHashKVResp{id: p.id, eps: p.eps, resp: nil, err: cerr})
|
||||
}
|
||||
}
|
||||
return resps
|
||||
|
|
|
|||
58
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal_stringer.go
generated
vendored
Normal file
58
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal_stringer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright 2018 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 etcdserverpb
|
||||
|
||||
import "fmt"
|
||||
|
||||
// InternalRaftStringer implements custom proto Stringer:
|
||||
// redact password, shorten output(TODO).
|
||||
type InternalRaftStringer struct {
|
||||
Request *InternalRaftRequest
|
||||
}
|
||||
|
||||
func (as *InternalRaftStringer) String() string {
|
||||
switch {
|
||||
case as.Request.LeaseGrant != nil:
|
||||
return fmt.Sprintf("header:<%s> lease_grant:<ttl:%d-second id:%016x>",
|
||||
as.Request.Header.String(),
|
||||
as.Request.LeaseGrant.TTL,
|
||||
as.Request.LeaseGrant.ID,
|
||||
)
|
||||
case as.Request.LeaseRevoke != nil:
|
||||
return fmt.Sprintf("header:<%s> lease_revoke:<id:%016x>",
|
||||
as.Request.Header.String(),
|
||||
as.Request.LeaseRevoke.ID,
|
||||
)
|
||||
case as.Request.Authenticate != nil:
|
||||
return fmt.Sprintf("header:<%s> authenticate:<name:%s simple_token:%s>",
|
||||
as.Request.Header.String(),
|
||||
as.Request.Authenticate.Name,
|
||||
as.Request.Authenticate.SimpleToken,
|
||||
)
|
||||
case as.Request.AuthUserAdd != nil:
|
||||
return fmt.Sprintf("header:<%s> auth_user_add:<name:%s>",
|
||||
as.Request.Header.String(),
|
||||
as.Request.AuthUserAdd.Name,
|
||||
)
|
||||
case as.Request.AuthUserChangePassword != nil:
|
||||
return fmt.Sprintf("header:<%s> auth_user_change_password:<name:%s>",
|
||||
as.Request.Header.String(),
|
||||
as.Request.AuthUserChangePassword.Name,
|
||||
)
|
||||
default:
|
||||
// nothing to redact
|
||||
}
|
||||
return as.Request.String()
|
||||
}
|
||||
|
|
@ -12,9 +12,9 @@ import (
|
|||
|
||||
_ "github.com/gogo/protobuf/gogoproto"
|
||||
|
||||
mvccpb "github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
|
||||
|
||||
authpb "github.com/coreos/etcd/internal/auth/authpb"
|
||||
authpb "github.com/coreos/etcd/auth/authpb"
|
||||
|
||||
context "golang.org/x/net/context"
|
||||
|
||||
|
|
@ -296,7 +296,7 @@ type RangeRequest struct {
|
|||
// greater mod revisions will be filtered away.
|
||||
MaxModRevision int64 `protobuf:"varint,11,opt,name=max_mod_revision,json=maxModRevision,proto3" json:"max_mod_revision,omitempty"`
|
||||
// min_create_revision is the lower bound for returned key create revisions; all keys with
|
||||
// lesser create trevisions will be filtered away.
|
||||
// lesser create revisions will be filtered away.
|
||||
MinCreateRevision int64 `protobuf:"varint,12,opt,name=min_create_revision,json=minCreateRevision,proto3" json:"min_create_revision,omitempty"`
|
||||
// max_create_revision is the upper bound for returned key create revisions; all keys with
|
||||
// greater create revisions will be filtered away.
|
||||
|
|
@ -2436,11 +2436,11 @@ type StatusResponse struct {
|
|||
Header *ResponseHeader `protobuf:"bytes,1,opt,name=header" json:"header,omitempty"`
|
||||
// version is the cluster protocol version used by the responding member.
|
||||
Version string `protobuf:"bytes,2,opt,name=version,proto3" json:"version,omitempty"`
|
||||
// dbSize is the size of the backend database, in bytes, of the responding member.
|
||||
// dbSize is the size of the backend database physically allocated, in bytes, of the responding member.
|
||||
DbSize int64 `protobuf:"varint,3,opt,name=dbSize,proto3" json:"dbSize,omitempty"`
|
||||
// leader is the member ID which the responding member believes is the current leader.
|
||||
Leader uint64 `protobuf:"varint,4,opt,name=leader,proto3" json:"leader,omitempty"`
|
||||
// raftIndex is the current raft index of the responding member.
|
||||
// raftIndex is the current raft committed index of the responding member.
|
||||
RaftIndex uint64 `protobuf:"varint,5,opt,name=raftIndex,proto3" json:"raftIndex,omitempty"`
|
||||
// raftTerm is the current raft term of the responding member.
|
||||
RaftTerm uint64 `protobuf:"varint,6,opt,name=raftTerm,proto3" json:"raftTerm,omitempty"`
|
||||
|
|
@ -2448,6 +2448,8 @@ type StatusResponse struct {
|
|||
RaftAppliedIndex uint64 `protobuf:"varint,7,opt,name=raftAppliedIndex,proto3" json:"raftAppliedIndex,omitempty"`
|
||||
// errors contains alarm/health information and status.
|
||||
Errors []string `protobuf:"bytes,8,rep,name=errors" json:"errors,omitempty"`
|
||||
// dbSizeInUse is the size of the backend database logically in use, in bytes, of the responding member.
|
||||
DbSizeInUse int64 `protobuf:"varint,9,opt,name=dbSizeInUse,proto3" json:"dbSizeInUse,omitempty"`
|
||||
}
|
||||
|
||||
func (m *StatusResponse) Reset() { *m = StatusResponse{} }
|
||||
|
|
@ -2511,6 +2513,13 @@ func (m *StatusResponse) GetErrors() []string {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (m *StatusResponse) GetDbSizeInUse() int64 {
|
||||
if m != nil {
|
||||
return m.DbSizeInUse
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type AuthEnableRequest struct {
|
||||
}
|
||||
|
||||
|
|
@ -2781,8 +2790,8 @@ func (m *AuthRoleGrantPermissionRequest) GetPerm() *authpb.Permission {
|
|||
|
||||
type AuthRoleRevokePermissionRequest struct {
|
||||
Role string `protobuf:"bytes,1,opt,name=role,proto3" json:"role,omitempty"`
|
||||
Key string `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
|
||||
RangeEnd string `protobuf:"bytes,3,opt,name=range_end,json=rangeEnd,proto3" json:"range_end,omitempty"`
|
||||
Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"`
|
||||
RangeEnd []byte `protobuf:"bytes,3,opt,name=range_end,json=rangeEnd,proto3" json:"range_end,omitempty"`
|
||||
}
|
||||
|
||||
func (m *AuthRoleRevokePermissionRequest) Reset() { *m = AuthRoleRevokePermissionRequest{} }
|
||||
|
|
@ -2799,18 +2808,18 @@ func (m *AuthRoleRevokePermissionRequest) GetRole() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (m *AuthRoleRevokePermissionRequest) GetKey() string {
|
||||
func (m *AuthRoleRevokePermissionRequest) GetKey() []byte {
|
||||
if m != nil {
|
||||
return m.Key
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *AuthRoleRevokePermissionRequest) GetRangeEnd() string {
|
||||
func (m *AuthRoleRevokePermissionRequest) GetRangeEnd() []byte {
|
||||
if m != nil {
|
||||
return m.RangeEnd
|
||||
}
|
||||
return ""
|
||||
return nil
|
||||
}
|
||||
|
||||
type AuthEnableResponse struct {
|
||||
|
|
@ -3974,11 +3983,15 @@ type MaintenanceClient interface {
|
|||
Status(ctx context.Context, in *StatusRequest, opts ...grpc.CallOption) (*StatusResponse, error)
|
||||
// Defragment defragments a member's backend database to recover storage space.
|
||||
Defragment(ctx context.Context, in *DefragmentRequest, opts ...grpc.CallOption) (*DefragmentResponse, error)
|
||||
// Hash computes the hash of the KV's backend.
|
||||
// This is designed for testing; do not use this in production when there
|
||||
// are ongoing transactions.
|
||||
// Hash computes the hash of whole backend keyspace,
|
||||
// including key, lease, and other buckets in storage.
|
||||
// This is designed for testing ONLY!
|
||||
// Do not rely on this in production with ongoing transactions,
|
||||
// since Hash operation does not hold MVCC locks.
|
||||
// Use "HashKV" API instead for "key" bucket consistency checks.
|
||||
Hash(ctx context.Context, in *HashRequest, opts ...grpc.CallOption) (*HashResponse, error)
|
||||
// HashKV computes the hash of all MVCC keys up to a given revision.
|
||||
// It only iterates "key" bucket in backend storage.
|
||||
HashKV(ctx context.Context, in *HashKVRequest, opts ...grpc.CallOption) (*HashKVResponse, error)
|
||||
// Snapshot sends a snapshot of the entire backend from a member over a stream to a client.
|
||||
Snapshot(ctx context.Context, in *SnapshotRequest, opts ...grpc.CallOption) (Maintenance_SnapshotClient, error)
|
||||
|
|
@ -4089,11 +4102,15 @@ type MaintenanceServer interface {
|
|||
Status(context.Context, *StatusRequest) (*StatusResponse, error)
|
||||
// Defragment defragments a member's backend database to recover storage space.
|
||||
Defragment(context.Context, *DefragmentRequest) (*DefragmentResponse, error)
|
||||
// Hash computes the hash of the KV's backend.
|
||||
// This is designed for testing; do not use this in production when there
|
||||
// are ongoing transactions.
|
||||
// Hash computes the hash of whole backend keyspace,
|
||||
// including key, lease, and other buckets in storage.
|
||||
// This is designed for testing ONLY!
|
||||
// Do not rely on this in production with ongoing transactions,
|
||||
// since Hash operation does not hold MVCC locks.
|
||||
// Use "HashKV" API instead for "key" bucket consistency checks.
|
||||
Hash(context.Context, *HashRequest) (*HashResponse, error)
|
||||
// HashKV computes the hash of all MVCC keys up to a given revision.
|
||||
// It only iterates "key" bucket in backend storage.
|
||||
HashKV(context.Context, *HashKVRequest) (*HashKVResponse, error)
|
||||
// Snapshot sends a snapshot of the entire backend from a member over a stream to a client.
|
||||
Snapshot(*SnapshotRequest, Maintenance_SnapshotServer) error
|
||||
|
|
@ -7034,6 +7051,11 @@ func (m *StatusResponse) MarshalTo(dAtA []byte) (int, error) {
|
|||
i += copy(dAtA[i:], s)
|
||||
}
|
||||
}
|
||||
if m.DbSizeInUse != 0 {
|
||||
dAtA[i] = 0x48
|
||||
i++
|
||||
i = encodeVarintRpc(dAtA, i, uint64(m.DbSizeInUse))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
|
|
@ -8906,6 +8928,9 @@ func (m *StatusResponse) Size() (n int) {
|
|||
n += 1 + l + sovRpc(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.DbSizeInUse != 0 {
|
||||
n += 1 + sovRpc(uint64(m.DbSizeInUse))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
|
|
@ -15578,6 +15603,25 @@ func (m *StatusResponse) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
m.Errors = append(m.Errors, string(dAtA[iNdEx:postIndex]))
|
||||
iNdEx = postIndex
|
||||
case 9:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DbSizeInUse", wireType)
|
||||
}
|
||||
m.DbSizeInUse = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRpc
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
m.DbSizeInUse |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRpc(dAtA[iNdEx:])
|
||||
|
|
@ -16908,7 +16952,7 @@ func (m *AuthRoleRevokePermissionRequest) Unmarshal(dAtA []byte) error {
|
|||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRpc
|
||||
|
|
@ -16918,26 +16962,28 @@ func (m *AuthRoleRevokePermissionRequest) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthRpc
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Key = string(dAtA[iNdEx:postIndex])
|
||||
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.Key == nil {
|
||||
m.Key = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field RangeEnd", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRpc
|
||||
|
|
@ -16947,20 +16993,22 @@ func (m *AuthRoleRevokePermissionRequest) Unmarshal(dAtA []byte) error {
|
|||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthRpc
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.RangeEnd = string(dAtA[iNdEx:postIndex])
|
||||
m.RangeEnd = append(m.RangeEnd[:0], dAtA[iNdEx:postIndex]...)
|
||||
if m.RangeEnd == nil {
|
||||
m.RangeEnd = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
|
|
@ -18566,237 +18614,239 @@ var (
|
|||
func init() { proto.RegisterFile("rpc.proto", fileDescriptorRpc) }
|
||||
|
||||
var fileDescriptorRpc = []byte{
|
||||
// 3708 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0x5b, 0x6f, 0x1b, 0x49,
|
||||
0x76, 0x56, 0x93, 0xe2, 0xed, 0xf0, 0x22, 0xba, 0x24, 0xdb, 0x34, 0x6d, 0xcb, 0x72, 0xf9, 0x26,
|
||||
0x5f, 0x46, 0xdc, 0xd5, 0x6e, 0xf2, 0xe0, 0x04, 0x8b, 0x95, 0x25, 0xae, 0xa5, 0x95, 0x2c, 0x69,
|
||||
0x5b, 0x94, 0x67, 0x02, 0x6c, 0x22, 0xb4, 0xc8, 0x92, 0xd4, 0x11, 0xd9, 0xcd, 0x74, 0x37, 0x69,
|
||||
0xc9, 0x59, 0x24, 0xc0, 0x66, 0x13, 0xe4, 0x25, 0x79, 0xc8, 0x02, 0x41, 0x92, 0xd7, 0x20, 0x58,
|
||||
0xec, 0x0f, 0x18, 0xe4, 0x2f, 0xe4, 0x2d, 0x01, 0xf2, 0x07, 0x82, 0x49, 0x5e, 0xf2, 0x0b, 0x72,
|
||||
0x79, 0x5a, 0xd4, 0xad, 0xbb, 0xfa, 0x46, 0x69, 0x86, 0x33, 0xf3, 0x22, 0x77, 0x9d, 0x3e, 0x75,
|
||||
0xce, 0xa9, 0x53, 0x75, 0x2e, 0xf5, 0x35, 0x0d, 0x25, 0x67, 0xd8, 0x5d, 0x19, 0x3a, 0xb6, 0x67,
|
||||
0xa3, 0x0a, 0xf1, 0xba, 0x3d, 0x97, 0x38, 0x63, 0xe2, 0x0c, 0x8f, 0x9b, 0x0b, 0xa7, 0xf6, 0xa9,
|
||||
0xcd, 0x5e, 0xb4, 0xe8, 0x13, 0xe7, 0x69, 0x62, 0xca, 0xd3, 0x32, 0x2d, 0x8f, 0x38, 0x96, 0xd1,
|
||||
0x6f, 0x0d, 0xc6, 0xdd, 0x2e, 0xfb, 0x33, 0x3c, 0x6e, 0x9d, 0x8f, 0x05, 0xcf, 0xe3, 0x30, 0x8f,
|
||||
0x31, 0xf2, 0xce, 0xd8, 0x9f, 0xe1, 0x31, 0xfb, 0x47, 0x70, 0xdd, 0x3b, 0xb5, 0xed, 0xd3, 0x3e,
|
||||
0x69, 0x19, 0x43, 0xb3, 0x65, 0x58, 0x96, 0xed, 0x19, 0x9e, 0x69, 0x5b, 0x2e, 0x7f, 0x8b, 0xff,
|
||||
0x5c, 0x83, 0x9a, 0x4e, 0xdc, 0xa1, 0x6d, 0xb9, 0x64, 0x93, 0x18, 0x3d, 0xe2, 0xa0, 0xfb, 0x00,
|
||||
0xdd, 0xfe, 0xc8, 0xf5, 0x88, 0x73, 0x64, 0xf6, 0x1a, 0xda, 0x92, 0xb6, 0x3c, 0xab, 0x97, 0x04,
|
||||
0x65, 0xab, 0x87, 0xee, 0x42, 0x69, 0x40, 0x06, 0xc7, 0xfc, 0x6d, 0x86, 0xbd, 0x2d, 0x72, 0xc2,
|
||||
0x56, 0x0f, 0x35, 0xa1, 0xe8, 0x90, 0xb1, 0xe9, 0x9a, 0xb6, 0xd5, 0xc8, 0x2e, 0x69, 0xcb, 0x59,
|
||||
0xdd, 0x1f, 0xd3, 0x89, 0x8e, 0x71, 0xe2, 0x1d, 0x79, 0xc4, 0x19, 0x34, 0x66, 0xf9, 0x44, 0x4a,
|
||||
0xe8, 0x10, 0x67, 0x80, 0x7f, 0x91, 0x83, 0x8a, 0x6e, 0x58, 0xa7, 0x44, 0x27, 0x7f, 0x34, 0x22,
|
||||
0xae, 0x87, 0xea, 0x90, 0x3d, 0x27, 0x97, 0x4c, 0x7d, 0x45, 0xa7, 0x8f, 0x7c, 0xbe, 0x75, 0x4a,
|
||||
0x8e, 0x88, 0xc5, 0x15, 0x57, 0xe8, 0x7c, 0xeb, 0x94, 0xb4, 0xad, 0x1e, 0x5a, 0x80, 0x5c, 0xdf,
|
||||
0x1c, 0x98, 0x9e, 0xd0, 0xca, 0x07, 0x21, 0x73, 0x66, 0x23, 0xe6, 0xac, 0x03, 0xb8, 0xb6, 0xe3,
|
||||
0x1d, 0xd9, 0x4e, 0x8f, 0x38, 0x8d, 0xdc, 0x92, 0xb6, 0x5c, 0x5b, 0x7d, 0xbc, 0xa2, 0x6e, 0xcd,
|
||||
0x8a, 0x6a, 0xd0, 0xca, 0x81, 0xed, 0x78, 0x7b, 0x94, 0x57, 0x2f, 0xb9, 0xf2, 0x11, 0xfd, 0x08,
|
||||
0xca, 0x4c, 0x88, 0x67, 0x38, 0xa7, 0xc4, 0x6b, 0xe4, 0x99, 0x94, 0x27, 0x57, 0x48, 0xe9, 0x30,
|
||||
0x66, 0x9d, 0xa9, 0xe7, 0xcf, 0x08, 0x43, 0xc5, 0x25, 0x8e, 0x69, 0xf4, 0xcd, 0x8f, 0xc6, 0x71,
|
||||
0x9f, 0x34, 0x0a, 0x4b, 0xda, 0x72, 0x51, 0x0f, 0xd1, 0xe8, 0xfa, 0xcf, 0xc9, 0xa5, 0x7b, 0x64,
|
||||
0x5b, 0xfd, 0xcb, 0x46, 0x91, 0x31, 0x14, 0x29, 0x61, 0xcf, 0xea, 0x5f, 0xb2, 0x4d, 0xb3, 0x47,
|
||||
0x96, 0xc7, 0xdf, 0x96, 0xd8, 0xdb, 0x12, 0xa3, 0xb0, 0xd7, 0xcb, 0x50, 0x1f, 0x98, 0xd6, 0xd1,
|
||||
0xc0, 0xee, 0x1d, 0xf9, 0x0e, 0x01, 0xe6, 0x90, 0xda, 0xc0, 0xb4, 0xde, 0xd9, 0x3d, 0x5d, 0xba,
|
||||
0x85, 0x72, 0x1a, 0x17, 0x61, 0xce, 0xb2, 0xe0, 0x34, 0x2e, 0x54, 0xce, 0x15, 0x98, 0xa7, 0x32,
|
||||
0xbb, 0x0e, 0x31, 0x3c, 0x12, 0x30, 0x57, 0x18, 0xf3, 0x8d, 0x81, 0x69, 0xad, 0xb3, 0x37, 0x21,
|
||||
0x7e, 0xe3, 0x22, 0xc6, 0x5f, 0x15, 0xfc, 0xc6, 0x45, 0x98, 0x1f, 0xaf, 0x40, 0xc9, 0xf7, 0x39,
|
||||
0x2a, 0xc2, 0xec, 0xee, 0xde, 0x6e, 0xbb, 0x3e, 0x83, 0x00, 0xf2, 0x6b, 0x07, 0xeb, 0xed, 0xdd,
|
||||
0x8d, 0xba, 0x86, 0xca, 0x50, 0xd8, 0x68, 0xf3, 0x41, 0x06, 0xbf, 0x01, 0x08, 0xbc, 0x8b, 0x0a,
|
||||
0x90, 0xdd, 0x6e, 0xff, 0x5e, 0x7d, 0x86, 0xf2, 0xbc, 0x6f, 0xeb, 0x07, 0x5b, 0x7b, 0xbb, 0x75,
|
||||
0x8d, 0x4e, 0x5e, 0xd7, 0xdb, 0x6b, 0x9d, 0x76, 0x3d, 0x43, 0x39, 0xde, 0xed, 0x6d, 0xd4, 0xb3,
|
||||
0xa8, 0x04, 0xb9, 0xf7, 0x6b, 0x3b, 0x87, 0xed, 0xfa, 0x2c, 0xfe, 0xa5, 0x06, 0x55, 0xb1, 0x5f,
|
||||
0x3c, 0x26, 0xd0, 0xf7, 0x21, 0x7f, 0xc6, 0xe2, 0x82, 0x1d, 0xc5, 0xf2, 0xea, 0xbd, 0xc8, 0xe6,
|
||||
0x86, 0x62, 0x47, 0x17, 0xbc, 0x08, 0x43, 0xf6, 0x7c, 0xec, 0x36, 0x32, 0x4b, 0xd9, 0xe5, 0xf2,
|
||||
0x6a, 0x7d, 0x85, 0x47, 0xee, 0xca, 0x36, 0xb9, 0x7c, 0x6f, 0xf4, 0x47, 0x44, 0xa7, 0x2f, 0x11,
|
||||
0x82, 0xd9, 0x81, 0xed, 0x10, 0x76, 0x62, 0x8b, 0x3a, 0x7b, 0xa6, 0xc7, 0x98, 0x6d, 0x9a, 0x38,
|
||||
0xad, 0x7c, 0x80, 0x7f, 0xad, 0x01, 0xec, 0x8f, 0xbc, 0xf4, 0xd0, 0x58, 0x80, 0xdc, 0x98, 0x0a,
|
||||
0x16, 0x61, 0xc1, 0x07, 0x2c, 0x26, 0x88, 0xe1, 0x12, 0x3f, 0x26, 0xe8, 0x00, 0xdd, 0x86, 0xc2,
|
||||
0xd0, 0x21, 0xe3, 0xa3, 0xf3, 0x31, 0x53, 0x52, 0xd4, 0xf3, 0x74, 0xb8, 0x3d, 0x46, 0x0f, 0xa1,
|
||||
0x62, 0x9e, 0x5a, 0xb6, 0x43, 0x8e, 0xb8, 0xac, 0x1c, 0x7b, 0x5b, 0xe6, 0x34, 0x66, 0xb7, 0xc2,
|
||||
0xc2, 0x05, 0xe7, 0x55, 0x96, 0x1d, 0x4a, 0xc2, 0x16, 0x94, 0x99, 0xa9, 0x53, 0xb9, 0xef, 0x79,
|
||||
0x60, 0x63, 0x86, 0x4d, 0x8b, 0xbb, 0x50, 0x58, 0x8d, 0x7f, 0x0a, 0x68, 0x83, 0xf4, 0x89, 0x47,
|
||||
0xa6, 0xc9, 0x1e, 0x8a, 0x4f, 0xb2, 0xaa, 0x4f, 0xf0, 0xdf, 0x68, 0x30, 0x1f, 0x12, 0x3f, 0xd5,
|
||||
0xb2, 0x1a, 0x50, 0xe8, 0x31, 0x61, 0xdc, 0x82, 0xac, 0x2e, 0x87, 0xe8, 0x25, 0x14, 0x85, 0x01,
|
||||
0x6e, 0x23, 0x9b, 0x72, 0x68, 0x0a, 0xdc, 0x26, 0x17, 0xff, 0x3a, 0x03, 0x25, 0xb1, 0xd0, 0xbd,
|
||||
0x21, 0x5a, 0x83, 0xaa, 0xc3, 0x07, 0x47, 0x6c, 0x3d, 0xc2, 0xa2, 0x66, 0x7a, 0x12, 0xda, 0x9c,
|
||||
0xd1, 0x2b, 0x62, 0x0a, 0x23, 0xa3, 0xdf, 0x81, 0xb2, 0x14, 0x31, 0x1c, 0x79, 0xc2, 0xe5, 0x8d,
|
||||
0xb0, 0x80, 0xe0, 0xfc, 0x6d, 0xce, 0xe8, 0x20, 0xd8, 0xf7, 0x47, 0x1e, 0xea, 0xc0, 0x82, 0x9c,
|
||||
0xcc, 0x57, 0x23, 0xcc, 0xc8, 0x32, 0x29, 0x4b, 0x61, 0x29, 0xf1, 0xad, 0xda, 0x9c, 0xd1, 0x91,
|
||||
0x98, 0xaf, 0xbc, 0x54, 0x4d, 0xf2, 0x2e, 0x78, 0xf2, 0x8e, 0x99, 0xd4, 0xb9, 0xb0, 0xe2, 0x26,
|
||||
0x75, 0x2e, 0xac, 0x37, 0x25, 0x28, 0x88, 0x11, 0xfe, 0xe7, 0x0c, 0x80, 0xdc, 0x8d, 0xbd, 0x21,
|
||||
0xda, 0x80, 0x9a, 0x23, 0x46, 0x21, 0x6f, 0xdd, 0x4d, 0xf4, 0x96, 0xd8, 0xc4, 0x19, 0xbd, 0x2a,
|
||||
0x27, 0x71, 0xe3, 0x7e, 0x00, 0x15, 0x5f, 0x4a, 0xe0, 0xb0, 0x3b, 0x09, 0x0e, 0xf3, 0x25, 0x94,
|
||||
0xe5, 0x04, 0xea, 0xb2, 0x4f, 0xe1, 0xa6, 0x3f, 0x3f, 0xc1, 0x67, 0x0f, 0x27, 0xf8, 0xcc, 0x17,
|
||||
0x38, 0x2f, 0x25, 0xa8, 0x5e, 0x53, 0x0d, 0x0b, 0xdc, 0x76, 0x27, 0xc1, 0x6d, 0x71, 0xc3, 0xa8,
|
||||
0xe3, 0x80, 0xd6, 0x4b, 0x3e, 0xc4, 0xff, 0x9d, 0x85, 0xc2, 0xba, 0x3d, 0x18, 0x1a, 0x0e, 0xdd,
|
||||
0x8d, 0xbc, 0x43, 0xdc, 0x51, 0xdf, 0x63, 0xee, 0xaa, 0xad, 0x3e, 0x0a, 0x4b, 0x14, 0x6c, 0xf2,
|
||||
0x5f, 0x9d, 0xb1, 0xea, 0x62, 0x0a, 0x9d, 0x2c, 0xca, 0x63, 0xe6, 0x1a, 0x93, 0x45, 0x71, 0x14,
|
||||
0x53, 0x64, 0x20, 0x67, 0x83, 0x40, 0x6e, 0x42, 0x61, 0x4c, 0x9c, 0xa0, 0xa4, 0x6f, 0xce, 0xe8,
|
||||
0x92, 0x80, 0x9e, 0xc3, 0x5c, 0xb4, 0xbc, 0xe4, 0x04, 0x4f, 0xad, 0x1b, 0xae, 0x46, 0x8f, 0xa0,
|
||||
0x12, 0xaa, 0x71, 0x79, 0xc1, 0x57, 0x1e, 0x28, 0x25, 0xee, 0x96, 0xcc, 0xab, 0xb4, 0x1e, 0x57,
|
||||
0x36, 0x67, 0x64, 0x66, 0xbd, 0x25, 0x33, 0x6b, 0x51, 0xcc, 0x12, 0xb9, 0x35, 0x94, 0x64, 0x7e,
|
||||
0x18, 0x4e, 0x32, 0xf8, 0x87, 0x50, 0x0d, 0x39, 0x88, 0xd6, 0x9d, 0xf6, 0x4f, 0x0e, 0xd7, 0x76,
|
||||
0x78, 0x91, 0x7a, 0xcb, 0xea, 0x92, 0x5e, 0xd7, 0x68, 0xad, 0xdb, 0x69, 0x1f, 0x1c, 0xd4, 0x33,
|
||||
0xa8, 0x0a, 0xa5, 0xdd, 0xbd, 0xce, 0x11, 0xe7, 0xca, 0xe2, 0xb7, 0xbe, 0x04, 0x51, 0xe4, 0x94,
|
||||
0xda, 0x36, 0xa3, 0xd4, 0x36, 0x4d, 0xd6, 0xb6, 0x4c, 0x50, 0xdb, 0x58, 0x99, 0xdb, 0x69, 0xaf,
|
||||
0x1d, 0xb4, 0xeb, 0xb3, 0x6f, 0x6a, 0x50, 0xe1, 0xfe, 0x3d, 0x1a, 0x59, 0xb4, 0xd4, 0xfe, 0xa3,
|
||||
0x06, 0x10, 0x44, 0x13, 0x6a, 0x41, 0xa1, 0xcb, 0xf5, 0x34, 0x34, 0x96, 0x8c, 0x6e, 0x26, 0x6e,
|
||||
0x99, 0x2e, 0xb9, 0xd0, 0x77, 0xa1, 0xe0, 0x8e, 0xba, 0x5d, 0xe2, 0xca, 0x92, 0x77, 0x3b, 0x9a,
|
||||
0x0f, 0x45, 0xb6, 0xd2, 0x25, 0x1f, 0x9d, 0x72, 0x62, 0x98, 0xfd, 0x11, 0x2b, 0x80, 0x93, 0xa7,
|
||||
0x08, 0x3e, 0xfc, 0xf7, 0x1a, 0x94, 0x95, 0xc3, 0xfb, 0x15, 0x93, 0xf0, 0x3d, 0x28, 0x31, 0x1b,
|
||||
0x48, 0x4f, 0xa4, 0xe1, 0xa2, 0x1e, 0x10, 0xd0, 0x6f, 0x43, 0x49, 0x46, 0x80, 0xcc, 0xc4, 0x8d,
|
||||
0x64, 0xb1, 0x7b, 0x43, 0x3d, 0x60, 0xc5, 0xdb, 0x70, 0x83, 0x79, 0xa5, 0x4b, 0x9b, 0x6b, 0xe9,
|
||||
0x47, 0xb5, 0xfd, 0xd4, 0x22, 0xed, 0x67, 0x13, 0x8a, 0xc3, 0xb3, 0x4b, 0xd7, 0xec, 0x1a, 0x7d,
|
||||
0x61, 0x85, 0x3f, 0xc6, 0x3f, 0x06, 0xa4, 0x0a, 0x9b, 0x66, 0xb9, 0xb8, 0x0a, 0xe5, 0x4d, 0xc3,
|
||||
0x3d, 0x13, 0x26, 0xe1, 0x97, 0x50, 0xa5, 0xc3, 0xed, 0xf7, 0xd7, 0xb0, 0x91, 0x5d, 0x0e, 0x24,
|
||||
0xf7, 0x54, 0x3e, 0x47, 0x30, 0x7b, 0x66, 0xb8, 0x67, 0x6c, 0xa1, 0x55, 0x9d, 0x3d, 0xa3, 0xe7,
|
||||
0x50, 0xef, 0xf2, 0x45, 0x1e, 0x45, 0xae, 0x0c, 0x73, 0x82, 0xee, 0x77, 0x82, 0x9f, 0x41, 0x85,
|
||||
0xaf, 0xe1, 0xeb, 0x36, 0x02, 0xdf, 0x80, 0xb9, 0x03, 0xcb, 0x18, 0xba, 0x67, 0xb6, 0xac, 0x6e,
|
||||
0x74, 0xd1, 0xf5, 0x80, 0x36, 0x95, 0xc6, 0x67, 0x30, 0xe7, 0x90, 0x81, 0x61, 0x5a, 0xa6, 0x75,
|
||||
0x7a, 0x74, 0x7c, 0xe9, 0x11, 0x57, 0x5c, 0x98, 0x6a, 0x3e, 0xf9, 0x0d, 0xa5, 0x52, 0xd3, 0x8e,
|
||||
0xfb, 0xf6, 0xb1, 0x48, 0x73, 0xec, 0x19, 0x7f, 0xae, 0x41, 0xe5, 0x53, 0xc3, 0xeb, 0xca, 0xad,
|
||||
0x43, 0x5b, 0x50, 0xf3, 0x93, 0x1b, 0xa3, 0x08, 0x5b, 0x22, 0x25, 0x96, 0xcd, 0x91, 0xad, 0xb4,
|
||||
0xac, 0x8e, 0xd5, 0xae, 0x4a, 0x60, 0xa2, 0x0c, 0xab, 0x4b, 0xfa, 0xbe, 0xa8, 0x4c, 0xba, 0x28,
|
||||
0xc6, 0xa8, 0x8a, 0x52, 0x09, 0x6f, 0xe6, 0x82, 0xf6, 0x83, 0xe7, 0x92, 0xcf, 0x33, 0x80, 0xe2,
|
||||
0x36, 0x7c, 0xd9, 0x8e, 0xec, 0x09, 0xd4, 0x5c, 0xcf, 0x70, 0x62, 0x67, 0xa3, 0xca, 0xa8, 0x7e,
|
||||
0x82, 0x7e, 0x06, 0x73, 0x43, 0xc7, 0x3e, 0x75, 0x88, 0xeb, 0x1e, 0x59, 0xb6, 0x67, 0x9e, 0x5c,
|
||||
0x8a, 0xa6, 0xb6, 0x26, 0xc9, 0xbb, 0x8c, 0x8a, 0xda, 0x50, 0x38, 0x31, 0xfb, 0x1e, 0x71, 0xdc,
|
||||
0x46, 0x6e, 0x29, 0xbb, 0x5c, 0x5b, 0x7d, 0x79, 0x95, 0xd7, 0x56, 0x7e, 0xc4, 0xf8, 0x3b, 0x97,
|
||||
0x43, 0xa2, 0xcb, 0xb9, 0x6a, 0xa3, 0x98, 0x0f, 0x35, 0xcf, 0x77, 0xa0, 0xf8, 0x81, 0x8a, 0xa0,
|
||||
0x97, 0xe2, 0x02, 0xef, 0xed, 0xd8, 0x78, 0xab, 0x87, 0x9f, 0x00, 0x04, 0xa2, 0x68, 0x16, 0xde,
|
||||
0xdd, 0xdb, 0x3f, 0xec, 0xd4, 0x67, 0x50, 0x05, 0x8a, 0xbb, 0x7b, 0x1b, 0xed, 0x9d, 0x36, 0x4d,
|
||||
0xd9, 0xb8, 0x25, 0xdd, 0xa6, 0xba, 0x37, 0x24, 0x57, 0x0b, 0xcb, 0xfd, 0xab, 0x0c, 0x54, 0xc5,
|
||||
0x01, 0x99, 0xea, 0x94, 0xaa, 0x2a, 0x32, 0x21, 0x15, 0xb4, 0x61, 0xe5, 0x07, 0xa7, 0x27, 0xfa,
|
||||
0x62, 0x39, 0xa4, 0x69, 0x83, 0x9f, 0x03, 0xd2, 0x13, 0x1e, 0xf7, 0xc7, 0x89, 0x91, 0x9d, 0x4b,
|
||||
0x8c, 0x6c, 0xf4, 0x08, 0xaa, 0xfe, 0x41, 0x34, 0x5c, 0x51, 0x86, 0x4b, 0x7a, 0x45, 0x9e, 0x31,
|
||||
0x4a, 0x43, 0x4f, 0x20, 0x4f, 0xc6, 0xc4, 0xf2, 0xdc, 0x46, 0x99, 0x25, 0xe4, 0xaa, 0x6c, 0x8d,
|
||||
0xdb, 0x94, 0xaa, 0x8b, 0x97, 0xf8, 0xb7, 0xe0, 0x06, 0xbb, 0x82, 0xbc, 0x75, 0x0c, 0x4b, 0xbd,
|
||||
0x2b, 0x75, 0x3a, 0x3b, 0xc2, 0x75, 0xf4, 0x11, 0xd5, 0x20, 0xb3, 0xb5, 0x21, 0x16, 0x9a, 0xd9,
|
||||
0xda, 0xc0, 0x3f, 0xd7, 0x00, 0xa9, 0xf3, 0xa6, 0xf2, 0x65, 0x44, 0xb8, 0x54, 0x9f, 0x0d, 0xd4,
|
||||
0x2f, 0x40, 0x8e, 0x38, 0x8e, 0xed, 0x30, 0xaf, 0x95, 0x74, 0x3e, 0xc0, 0x8f, 0x85, 0x0d, 0x3a,
|
||||
0x19, 0xdb, 0xe7, 0x7e, 0xcc, 0x70, 0x69, 0x9a, 0x6f, 0xea, 0x36, 0xcc, 0x87, 0xb8, 0xa6, 0x2a,
|
||||
0x0c, 0xcf, 0xe0, 0x26, 0x13, 0xb6, 0x4d, 0xc8, 0x70, 0xad, 0x6f, 0x8e, 0x53, 0xb5, 0x0e, 0xe1,
|
||||
0x56, 0x94, 0xf1, 0x9b, 0xf5, 0x11, 0xfe, 0x5d, 0xa1, 0xb1, 0x63, 0x0e, 0x48, 0xc7, 0xde, 0x49,
|
||||
0xb7, 0x8d, 0x26, 0xce, 0x73, 0x72, 0xe9, 0x8a, 0x0a, 0xca, 0x9e, 0xf1, 0x3f, 0x69, 0x70, 0x3b,
|
||||
0x36, 0xfd, 0x1b, 0xde, 0xd5, 0x45, 0x80, 0x53, 0x7a, 0x7c, 0x48, 0x8f, 0xbe, 0xe0, 0x97, 0x77,
|
||||
0x85, 0xe2, 0xdb, 0x49, 0x73, 0x4f, 0x45, 0xd8, 0xb9, 0x20, 0xf6, 0x9c, 0xfd, 0x71, 0x65, 0xf9,
|
||||
0xb9, 0x0f, 0x65, 0x46, 0x38, 0xf0, 0x0c, 0x6f, 0xe4, 0xc6, 0x36, 0xe3, 0x4f, 0xc4, 0x11, 0x90,
|
||||
0x93, 0xa6, 0x5a, 0xd7, 0x77, 0x21, 0xcf, 0xfa, 0x56, 0xd9, 0xb5, 0x45, 0x2e, 0x0a, 0x8a, 0x1d,
|
||||
0xba, 0x60, 0xc4, 0x67, 0x90, 0x7f, 0xc7, 0xc0, 0x3e, 0xc5, 0xb2, 0x59, 0xb9, 0x15, 0x96, 0x31,
|
||||
0xe0, 0x10, 0x44, 0x49, 0x67, 0xcf, 0xac, 0xc9, 0x21, 0xc4, 0x39, 0xd4, 0x77, 0x78, 0x33, 0x55,
|
||||
0xd2, 0xfd, 0x31, 0x75, 0x59, 0xb7, 0x6f, 0x12, 0xcb, 0x63, 0x6f, 0x67, 0xd9, 0x5b, 0x85, 0x82,
|
||||
0x57, 0xa0, 0xce, 0x35, 0xad, 0xf5, 0x7a, 0x4a, 0xb3, 0xe2, 0xcb, 0xd3, 0xc2, 0xf2, 0xf0, 0xaf,
|
||||
0x34, 0xb8, 0xa1, 0x4c, 0x98, 0xca, 0x31, 0xaf, 0x20, 0xcf, 0x21, 0x4d, 0x51, 0x17, 0x17, 0xc2,
|
||||
0xb3, 0xb8, 0x1a, 0x5d, 0xf0, 0xa0, 0x15, 0x28, 0xf0, 0x27, 0xd9, 0x31, 0x26, 0xb3, 0x4b, 0x26,
|
||||
0xfc, 0x04, 0xe6, 0x05, 0x89, 0x0c, 0xec, 0xa4, 0xb3, 0xcd, 0x1c, 0x8a, 0x7f, 0x06, 0x0b, 0x61,
|
||||
0xb6, 0xa9, 0x96, 0xa4, 0x18, 0x99, 0xb9, 0x8e, 0x91, 0x6b, 0xd2, 0xc8, 0xc3, 0x61, 0x4f, 0x29,
|
||||
0xe3, 0xd1, 0x5d, 0x57, 0x77, 0x24, 0x13, 0xd9, 0x11, 0x7f, 0x01, 0x52, 0xc4, 0xb7, 0xba, 0x80,
|
||||
0x79, 0x79, 0x1c, 0x76, 0x4c, 0xd7, 0x6f, 0xee, 0x3e, 0x02, 0x52, 0x89, 0xdf, 0xb6, 0x41, 0x1b,
|
||||
0xe4, 0xc4, 0x31, 0x4e, 0x07, 0xc4, 0xaf, 0x4f, 0xb4, 0xd5, 0x57, 0x89, 0x53, 0x65, 0xf4, 0x16,
|
||||
0xdc, 0x78, 0x67, 0x8f, 0x69, 0x6a, 0xa0, 0xd4, 0x20, 0x64, 0xf8, 0x55, 0xcf, 0xdf, 0x36, 0x7f,
|
||||
0x4c, 0x95, 0xab, 0x13, 0xa6, 0x52, 0xfe, 0xaf, 0x1a, 0x54, 0xd6, 0xfa, 0x86, 0x33, 0x90, 0x8a,
|
||||
0x7f, 0x00, 0x79, 0x7e, 0x81, 0x11, 0x98, 0xc1, 0xd3, 0xb0, 0x18, 0x95, 0x97, 0x0f, 0xd6, 0xf8,
|
||||
0x75, 0x47, 0xcc, 0xa2, 0x86, 0x8b, 0xcf, 0x0a, 0x1b, 0x91, 0xcf, 0x0c, 0x1b, 0xe8, 0x13, 0xc8,
|
||||
0x19, 0x74, 0x0a, 0x4b, 0xc1, 0xb5, 0xe8, 0xd5, 0x91, 0x49, 0x63, 0x7d, 0x1b, 0xe7, 0xc2, 0xdf,
|
||||
0x87, 0xb2, 0xa2, 0x81, 0x5e, 0x8e, 0xdf, 0xb6, 0x45, 0x03, 0xb6, 0xb6, 0xde, 0xd9, 0x7a, 0xcf,
|
||||
0xef, 0xcc, 0x35, 0x80, 0x8d, 0xb6, 0x3f, 0xce, 0xe0, 0xcf, 0xc4, 0x2c, 0x91, 0xef, 0x54, 0x7b,
|
||||
0xb4, 0x34, 0x7b, 0x32, 0xd7, 0xb2, 0xe7, 0x02, 0xaa, 0x62, 0xf9, 0xd3, 0xa6, 0x6f, 0x26, 0x2f,
|
||||
0x25, 0x7d, 0x2b, 0xc6, 0xeb, 0x82, 0x11, 0xcf, 0x41, 0x55, 0x24, 0x74, 0x71, 0xfe, 0xfe, 0x3a,
|
||||
0x03, 0x35, 0x49, 0x99, 0x16, 0xdb, 0x94, 0xb0, 0x0c, 0xaf, 0x00, 0x3e, 0x28, 0x73, 0x0b, 0xf2,
|
||||
0xbd, 0xe3, 0x03, 0xf3, 0xa3, 0xc4, 0xa1, 0xc5, 0x88, 0xd2, 0xfb, 0x5c, 0x0f, 0xff, 0x18, 0x24,
|
||||
0x46, 0xf4, 0x82, 0xee, 0x18, 0x27, 0xde, 0x96, 0xd5, 0x23, 0x17, 0xac, 0x6f, 0x9c, 0xd5, 0x03,
|
||||
0x02, 0xbb, 0xaf, 0x8a, 0x8f, 0x46, 0xac, 0x59, 0x54, 0x3e, 0x22, 0xa1, 0x17, 0x50, 0xa7, 0xcf,
|
||||
0x6b, 0xc3, 0x61, 0xdf, 0x24, 0x3d, 0x2e, 0xa0, 0xc0, 0x78, 0x62, 0x74, 0xaa, 0x9d, 0xb5, 0x5e,
|
||||
0x6e, 0xa3, 0xc8, 0xd2, 0x96, 0x18, 0xd1, 0x28, 0x5d, 0x1b, 0x79, 0x67, 0x6d, 0xcb, 0x38, 0xee,
|
||||
0xcb, 0xac, 0x47, 0x4b, 0x35, 0x25, 0x6e, 0x98, 0xae, 0x4a, 0x6d, 0xc3, 0x3c, 0xa5, 0x12, 0xcb,
|
||||
0x33, 0xbb, 0x4a, 0x8a, 0x94, 0x85, 0x50, 0x8b, 0x14, 0x42, 0xc3, 0x75, 0x3f, 0xd8, 0x4e, 0x4f,
|
||||
0xb8, 0xc7, 0x1f, 0xe3, 0x0d, 0x2e, 0xfc, 0xd0, 0x0d, 0x95, 0xba, 0x2f, 0x2b, 0x65, 0x39, 0x90,
|
||||
0xf2, 0x96, 0x78, 0x13, 0xa4, 0xe0, 0x97, 0x70, 0x53, 0x72, 0x0a, 0xec, 0x70, 0x02, 0xf3, 0x1e,
|
||||
0xdc, 0x97, 0xcc, 0xeb, 0x67, 0xf4, 0x72, 0xb6, 0x2f, 0x14, 0x7e, 0x55, 0x3b, 0xdf, 0x40, 0xc3,
|
||||
0xb7, 0x93, 0x35, 0xdc, 0x76, 0x5f, 0x35, 0x60, 0xe4, 0x8a, 0x73, 0x57, 0xd2, 0xd9, 0x33, 0xa5,
|
||||
0x39, 0x76, 0xdf, 0x6f, 0x2b, 0xe8, 0x33, 0x5e, 0x87, 0x3b, 0x52, 0x86, 0x68, 0x85, 0xc3, 0x42,
|
||||
0x62, 0x06, 0x25, 0x09, 0x11, 0x0e, 0xa3, 0x53, 0x27, 0xbb, 0x5d, 0xe5, 0x0c, 0xbb, 0x96, 0xc9,
|
||||
0xd4, 0x14, 0x99, 0x37, 0xf9, 0x89, 0xa0, 0x86, 0xa9, 0x55, 0x47, 0x90, 0xa9, 0x00, 0x95, 0x2c,
|
||||
0x36, 0x82, 0x92, 0x63, 0x1b, 0x11, 0x13, 0xfd, 0x53, 0x58, 0xf4, 0x8d, 0xa0, 0x7e, 0xdb, 0x27,
|
||||
0xce, 0xc0, 0x74, 0x5d, 0x05, 0x6d, 0x4a, 0x5a, 0xf8, 0x53, 0x98, 0x1d, 0x12, 0x91, 0x97, 0xca,
|
||||
0xab, 0x68, 0x85, 0x7f, 0x1e, 0x5e, 0x51, 0x26, 0xb3, 0xf7, 0xb8, 0x07, 0x0f, 0xa4, 0x74, 0xee,
|
||||
0xd1, 0x44, 0xf1, 0x51, 0xa3, 0xe4, 0xa5, 0x9e, 0xbb, 0x35, 0x7e, 0xa9, 0xcf, 0xf2, 0xbd, 0xf7,
|
||||
0x11, 0xd0, 0x1f, 0x73, 0x47, 0xca, 0xd8, 0x9a, 0xaa, 0xde, 0x6c, 0x73, 0x9f, 0xfa, 0x21, 0x39,
|
||||
0x95, 0xb0, 0x63, 0x58, 0x08, 0x47, 0xf2, 0x54, 0xa9, 0x70, 0x01, 0x72, 0x9e, 0x7d, 0x4e, 0x64,
|
||||
0x22, 0xe4, 0x03, 0x69, 0xb0, 0x1f, 0xe6, 0x53, 0x19, 0x6c, 0x04, 0xc2, 0xd8, 0x91, 0x9c, 0xd6,
|
||||
0x5e, 0xba, 0x9b, 0xb2, 0x81, 0xe3, 0x03, 0xbc, 0x0b, 0xb7, 0xa2, 0x69, 0x62, 0x2a, 0x93, 0xdf,
|
||||
0xf3, 0x03, 0x9c, 0x94, 0x49, 0xa6, 0x92, 0xfb, 0x93, 0x20, 0x19, 0x28, 0x09, 0x65, 0x2a, 0x91,
|
||||
0x3a, 0x34, 0x93, 0xf2, 0xcb, 0xd7, 0x71, 0x5e, 0xfd, 0x74, 0x33, 0x95, 0x30, 0x37, 0x10, 0x36,
|
||||
0xfd, 0xf6, 0x07, 0x39, 0x22, 0x3b, 0x31, 0x47, 0x88, 0x20, 0x09, 0xb2, 0xd8, 0x37, 0x70, 0xe8,
|
||||
0x84, 0x8e, 0x20, 0x81, 0x4e, 0xab, 0x83, 0xd6, 0x10, 0x5f, 0x07, 0x1b, 0xc8, 0x83, 0xad, 0xa6,
|
||||
0xdd, 0xa9, 0x36, 0xe3, 0xd3, 0x20, 0x77, 0xc6, 0x32, 0xf3, 0x54, 0x82, 0x3f, 0x83, 0xa5, 0xf4,
|
||||
0xa4, 0x3c, 0x8d, 0xe4, 0x17, 0x2d, 0x28, 0xf9, 0x4d, 0xa9, 0xf2, 0xd3, 0x8a, 0x32, 0x14, 0x76,
|
||||
0xf7, 0x0e, 0xf6, 0xd7, 0xd6, 0xdb, 0xfc, 0xb7, 0x15, 0xeb, 0x7b, 0xba, 0x7e, 0xb8, 0xdf, 0xa9,
|
||||
0x67, 0x56, 0xff, 0x37, 0x0b, 0x99, 0xed, 0xf7, 0xe8, 0xf7, 0x21, 0xc7, 0x3f, 0x34, 0x4e, 0xf8,
|
||||
0xba, 0xdc, 0x9c, 0xf4, 0x2d, 0x15, 0xdf, 0xfd, 0xf9, 0xbf, 0xff, 0xd7, 0x2f, 0x33, 0x37, 0x71,
|
||||
0xbd, 0x35, 0xfe, 0xde, 0x31, 0xf1, 0x8c, 0xd6, 0xf9, 0xb8, 0xc5, 0xea, 0xc3, 0x6b, 0xed, 0x05,
|
||||
0x3a, 0x84, 0xec, 0xfe, 0xc8, 0x43, 0xa9, 0x5f, 0x9e, 0x9b, 0xe9, 0x9f, 0x58, 0xf1, 0x1d, 0x26,
|
||||
0x78, 0x1e, 0xd7, 0x14, 0xc1, 0xc3, 0x91, 0x47, 0xc5, 0x8e, 0xa0, 0xac, 0x7e, 0x24, 0xbd, 0xf2,
|
||||
0x93, 0x74, 0xf3, 0xea, 0x0f, 0xb0, 0xf8, 0x21, 0x53, 0x77, 0x17, 0xdf, 0x52, 0xd4, 0xf1, 0x4f,
|
||||
0xb9, 0xea, 0x6a, 0x3a, 0x17, 0x16, 0x4a, 0xfd, 0x68, 0xdd, 0x4c, 0xff, 0x2e, 0x9b, 0xb8, 0x1a,
|
||||
0xef, 0xc2, 0xa2, 0x62, 0x2d, 0xf1, 0x59, 0xb6, 0xeb, 0xa1, 0x07, 0x09, 0x9f, 0xe5, 0xd4, 0x0f,
|
||||
0x50, 0xcd, 0xa5, 0x74, 0x06, 0xa1, 0x68, 0x89, 0x29, 0x6a, 0xe2, 0x9b, 0x8a, 0xa2, 0xae, 0xcf,
|
||||
0xf6, 0x5a, 0x7b, 0xb1, 0x7a, 0x0a, 0x39, 0x86, 0x32, 0xa3, 0x3f, 0x90, 0x0f, 0xcd, 0x04, 0xe8,
|
||||
0x3c, 0x65, 0xf3, 0x43, 0xf8, 0x34, 0x6e, 0x30, 0x65, 0x08, 0x57, 0xa5, 0x32, 0x86, 0x33, 0xbf,
|
||||
0xd6, 0x5e, 0x2c, 0x6b, 0xdf, 0xd1, 0x56, 0xff, 0x67, 0x16, 0x72, 0x0c, 0x72, 0x42, 0x36, 0x40,
|
||||
0x80, 0xc8, 0x46, 0x57, 0x19, 0xc3, 0x78, 0xa3, 0xab, 0x8c, 0x83, 0xb9, 0x78, 0x91, 0x29, 0x6e,
|
||||
0xe0, 0x79, 0xa9, 0x98, 0xa1, 0x59, 0x2d, 0x06, 0xd0, 0x51, 0x9f, 0x8e, 0x05, 0xe8, 0xc6, 0xc3,
|
||||
0x0c, 0x25, 0x09, 0x0c, 0x21, 0xb3, 0xd1, 0x13, 0x92, 0x80, 0xca, 0x62, 0xcc, 0x74, 0xde, 0xc3,
|
||||
0xb7, 0x15, 0xcf, 0x72, 0xb5, 0x0e, 0x63, 0xa4, 0x7a, 0xff, 0x4c, 0x83, 0x5a, 0x18, 0x5b, 0x45,
|
||||
0x8f, 0x12, 0x24, 0x47, 0x21, 0xda, 0xe6, 0xe3, 0xc9, 0x4c, 0x69, 0x16, 0x70, 0xf5, 0xe7, 0x84,
|
||||
0x0c, 0x0d, 0xca, 0x28, 0x1c, 0x8f, 0xfe, 0x42, 0x83, 0xb9, 0x08, 0x60, 0x8a, 0x92, 0x34, 0xc4,
|
||||
0xe0, 0xd8, 0xe6, 0x93, 0x2b, 0xb8, 0x84, 0x21, 0x4f, 0x99, 0x21, 0x4b, 0xf8, 0x6e, 0xcc, 0x15,
|
||||
0x9e, 0x39, 0x20, 0x9e, 0x2d, 0x8c, 0xf1, 0xb7, 0x81, 0x83, 0x9b, 0x89, 0xdb, 0x10, 0x02, 0x4b,
|
||||
0x13, 0xb7, 0x21, 0x8c, 0x8c, 0x4e, 0xd8, 0x06, 0x8e, 0x68, 0xd2, 0x23, 0xfe, 0x7f, 0x59, 0x28,
|
||||
0xac, 0xf3, 0x1f, 0x38, 0x22, 0x17, 0x4a, 0x3e, 0x8a, 0x88, 0x16, 0x93, 0x10, 0x9d, 0xe0, 0xb6,
|
||||
0xd0, 0x7c, 0x90, 0xfa, 0x5e, 0x68, 0x7f, 0xc2, 0xb4, 0x3f, 0xc0, 0x4d, 0xa9, 0x5d, 0xfc, 0x8e,
|
||||
0xb2, 0xc5, 0xa1, 0x83, 0x96, 0xd1, 0xeb, 0xd1, 0x85, 0xff, 0x29, 0x54, 0x54, 0xa8, 0x0f, 0x3d,
|
||||
0x4c, 0x44, 0x92, 0x54, 0xb4, 0xb0, 0x89, 0x27, 0xb1, 0x08, 0xed, 0xcb, 0x4c, 0x3b, 0xc6, 0xf7,
|
||||
0x53, 0xb4, 0x3b, 0x8c, 0x3d, 0x64, 0x00, 0x87, 0xea, 0x92, 0x0d, 0x08, 0x21, 0x81, 0xc9, 0x06,
|
||||
0x84, 0x91, 0xbe, 0x2b, 0x0d, 0x18, 0x31, 0x76, 0x6a, 0xc0, 0x07, 0x80, 0x00, 0x98, 0x43, 0x89,
|
||||
0x7e, 0x55, 0xae, 0x4e, 0xd1, 0x90, 0x8f, 0x63, 0x7a, 0xf1, 0x33, 0x17, 0x51, 0xdd, 0x37, 0x5d,
|
||||
0x1a, 0xfa, 0xab, 0xbf, 0xca, 0x43, 0xf9, 0x9d, 0x61, 0x5a, 0x1e, 0xb1, 0x0c, 0xab, 0x4b, 0xd0,
|
||||
0x09, 0xe4, 0x58, 0x69, 0x8c, 0x66, 0x39, 0x15, 0xaf, 0x8a, 0x66, 0xb9, 0x10, 0x98, 0x83, 0x1f,
|
||||
0x33, 0xcd, 0x8b, 0xf8, 0x8e, 0xd4, 0x3c, 0x08, 0xc4, 0xb7, 0x18, 0x0e, 0x43, 0x17, 0xfc, 0x87,
|
||||
0x90, 0x17, 0x10, 0x7f, 0x44, 0x58, 0x08, 0x9f, 0x69, 0xde, 0x4b, 0x7e, 0x99, 0x76, 0xbc, 0x54,
|
||||
0x55, 0x2e, 0xe3, 0xa5, 0xba, 0x3e, 0x02, 0x04, 0x20, 0x63, 0xd4, 0xb9, 0x31, 0x4c, 0xb2, 0xb9,
|
||||
0x94, 0xce, 0x20, 0xf4, 0x3e, 0x67, 0x7a, 0x1f, 0xe1, 0xc5, 0x24, 0xbd, 0x3d, 0x9f, 0x9f, 0xea,
|
||||
0x3e, 0x86, 0xd9, 0x4d, 0xc3, 0x3d, 0x43, 0x91, 0x62, 0xa7, 0xfc, 0x26, 0xa1, 0xd9, 0x4c, 0x7a,
|
||||
0x25, 0x34, 0x3d, 0x62, 0x9a, 0xee, 0xe3, 0x46, 0x92, 0xa6, 0x33, 0xc3, 0xa5, 0xd5, 0x03, 0x9d,
|
||||
0x41, 0x9e, 0xff, 0x4c, 0x21, 0xea, 0xcb, 0xd0, 0x4f, 0x1d, 0xa2, 0xbe, 0x0c, 0xff, 0xb2, 0xe1,
|
||||
0x7a, 0x9a, 0x3c, 0x28, 0xca, 0xdf, 0x06, 0xa0, 0xfb, 0x91, 0xad, 0x09, 0xff, 0x8e, 0xa0, 0xb9,
|
||||
0x98, 0xf6, 0x5a, 0xe8, 0x7b, 0xc6, 0xf4, 0x3d, 0xc4, 0xf7, 0x12, 0xf7, 0x4e, 0x70, 0xbf, 0xd6,
|
||||
0x5e, 0x7c, 0x47, 0xa3, 0x65, 0x02, 0x02, 0xa0, 0x36, 0x16, 0x1d, 0x51, 0xcc, 0x37, 0x16, 0x1d,
|
||||
0x31, 0x8c, 0x17, 0xaf, 0x32, 0xe5, 0xaf, 0xf0, 0xb3, 0x24, 0xe5, 0x9e, 0x63, 0x58, 0xee, 0x09,
|
||||
0x71, 0x3e, 0xe1, 0x80, 0x9c, 0x7b, 0x66, 0x0e, 0x69, 0xa4, 0xfc, 0xff, 0x1c, 0xcc, 0xd2, 0x7e,
|
||||
0x94, 0x96, 0xe7, 0xe0, 0x1a, 0x1f, 0xb5, 0x26, 0x06, 0x9e, 0x45, 0xad, 0x89, 0x23, 0x00, 0xf1,
|
||||
0xf2, 0xcc, 0x7e, 0xca, 0x4e, 0x18, 0x13, 0xf5, 0xba, 0x0b, 0x65, 0xe5, 0xae, 0x8f, 0x12, 0x04,
|
||||
0x86, 0x91, 0xb9, 0x68, 0x5d, 0x48, 0x00, 0x0a, 0xf0, 0x03, 0xa6, 0xf3, 0x0e, 0x5e, 0x08, 0xe9,
|
||||
0xec, 0x71, 0x2e, 0xaa, 0xf4, 0x8f, 0xa1, 0xa2, 0x62, 0x02, 0x28, 0x41, 0x66, 0x04, 0xf9, 0x8b,
|
||||
0xa6, 0xc4, 0x24, 0x48, 0x21, 0x9e, 0x1d, 0xfc, 0x9f, 0xed, 0x4b, 0x56, 0xaa, 0x7c, 0x08, 0x05,
|
||||
0x01, 0x14, 0x24, 0xad, 0x36, 0x0c, 0x15, 0x26, 0xad, 0x36, 0x82, 0x32, 0xc4, 0xdb, 0x3c, 0xa6,
|
||||
0x95, 0xde, 0x87, 0x64, 0x09, 0x12, 0x1a, 0xdf, 0x12, 0x2f, 0x4d, 0x63, 0x80, 0x7d, 0xa5, 0x69,
|
||||
0x54, 0xee, 0xa2, 0x93, 0x34, 0x9e, 0x12, 0x4f, 0xc4, 0x92, 0xbc, 0xe7, 0xa1, 0x14, 0x81, 0x6a,
|
||||
0xca, 0xc7, 0x93, 0x58, 0xd2, 0xba, 0xf2, 0x40, 0xa9, 0xc8, 0xf7, 0xe8, 0x67, 0x00, 0x01, 0xa4,
|
||||
0x11, 0xed, 0xb6, 0x12, 0x71, 0xd1, 0x68, 0xb7, 0x95, 0x8c, 0x8a, 0xc4, 0xf3, 0x47, 0xa0, 0x9b,
|
||||
0x5f, 0x0c, 0xa8, 0xf6, 0xbf, 0xd5, 0x00, 0xc5, 0x11, 0x10, 0xf4, 0x32, 0x59, 0x43, 0x22, 0xe2,
|
||||
0xda, 0x7c, 0x75, 0x3d, 0xe6, 0xb4, 0x12, 0x11, 0x98, 0xd5, 0x65, 0x33, 0x86, 0x1f, 0xa8, 0x61,
|
||||
0xbf, 0xd0, 0xa0, 0x1a, 0x82, 0x50, 0xd0, 0xd3, 0x94, 0x3d, 0x8e, 0x80, 0xb6, 0xcd, 0x67, 0x57,
|
||||
0xf2, 0xa5, 0x75, 0x62, 0xca, 0x89, 0x90, 0x8d, 0xf8, 0x5f, 0x6a, 0x50, 0x0b, 0xc3, 0x2e, 0x28,
|
||||
0x45, 0x7e, 0x0c, 0xf8, 0x6d, 0x2e, 0x5f, 0xcd, 0x78, 0xf5, 0x56, 0x05, 0xbd, 0xf9, 0x10, 0x0a,
|
||||
0x02, 0xac, 0x49, 0x0a, 0x88, 0x30, 0x6c, 0x9c, 0x14, 0x10, 0x11, 0xa4, 0x27, 0x25, 0x20, 0x1c,
|
||||
0xbb, 0x4f, 0x94, 0x10, 0x14, 0x88, 0x4e, 0x9a, 0xc6, 0xc9, 0x21, 0x18, 0x81, 0x83, 0x26, 0x69,
|
||||
0x0c, 0x42, 0x50, 0xc2, 0x39, 0x28, 0x45, 0xe0, 0x15, 0x21, 0x18, 0x45, 0x83, 0x52, 0x42, 0x90,
|
||||
0x29, 0x55, 0x42, 0x30, 0x00, 0x5f, 0x92, 0x42, 0x30, 0x86, 0x88, 0x27, 0x85, 0x60, 0x1c, 0xbf,
|
||||
0x49, 0xd9, 0x57, 0xa6, 0x3b, 0x14, 0x82, 0xf3, 0x09, 0x58, 0x0d, 0x7a, 0x95, 0xe2, 0xd0, 0x44,
|
||||
0xb0, 0xbd, 0xf9, 0xc9, 0x35, 0xb9, 0x27, 0x9e, 0x7d, 0xbe, 0x15, 0xf2, 0xec, 0xff, 0x83, 0x06,
|
||||
0x0b, 0x49, 0x58, 0x0f, 0x4a, 0xd1, 0x95, 0x02, 0xd4, 0x37, 0x57, 0xae, 0xcb, 0x7e, 0xb5, 0xd7,
|
||||
0xfc, 0x68, 0x78, 0x53, 0xff, 0x97, 0x2f, 0x16, 0xb5, 0x7f, 0xfb, 0x62, 0x51, 0xfb, 0x8f, 0x2f,
|
||||
0x16, 0xb5, 0xbf, 0xfb, 0xcf, 0xc5, 0x99, 0xe3, 0x3c, 0xfb, 0x0f, 0x64, 0xdf, 0xfb, 0x4d, 0x00,
|
||||
0x00, 0x00, 0xff, 0xff, 0x80, 0x82, 0x49, 0x96, 0xd9, 0x36, 0x00, 0x00,
|
||||
// 3738 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xbc, 0x5b, 0xdd, 0x6f, 0x1b, 0xc7,
|
||||
0x76, 0xd7, 0x92, 0xe2, 0xd7, 0xe1, 0x87, 0xa8, 0x91, 0x64, 0xd3, 0xb4, 0x2d, 0xcb, 0x63, 0x3b,
|
||||
0x56, 0xec, 0x44, 0x4c, 0x94, 0xa4, 0x05, 0xdc, 0x36, 0x88, 0x2c, 0x31, 0x96, 0x22, 0x59, 0x52,
|
||||
0x56, 0x94, 0xf3, 0x81, 0xa0, 0xc2, 0x8a, 0x1c, 0x49, 0x5b, 0x91, 0xbb, 0xcc, 0xee, 0x92, 0x96,
|
||||
0xd2, 0xa2, 0x29, 0x82, 0xf4, 0xa1, 0x05, 0xfa, 0x92, 0x00, 0x45, 0xfb, 0xd0, 0xa7, 0xa2, 0x28,
|
||||
0xf2, 0x50, 0xa0, 0x2f, 0xc1, 0x05, 0xee, 0x5f, 0x70, 0xdf, 0xee, 0x05, 0xee, 0x3f, 0x70, 0x91,
|
||||
0x9b, 0x97, 0xfb, 0x5f, 0x5c, 0xcc, 0xd7, 0xee, 0xec, 0x72, 0x57, 0x72, 0xc2, 0x24, 0x2f, 0xf2,
|
||||
0xce, 0xcc, 0x99, 0xf3, 0x3b, 0x73, 0x66, 0xe6, 0x9c, 0x99, 0xdf, 0xd0, 0x50, 0x70, 0xfa, 0xed,
|
||||
0xa5, 0xbe, 0x63, 0x7b, 0x36, 0x2a, 0x11, 0xaf, 0xdd, 0x71, 0x89, 0x33, 0x24, 0x4e, 0xff, 0xb0,
|
||||
0x3e, 0x7b, 0x6c, 0x1f, 0xdb, 0xac, 0xa1, 0x41, 0xbf, 0xb8, 0x4c, 0xfd, 0x1a, 0x95, 0x69, 0xf4,
|
||||
0x86, 0xed, 0x36, 0xfb, 0xd3, 0x3f, 0x6c, 0x9c, 0x0e, 0x45, 0xd3, 0x75, 0xd6, 0x64, 0x0c, 0xbc,
|
||||
0x13, 0xf6, 0xa7, 0x7f, 0xc8, 0xfe, 0x11, 0x8d, 0x37, 0x8e, 0x6d, 0xfb, 0xb8, 0x4b, 0x1a, 0x46,
|
||||
0xdf, 0x6c, 0x18, 0x96, 0x65, 0x7b, 0x86, 0x67, 0xda, 0x96, 0xcb, 0x5b, 0xf1, 0x3f, 0x6b, 0x50,
|
||||
0xd1, 0x89, 0xdb, 0xb7, 0x2d, 0x97, 0xac, 0x13, 0xa3, 0x43, 0x1c, 0x74, 0x13, 0xa0, 0xdd, 0x1d,
|
||||
0xb8, 0x1e, 0x71, 0x0e, 0xcc, 0x4e, 0x4d, 0x5b, 0xd0, 0x16, 0x27, 0xf5, 0x82, 0xa8, 0xd9, 0xe8,
|
||||
0xa0, 0xeb, 0x50, 0xe8, 0x91, 0xde, 0x21, 0x6f, 0x4d, 0xb1, 0xd6, 0x3c, 0xaf, 0xd8, 0xe8, 0xa0,
|
||||
0x3a, 0xe4, 0x1d, 0x32, 0x34, 0x5d, 0xd3, 0xb6, 0x6a, 0xe9, 0x05, 0x6d, 0x31, 0xad, 0xfb, 0x65,
|
||||
0xda, 0xd1, 0x31, 0x8e, 0xbc, 0x03, 0x8f, 0x38, 0xbd, 0xda, 0x24, 0xef, 0x48, 0x2b, 0x5a, 0xc4,
|
||||
0xe9, 0xe1, 0x2f, 0x33, 0x50, 0xd2, 0x0d, 0xeb, 0x98, 0xe8, 0xe4, 0xd3, 0x01, 0x71, 0x3d, 0x54,
|
||||
0x85, 0xf4, 0x29, 0x39, 0x67, 0xf0, 0x25, 0x9d, 0x7e, 0xf2, 0xfe, 0xd6, 0x31, 0x39, 0x20, 0x16,
|
||||
0x07, 0x2e, 0xd1, 0xfe, 0xd6, 0x31, 0x69, 0x5a, 0x1d, 0x34, 0x0b, 0x99, 0xae, 0xd9, 0x33, 0x3d,
|
||||
0x81, 0xca, 0x0b, 0x21, 0x73, 0x26, 0x23, 0xe6, 0xac, 0x02, 0xb8, 0xb6, 0xe3, 0x1d, 0xd8, 0x4e,
|
||||
0x87, 0x38, 0xb5, 0xcc, 0x82, 0xb6, 0x58, 0x59, 0xbe, 0xbb, 0xa4, 0x4e, 0xc4, 0x92, 0x6a, 0xd0,
|
||||
0xd2, 0x9e, 0xed, 0x78, 0x3b, 0x54, 0x56, 0x2f, 0xb8, 0xf2, 0x13, 0xbd, 0x0b, 0x45, 0xa6, 0xc4,
|
||||
0x33, 0x9c, 0x63, 0xe2, 0xd5, 0xb2, 0x4c, 0xcb, 0xbd, 0x4b, 0xb4, 0xb4, 0x98, 0xb0, 0xce, 0xe0,
|
||||
0xf9, 0x37, 0xc2, 0x50, 0x72, 0x89, 0x63, 0x1a, 0x5d, 0xf3, 0x33, 0xe3, 0xb0, 0x4b, 0x6a, 0xb9,
|
||||
0x05, 0x6d, 0x31, 0xaf, 0x87, 0xea, 0xe8, 0xf8, 0x4f, 0xc9, 0xb9, 0x7b, 0x60, 0x5b, 0xdd, 0xf3,
|
||||
0x5a, 0x9e, 0x09, 0xe4, 0x69, 0xc5, 0x8e, 0xd5, 0x3d, 0x67, 0x93, 0x66, 0x0f, 0x2c, 0x8f, 0xb7,
|
||||
0x16, 0x58, 0x6b, 0x81, 0xd5, 0xb0, 0xe6, 0x45, 0xa8, 0xf6, 0x4c, 0xeb, 0xa0, 0x67, 0x77, 0x0e,
|
||||
0x7c, 0x87, 0x00, 0x73, 0x48, 0xa5, 0x67, 0x5a, 0x4f, 0xed, 0x8e, 0x2e, 0xdd, 0x42, 0x25, 0x8d,
|
||||
0xb3, 0xb0, 0x64, 0x51, 0x48, 0x1a, 0x67, 0xaa, 0xe4, 0x12, 0xcc, 0x50, 0x9d, 0x6d, 0x87, 0x18,
|
||||
0x1e, 0x09, 0x84, 0x4b, 0x4c, 0x78, 0xba, 0x67, 0x5a, 0xab, 0xac, 0x25, 0x24, 0x6f, 0x9c, 0x8d,
|
||||
0xc8, 0x97, 0x85, 0xbc, 0x71, 0x16, 0x96, 0xc7, 0x4b, 0x50, 0xf0, 0x7d, 0x8e, 0xf2, 0x30, 0xb9,
|
||||
0xbd, 0xb3, 0xdd, 0xac, 0x4e, 0x20, 0x80, 0xec, 0xca, 0xde, 0x6a, 0x73, 0x7b, 0xad, 0xaa, 0xa1,
|
||||
0x22, 0xe4, 0xd6, 0x9a, 0xbc, 0x90, 0xc2, 0x8f, 0x01, 0x02, 0xef, 0xa2, 0x1c, 0xa4, 0x37, 0x9b,
|
||||
0x1f, 0x55, 0x27, 0xa8, 0xcc, 0xb3, 0xa6, 0xbe, 0xb7, 0xb1, 0xb3, 0x5d, 0xd5, 0x68, 0xe7, 0x55,
|
||||
0xbd, 0xb9, 0xd2, 0x6a, 0x56, 0x53, 0x54, 0xe2, 0xe9, 0xce, 0x5a, 0x35, 0x8d, 0x0a, 0x90, 0x79,
|
||||
0xb6, 0xb2, 0xb5, 0xdf, 0xac, 0x4e, 0xe2, 0xaf, 0x35, 0x28, 0x8b, 0xf9, 0xe2, 0x7b, 0x02, 0xbd,
|
||||
0x09, 0xd9, 0x13, 0xb6, 0x2f, 0xd8, 0x52, 0x2c, 0x2e, 0xdf, 0x88, 0x4c, 0x6e, 0x68, 0xef, 0xe8,
|
||||
0x42, 0x16, 0x61, 0x48, 0x9f, 0x0e, 0xdd, 0x5a, 0x6a, 0x21, 0xbd, 0x58, 0x5c, 0xae, 0x2e, 0xf1,
|
||||
0x0d, 0xbb, 0xb4, 0x49, 0xce, 0x9f, 0x19, 0xdd, 0x01, 0xd1, 0x69, 0x23, 0x42, 0x30, 0xd9, 0xb3,
|
||||
0x1d, 0xc2, 0x56, 0x6c, 0x5e, 0x67, 0xdf, 0x74, 0x19, 0xb3, 0x49, 0x13, 0xab, 0x95, 0x17, 0xf0,
|
||||
0x37, 0x1a, 0xc0, 0xee, 0xc0, 0x4b, 0xde, 0x1a, 0xb3, 0x90, 0x19, 0x52, 0xc5, 0x62, 0x5b, 0xf0,
|
||||
0x02, 0xdb, 0x13, 0xc4, 0x70, 0x89, 0xbf, 0x27, 0x68, 0x01, 0x5d, 0x85, 0x5c, 0xdf, 0x21, 0xc3,
|
||||
0x83, 0xd3, 0x21, 0x03, 0xc9, 0xeb, 0x59, 0x5a, 0xdc, 0x1c, 0xa2, 0xdb, 0x50, 0x32, 0x8f, 0x2d,
|
||||
0xdb, 0x21, 0x07, 0x5c, 0x57, 0x86, 0xb5, 0x16, 0x79, 0x1d, 0xb3, 0x5b, 0x11, 0xe1, 0x8a, 0xb3,
|
||||
0xaa, 0xc8, 0x16, 0xad, 0xc2, 0x16, 0x14, 0x99, 0xa9, 0x63, 0xb9, 0xef, 0xe5, 0xc0, 0xc6, 0x14,
|
||||
0xeb, 0x36, 0xea, 0x42, 0x61, 0x35, 0xfe, 0x04, 0xd0, 0x1a, 0xe9, 0x12, 0x8f, 0x8c, 0x13, 0x3d,
|
||||
0x14, 0x9f, 0xa4, 0x55, 0x9f, 0xe0, 0xaf, 0x34, 0x98, 0x09, 0xa9, 0x1f, 0x6b, 0x58, 0x35, 0xc8,
|
||||
0x75, 0x98, 0x32, 0x6e, 0x41, 0x5a, 0x97, 0x45, 0xf4, 0x10, 0xf2, 0xc2, 0x00, 0xb7, 0x96, 0x4e,
|
||||
0x58, 0x34, 0x39, 0x6e, 0x93, 0x8b, 0xbf, 0x49, 0x41, 0x41, 0x0c, 0x74, 0xa7, 0x8f, 0x56, 0xa0,
|
||||
0xec, 0xf0, 0xc2, 0x01, 0x1b, 0x8f, 0xb0, 0xa8, 0x9e, 0x1c, 0x84, 0xd6, 0x27, 0xf4, 0x92, 0xe8,
|
||||
0xc2, 0xaa, 0xd1, 0x5f, 0x41, 0x51, 0xaa, 0xe8, 0x0f, 0x3c, 0xe1, 0xf2, 0x5a, 0x58, 0x41, 0xb0,
|
||||
0xfe, 0xd6, 0x27, 0x74, 0x10, 0xe2, 0xbb, 0x03, 0x0f, 0xb5, 0x60, 0x56, 0x76, 0xe6, 0xa3, 0x11,
|
||||
0x66, 0xa4, 0x99, 0x96, 0x85, 0xb0, 0x96, 0xd1, 0xa9, 0x5a, 0x9f, 0xd0, 0x91, 0xe8, 0xaf, 0x34,
|
||||
0xaa, 0x26, 0x79, 0x67, 0x3c, 0x78, 0x8f, 0x98, 0xd4, 0x3a, 0xb3, 0x46, 0x4d, 0x6a, 0x9d, 0x59,
|
||||
0x8f, 0x0b, 0x90, 0x13, 0x25, 0xfc, 0xab, 0x14, 0x80, 0x9c, 0x8d, 0x9d, 0x3e, 0x5a, 0x83, 0x8a,
|
||||
0x23, 0x4a, 0x21, 0x6f, 0x5d, 0x8f, 0xf5, 0x96, 0x98, 0xc4, 0x09, 0xbd, 0x2c, 0x3b, 0x71, 0xe3,
|
||||
0xde, 0x86, 0x92, 0xaf, 0x25, 0x70, 0xd8, 0xb5, 0x18, 0x87, 0xf9, 0x1a, 0x8a, 0xb2, 0x03, 0x75,
|
||||
0xd9, 0x07, 0x30, 0xe7, 0xf7, 0x8f, 0xf1, 0xd9, 0xed, 0x0b, 0x7c, 0xe6, 0x2b, 0x9c, 0x91, 0x1a,
|
||||
0x54, 0xaf, 0xa9, 0x86, 0x05, 0x6e, 0xbb, 0x16, 0xe3, 0xb6, 0x51, 0xc3, 0xa8, 0xe3, 0x80, 0xe6,
|
||||
0x4b, 0x5e, 0xc4, 0x7f, 0x4a, 0x43, 0x6e, 0xd5, 0xee, 0xf5, 0x0d, 0x87, 0xce, 0x46, 0xd6, 0x21,
|
||||
0xee, 0xa0, 0xeb, 0x31, 0x77, 0x55, 0x96, 0xef, 0x84, 0x35, 0x0a, 0x31, 0xf9, 0xaf, 0xce, 0x44,
|
||||
0x75, 0xd1, 0x85, 0x76, 0x16, 0xe9, 0x31, 0xf5, 0x02, 0x9d, 0x45, 0x72, 0x14, 0x5d, 0xe4, 0x46,
|
||||
0x4e, 0x07, 0x1b, 0xb9, 0x0e, 0xb9, 0x21, 0x71, 0x82, 0x94, 0xbe, 0x3e, 0xa1, 0xcb, 0x0a, 0xf4,
|
||||
0x32, 0x4c, 0x45, 0xd3, 0x4b, 0x46, 0xc8, 0x54, 0xda, 0xe1, 0x6c, 0x74, 0x07, 0x4a, 0xa1, 0x1c,
|
||||
0x97, 0x15, 0x72, 0xc5, 0x9e, 0x92, 0xe2, 0xae, 0xc8, 0xb8, 0x4a, 0xf3, 0x71, 0x69, 0x7d, 0x42,
|
||||
0x46, 0xd6, 0x2b, 0x32, 0xb2, 0xe6, 0x45, 0x2f, 0x11, 0x5b, 0x43, 0x41, 0xe6, 0x9d, 0x70, 0x90,
|
||||
0xc1, 0xef, 0x40, 0x39, 0xe4, 0x20, 0x9a, 0x77, 0x9a, 0xef, 0xef, 0xaf, 0x6c, 0xf1, 0x24, 0xf5,
|
||||
0x84, 0xe5, 0x25, 0xbd, 0xaa, 0xd1, 0x5c, 0xb7, 0xd5, 0xdc, 0xdb, 0xab, 0xa6, 0x50, 0x19, 0x0a,
|
||||
0xdb, 0x3b, 0xad, 0x03, 0x2e, 0x95, 0xc6, 0x4f, 0x7c, 0x0d, 0x22, 0xc9, 0x29, 0xb9, 0x6d, 0x42,
|
||||
0xc9, 0x6d, 0x9a, 0xcc, 0x6d, 0xa9, 0x20, 0xb7, 0xb1, 0x34, 0xb7, 0xd5, 0x5c, 0xd9, 0x6b, 0x56,
|
||||
0x27, 0x1f, 0x57, 0xa0, 0xc4, 0xfd, 0x7b, 0x30, 0xb0, 0x68, 0xaa, 0xfd, 0x6f, 0x0d, 0x20, 0xd8,
|
||||
0x4d, 0xa8, 0x01, 0xb9, 0x36, 0xc7, 0xa9, 0x69, 0x2c, 0x18, 0xcd, 0xc5, 0x4e, 0x99, 0x2e, 0xa5,
|
||||
0xd0, 0xeb, 0x90, 0x73, 0x07, 0xed, 0x36, 0x71, 0x65, 0xca, 0xbb, 0x1a, 0x8d, 0x87, 0x22, 0x5a,
|
||||
0xe9, 0x52, 0x8e, 0x76, 0x39, 0x32, 0xcc, 0xee, 0x80, 0x25, 0xc0, 0x8b, 0xbb, 0x08, 0x39, 0xfc,
|
||||
0x9f, 0x1a, 0x14, 0x95, 0xc5, 0xfb, 0x23, 0x83, 0xf0, 0x0d, 0x28, 0x30, 0x1b, 0x48, 0x47, 0x84,
|
||||
0xe1, 0xbc, 0x1e, 0x54, 0xa0, 0xbf, 0x80, 0x82, 0xdc, 0x01, 0x32, 0x12, 0xd7, 0xe2, 0xd5, 0xee,
|
||||
0xf4, 0xf5, 0x40, 0x14, 0x6f, 0xc2, 0x34, 0xf3, 0x4a, 0x9b, 0x1e, 0xae, 0xa5, 0x1f, 0xd5, 0xe3,
|
||||
0xa7, 0x16, 0x39, 0x7e, 0xd6, 0x21, 0xdf, 0x3f, 0x39, 0x77, 0xcd, 0xb6, 0xd1, 0x15, 0x56, 0xf8,
|
||||
0x65, 0xfc, 0x1e, 0x20, 0x55, 0xd9, 0x38, 0xc3, 0xc5, 0x65, 0x28, 0xae, 0x1b, 0xee, 0x89, 0x30,
|
||||
0x09, 0x3f, 0x84, 0x32, 0x2d, 0x6e, 0x3e, 0x7b, 0x01, 0x1b, 0xd9, 0xe5, 0x40, 0x4a, 0x8f, 0xe5,
|
||||
0x73, 0x04, 0x93, 0x27, 0x86, 0x7b, 0xc2, 0x06, 0x5a, 0xd6, 0xd9, 0x37, 0x7a, 0x19, 0xaa, 0x6d,
|
||||
0x3e, 0xc8, 0x83, 0xc8, 0x95, 0x61, 0x4a, 0xd4, 0xfb, 0x27, 0xc1, 0x0f, 0xa1, 0xc4, 0xc7, 0xf0,
|
||||
0x53, 0x1b, 0x81, 0xa7, 0x61, 0x6a, 0xcf, 0x32, 0xfa, 0xee, 0x89, 0x2d, 0xb3, 0x1b, 0x1d, 0x74,
|
||||
0x35, 0xa8, 0x1b, 0x0b, 0xf1, 0x3e, 0x4c, 0x39, 0xa4, 0x67, 0x98, 0x96, 0x69, 0x1d, 0x1f, 0x1c,
|
||||
0x9e, 0x7b, 0xc4, 0x15, 0x17, 0xa6, 0x8a, 0x5f, 0xfd, 0x98, 0xd6, 0x52, 0xd3, 0x0e, 0xbb, 0xf6,
|
||||
0xa1, 0x08, 0x73, 0xec, 0x1b, 0x7f, 0xab, 0x41, 0xe9, 0x03, 0xc3, 0x6b, 0xcb, 0xa9, 0x43, 0x1b,
|
||||
0x50, 0xf1, 0x83, 0x1b, 0xab, 0x11, 0xb6, 0x44, 0x52, 0x2c, 0xeb, 0x23, 0x8f, 0xd2, 0x32, 0x3b,
|
||||
0x96, 0xdb, 0x6a, 0x05, 0x53, 0x65, 0x58, 0x6d, 0xd2, 0xf5, 0x55, 0xa5, 0x92, 0x55, 0x31, 0x41,
|
||||
0x55, 0x95, 0x5a, 0xf1, 0x78, 0x2a, 0x38, 0x7e, 0xf0, 0x58, 0xf2, 0x6d, 0x0a, 0xd0, 0xa8, 0x0d,
|
||||
0x3f, 0xf4, 0x44, 0x76, 0x0f, 0x2a, 0xae, 0x67, 0x38, 0x23, 0x6b, 0xa3, 0xcc, 0x6a, 0xfd, 0x00,
|
||||
0x7d, 0x1f, 0xa6, 0xfa, 0x8e, 0x7d, 0xec, 0x10, 0xd7, 0x3d, 0xb0, 0x6c, 0xcf, 0x3c, 0x3a, 0x17,
|
||||
0x87, 0xda, 0x8a, 0xac, 0xde, 0x66, 0xb5, 0xa8, 0x09, 0xb9, 0x23, 0xb3, 0xeb, 0x11, 0xc7, 0xad,
|
||||
0x65, 0x16, 0xd2, 0x8b, 0x95, 0xe5, 0x87, 0x97, 0x79, 0x6d, 0xe9, 0x5d, 0x26, 0xdf, 0x3a, 0xef,
|
||||
0x13, 0x5d, 0xf6, 0x55, 0x0f, 0x8a, 0xd9, 0xd0, 0xe1, 0xf9, 0x1a, 0xe4, 0x9f, 0x53, 0x15, 0xf4,
|
||||
0x52, 0x9c, 0xe3, 0x67, 0x3b, 0x56, 0xde, 0xe8, 0xe0, 0x7b, 0x00, 0x81, 0x2a, 0x1a, 0x85, 0xb7,
|
||||
0x77, 0x76, 0xf7, 0x5b, 0xd5, 0x09, 0x54, 0x82, 0xfc, 0xf6, 0xce, 0x5a, 0x73, 0xab, 0x49, 0x43,
|
||||
0x36, 0x6e, 0x48, 0xb7, 0xa9, 0xee, 0x0d, 0xe9, 0xd5, 0xc2, 0x7a, 0xff, 0x2d, 0x05, 0x65, 0xb1,
|
||||
0x40, 0xc6, 0x5a, 0xa5, 0x2a, 0x44, 0x2a, 0x04, 0x41, 0x0f, 0xac, 0x7c, 0xe1, 0x74, 0xc4, 0xb9,
|
||||
0x58, 0x16, 0x69, 0xd8, 0xe0, 0xeb, 0x80, 0x74, 0x84, 0xc7, 0xfd, 0x72, 0xec, 0xce, 0xce, 0xc4,
|
||||
0xee, 0x6c, 0x74, 0x07, 0xca, 0xfe, 0x42, 0x34, 0x5c, 0x91, 0x86, 0x0b, 0x7a, 0x49, 0xae, 0x31,
|
||||
0x5a, 0x87, 0xee, 0x41, 0x96, 0x0c, 0x89, 0xe5, 0xb9, 0xb5, 0x22, 0x0b, 0xc8, 0x65, 0x79, 0x34,
|
||||
0x6e, 0xd2, 0x5a, 0x5d, 0x34, 0xe2, 0xb7, 0x60, 0x9a, 0x5d, 0x41, 0x9e, 0x38, 0x86, 0xa5, 0xde,
|
||||
0x95, 0x5a, 0xad, 0x2d, 0xe1, 0x3a, 0xfa, 0x89, 0x2a, 0x90, 0xda, 0x58, 0x13, 0x03, 0x4d, 0x6d,
|
||||
0xac, 0xe1, 0x2f, 0x34, 0x40, 0x6a, 0xbf, 0xb1, 0x7c, 0x19, 0x51, 0x2e, 0xe1, 0xd3, 0x01, 0xfc,
|
||||
0x2c, 0x64, 0x88, 0xe3, 0xd8, 0x0e, 0xf3, 0x5a, 0x41, 0xe7, 0x05, 0x7c, 0x57, 0xd8, 0xa0, 0x93,
|
||||
0xa1, 0x7d, 0xea, 0xef, 0x19, 0xae, 0x4d, 0xf3, 0x4d, 0xdd, 0x84, 0x99, 0x90, 0xd4, 0x58, 0x89,
|
||||
0xe1, 0x3e, 0xcc, 0x31, 0x65, 0x9b, 0x84, 0xf4, 0x57, 0xba, 0xe6, 0x30, 0x11, 0xb5, 0x0f, 0x57,
|
||||
0xa2, 0x82, 0x3f, 0xaf, 0x8f, 0xf0, 0x5f, 0x0b, 0xc4, 0x96, 0xd9, 0x23, 0x2d, 0x7b, 0x2b, 0xd9,
|
||||
0x36, 0x1a, 0x38, 0x4f, 0xc9, 0xb9, 0x2b, 0x32, 0x28, 0xfb, 0xc6, 0xff, 0xa3, 0xc1, 0xd5, 0x91,
|
||||
0xee, 0x3f, 0xf3, 0xac, 0xce, 0x03, 0x1c, 0xd3, 0xe5, 0x43, 0x3a, 0xb4, 0x81, 0x5f, 0xde, 0x95,
|
||||
0x1a, 0xdf, 0x4e, 0x1a, 0x7b, 0x4a, 0xc2, 0xce, 0x59, 0x31, 0xe7, 0xec, 0x8f, 0x2b, 0xd3, 0xcf,
|
||||
0x4d, 0x28, 0xb2, 0x8a, 0x3d, 0xcf, 0xf0, 0x06, 0xee, 0xc8, 0x64, 0xfc, 0xa3, 0x58, 0x02, 0xb2,
|
||||
0xd3, 0x58, 0xe3, 0x7a, 0x1d, 0xb2, 0xec, 0xdc, 0x2a, 0x4f, 0x6d, 0x91, 0x8b, 0x82, 0x62, 0x87,
|
||||
0x2e, 0x04, 0xf1, 0x09, 0x64, 0x9f, 0x32, 0xb2, 0x4f, 0xb1, 0x6c, 0x52, 0x4e, 0x85, 0x65, 0xf4,
|
||||
0x38, 0x05, 0x51, 0xd0, 0xd9, 0x37, 0x3b, 0xe4, 0x10, 0xe2, 0xec, 0xeb, 0x5b, 0xfc, 0x30, 0x55,
|
||||
0xd0, 0xfd, 0x32, 0x75, 0x59, 0xbb, 0x6b, 0x12, 0xcb, 0x63, 0xad, 0x93, 0xac, 0x55, 0xa9, 0xc1,
|
||||
0x4b, 0x50, 0xe5, 0x48, 0x2b, 0x9d, 0x8e, 0x72, 0x58, 0xf1, 0xf5, 0x69, 0x61, 0x7d, 0xf8, 0x7f,
|
||||
0x35, 0x98, 0x56, 0x3a, 0x8c, 0xe5, 0x98, 0x57, 0x20, 0xcb, 0x29, 0x4d, 0x91, 0x17, 0x67, 0xc3,
|
||||
0xbd, 0x38, 0x8c, 0x2e, 0x64, 0xd0, 0x12, 0xe4, 0xf8, 0x97, 0x3c, 0x31, 0xc6, 0x8b, 0x4b, 0x21,
|
||||
0x7c, 0x0f, 0x66, 0x44, 0x15, 0xe9, 0xd9, 0x71, 0x6b, 0x9b, 0x39, 0x14, 0xff, 0x03, 0xcc, 0x86,
|
||||
0xc5, 0xc6, 0x1a, 0x92, 0x62, 0x64, 0xea, 0x45, 0x8c, 0x5c, 0x91, 0x46, 0xee, 0xf7, 0x3b, 0x4a,
|
||||
0x1a, 0x8f, 0xce, 0xba, 0x3a, 0x23, 0xa9, 0xc8, 0x8c, 0xf8, 0x03, 0x90, 0x2a, 0x7e, 0xd1, 0x01,
|
||||
0xcc, 0xc8, 0xe5, 0xb0, 0x65, 0xba, 0xfe, 0xe1, 0xee, 0x33, 0x40, 0x6a, 0xe5, 0x2f, 0x6d, 0xd0,
|
||||
0x1a, 0x39, 0x72, 0x8c, 0xe3, 0x1e, 0xf1, 0xf3, 0x13, 0x3d, 0xea, 0xab, 0x95, 0x63, 0x45, 0xf4,
|
||||
0x06, 0x4c, 0x3f, 0xb5, 0x87, 0x34, 0x34, 0xd0, 0xda, 0x60, 0xcb, 0xf0, 0xab, 0x9e, 0x3f, 0x6d,
|
||||
0x7e, 0x99, 0x82, 0xab, 0x1d, 0xc6, 0x02, 0xff, 0xad, 0x06, 0xa5, 0x95, 0xae, 0xe1, 0xf4, 0x24,
|
||||
0xf0, 0xdb, 0x90, 0xe5, 0x17, 0x18, 0xc1, 0x19, 0xbc, 0x14, 0x56, 0xa3, 0xca, 0xf2, 0xc2, 0x0a,
|
||||
0xbf, 0xee, 0x88, 0x5e, 0xd4, 0x70, 0xf1, 0xac, 0xb0, 0x16, 0x79, 0x66, 0x58, 0x43, 0xaf, 0x42,
|
||||
0xc6, 0xa0, 0x5d, 0x58, 0x08, 0xae, 0x44, 0xaf, 0x8e, 0x4c, 0x1b, 0x3b, 0xb7, 0x71, 0x29, 0xfc,
|
||||
0x26, 0x14, 0x15, 0x04, 0x7a, 0x39, 0x7e, 0xd2, 0x14, 0x07, 0xb0, 0x95, 0xd5, 0xd6, 0xc6, 0x33,
|
||||
0x7e, 0x67, 0xae, 0x00, 0xac, 0x35, 0xfd, 0x72, 0x0a, 0x7f, 0x28, 0x7a, 0x89, 0x78, 0xa7, 0xda,
|
||||
0xa3, 0x25, 0xd9, 0x93, 0x7a, 0x21, 0x7b, 0xce, 0xa0, 0x2c, 0x86, 0x3f, 0x6e, 0xf8, 0x66, 0xfa,
|
||||
0x12, 0xc2, 0xb7, 0x62, 0xbc, 0x2e, 0x04, 0xf1, 0x14, 0x94, 0x45, 0x40, 0x17, 0xeb, 0xef, 0xff,
|
||||
0x53, 0x50, 0x91, 0x35, 0xe3, 0x72, 0x9b, 0x92, 0x96, 0xe1, 0x19, 0xc0, 0x27, 0x65, 0xae, 0x40,
|
||||
0xb6, 0x73, 0xb8, 0x67, 0x7e, 0x26, 0x79, 0x68, 0x51, 0xa2, 0xf5, 0x5d, 0x8e, 0xc3, 0x1f, 0x83,
|
||||
0x44, 0x89, 0x5e, 0xd0, 0x1d, 0xe3, 0xc8, 0xdb, 0xb0, 0x3a, 0xe4, 0x8c, 0x9d, 0x1b, 0x27, 0xf5,
|
||||
0xa0, 0x82, 0xdd, 0x57, 0xc5, 0xa3, 0x11, 0x3b, 0x2c, 0x2a, 0x8f, 0x48, 0xe8, 0x01, 0x54, 0xe9,
|
||||
0xf7, 0x4a, 0xbf, 0xdf, 0x35, 0x49, 0x87, 0x2b, 0xc8, 0x31, 0x99, 0x91, 0x7a, 0x8a, 0xce, 0x8e,
|
||||
0x5e, 0x6e, 0x2d, 0xcf, 0xc2, 0x96, 0x28, 0xa1, 0x05, 0x28, 0x72, 0xfb, 0x36, 0xac, 0x7d, 0x97,
|
||||
0xb0, 0x97, 0x94, 0xb4, 0xae, 0x56, 0xd1, 0x7d, 0xbc, 0x32, 0xf0, 0x4e, 0x9a, 0x96, 0x71, 0xd8,
|
||||
0x95, 0x71, 0x91, 0x26, 0x73, 0x5a, 0xb9, 0x66, 0xba, 0x6a, 0x6d, 0x13, 0x66, 0x68, 0x2d, 0xb1,
|
||||
0x3c, 0xb3, 0xad, 0x04, 0x51, 0x99, 0x2a, 0xb5, 0x48, 0xaa, 0x34, 0x5c, 0xf7, 0xb9, 0xed, 0x74,
|
||||
0x84, 0x03, 0xfd, 0x32, 0x5e, 0xe3, 0xca, 0xf7, 0xdd, 0x50, 0x32, 0xfc, 0xa1, 0x5a, 0x16, 0x03,
|
||||
0x2d, 0x4f, 0x88, 0x77, 0x81, 0x16, 0xfc, 0x10, 0xe6, 0xa4, 0xa4, 0x60, 0x17, 0x2f, 0x10, 0xde,
|
||||
0x81, 0x9b, 0x52, 0x78, 0xf5, 0x84, 0x5e, 0xdf, 0x76, 0x05, 0xe0, 0x8f, 0xb5, 0xf3, 0x31, 0xd4,
|
||||
0x7c, 0x3b, 0xd9, 0x91, 0xdc, 0xee, 0xaa, 0x06, 0x0c, 0x5c, 0xb1, 0x32, 0x0b, 0x3a, 0xfb, 0xa6,
|
||||
0x75, 0x8e, 0xdd, 0xf5, 0x0f, 0x1e, 0xf4, 0x1b, 0xaf, 0xc2, 0x35, 0xa9, 0x43, 0x1c, 0x96, 0xc3,
|
||||
0x4a, 0x46, 0x0c, 0x8a, 0x53, 0x22, 0x1c, 0x46, 0xbb, 0x5e, 0xec, 0x76, 0x55, 0x32, 0xec, 0x5a,
|
||||
0xa6, 0x53, 0x53, 0x74, 0xce, 0xf1, 0x15, 0x41, 0x0d, 0x53, 0xf3, 0x92, 0xa8, 0xa6, 0x0a, 0xd4,
|
||||
0x6a, 0x31, 0x11, 0xb4, 0x7a, 0x64, 0x22, 0x46, 0x54, 0x7f, 0x02, 0xf3, 0xbe, 0x11, 0xd4, 0x6f,
|
||||
0xbb, 0xc4, 0xe9, 0x99, 0xae, 0xab, 0xf0, 0x51, 0x71, 0x03, 0x7f, 0x09, 0x26, 0xfb, 0x44, 0x44,
|
||||
0xae, 0xe2, 0x32, 0x5a, 0xe2, 0x0f, 0xc8, 0x4b, 0x4a, 0x67, 0xd6, 0x8e, 0x3b, 0x70, 0x4b, 0x6a,
|
||||
0xe7, 0x1e, 0x8d, 0x55, 0x1f, 0x35, 0x4a, 0x5e, 0xfb, 0x53, 0x09, 0xd7, 0xfe, 0x74, 0x84, 0x23,
|
||||
0x7d, 0x8f, 0x3b, 0x52, 0xee, 0xad, 0xb1, 0x32, 0xd2, 0x26, 0xf7, 0xa9, 0xbf, 0x25, 0xc7, 0x52,
|
||||
0x76, 0x08, 0xb3, 0xe1, 0x9d, 0x3c, 0x56, 0xb0, 0x9c, 0x85, 0x8c, 0x67, 0x9f, 0x12, 0x19, 0x2a,
|
||||
0x79, 0x41, 0x1a, 0xec, 0x6f, 0xf3, 0xb1, 0x0c, 0x36, 0x02, 0x65, 0x6c, 0x49, 0x8e, 0x6b, 0x2f,
|
||||
0x9d, 0x4d, 0x79, 0xc4, 0xe3, 0x05, 0xbc, 0x0d, 0x57, 0xa2, 0x61, 0x62, 0x2c, 0x93, 0x9f, 0xf1,
|
||||
0x05, 0x1c, 0x17, 0x49, 0xc6, 0xd2, 0xfb, 0x7e, 0x10, 0x0c, 0x94, 0x80, 0x32, 0x96, 0x4a, 0x1d,
|
||||
0xea, 0x71, 0xf1, 0xe5, 0xa7, 0x58, 0xaf, 0x7e, 0xb8, 0x19, 0x4b, 0x99, 0x1b, 0x28, 0x1b, 0x7f,
|
||||
0xfa, 0x83, 0x18, 0x91, 0xbe, 0x30, 0x46, 0x88, 0x4d, 0x12, 0x44, 0xb1, 0x9f, 0x61, 0xd1, 0x09,
|
||||
0x8c, 0x20, 0x80, 0x8e, 0x8b, 0x41, 0x73, 0x88, 0x8f, 0xc1, 0x0a, 0x72, 0x61, 0xab, 0x61, 0x77,
|
||||
0xac, 0xc9, 0xf8, 0x20, 0x88, 0x9d, 0x23, 0x91, 0x79, 0x2c, 0xc5, 0x1f, 0xc2, 0x42, 0x72, 0x50,
|
||||
0x1e, 0x47, 0xf3, 0x83, 0x06, 0x14, 0xfc, 0x63, 0xab, 0xf2, 0xe3, 0x8b, 0x22, 0xe4, 0xb6, 0x77,
|
||||
0xf6, 0x76, 0x57, 0x56, 0x9b, 0xfc, 0xd7, 0x17, 0xab, 0x3b, 0xba, 0xbe, 0xbf, 0xdb, 0xaa, 0xa6,
|
||||
0x96, 0xbf, 0x4f, 0x43, 0x6a, 0xf3, 0x19, 0xfa, 0x08, 0x32, 0xfc, 0x29, 0xf2, 0x82, 0xf7, 0xe7,
|
||||
0xfa, 0x45, 0xaf, 0xad, 0xf8, 0xea, 0x17, 0xbf, 0xff, 0xfe, 0xeb, 0xd4, 0x34, 0x2e, 0x35, 0x86,
|
||||
0x6f, 0x34, 0x4e, 0x87, 0x0d, 0x96, 0x1b, 0x1e, 0x69, 0x0f, 0xd0, 0xfb, 0x90, 0xde, 0x1d, 0x78,
|
||||
0x28, 0xf1, 0x5d, 0xba, 0x9e, 0xfc, 0x00, 0x8b, 0xe7, 0x98, 0xd2, 0x29, 0x0c, 0x42, 0x69, 0x7f,
|
||||
0xe0, 0x51, 0x95, 0x9f, 0x42, 0x51, 0x7d, 0x3e, 0xbd, 0xf4, 0xb1, 0xba, 0x7e, 0xf9, 0xd3, 0x2c,
|
||||
0xbe, 0xc9, 0xa0, 0xae, 0x62, 0x24, 0xa0, 0xf8, 0x03, 0xaf, 0x3a, 0x8a, 0xd6, 0x99, 0x85, 0x12,
|
||||
0x9f, 0xb2, 0xeb, 0xc9, 0xaf, 0xb5, 0x23, 0xa3, 0xf0, 0xce, 0x2c, 0xaa, 0xf2, 0xef, 0xc4, 0x43,
|
||||
0x6d, 0xdb, 0x43, 0xb7, 0x62, 0x1e, 0xea, 0xd4, 0x27, 0xa9, 0xfa, 0x42, 0xb2, 0x80, 0x00, 0xb9,
|
||||
0xc1, 0x40, 0xae, 0xe0, 0x69, 0x01, 0xd2, 0xf6, 0x45, 0x1e, 0x69, 0x0f, 0x96, 0xdb, 0x90, 0x61,
|
||||
0x9c, 0x33, 0xfa, 0x58, 0x7e, 0xd4, 0x63, 0x88, 0xf4, 0x84, 0x89, 0x0e, 0xb1, 0xd5, 0x78, 0x96,
|
||||
0x01, 0x55, 0x70, 0x81, 0x02, 0x31, 0xc6, 0xf9, 0x91, 0xf6, 0x60, 0x51, 0x7b, 0x4d, 0x5b, 0xfe,
|
||||
0xbf, 0x0c, 0x64, 0x18, 0xf9, 0x84, 0x4e, 0x01, 0x02, 0x6e, 0x36, 0x3a, 0xba, 0x11, 0xb6, 0x37,
|
||||
0x3a, 0xba, 0x51, 0x5a, 0x17, 0xd7, 0x19, 0xe8, 0x2c, 0x9e, 0xa2, 0xa0, 0x8c, 0xd3, 0x6a, 0x30,
|
||||
0x9a, 0x8e, 0xfa, 0xf1, 0x5f, 0x34, 0xc1, 0xbd, 0xf1, 0xbd, 0x84, 0xe2, 0xb4, 0x85, 0x08, 0xda,
|
||||
0xe8, 0x72, 0x88, 0x21, 0x67, 0xf1, 0x5b, 0x0c, 0xb0, 0x81, 0xab, 0x01, 0xa0, 0xc3, 0x24, 0x1e,
|
||||
0x69, 0x0f, 0x3e, 0xae, 0xe1, 0x19, 0xe1, 0xe5, 0x48, 0x0b, 0xfa, 0x1c, 0x2a, 0x61, 0xd2, 0x15,
|
||||
0xdd, 0x89, 0xc1, 0x8a, 0x72, 0xb7, 0xf5, 0xbb, 0x17, 0x0b, 0x09, 0x9b, 0xe6, 0x99, 0x4d, 0x02,
|
||||
0x9c, 0x23, 0x9f, 0x12, 0xd2, 0x37, 0xa8, 0x90, 0x98, 0x03, 0xf4, 0x5f, 0x1a, 0x4c, 0x45, 0x58,
|
||||
0x54, 0x14, 0xa7, 0x7d, 0x84, 0xa3, 0xad, 0xdf, 0xbb, 0x44, 0x4a, 0x18, 0xf1, 0x37, 0xcc, 0x88,
|
||||
0xbf, 0xc4, 0xb3, 0x81, 0x11, 0x9e, 0xd9, 0x23, 0x9e, 0x2d, 0xac, 0xf8, 0xf8, 0x06, 0xbe, 0x1a,
|
||||
0x72, 0x4e, 0xa8, 0x35, 0x98, 0x2c, 0xce, 0x84, 0xc6, 0x4e, 0x56, 0x88, 0x59, 0x8d, 0x9d, 0xac,
|
||||
0x30, 0x8d, 0x1a, 0x37, 0x59, 0x9c, 0xf7, 0x8c, 0x9b, 0x2c, 0xbf, 0x65, 0x99, 0xfd, 0x54, 0x82,
|
||||
0xff, 0x40, 0x12, 0xd9, 0x50, 0xf0, 0x59, 0x48, 0x34, 0x1f, 0xc7, 0x08, 0x05, 0x77, 0x89, 0xfa,
|
||||
0xad, 0xc4, 0x76, 0x61, 0xd0, 0x6d, 0x66, 0xd0, 0x75, 0x7c, 0x85, 0x22, 0x8b, 0xdf, 0x60, 0x36,
|
||||
0x38, 0xed, 0xd0, 0x30, 0x3a, 0x1d, 0xea, 0x88, 0xbf, 0x87, 0x92, 0x4a, 0x13, 0xa2, 0xdb, 0xb1,
|
||||
0x2c, 0x94, 0xca, 0x34, 0xd6, 0xf1, 0x45, 0x22, 0x02, 0xf9, 0x2e, 0x43, 0x9e, 0xc7, 0xd7, 0x62,
|
||||
0x90, 0x1d, 0x26, 0x1a, 0x02, 0xe7, 0x14, 0x5f, 0x3c, 0x78, 0x88, 0x41, 0x8c, 0x07, 0x0f, 0x33,
|
||||
0x84, 0x17, 0x82, 0x0f, 0x98, 0x28, 0x05, 0x77, 0x01, 0x02, 0x32, 0x0f, 0xc5, 0xfa, 0x52, 0xb9,
|
||||
0x4c, 0x45, 0x83, 0xc3, 0x28, 0x0f, 0x88, 0x31, 0x83, 0x15, 0xeb, 0x2e, 0x02, 0xdb, 0x35, 0x5d,
|
||||
0x1a, 0x24, 0x96, 0xff, 0x35, 0x0b, 0xc5, 0xa7, 0x86, 0x69, 0x79, 0xc4, 0x32, 0xac, 0x36, 0x41,
|
||||
0x87, 0x90, 0x61, 0x89, 0x32, 0x1a, 0x07, 0x55, 0x7e, 0x2b, 0x1a, 0x07, 0x43, 0xe4, 0x0f, 0x5e,
|
||||
0x60, 0xa8, 0x75, 0x3c, 0x47, 0x51, 0x7b, 0x81, 0xea, 0x06, 0xe3, 0x6c, 0xe8, 0x40, 0x8f, 0x20,
|
||||
0x2b, 0x9e, 0x03, 0x22, 0x8a, 0x42, 0x5c, 0x4e, 0xfd, 0x46, 0x7c, 0x63, 0xdc, 0x52, 0x52, 0x61,
|
||||
0x5c, 0x26, 0x47, 0x71, 0x86, 0x00, 0x01, 0x19, 0x19, 0x75, 0xe8, 0x08, 0x77, 0x59, 0x5f, 0x48,
|
||||
0x16, 0x10, 0x98, 0xf7, 0x18, 0xe6, 0x2d, 0x5c, 0x8f, 0x62, 0x76, 0x7c, 0x59, 0x8a, 0xfb, 0xb7,
|
||||
0x30, 0xb9, 0x6e, 0xb8, 0x27, 0x28, 0x92, 0xfa, 0x94, 0xdf, 0x2d, 0xd4, 0xeb, 0x71, 0x4d, 0x02,
|
||||
0xe5, 0x16, 0x43, 0xb9, 0xc6, 0x23, 0x89, 0x8a, 0x72, 0x62, 0xb8, 0x34, 0xa7, 0xa0, 0x0e, 0x64,
|
||||
0xf9, 0xcf, 0x18, 0xa2, 0xfe, 0x0b, 0xfd, 0x14, 0x22, 0xea, 0xbf, 0xf0, 0x2f, 0x1f, 0x2e, 0x47,
|
||||
0xe9, 0x43, 0x5e, 0xfe, 0x6e, 0x00, 0xdd, 0x8c, 0x4c, 0x45, 0xf8, 0x37, 0x06, 0xf5, 0xf9, 0xa4,
|
||||
0x66, 0x81, 0x75, 0x87, 0x61, 0xdd, 0xc4, 0xb5, 0x91, 0xb9, 0x12, 0x92, 0x8f, 0xb4, 0x07, 0xaf,
|
||||
0x69, 0xe8, 0x73, 0x80, 0x80, 0xbf, 0x1d, 0xd9, 0x00, 0x51, 0x2a, 0x78, 0x64, 0x03, 0x8c, 0x50,
|
||||
0xbf, 0x78, 0x89, 0xe1, 0x2e, 0xe2, 0x3b, 0x51, 0x5c, 0xcf, 0x31, 0x2c, 0xf7, 0x88, 0x38, 0xaf,
|
||||
0x72, 0x8e, 0xce, 0x3d, 0x31, 0xfb, 0x74, 0x33, 0xfc, 0x7a, 0x0a, 0x26, 0xe9, 0x01, 0x94, 0xe6,
|
||||
0xe9, 0xe0, 0xde, 0x1e, 0xb5, 0x64, 0x84, 0x2d, 0x8b, 0x5a, 0x32, 0x7a, 0xe5, 0x0f, 0xe7, 0x69,
|
||||
0xf6, 0xcb, 0x76, 0xc2, 0x04, 0xa8, 0xa3, 0x6d, 0x28, 0x2a, 0x17, 0x7b, 0x14, 0xa3, 0x2c, 0x4c,
|
||||
0xc3, 0x45, 0x23, 0x7f, 0x0c, 0x2b, 0x80, 0xaf, 0x33, 0xbc, 0x39, 0x1e, 0xf9, 0x19, 0x5e, 0x87,
|
||||
0x4b, 0x50, 0xc0, 0xe7, 0x50, 0x52, 0x2f, 0xff, 0x28, 0x46, 0x5f, 0x84, 0xe2, 0x8b, 0x46, 0xb9,
|
||||
0x38, 0xee, 0x20, 0xbc, 0xf1, 0xfd, 0x5f, 0xef, 0x4b, 0x31, 0x0a, 0xdc, 0x85, 0x9c, 0x60, 0x03,
|
||||
0xe2, 0x46, 0x19, 0xe6, 0x03, 0xe3, 0x46, 0x19, 0xa1, 0x12, 0xc2, 0x67, 0x3b, 0x86, 0x48, 0x2f,
|
||||
0x3c, 0x32, 0x93, 0x08, 0xb4, 0x27, 0xc4, 0x4b, 0x42, 0x0b, 0xc8, 0xad, 0x24, 0x34, 0xe5, 0xb2,
|
||||
0x99, 0x84, 0x76, 0x4c, 0x3c, 0xb1, 0x5d, 0xe4, 0x25, 0x0e, 0x25, 0x28, 0x53, 0xa3, 0x37, 0xbe,
|
||||
0x48, 0x24, 0xee, 0xe8, 0x1d, 0x00, 0x8a, 0xd0, 0x8d, 0xce, 0x00, 0x02, 0xae, 0x22, 0x7a, 0x9e,
|
||||
0x8a, 0x25, 0x3c, 0xa3, 0xe7, 0xa9, 0x78, 0xba, 0x23, 0x1c, 0x1a, 0x02, 0x5c, 0x7e, 0xf2, 0xa7,
|
||||
0xc8, 0x5f, 0x69, 0x80, 0x46, 0x69, 0x0d, 0xf4, 0x30, 0x5e, 0x7b, 0x2c, 0x8d, 0x5a, 0x7f, 0xe5,
|
||||
0xc5, 0x84, 0xe3, 0xa2, 0x7d, 0x60, 0x52, 0x9b, 0x49, 0xf7, 0x9f, 0x53, 0xa3, 0xfe, 0x49, 0x83,
|
||||
0x72, 0x88, 0x13, 0x41, 0x2f, 0x25, 0xcc, 0x69, 0x84, 0x85, 0xad, 0xdf, 0xbf, 0x54, 0x2e, 0xee,
|
||||
0xa0, 0xa9, 0xac, 0x00, 0x79, 0xe2, 0xfe, 0x52, 0x83, 0x4a, 0x98, 0x43, 0x41, 0x09, 0xba, 0x47,
|
||||
0x58, 0xdc, 0xfa, 0xe2, 0xe5, 0x82, 0x17, 0x4f, 0x4f, 0x70, 0xd8, 0xee, 0x42, 0x4e, 0xb0, 0x2e,
|
||||
0x71, 0x0b, 0x3f, 0xcc, 0xff, 0xc6, 0x2d, 0xfc, 0x08, 0x65, 0x13, 0xb3, 0xf0, 0x1d, 0xbb, 0x4b,
|
||||
0x94, 0x6d, 0x26, 0x68, 0x99, 0x24, 0xb4, 0x8b, 0xb7, 0x59, 0x84, 0xd3, 0x49, 0x42, 0x0b, 0xb6,
|
||||
0x99, 0xe4, 0x63, 0x50, 0x82, 0xb2, 0x4b, 0xb6, 0x59, 0x94, 0xce, 0x89, 0xd9, 0x66, 0x0c, 0x50,
|
||||
0xd9, 0x66, 0x01, 0x73, 0x12, 0xb7, 0xcd, 0x46, 0xe8, 0xec, 0xb8, 0x6d, 0x36, 0x4a, 0xbe, 0xc4,
|
||||
0xcc, 0x23, 0xc3, 0x0d, 0x6d, 0xb3, 0x99, 0x18, 0x92, 0x05, 0xbd, 0x92, 0xe0, 0xc4, 0x58, 0x96,
|
||||
0xbc, 0xfe, 0xea, 0x0b, 0x4a, 0x27, 0xae, 0x71, 0xee, 0x7e, 0xb9, 0xc6, 0xff, 0x5d, 0x83, 0xd9,
|
||||
0x38, 0x82, 0x06, 0x25, 0xe0, 0x24, 0xb0, 0xeb, 0xf5, 0xa5, 0x17, 0x15, 0xbf, 0xd8, 0x5b, 0xfe,
|
||||
0xaa, 0x7f, 0x5c, 0xfd, 0xcd, 0x77, 0xf3, 0xda, 0xef, 0xbe, 0x9b, 0xd7, 0xfe, 0xf0, 0xdd, 0xbc,
|
||||
0xf6, 0x1f, 0x7f, 0x9c, 0x9f, 0x38, 0xcc, 0xb2, 0xff, 0x13, 0xf6, 0xc6, 0x9f, 0x03, 0x00, 0x00,
|
||||
0xff, 0xff, 0x3f, 0x89, 0x92, 0xdc, 0x9a, 0x36, 0x00, 0x00,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,8 +17,8 @@ package etcdserver
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/internal/version"
|
||||
"github.com/coreos/etcd/pkg/runtime"
|
||||
"github.com/coreos/etcd/version"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
|
@ -30,6 +30,12 @@ var (
|
|||
Name: "has_leader",
|
||||
Help: "Whether or not a leader exists. 1 is existence, 0 is not.",
|
||||
})
|
||||
isLeader = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "is_leader",
|
||||
Help: "Whether or not this member is a leader. 1 if is, 0 otherwise.",
|
||||
})
|
||||
leaderChanges = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
|
|
@ -77,6 +83,7 @@ var (
|
|||
|
||||
func init() {
|
||||
prometheus.MustRegister(hasLeader)
|
||||
prometheus.MustRegister(isLeader)
|
||||
prometheus.MustRegister(leaderChanges)
|
||||
prometheus.MustRegister(proposalsCommitted)
|
||||
prometheus.MustRegister(proposalsApplied)
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ package etcdserver
|
|||
|
||||
import (
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -57,18 +60,58 @@ const (
|
|||
kvOverhead = 256
|
||||
)
|
||||
|
||||
func NewBackendQuota(s *EtcdServer) Quota {
|
||||
func NewBackendQuota(s *EtcdServer, name string) Quota {
|
||||
lg := s.getLogger()
|
||||
|
||||
if s.Cfg.QuotaBackendBytes < 0 {
|
||||
// disable quotas if negative
|
||||
plog.Warningf("disabling backend quota")
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"disabled backend quota",
|
||||
zap.String("quota-name", name),
|
||||
zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("disabling backend quota")
|
||||
}
|
||||
return &passthroughQuota{}
|
||||
}
|
||||
|
||||
if s.Cfg.QuotaBackendBytes == 0 {
|
||||
// use default size if no quota size given
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"enabled backend quota with default value",
|
||||
zap.String("quota-name", name),
|
||||
zap.Int64("quota-size-bytes", DefaultQuotaBytes),
|
||||
zap.String("quota-size", humanize.Bytes(uint64(DefaultQuotaBytes))),
|
||||
)
|
||||
}
|
||||
return &backendQuota{s, DefaultQuotaBytes}
|
||||
}
|
||||
|
||||
if s.Cfg.QuotaBackendBytes > MaxQuotaBytes {
|
||||
plog.Warningf("backend quota %v exceeds maximum recommended quota %v", s.Cfg.QuotaBackendBytes, MaxQuotaBytes)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"quota exceeds the maximum value",
|
||||
zap.String("quota-name", name),
|
||||
zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
|
||||
zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
|
||||
zap.Int64("quota-maximum-size-bytes", MaxQuotaBytes),
|
||||
zap.String("quota-maximum-size", humanize.Bytes(uint64(MaxQuotaBytes))),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("backend quota %v exceeds maximum recommended quota %v", s.Cfg.QuotaBackendBytes, MaxQuotaBytes)
|
||||
}
|
||||
}
|
||||
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"enabled backend quota",
|
||||
zap.String("quota-name", name),
|
||||
zap.Int64("quota-size-bytes", s.Cfg.QuotaBackendBytes),
|
||||
zap.String("quota-size", humanize.Bytes(uint64(s.Cfg.QuotaBackendBytes))),
|
||||
)
|
||||
}
|
||||
return &backendQuota{s, s.Cfg.QuotaBackendBytes}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,14 +17,15 @@ package etcdserver
|
|||
import (
|
||||
"encoding/json"
|
||||
"expvar"
|
||||
"log"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/contention"
|
||||
"github.com/coreos/etcd/pkg/logutil"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft"
|
||||
|
|
@ -32,7 +33,9 @@ import (
|
|||
"github.com/coreos/etcd/rafthttp"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -71,12 +74,6 @@ func init() {
|
|||
}))
|
||||
}
|
||||
|
||||
type RaftTimer interface {
|
||||
Index() uint64
|
||||
AppliedIndex() uint64
|
||||
Term() uint64
|
||||
}
|
||||
|
||||
// apply contains entries, snapshot to be applied. Once
|
||||
// an apply is consumed, the entries will be persisted to
|
||||
// to raft storage concurrently; the application must read
|
||||
|
|
@ -89,14 +86,9 @@ type apply struct {
|
|||
}
|
||||
|
||||
type raftNode struct {
|
||||
// Cache of the latest raft index and raft term the server has seen.
|
||||
// These three unit64 fields must be the first elements to keep 64-bit
|
||||
// alignment for atomic access to the fields.
|
||||
index uint64
|
||||
appliedindex uint64
|
||||
term uint64
|
||||
lead uint64
|
||||
lg *zap.Logger
|
||||
|
||||
tickMu *sync.Mutex
|
||||
raftNodeConfig
|
||||
|
||||
// a chan to send/receive snapshot
|
||||
|
|
@ -118,6 +110,8 @@ type raftNode struct {
|
|||
}
|
||||
|
||||
type raftNodeConfig struct {
|
||||
lg *zap.Logger
|
||||
|
||||
// to check if msg receiver is removed from cluster
|
||||
isIDRemoved func(id uint64) bool
|
||||
raft.Node
|
||||
|
|
@ -133,6 +127,8 @@ type raftNodeConfig struct {
|
|||
|
||||
func newRaftNode(cfg raftNodeConfig) *raftNode {
|
||||
r := &raftNode{
|
||||
lg: cfg.lg,
|
||||
tickMu: new(sync.Mutex),
|
||||
raftNodeConfig: cfg,
|
||||
// set up contention detectors for raft heartbeat message.
|
||||
// expect to send a heartbeat within 2 heartbeat intervals.
|
||||
|
|
@ -151,6 +147,13 @@ func newRaftNode(cfg raftNodeConfig) *raftNode {
|
|||
return r
|
||||
}
|
||||
|
||||
// raft.Node does not have locks in Raft package
|
||||
func (r *raftNode) tick() {
|
||||
r.tickMu.Lock()
|
||||
r.Tick()
|
||||
r.tickMu.Unlock()
|
||||
}
|
||||
|
||||
// start prepares and starts raftNode in a new goroutine. It is no longer safe
|
||||
// to modify the fields after it has been started.
|
||||
func (r *raftNode) start(rh *raftReadyHandler) {
|
||||
|
|
@ -163,10 +166,10 @@ func (r *raftNode) start(rh *raftReadyHandler) {
|
|||
for {
|
||||
select {
|
||||
case <-r.ticker.C:
|
||||
r.Tick()
|
||||
r.tick()
|
||||
case rd := <-r.Ready():
|
||||
if rd.SoftState != nil {
|
||||
newLeader := rd.SoftState.Lead != raft.None && atomic.LoadUint64(&r.lead) != rd.SoftState.Lead
|
||||
newLeader := rd.SoftState.Lead != raft.None && rh.getLead() != rd.SoftState.Lead
|
||||
if newLeader {
|
||||
leaderChanges.Inc()
|
||||
}
|
||||
|
|
@ -177,8 +180,13 @@ func (r *raftNode) start(rh *raftReadyHandler) {
|
|||
hasLeader.Set(1)
|
||||
}
|
||||
|
||||
atomic.StoreUint64(&r.lead, rd.SoftState.Lead)
|
||||
rh.updateLead(rd.SoftState.Lead)
|
||||
islead = rd.RaftState == raft.StateLeader
|
||||
if islead {
|
||||
isLeader.Set(1)
|
||||
} else {
|
||||
isLeader.Set(0)
|
||||
}
|
||||
rh.updateLeadership(newLeader)
|
||||
r.td.Reset()
|
||||
}
|
||||
|
|
@ -187,7 +195,11 @@ func (r *raftNode) start(rh *raftReadyHandler) {
|
|||
select {
|
||||
case r.readStateC <- rd.ReadStates[len(rd.ReadStates)-1]:
|
||||
case <-time.After(internalTimeout):
|
||||
plog.Warningf("timed out sending read state")
|
||||
if r.lg != nil {
|
||||
r.lg.Warn("timed out sending read state", zap.Duration("timeout", internalTimeout))
|
||||
} else {
|
||||
plog.Warningf("timed out sending read state")
|
||||
}
|
||||
case <-r.stopped:
|
||||
return
|
||||
}
|
||||
|
|
@ -218,7 +230,11 @@ func (r *raftNode) start(rh *raftReadyHandler) {
|
|||
|
||||
// gofail: var raftBeforeSave struct{}
|
||||
if err := r.storage.Save(rd.HardState, rd.Entries); err != nil {
|
||||
plog.Fatalf("raft save state and entries error: %v", err)
|
||||
if r.lg != nil {
|
||||
r.lg.Fatal("failed to save Raft hard state and entries", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("raft save state and entries error: %v", err)
|
||||
}
|
||||
}
|
||||
if !raft.IsEmptyHardState(rd.HardState) {
|
||||
proposalsCommitted.Set(float64(rd.HardState.Commit))
|
||||
|
|
@ -228,14 +244,22 @@ func (r *raftNode) start(rh *raftReadyHandler) {
|
|||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
// gofail: var raftBeforeSaveSnap struct{}
|
||||
if err := r.storage.SaveSnap(rd.Snapshot); err != nil {
|
||||
plog.Fatalf("raft save snapshot error: %v", err)
|
||||
if r.lg != nil {
|
||||
r.lg.Fatal("failed to save Raft snapshot", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("raft save snapshot error: %v", err)
|
||||
}
|
||||
}
|
||||
// etcdserver now claim the snapshot has been persisted onto the disk
|
||||
notifyc <- struct{}{}
|
||||
|
||||
// gofail: var raftAfterSaveSnap struct{}
|
||||
r.raftStorage.ApplySnapshot(rd.Snapshot)
|
||||
plog.Infof("raft applied incoming snapshot at index %d", rd.Snapshot.Metadata.Index)
|
||||
if r.lg != nil {
|
||||
r.lg.Info("applied incoming Raft snapshot", zap.Uint64("snapshot-index", rd.Snapshot.Metadata.Index))
|
||||
} else {
|
||||
plog.Infof("raft applied incoming snapshot at index %d", rd.Snapshot.Metadata.Index)
|
||||
}
|
||||
// gofail: var raftAfterApplySnap struct{}
|
||||
}
|
||||
|
||||
|
|
@ -332,8 +356,16 @@ func (r *raftNode) processMessages(ms []raftpb.Message) []raftpb.Message {
|
|||
ok, exceed := r.td.Observe(ms[i].To)
|
||||
if !ok {
|
||||
// TODO: limit request rate.
|
||||
plog.Warningf("failed to send out heartbeat on time (exceeded the %v timeout for %v)", r.heartbeat, exceed)
|
||||
plog.Warningf("server is likely overloaded")
|
||||
if r.lg != nil {
|
||||
r.lg.Warn(
|
||||
"heartbeat took too long to send out; server is overloaded, likely from slow disk",
|
||||
zap.Duration("exceeded", exceed),
|
||||
zap.Duration("heartbeat-interval", r.heartbeat),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to send out heartbeat on time (exceeded the %v timeout for %v)", r.heartbeat, exceed)
|
||||
plog.Warningf("server is likely overloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -354,7 +386,11 @@ func (r *raftNode) onStop() {
|
|||
r.ticker.Stop()
|
||||
r.transport.Stop()
|
||||
if err := r.storage.Close(); err != nil {
|
||||
plog.Panicf("raft close storage error: %v", err)
|
||||
if r.lg != nil {
|
||||
r.lg.Panic("failed to close Raft storage", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("raft close storage error: %v", err)
|
||||
}
|
||||
}
|
||||
close(r.done)
|
||||
}
|
||||
|
|
@ -370,13 +406,13 @@ func (r *raftNode) resumeSending() {
|
|||
p.Resume()
|
||||
}
|
||||
|
||||
// advanceTicksForElection advances ticks to the node for fast election.
|
||||
// This reduces the time to wait for first leader election if bootstrapping the whole
|
||||
// cluster, while leaving at least 1 heartbeat for possible existing leader
|
||||
// to contact it.
|
||||
func advanceTicksForElection(n raft.Node, electionTicks int) {
|
||||
for i := 0; i < electionTicks-1; i++ {
|
||||
n.Tick()
|
||||
// advanceTicks advances ticks of Raft node.
|
||||
// This can be used for fast-forwarding election
|
||||
// ticks in multi data-center deployments, thus
|
||||
// speeding up election process.
|
||||
func (r *raftNode) advanceTicks(ticks int) {
|
||||
for i := 0; i < ticks; i++ {
|
||||
r.tick()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -389,19 +425,36 @@ func startNode(cfg ServerConfig, cl *membership.RaftCluster, ids []types.ID) (id
|
|||
ClusterID: uint64(cl.ID()),
|
||||
},
|
||||
)
|
||||
if w, err = wal.Create(cfg.WALDir(), metadata); err != nil {
|
||||
plog.Fatalf("create wal error: %v", err)
|
||||
if w, err = wal.Create(cfg.Logger, cfg.WALDir(), metadata); err != nil {
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Fatal("failed to create WAL", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("create wal error: %v", err)
|
||||
}
|
||||
}
|
||||
peers := make([]raft.Peer, len(ids))
|
||||
for i, id := range ids {
|
||||
ctx, err := json.Marshal((*cl).Member(id))
|
||||
var ctx []byte
|
||||
ctx, err = json.Marshal((*cl).Member(id))
|
||||
if err != nil {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Panic("failed to marshal member", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
peers[i] = raft.Peer{ID: uint64(id), Context: ctx}
|
||||
}
|
||||
id = member.ID
|
||||
plog.Infof("starting member %s in cluster %s", id, cl.ID())
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info(
|
||||
"starting local member",
|
||||
zap.String("local-member-id", id.String()),
|
||||
zap.String("cluster-id", cl.ID().String()),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("starting member %s in cluster %s", id, cl.ID())
|
||||
}
|
||||
s = raft.NewMemoryStorage()
|
||||
c := &raft.Config{
|
||||
ID: uint64(id),
|
||||
|
|
@ -411,13 +464,24 @@ func startNode(cfg ServerConfig, cl *membership.RaftCluster, ids []types.ID) (id
|
|||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
CheckQuorum: true,
|
||||
PreVote: cfg.PreVote,
|
||||
}
|
||||
if cfg.Logger != nil {
|
||||
// called after capnslog setting in "init" function
|
||||
if cfg.LoggerConfig != nil {
|
||||
c.Logger, err = logutil.NewRaftLogger(cfg.LoggerConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create raft logger %v", err)
|
||||
}
|
||||
} else if cfg.LoggerCore != nil && cfg.LoggerWriteSyncer != nil {
|
||||
c.Logger = logutil.NewRaftLoggerFromZapCore(cfg.LoggerCore, cfg.LoggerWriteSyncer)
|
||||
}
|
||||
}
|
||||
|
||||
n = raft.StartNode(c, peers)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
raftStatusMu.Unlock()
|
||||
advanceTicksForElection(n, c.ElectionTick)
|
||||
return id, n, s, w
|
||||
}
|
||||
|
||||
|
|
@ -426,11 +490,20 @@ func restartNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *member
|
|||
if snapshot != nil {
|
||||
walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
}
|
||||
w, id, cid, st, ents := readWAL(cfg.WALDir(), walsnap)
|
||||
w, id, cid, st, ents := readWAL(cfg.Logger, cfg.WALDir(), walsnap)
|
||||
|
||||
plog.Infof("restarting member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
cl := membership.NewCluster("")
|
||||
cl.SetID(cid)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info(
|
||||
"restarting local member",
|
||||
zap.String("cluster-id", cid.String()),
|
||||
zap.String("local-member-id", id.String()),
|
||||
zap.Uint64("commit-index", st.Commit),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("restarting member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
}
|
||||
cl := membership.NewCluster(cfg.Logger, "")
|
||||
cl.SetID(id, cid)
|
||||
s := raft.NewMemoryStorage()
|
||||
if snapshot != nil {
|
||||
s.ApplySnapshot(*snapshot)
|
||||
|
|
@ -445,13 +518,25 @@ func restartNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *member
|
|||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
CheckQuorum: true,
|
||||
PreVote: cfg.PreVote,
|
||||
}
|
||||
if cfg.Logger != nil {
|
||||
// called after capnslog setting in "init" function
|
||||
var err error
|
||||
if cfg.LoggerConfig != nil {
|
||||
c.Logger, err = logutil.NewRaftLogger(cfg.LoggerConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create raft logger %v", err)
|
||||
}
|
||||
} else if cfg.LoggerCore != nil && cfg.LoggerWriteSyncer != nil {
|
||||
c.Logger = logutil.NewRaftLoggerFromZapCore(cfg.LoggerCore, cfg.LoggerWriteSyncer)
|
||||
}
|
||||
}
|
||||
|
||||
n := raft.RestartNode(c)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
raftStatusMu.Unlock()
|
||||
advanceTicksForElection(n, c.ElectionTick)
|
||||
return id, cl, n, s, w
|
||||
}
|
||||
|
||||
|
|
@ -460,33 +545,62 @@ func restartAsStandaloneNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types
|
|||
if snapshot != nil {
|
||||
walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
}
|
||||
w, id, cid, st, ents := readWAL(cfg.WALDir(), walsnap)
|
||||
w, id, cid, st, ents := readWAL(cfg.Logger, cfg.WALDir(), walsnap)
|
||||
|
||||
// discard the previously uncommitted entries
|
||||
for i, ent := range ents {
|
||||
if ent.Index > st.Commit {
|
||||
plog.Infof("discarding %d uncommitted WAL entries ", len(ents)-i)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info(
|
||||
"discarding uncommitted WAL entries",
|
||||
zap.Uint64("entry-index", ent.Index),
|
||||
zap.Uint64("commit-index-from-wal", st.Commit),
|
||||
zap.Int("number-of-discarded-entries", len(ents)-i),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("discarding %d uncommitted WAL entries ", len(ents)-i)
|
||||
}
|
||||
ents = ents[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// force append the configuration change entries
|
||||
toAppEnts := createConfigChangeEnts(getIDs(snapshot, ents), uint64(id), st.Term, st.Commit)
|
||||
toAppEnts := createConfigChangeEnts(
|
||||
cfg.Logger,
|
||||
getIDs(cfg.Logger, snapshot, ents),
|
||||
uint64(id),
|
||||
st.Term,
|
||||
st.Commit,
|
||||
)
|
||||
ents = append(ents, toAppEnts...)
|
||||
|
||||
// force commit newly appended entries
|
||||
err := w.Save(raftpb.HardState{}, toAppEnts)
|
||||
if err != nil {
|
||||
plog.Fatalf("%v", err)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Fatal("failed to save hard state and entries", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("%v", err)
|
||||
}
|
||||
}
|
||||
if len(ents) != 0 {
|
||||
st.Commit = ents[len(ents)-1].Index
|
||||
}
|
||||
|
||||
plog.Printf("forcing restart of member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
cl := membership.NewCluster("")
|
||||
cl.SetID(cid)
|
||||
if cfg.Logger != nil {
|
||||
cfg.Logger.Info(
|
||||
"forcing restart member",
|
||||
zap.String("cluster-id", cid.String()),
|
||||
zap.String("local-member-id", id.String()),
|
||||
zap.Uint64("commit-index", st.Commit),
|
||||
)
|
||||
} else {
|
||||
plog.Printf("forcing restart of member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
}
|
||||
|
||||
cl := membership.NewCluster(cfg.Logger, "")
|
||||
cl.SetID(id, cid)
|
||||
s := raft.NewMemoryStorage()
|
||||
if snapshot != nil {
|
||||
s.ApplySnapshot(*snapshot)
|
||||
|
|
@ -500,7 +614,21 @@ func restartAsStandaloneNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types
|
|||
Storage: s,
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
CheckQuorum: true,
|
||||
PreVote: cfg.PreVote,
|
||||
}
|
||||
if cfg.Logger != nil {
|
||||
// called after capnslog setting in "init" function
|
||||
if cfg.LoggerConfig != nil {
|
||||
c.Logger, err = logutil.NewRaftLogger(cfg.LoggerConfig)
|
||||
if err != nil {
|
||||
log.Fatalf("cannot create raft logger %v", err)
|
||||
}
|
||||
} else if cfg.LoggerCore != nil && cfg.LoggerWriteSyncer != nil {
|
||||
c.Logger = logutil.NewRaftLoggerFromZapCore(cfg.LoggerCore, cfg.LoggerWriteSyncer)
|
||||
}
|
||||
}
|
||||
|
||||
n := raft.RestartNode(c)
|
||||
raftStatus = n.Status
|
||||
return id, cl, n, s, w
|
||||
|
|
@ -511,7 +639,7 @@ func restartAsStandaloneNode(cfg ServerConfig, snapshot *raftpb.Snapshot) (types
|
|||
// ID-related entry:
|
||||
// - ConfChangeAddNode, in which case the contained ID will be added into the set.
|
||||
// - ConfChangeRemoveNode, in which case the contained ID will be removed from the set.
|
||||
func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
||||
func getIDs(lg *zap.Logger, snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
||||
ids := make(map[uint64]bool)
|
||||
if snap != nil {
|
||||
for _, id := range snap.Metadata.ConfState.Nodes {
|
||||
|
|
@ -532,7 +660,11 @@ func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
|||
case raftpb.ConfChangeUpdateNode:
|
||||
// do nothing
|
||||
default:
|
||||
plog.Panicf("ConfChange Type should be either ConfChangeAddNode or ConfChangeRemoveNode!")
|
||||
if lg != nil {
|
||||
lg.Panic("unknown ConfChange Type", zap.String("type", cc.Type.String()))
|
||||
} else {
|
||||
plog.Panicf("ConfChange Type should be either ConfChangeAddNode or ConfChangeRemoveNode!")
|
||||
}
|
||||
}
|
||||
}
|
||||
sids := make(types.Uint64Slice, 0, len(ids))
|
||||
|
|
@ -548,7 +680,7 @@ func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
|||
// `self` is _not_ removed, even if present in the set.
|
||||
// If `self` is not inside the given ids, it creates a Raft entry to add a
|
||||
// default member with the given `self`.
|
||||
func createConfigChangeEnts(ids []uint64, self uint64, term, index uint64) []raftpb.Entry {
|
||||
func createConfigChangeEnts(lg *zap.Logger, ids []uint64, self uint64, term, index uint64) []raftpb.Entry {
|
||||
ents := make([]raftpb.Entry, 0)
|
||||
next := index + 1
|
||||
found := false
|
||||
|
|
@ -577,7 +709,11 @@ func createConfigChangeEnts(ids []uint64, self uint64, term, index uint64) []raf
|
|||
}
|
||||
ctx, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to marshal member", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
cc := &raftpb.ConfChange{
|
||||
Type: raftpb.ConfChangeAddNode,
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,65 @@
|
|||
// Copyright 2018 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 "sync"
|
||||
|
||||
// AccessController controls etcd server HTTP request access.
|
||||
type AccessController struct {
|
||||
corsMu sync.RWMutex
|
||||
CORS map[string]struct{}
|
||||
hostWhitelistMu sync.RWMutex
|
||||
HostWhitelist map[string]struct{}
|
||||
}
|
||||
|
||||
// NewAccessController returns a new "AccessController" with default "*" values.
|
||||
func NewAccessController() *AccessController {
|
||||
return &AccessController{
|
||||
CORS: map[string]struct{}{"*": {}},
|
||||
HostWhitelist: map[string]struct{}{"*": {}},
|
||||
}
|
||||
}
|
||||
|
||||
// OriginAllowed determines whether the server will allow a given CORS origin.
|
||||
// If CORS is empty, allow all.
|
||||
func (ac *AccessController) OriginAllowed(origin string) bool {
|
||||
ac.corsMu.RLock()
|
||||
defer ac.corsMu.RUnlock()
|
||||
if len(ac.CORS) == 0 { // allow all
|
||||
return true
|
||||
}
|
||||
_, ok := ac.CORS["*"]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
_, ok = ac.CORS[origin]
|
||||
return ok
|
||||
}
|
||||
|
||||
// IsHostWhitelisted returns true if the host is whitelisted.
|
||||
// If whitelist is empty, allow all.
|
||||
func (ac *AccessController) IsHostWhitelisted(host string) bool {
|
||||
ac.hostWhitelistMu.RLock()
|
||||
defer ac.hostWhitelistMu.RUnlock()
|
||||
if len(ac.HostWhitelist) == 0 { // allow all
|
||||
return true
|
||||
}
|
||||
_, ok := ac.HostWhitelist["*"]
|
||||
if ok {
|
||||
return true
|
||||
}
|
||||
_, ok = ac.HostWhitelist[host]
|
||||
return ok
|
||||
}
|
||||
|
|
@ -17,9 +17,12 @@ package etcdserver
|
|||
import (
|
||||
"io"
|
||||
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/raftsnap"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/raftsnap"
|
||||
|
||||
humanize "github.com/dustin/go-humanize"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// createMergedSnapshotMessage creates a snapshot message that contains: raft status (term, conf),
|
||||
|
|
@ -27,17 +30,21 @@ import (
|
|||
// as ReadCloser.
|
||||
func (s *EtcdServer) createMergedSnapshotMessage(m raftpb.Message, snapt, snapi uint64, confState raftpb.ConfState) raftsnap.Message {
|
||||
// get a snapshot of v2 store as []byte
|
||||
clone := s.store.Clone()
|
||||
clone := s.v2store.Clone()
|
||||
d, err := clone.SaveNoCopy()
|
||||
if err != nil {
|
||||
plog.Panicf("store save should never fail: %v", err)
|
||||
if lg := s.getLogger(); lg != nil {
|
||||
lg.Panic("failed to save v2 store data", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("store save should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// commit kv to write metadata(for example: consistent index).
|
||||
s.KV().Commit()
|
||||
dbsnap := s.be.Snapshot()
|
||||
// get a snapshot of v3 KV as readCloser
|
||||
rc := newSnapshotReaderCloser(dbsnap)
|
||||
rc := newSnapshotReaderCloser(s.getLogger(), dbsnap)
|
||||
|
||||
// put the []byte snapshot of store into raft snapshot and return the merged snapshot with
|
||||
// KV readCloser snapshot.
|
||||
|
|
@ -54,19 +61,39 @@ func (s *EtcdServer) createMergedSnapshotMessage(m raftpb.Message, snapt, snapi
|
|||
return *raftsnap.NewMessage(m, rc, dbsnap.Size())
|
||||
}
|
||||
|
||||
func newSnapshotReaderCloser(snapshot backend.Snapshot) io.ReadCloser {
|
||||
func newSnapshotReaderCloser(lg *zap.Logger, snapshot backend.Snapshot) io.ReadCloser {
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
n, err := snapshot.WriteTo(pw)
|
||||
if err == nil {
|
||||
plog.Infof("wrote database snapshot out [total bytes: %d]", n)
|
||||
if lg != nil {
|
||||
lg.Info(
|
||||
"sent database snapshot to writer",
|
||||
zap.Int64("bytes", n),
|
||||
zap.String("size", humanize.Bytes(uint64(n))),
|
||||
)
|
||||
} else {
|
||||
plog.Infof("wrote database snapshot out [total bytes: %d]", n)
|
||||
}
|
||||
} else {
|
||||
plog.Warningf("failed to write database snapshot out [written bytes: %d]: %v", n, err)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"failed to send database snapshot to writer",
|
||||
zap.String("size", humanize.Bytes(uint64(n))),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("failed to write database snapshot out [written bytes: %d]: %v", n, err)
|
||||
}
|
||||
}
|
||||
pw.CloseWithError(err)
|
||||
err = snapshot.Close()
|
||||
if err != nil {
|
||||
plog.Panicf("failed to close database snapshot: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to close database snapshot", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("failed to close database snapshot: %v", err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
return pr
|
||||
|
|
|
|||
|
|
@ -18,12 +18,14 @@ import (
|
|||
"io"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/raftsnap"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/raftsnap"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
|
|
@ -63,7 +65,7 @@ func (st *storage) SaveSnap(snap raftpb.Snapshot) error {
|
|||
return st.WAL.ReleaseLockTo(snap.Metadata.Index)
|
||||
}
|
||||
|
||||
func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID, st raftpb.HardState, ents []raftpb.Entry) {
|
||||
func readWAL(lg *zap.Logger, waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID, st raftpb.HardState, ents []raftpb.Entry) {
|
||||
var (
|
||||
err error
|
||||
wmetadata []byte
|
||||
|
|
@ -71,19 +73,35 @@ func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID,
|
|||
|
||||
repaired := false
|
||||
for {
|
||||
if w, err = wal.Open(waldir, snap); err != nil {
|
||||
plog.Fatalf("open wal error: %v", err)
|
||||
if w, err = wal.Open(lg, waldir, snap); err != nil {
|
||||
if lg != nil {
|
||||
lg.Fatal("failed to open WAL", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("open wal error: %v", err)
|
||||
}
|
||||
}
|
||||
if wmetadata, st, ents, err = w.ReadAll(); err != nil {
|
||||
w.Close()
|
||||
// we can only repair ErrUnexpectedEOF and we never repair twice.
|
||||
if repaired || err != io.ErrUnexpectedEOF {
|
||||
plog.Fatalf("read wal error (%v) and cannot be repaired", err)
|
||||
if lg != nil {
|
||||
lg.Fatal("failed to read WAL, cannot be repaired", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("read wal error (%v) and cannot be repaired", err)
|
||||
}
|
||||
}
|
||||
if !wal.Repair(waldir) {
|
||||
plog.Fatalf("WAL error (%v) cannot be repaired", err)
|
||||
if !wal.Repair(lg, waldir) {
|
||||
if lg != nil {
|
||||
lg.Fatal("failed to repair WAL", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("WAL error (%v) cannot be repaired", err)
|
||||
}
|
||||
} else {
|
||||
plog.Infof("repaired WAL error (%v)", err)
|
||||
if lg != nil {
|
||||
lg.Info("repaired WAL", zap.Error(err))
|
||||
} else {
|
||||
plog.Infof("repaired WAL error (%v)", err)
|
||||
}
|
||||
repaired = true
|
||||
}
|
||||
continue
|
||||
|
|
|
|||
|
|
@ -15,11 +15,14 @@
|
|||
package etcdserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// isConnectedToQuorumSince checks whether the local member is connected to the
|
||||
|
|
@ -95,3 +98,29 @@ func (nc *notifier) notify(err error) {
|
|||
nc.err = err
|
||||
close(nc.c)
|
||||
}
|
||||
|
||||
func warnOfExpensiveRequest(lg *zap.Logger, now time.Time, stringer fmt.Stringer) {
|
||||
warnOfExpensiveGenericRequest(lg, now, stringer, "")
|
||||
}
|
||||
|
||||
func warnOfExpensiveReadOnlyRangeRequest(lg *zap.Logger, now time.Time, stringer fmt.Stringer) {
|
||||
warnOfExpensiveGenericRequest(lg, now, stringer, "read-only range ")
|
||||
}
|
||||
|
||||
func warnOfExpensiveGenericRequest(lg *zap.Logger, now time.Time, stringer fmt.Stringer, prefix string) {
|
||||
// TODO: add metrics
|
||||
d := time.Since(now)
|
||||
if d > warnApplyDuration {
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"request took too long",
|
||||
zap.Duration("took", d),
|
||||
zap.Duration("expected-duration", warnApplyDuration),
|
||||
zap.String("prefix", prefix),
|
||||
zap.String("request", stringer.String()),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("%srequest %q took too long (%v) to execute", prefix, stringer.String(), d)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/internal/store"
|
||||
"github.com/coreos/etcd/etcdserver/v2store"
|
||||
)
|
||||
|
||||
type RequestV2 pb.Request
|
||||
|
|
@ -39,11 +39,11 @@ type reqV2HandlerEtcdServer struct {
|
|||
}
|
||||
|
||||
type reqV2HandlerStore struct {
|
||||
store store.Store
|
||||
store v2store.Store
|
||||
applier ApplierV2
|
||||
}
|
||||
|
||||
func NewStoreRequestV2Handler(s store.Store, applier ApplierV2) RequestV2Handler {
|
||||
func NewStoreRequestV2Handler(s v2store.Store, applier ApplierV2) RequestV2Handler {
|
||||
return &reqV2HandlerStore{s, applier}
|
||||
}
|
||||
|
||||
|
|
@ -122,14 +122,14 @@ func (s *EtcdServer) Do(ctx context.Context, r pb.Request) (Response, error) {
|
|||
r.ID = s.reqIDGen.Next()
|
||||
h := &reqV2HandlerEtcdServer{
|
||||
reqV2HandlerStore: reqV2HandlerStore{
|
||||
store: s.store,
|
||||
store: s.v2store,
|
||||
applier: s.applyV2,
|
||||
},
|
||||
s: s,
|
||||
}
|
||||
rp := &r
|
||||
resp, err := ((*RequestV2)(rp)).Handle(ctx, h)
|
||||
resp.Term, resp.Index = s.Term(), s.Index()
|
||||
resp.Term, resp.Index = s.Term(), s.CommittedIndex()
|
||||
return resp, err
|
||||
}
|
||||
|
||||
|
|
@ -158,3 +158,8 @@ func (r *RequestV2) Handle(ctx context.Context, v2api RequestV2Handler) (Respons
|
|||
}
|
||||
return Response{}, ErrUnknownMethod
|
||||
}
|
||||
|
||||
func (r *RequestV2) String() string {
|
||||
rpb := pb.Request(*r)
|
||||
return rpb.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,17 +18,19 @@ import (
|
|||
"bytes"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/internal/auth"
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/lease/leasehttp"
|
||||
"github.com/coreos/etcd/internal/mvcc"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/lease/leasehttp"
|
||||
"github.com/coreos/etcd/mvcc"
|
||||
"github.com/coreos/etcd/raft"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
@ -84,6 +86,8 @@ type Authenticator interface {
|
|||
}
|
||||
|
||||
func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
defer warnOfExpensiveReadOnlyRangeRequest(s.getLogger(), time.Now(), r)
|
||||
|
||||
if !r.Serializable {
|
||||
err := s.linearizableReadNotify(ctx)
|
||||
if err != nil {
|
||||
|
|
@ -95,6 +99,7 @@ func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeRe
|
|||
chk := func(ai *auth.AuthInfo) error {
|
||||
return s.authStore.IsRangePermitted(ai, r.Key, r.RangeEnd)
|
||||
}
|
||||
|
||||
get := func() { resp, err = s.applyV3Base.Range(nil, r) }
|
||||
if serr := s.doSerialize(ctx, chk, get); serr != nil {
|
||||
return nil, serr
|
||||
|
|
@ -131,12 +136,16 @@ func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse
|
|||
chk := func(ai *auth.AuthInfo) error {
|
||||
return checkTxnAuth(s.authStore, ai, r)
|
||||
}
|
||||
|
||||
defer warnOfExpensiveReadOnlyRangeRequest(s.getLogger(), time.Now(), r)
|
||||
|
||||
get := func() { resp, err = s.applyV3Base.Txn(r) }
|
||||
if serr := s.doSerialize(ctx, chk, get); serr != nil {
|
||||
return nil, serr
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
resp, err := s.raftRequest(ctx, pb.InternalRaftRequest{Txn: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -351,12 +360,22 @@ func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest
|
|||
return nil, err
|
||||
}
|
||||
|
||||
lg := s.getLogger()
|
||||
|
||||
var resp proto.Message
|
||||
for {
|
||||
checkedRevision, err := s.AuthStore().CheckPassword(r.Name, r.Password)
|
||||
if err != nil {
|
||||
if err != auth.ErrAuthNotEnabled {
|
||||
plog.Errorf("invalid authentication request to user %s was issued", r.Name)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"invalid authentication was requested",
|
||||
zap.String("user", r.Name),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("invalid authentication request to user %s was issued", r.Name)
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -379,7 +398,12 @@ func (s *EtcdServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest
|
|||
if checkedRevision == s.AuthStore().Revision() {
|
||||
break
|
||||
}
|
||||
plog.Infof("revision when password checked is obsolete, retrying")
|
||||
|
||||
if lg != nil {
|
||||
lg.Info("revision when password checked became stale; retrying")
|
||||
} else {
|
||||
plog.Infof("revision when password checked is obsolete, retrying")
|
||||
}
|
||||
}
|
||||
|
||||
return resp.(*pb.AuthenticateResponse), nil
|
||||
|
|
@ -575,7 +599,12 @@ func (s *EtcdServer) processInternalRaftRequestOnce(ctx context.Context, r pb.In
|
|||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
s.r.Propose(cctx, data)
|
||||
err = s.r.Propose(cctx, data)
|
||||
if err != nil {
|
||||
proposalsFailed.Inc()
|
||||
s.w.Trigger(id, nil) // GC wait
|
||||
return nil, err
|
||||
}
|
||||
proposalsPending.Inc()
|
||||
defer proposalsPending.Dec()
|
||||
|
||||
|
|
@ -614,13 +643,18 @@ func (s *EtcdServer) linearizableReadLoop() {
|
|||
s.readNotifier = nextnr
|
||||
s.readMu.Unlock()
|
||||
|
||||
lg := s.getLogger()
|
||||
cctx, cancel := context.WithTimeout(context.Background(), s.Cfg.ReqTimeout())
|
||||
if err := s.r.ReadIndex(cctx, ctx); err != nil {
|
||||
cancel()
|
||||
if err == raft.ErrStopped {
|
||||
return
|
||||
}
|
||||
plog.Errorf("failed to get read index from raft: %v", err)
|
||||
if lg != nil {
|
||||
lg.Warn("failed to get read index from Raft", zap.Error(err))
|
||||
} else {
|
||||
plog.Errorf("failed to get read index from raft: %v", err)
|
||||
}
|
||||
nr.notify(err)
|
||||
continue
|
||||
}
|
||||
|
|
@ -637,10 +671,22 @@ func (s *EtcdServer) linearizableReadLoop() {
|
|||
if !done {
|
||||
// a previous request might time out. now we should ignore the response of it and
|
||||
// continue waiting for the response of the current requests.
|
||||
plog.Warningf("ignored out-of-date read index response (want %v, got %v)", rs.RequestCtx, ctx)
|
||||
if lg != nil {
|
||||
lg.Warn(
|
||||
"ignored out-of-date read index response",
|
||||
zap.String("ctx-expected", fmt.Sprintf("%+v", string(rs.RequestCtx))),
|
||||
zap.String("ctx-got", fmt.Sprintf("%+v", string(ctx))),
|
||||
)
|
||||
} else {
|
||||
plog.Warningf("ignored out-of-date read index response (want %v, got %v)", rs.RequestCtx, ctx)
|
||||
}
|
||||
}
|
||||
case <-time.After(s.Cfg.ReqTimeout()):
|
||||
plog.Warningf("timed out waiting for read index response")
|
||||
if lg != nil {
|
||||
lg.Warn("timed out waiting for read index response", zap.Duration("timeout", s.Cfg.ReqTimeout()))
|
||||
} else {
|
||||
plog.Warningf("timed out waiting for read index response")
|
||||
}
|
||||
nr.notify(ErrTimeout)
|
||||
timeout = true
|
||||
case <-s.stopping:
|
||||
|
|
|
|||
|
|
@ -1,139 +0,0 @@
|
|||
// 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 auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rsa"
|
||||
"io/ioutil"
|
||||
|
||||
jwt "github.com/dgrijalva/jwt-go"
|
||||
)
|
||||
|
||||
type tokenJWT struct {
|
||||
signMethod string
|
||||
signKey *rsa.PrivateKey
|
||||
verifyKey *rsa.PublicKey
|
||||
}
|
||||
|
||||
func (t *tokenJWT) enable() {}
|
||||
func (t *tokenJWT) disable() {}
|
||||
func (t *tokenJWT) invalidateUser(string) {}
|
||||
func (t *tokenJWT) genTokenPrefix() (string, error) { return "", nil }
|
||||
|
||||
func (t *tokenJWT) info(ctx context.Context, token string, rev uint64) (*AuthInfo, bool) {
|
||||
// rev isn't used in JWT, it is only used in simple token
|
||||
var (
|
||||
username string
|
||||
revision uint64
|
||||
)
|
||||
|
||||
parsed, err := jwt.Parse(token, func(token *jwt.Token) (interface{}, error) {
|
||||
return t.verifyKey, nil
|
||||
})
|
||||
|
||||
switch err.(type) {
|
||||
case nil:
|
||||
if !parsed.Valid {
|
||||
plog.Warningf("invalid jwt token: %s", token)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
claims := parsed.Claims.(jwt.MapClaims)
|
||||
|
||||
username = claims["username"].(string)
|
||||
revision = uint64(claims["revision"].(float64))
|
||||
default:
|
||||
plog.Warningf("failed to parse jwt token: %s", err)
|
||||
return nil, false
|
||||
}
|
||||
|
||||
return &AuthInfo{Username: username, Revision: revision}, true
|
||||
}
|
||||
|
||||
func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64) (string, error) {
|
||||
// Future work: let a jwt token include permission information would be useful for
|
||||
// permission checking in proxy side.
|
||||
tk := jwt.NewWithClaims(jwt.GetSigningMethod(t.signMethod),
|
||||
jwt.MapClaims{
|
||||
"username": username,
|
||||
"revision": revision,
|
||||
})
|
||||
|
||||
token, err := tk.SignedString(t.signKey)
|
||||
if err != nil {
|
||||
plog.Debugf("failed to sign jwt token: %s", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
plog.Debugf("jwt token: %s", token)
|
||||
|
||||
return token, err
|
||||
}
|
||||
|
||||
func prepareOpts(opts map[string]string) (jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath string, err error) {
|
||||
for k, v := range opts {
|
||||
switch k {
|
||||
case "sign-method":
|
||||
jwtSignMethod = v
|
||||
case "pub-key":
|
||||
jwtPubKeyPath = v
|
||||
case "priv-key":
|
||||
jwtPrivKeyPath = v
|
||||
default:
|
||||
plog.Errorf("unknown token specific option: %s", k)
|
||||
return "", "", "", ErrInvalidAuthOpts
|
||||
}
|
||||
}
|
||||
if len(jwtSignMethod) == 0 {
|
||||
return "", "", "", ErrInvalidAuthOpts
|
||||
}
|
||||
return jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, nil
|
||||
}
|
||||
|
||||
func newTokenProviderJWT(opts map[string]string) (*tokenJWT, error) {
|
||||
jwtSignMethod, jwtPubKeyPath, jwtPrivKeyPath, err := prepareOpts(opts)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidAuthOpts
|
||||
}
|
||||
|
||||
t := &tokenJWT{}
|
||||
|
||||
t.signMethod = jwtSignMethod
|
||||
|
||||
verifyBytes, err := ioutil.ReadFile(jwtPubKeyPath)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to read public key (%s) for jwt: %s", jwtPubKeyPath, err)
|
||||
return nil, err
|
||||
}
|
||||
t.verifyKey, err = jwt.ParseRSAPublicKeyFromPEM(verifyBytes)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to parse public key (%s): %s", jwtPubKeyPath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
signBytes, err := ioutil.ReadFile(jwtPrivKeyPath)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to read private key (%s) for jwt: %s", jwtPrivKeyPath, err)
|
||||
return nil, err
|
||||
}
|
||||
t.signKey, err = jwt.ParseRSAPrivateKeyFromPEM(signBytes)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to parse private key (%s): %s", jwtPrivKeyPath, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return t, nil
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import (
|
|||
"sync"
|
||||
|
||||
"github.com/google/btree"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type index interface {
|
||||
|
|
@ -39,11 +40,13 @@ type index interface {
|
|||
type treeIndex struct {
|
||||
sync.RWMutex
|
||||
tree *btree.BTree
|
||||
lg *zap.Logger
|
||||
}
|
||||
|
||||
func newTreeIndex() index {
|
||||
func newTreeIndex(lg *zap.Logger) index {
|
||||
return &treeIndex{
|
||||
tree: btree.New(32),
|
||||
lg: lg,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -54,12 +57,12 @@ func (ti *treeIndex) Put(key []byte, rev revision) {
|
|||
defer ti.Unlock()
|
||||
item := ti.tree.Get(keyi)
|
||||
if item == nil {
|
||||
keyi.put(rev.main, rev.sub)
|
||||
keyi.put(ti.lg, rev.main, rev.sub)
|
||||
ti.tree.ReplaceOrInsert(keyi)
|
||||
return
|
||||
}
|
||||
okeyi := item.(*keyIndex)
|
||||
okeyi.put(rev.main, rev.sub)
|
||||
okeyi.put(ti.lg, rev.main, rev.sub)
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Get(key []byte, atRev int64) (modified, created revision, ver int64, err error) {
|
||||
|
|
@ -69,7 +72,7 @@ func (ti *treeIndex) Get(key []byte, atRev int64) (modified, created revision, v
|
|||
if keyi = ti.keyIndex(keyi); keyi == nil {
|
||||
return revision{}, revision{}, 0, ErrRevisionNotFound
|
||||
}
|
||||
return keyi.get(atRev)
|
||||
return keyi.get(ti.lg, atRev)
|
||||
}
|
||||
|
||||
func (ti *treeIndex) KeyIndex(keyi *keyIndex) *keyIndex {
|
||||
|
|
@ -109,7 +112,7 @@ func (ti *treeIndex) Revisions(key, end []byte, atRev int64) (revs []revision) {
|
|||
return []revision{rev}
|
||||
}
|
||||
ti.visit(key, end, func(ki *keyIndex) {
|
||||
if rev, _, _, err := ki.get(atRev); err == nil {
|
||||
if rev, _, _, err := ki.get(ti.lg, atRev); err == nil {
|
||||
revs = append(revs, rev)
|
||||
}
|
||||
})
|
||||
|
|
@ -125,7 +128,7 @@ func (ti *treeIndex) Range(key, end []byte, atRev int64) (keys [][]byte, revs []
|
|||
return [][]byte{key}, []revision{rev}
|
||||
}
|
||||
ti.visit(key, end, func(ki *keyIndex) {
|
||||
if rev, _, _, err := ki.get(atRev); err == nil {
|
||||
if rev, _, _, err := ki.get(ti.lg, atRev); err == nil {
|
||||
revs = append(revs, rev)
|
||||
keys = append(keys, ki.key)
|
||||
}
|
||||
|
|
@ -144,7 +147,7 @@ func (ti *treeIndex) Tombstone(key []byte, rev revision) error {
|
|||
}
|
||||
|
||||
ki := item.(*keyIndex)
|
||||
return ki.tombstone(rev.main, rev.sub)
|
||||
return ki.tombstone(ti.lg, rev.main, rev.sub)
|
||||
}
|
||||
|
||||
// RangeSince returns all revisions from key(including) to end(excluding)
|
||||
|
|
@ -162,7 +165,7 @@ func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision {
|
|||
return nil
|
||||
}
|
||||
keyi = item.(*keyIndex)
|
||||
return keyi.since(rev)
|
||||
return keyi.since(ti.lg, rev)
|
||||
}
|
||||
|
||||
endi := &keyIndex{key: end}
|
||||
|
|
@ -172,7 +175,7 @@ func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision {
|
|||
return false
|
||||
}
|
||||
curKeyi := item.(*keyIndex)
|
||||
revs = append(revs, curKeyi.since(rev)...)
|
||||
revs = append(revs, curKeyi.since(ti.lg, rev)...)
|
||||
return true
|
||||
})
|
||||
sort.Sort(revisions(revs))
|
||||
|
|
@ -182,19 +185,34 @@ func (ti *treeIndex) RangeSince(key, end []byte, rev int64) []revision {
|
|||
|
||||
func (ti *treeIndex) Compact(rev int64) map[revision]struct{} {
|
||||
available := make(map[revision]struct{})
|
||||
var emptyki []*keyIndex
|
||||
plog.Printf("store.index: compact %d", rev)
|
||||
// TODO: do not hold the lock for long time?
|
||||
// This is probably OK. Compacting 10M keys takes O(10ms).
|
||||
ti.Lock()
|
||||
defer ti.Unlock()
|
||||
ti.tree.Ascend(compactIndex(rev, available, &emptyki))
|
||||
for _, ki := range emptyki {
|
||||
item := ti.tree.Delete(ki)
|
||||
if item == nil {
|
||||
plog.Panic("store.index: unexpected delete failure during compaction")
|
||||
}
|
||||
if ti.lg != nil {
|
||||
ti.lg.Info("compact tree index", zap.Int64("revision", rev))
|
||||
} else {
|
||||
plog.Printf("store.index: compact %d", rev)
|
||||
}
|
||||
ti.Lock()
|
||||
clone := ti.tree.Clone()
|
||||
ti.Unlock()
|
||||
|
||||
clone.Ascend(func(item btree.Item) bool {
|
||||
keyi := item.(*keyIndex)
|
||||
//Lock is needed here to prevent modification to the keyIndex while
|
||||
//compaction is going on or revision added to empty before deletion
|
||||
ti.Lock()
|
||||
keyi.compact(ti.lg, rev, available)
|
||||
if keyi.isEmpty() {
|
||||
item := ti.tree.Delete(keyi)
|
||||
if item == nil {
|
||||
if ti.lg != nil {
|
||||
ti.lg.Panic("failed to delete during compaction")
|
||||
} else {
|
||||
plog.Panic("store.index: unexpected delete failure during compaction")
|
||||
}
|
||||
}
|
||||
}
|
||||
ti.Unlock()
|
||||
return true
|
||||
})
|
||||
return available
|
||||
}
|
||||
|
||||
|
|
@ -211,17 +229,6 @@ func (ti *treeIndex) Keep(rev int64) map[revision]struct{} {
|
|||
return available
|
||||
}
|
||||
|
||||
func compactIndex(rev int64, available map[revision]struct{}, emptyki *[]*keyIndex) func(i btree.Item) bool {
|
||||
return func(i btree.Item) bool {
|
||||
keyi := i.(*keyIndex)
|
||||
keyi.compact(rev, available)
|
||||
if keyi.isEmpty() {
|
||||
*emptyki = append(*emptyki, keyi)
|
||||
}
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
func (ti *treeIndex) Equal(bi index) bool {
|
||||
b := bi.(*treeIndex)
|
||||
|
||||
|
|
@ -20,6 +20,7 @@ import (
|
|||
"fmt"
|
||||
|
||||
"github.com/google/btree"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -73,11 +74,21 @@ type keyIndex struct {
|
|||
}
|
||||
|
||||
// put puts a revision to the keyIndex.
|
||||
func (ki *keyIndex) put(main int64, sub int64) {
|
||||
func (ki *keyIndex) put(lg *zap.Logger, main int64, sub int64) {
|
||||
rev := revision{main: main, sub: sub}
|
||||
|
||||
if !rev.GreaterThan(ki.modified) {
|
||||
plog.Panicf("store.keyindex: put with unexpected smaller revision [%v / %v]", rev, ki.modified)
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'put' with an unexpected smaller revision",
|
||||
zap.Int64("given-revision-main", rev.main),
|
||||
zap.Int64("given-revision-sub", rev.sub),
|
||||
zap.Int64("modified-revision-main", ki.modified.main),
|
||||
zap.Int64("modified-revision-sub", ki.modified.sub),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: put with unexpected smaller revision [%v / %v]", rev, ki.modified)
|
||||
}
|
||||
}
|
||||
if len(ki.generations) == 0 {
|
||||
ki.generations = append(ki.generations, generation{})
|
||||
|
|
@ -92,9 +103,16 @@ func (ki *keyIndex) put(main int64, sub int64) {
|
|||
ki.modified = rev
|
||||
}
|
||||
|
||||
func (ki *keyIndex) restore(created, modified revision, ver int64) {
|
||||
func (ki *keyIndex) restore(lg *zap.Logger, created, modified revision, ver int64) {
|
||||
if len(ki.generations) != 0 {
|
||||
plog.Panicf("store.keyindex: cannot restore non-empty keyIndex")
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'restore' got an unexpected non-empty generations",
|
||||
zap.Int("generations-size", len(ki.generations)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: cannot restore non-empty keyIndex")
|
||||
}
|
||||
}
|
||||
|
||||
ki.modified = modified
|
||||
|
|
@ -106,14 +124,21 @@ func (ki *keyIndex) restore(created, modified revision, ver int64) {
|
|||
// tombstone puts a revision, pointing to a tombstone, to the keyIndex.
|
||||
// It also creates a new empty generation in the keyIndex.
|
||||
// It returns ErrRevisionNotFound when tombstone on an empty generation.
|
||||
func (ki *keyIndex) tombstone(main int64, sub int64) error {
|
||||
func (ki *keyIndex) tombstone(lg *zap.Logger, main int64, sub int64) error {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected tombstone on empty keyIndex %s", string(ki.key))
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'tombstone' got an unexpected empty keyIndex",
|
||||
zap.String("key", string(ki.key)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: unexpected tombstone on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
}
|
||||
if ki.generations[len(ki.generations)-1].isEmpty() {
|
||||
return ErrRevisionNotFound
|
||||
}
|
||||
ki.put(main, sub)
|
||||
ki.put(lg, main, sub)
|
||||
ki.generations = append(ki.generations, generation{})
|
||||
keysGauge.Dec()
|
||||
return nil
|
||||
|
|
@ -121,9 +146,16 @@ func (ki *keyIndex) tombstone(main int64, sub int64) error {
|
|||
|
||||
// get gets the modified, created revision and version of the key that satisfies the given atRev.
|
||||
// Rev must be higher than or equal to the given atRev.
|
||||
func (ki *keyIndex) get(atRev int64) (modified, created revision, ver int64, err error) {
|
||||
func (ki *keyIndex) get(lg *zap.Logger, atRev int64) (modified, created revision, ver int64, err error) {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'get' got an unexpected empty keyIndex",
|
||||
zap.String("key", string(ki.key)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
}
|
||||
g := ki.findGeneration(atRev)
|
||||
if g.isEmpty() {
|
||||
|
|
@ -141,9 +173,16 @@ func (ki *keyIndex) get(atRev int64) (modified, created revision, ver int64, err
|
|||
// since returns revisions since the given rev. Only the revision with the
|
||||
// largest sub revision will be returned if multiple revisions have the same
|
||||
// main revision.
|
||||
func (ki *keyIndex) since(rev int64) []revision {
|
||||
func (ki *keyIndex) since(lg *zap.Logger, rev int64) []revision {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'since' got an unexpected empty keyIndex",
|
||||
zap.String("key", string(ki.key)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: unexpected get on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
}
|
||||
since := revision{rev, 0}
|
||||
var gi int
|
||||
|
|
@ -182,9 +221,16 @@ func (ki *keyIndex) since(rev int64) []revision {
|
|||
// revision than the given atRev except the largest one (If the largest one is
|
||||
// a tombstone, it will not be kept).
|
||||
// If a generation becomes empty during compaction, it will be removed.
|
||||
func (ki *keyIndex) compact(atRev int64, available map[revision]struct{}) {
|
||||
func (ki *keyIndex) compact(lg *zap.Logger, atRev int64, available map[revision]struct{}) {
|
||||
if ki.isEmpty() {
|
||||
plog.Panicf("store.keyindex: unexpected compact on empty keyIndex %s", string(ki.key))
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"'compact' got an unexpected empty keyIndex",
|
||||
zap.String("key", string(ki.key)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("store.keyindex: unexpected compact on empty keyIndex %s", string(ki.key))
|
||||
}
|
||||
}
|
||||
|
||||
genIdx, revIndex := ki.doCompact(atRev, available)
|
||||
|
|
@ -15,9 +15,9 @@
|
|||
package mvcc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
type RangeOptions struct {
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
package mvcc
|
||||
|
||||
import "github.com/coreos/etcd/internal/lease"
|
||||
import "github.com/coreos/etcd/lease"
|
||||
|
||||
type readView struct{ kv KV }
|
||||
|
||||
|
|
@ -18,17 +18,20 @@ import (
|
|||
"context"
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/crc32"
|
||||
"math"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/schedule"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -99,15 +102,17 @@ type store struct {
|
|||
fifoSched schedule.Scheduler
|
||||
|
||||
stopc chan struct{}
|
||||
|
||||
lg *zap.Logger
|
||||
}
|
||||
|
||||
// NewStore returns a new store. It is useful to create a store inside
|
||||
// mvcc pkg. It should only be used for testing externally.
|
||||
func NewStore(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *store {
|
||||
func NewStore(lg *zap.Logger, b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *store {
|
||||
s := &store{
|
||||
b: b,
|
||||
ig: ig,
|
||||
kvindex: newTreeIndex(),
|
||||
kvindex: newTreeIndex(lg),
|
||||
|
||||
le: le,
|
||||
|
||||
|
|
@ -118,6 +123,8 @@ func NewStore(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *sto
|
|||
fifoSched: schedule.NewFIFOScheduler(),
|
||||
|
||||
stopc: make(chan struct{}),
|
||||
|
||||
lg: lg,
|
||||
}
|
||||
s.ReadView = &readView{s}
|
||||
s.WriteView = &writeView{s}
|
||||
|
|
@ -211,17 +218,18 @@ func (s *store) HashByRev(rev int64) (hash uint32, currentRev int64, compactRev
|
|||
|
||||
func (s *store) Compact(rev int64) (<-chan struct{}, error) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.revMu.Lock()
|
||||
defer s.revMu.Unlock()
|
||||
|
||||
if rev <= s.compactMainRev {
|
||||
ch := make(chan struct{})
|
||||
f := func(ctx context.Context) { s.compactBarrier(ctx, ch) }
|
||||
s.fifoSched.Schedule(f)
|
||||
s.mu.Unlock()
|
||||
s.revMu.Unlock()
|
||||
return ch, ErrCompacted
|
||||
}
|
||||
if rev > s.currentRev {
|
||||
s.mu.Unlock()
|
||||
s.revMu.Unlock()
|
||||
return nil, ErrFutureRev
|
||||
}
|
||||
|
||||
|
|
@ -239,6 +247,8 @@ func (s *store) Compact(rev int64) (<-chan struct{}, error) {
|
|||
// ensure that desired compaction is persisted
|
||||
s.b.ForceCommit()
|
||||
|
||||
s.mu.Unlock()
|
||||
s.revMu.Unlock()
|
||||
keep := s.kvindex.Compact(rev)
|
||||
ch := make(chan struct{})
|
||||
var j = func(ctx context.Context) {
|
||||
|
|
@ -290,7 +300,7 @@ func (s *store) Restore(b backend.Backend) error {
|
|||
|
||||
atomic.StoreUint64(&s.consistentIndex, 0)
|
||||
s.b = b
|
||||
s.kvindex = newTreeIndex()
|
||||
s.kvindex = newTreeIndex(s.lg)
|
||||
s.currentRev = 1
|
||||
s.compactMainRev = -1
|
||||
s.fifoSched = schedule.NewFIFOScheduler()
|
||||
|
|
@ -300,10 +310,13 @@ func (s *store) Restore(b backend.Backend) error {
|
|||
}
|
||||
|
||||
func (s *store) restore() error {
|
||||
reportDbTotalSizeInBytesMu.Lock()
|
||||
b := s.b
|
||||
reportDbTotalSizeInBytesMu.Lock()
|
||||
reportDbTotalSizeInBytes = func() float64 { return float64(b.Size()) }
|
||||
reportDbTotalSizeInBytesMu.Unlock()
|
||||
reportDbTotalSizeInUseInBytesMu.Lock()
|
||||
reportDbTotalSizeInUseInBytes = func() float64 { return float64(b.SizeInUse()) }
|
||||
reportDbTotalSizeInUseInBytesMu.Unlock()
|
||||
|
||||
min, max := newRevBytes(), newRevBytes()
|
||||
revToBytes(revision{main: 1}, min)
|
||||
|
|
@ -318,7 +331,17 @@ func (s *store) restore() error {
|
|||
_, finishedCompactBytes := tx.UnsafeRange(metaBucketName, finishedCompactKeyName, nil, 0)
|
||||
if len(finishedCompactBytes) != 0 {
|
||||
s.compactMainRev = bytesToRev(finishedCompactBytes[0]).main
|
||||
plog.Printf("restore compact to %d", s.compactMainRev)
|
||||
|
||||
if s.lg != nil {
|
||||
s.lg.Info(
|
||||
"restored last compact revision",
|
||||
zap.String("meta-bucket-name", string(metaBucketName)),
|
||||
zap.String("meta-bucket-name-key", string(finishedCompactKeyName)),
|
||||
zap.Int64("restored-compact-revision", s.compactMainRev),
|
||||
)
|
||||
} else {
|
||||
plog.Printf("restore compact to %d", s.compactMainRev)
|
||||
}
|
||||
}
|
||||
_, scheduledCompactBytes := tx.UnsafeRange(metaBucketName, scheduledCompactKeyName, nil, 0)
|
||||
scheduledCompact := int64(0)
|
||||
|
|
@ -328,7 +351,7 @@ func (s *store) restore() error {
|
|||
|
||||
// index keys concurrently as they're loaded in from tx
|
||||
keysGauge.Set(0)
|
||||
rkvc, revc := restoreIntoIndex(s.kvindex)
|
||||
rkvc, revc := restoreIntoIndex(s.lg, s.kvindex)
|
||||
for {
|
||||
keys, vals := tx.UnsafeRange(keyBucketName, min, max, int64(restoreChunkKeys))
|
||||
if len(keys) == 0 {
|
||||
|
|
@ -336,7 +359,7 @@ func (s *store) restore() error {
|
|||
}
|
||||
// rkvc blocks if the total pending keys exceeds the restore
|
||||
// chunk size to keep keys from consuming too much memory.
|
||||
restoreChunk(rkvc, keys, vals, keyToLease)
|
||||
restoreChunk(s.lg, rkvc, keys, vals, keyToLease)
|
||||
if len(keys) < restoreChunkKeys {
|
||||
// partial set implies final set
|
||||
break
|
||||
|
|
@ -365,7 +388,15 @@ func (s *store) restore() error {
|
|||
}
|
||||
err := s.le.Attach(lid, []lease.LeaseItem{{Key: key}})
|
||||
if err != nil {
|
||||
plog.Errorf("unexpected Attach error: %v", err)
|
||||
if s.lg != nil {
|
||||
s.lg.Warn(
|
||||
"failed to attach a lease",
|
||||
zap.String("lease-id", fmt.Sprintf("%016x", lid)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("unexpected Attach error: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -373,7 +404,17 @@ func (s *store) restore() error {
|
|||
|
||||
if scheduledCompact != 0 {
|
||||
s.Compact(scheduledCompact)
|
||||
plog.Printf("resume scheduled compaction at %d", scheduledCompact)
|
||||
|
||||
if s.lg != nil {
|
||||
s.lg.Info(
|
||||
"resume scheduled compaction",
|
||||
zap.String("meta-bucket-name", string(metaBucketName)),
|
||||
zap.String("meta-bucket-name-key", string(scheduledCompactKeyName)),
|
||||
zap.Int64("scheduled-compact-revision", scheduledCompact),
|
||||
)
|
||||
} else {
|
||||
plog.Printf("resume scheduled compaction at %d", scheduledCompact)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
@ -385,7 +426,7 @@ type revKeyValue struct {
|
|||
kstr string
|
||||
}
|
||||
|
||||
func restoreIntoIndex(idx index) (chan<- revKeyValue, <-chan int64) {
|
||||
func restoreIntoIndex(lg *zap.Logger, idx index) (chan<- revKeyValue, <-chan int64) {
|
||||
rkvc, revc := make(chan revKeyValue, restoreChunkKeys), make(chan int64, 1)
|
||||
go func() {
|
||||
currentRev := int64(1)
|
||||
|
|
@ -416,12 +457,12 @@ func restoreIntoIndex(idx index) (chan<- revKeyValue, <-chan int64) {
|
|||
currentRev = rev.main
|
||||
if ok {
|
||||
if isTombstone(rkv.key) {
|
||||
ki.tombstone(rev.main, rev.sub)
|
||||
ki.tombstone(lg, rev.main, rev.sub)
|
||||
continue
|
||||
}
|
||||
ki.put(rev.main, rev.sub)
|
||||
ki.put(lg, rev.main, rev.sub)
|
||||
} else if !isTombstone(rkv.key) {
|
||||
ki.restore(revision{rkv.kv.CreateRevision, 0}, rev, rkv.kv.Version)
|
||||
ki.restore(lg, revision{rkv.kv.CreateRevision, 0}, rev, rkv.kv.Version)
|
||||
idx.Insert(ki)
|
||||
kiCache[rkv.kstr] = ki
|
||||
}
|
||||
|
|
@ -430,11 +471,15 @@ func restoreIntoIndex(idx index) (chan<- revKeyValue, <-chan int64) {
|
|||
return rkvc, revc
|
||||
}
|
||||
|
||||
func restoreChunk(kvc chan<- revKeyValue, keys, vals [][]byte, keyToLease map[string]lease.LeaseID) {
|
||||
func restoreChunk(lg *zap.Logger, kvc chan<- revKeyValue, keys, vals [][]byte, keyToLease map[string]lease.LeaseID) {
|
||||
for i, key := range keys {
|
||||
rkv := revKeyValue{key: key}
|
||||
if err := rkv.kv.Unmarshal(vals[i]); err != nil {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
if lg != nil {
|
||||
lg.Fatal("failed to unmarshal mvccpb.KeyValue", zap.Error(err))
|
||||
} else {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
}
|
||||
rkv.kstr = string(rkv.kv.Key)
|
||||
if isTombstone(key) {
|
||||
|
|
@ -484,9 +529,17 @@ func (s *store) ConsistentIndex() uint64 {
|
|||
}
|
||||
|
||||
// appendMarkTombstone appends tombstone mark to normal revision bytes.
|
||||
func appendMarkTombstone(b []byte) []byte {
|
||||
func appendMarkTombstone(lg *zap.Logger, b []byte) []byte {
|
||||
if len(b) != revBytesLen {
|
||||
plog.Panicf("cannot append mark to non normal revision bytes")
|
||||
if lg != nil {
|
||||
lg.Panic(
|
||||
"cannot append tombstone mark to non-normal revision bytes",
|
||||
zap.Int("expected-revision-bytes-size", revBytesLen),
|
||||
zap.Int("given-revision-bytes-size", len(b)),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("cannot append mark to non normal revision bytes")
|
||||
}
|
||||
}
|
||||
return append(b, markTombstone)
|
||||
}
|
||||
|
|
@ -17,6 +17,8 @@ package mvcc
|
|||
import (
|
||||
"encoding/binary"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struct{}) bool {
|
||||
|
|
@ -51,7 +53,15 @@ func (s *store) scheduleCompaction(compactMainRev int64, keep map[revision]struc
|
|||
revToBytes(revision{main: compactMainRev}, rbytes)
|
||||
tx.UnsafePut(metaBucketName, finishedCompactKeyName, rbytes)
|
||||
tx.Unlock()
|
||||
plog.Printf("finished scheduled compaction at %d (took %v)", compactMainRev, time.Since(totalStart))
|
||||
if s.lg != nil {
|
||||
s.lg.Info(
|
||||
"finished scheduled compaction",
|
||||
zap.Int64("compact-revision", compactMainRev),
|
||||
zap.Duration("took", time.Since(totalStart)),
|
||||
)
|
||||
} else {
|
||||
plog.Printf("finished scheduled compaction at %d (took %v)", compactMainRev, time.Since(totalStart))
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -15,9 +15,10 @@
|
|||
package mvcc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
type storeTxnRead struct {
|
||||
|
|
@ -83,14 +84,14 @@ func (tw *storeTxnWrite) Range(key, end []byte, ro RangeOptions) (r *RangeResult
|
|||
|
||||
func (tw *storeTxnWrite) DeleteRange(key, end []byte) (int64, int64) {
|
||||
if n := tw.deleteRange(key, end); n != 0 || len(tw.changes) > 0 {
|
||||
return n, int64(tw.beginRev + 1)
|
||||
return n, tw.beginRev + 1
|
||||
}
|
||||
return 0, int64(tw.beginRev)
|
||||
return 0, tw.beginRev
|
||||
}
|
||||
|
||||
func (tw *storeTxnWrite) Put(key, value []byte, lease lease.LeaseID) int64 {
|
||||
tw.put(key, value, lease)
|
||||
return int64(tw.beginRev + 1)
|
||||
return tw.beginRev + 1
|
||||
}
|
||||
|
||||
func (tw *storeTxnWrite) End() {
|
||||
|
|
@ -120,7 +121,7 @@ func (tr *storeTxnRead) rangeKeys(key, end []byte, curRev int64, ro RangeOptions
|
|||
return &RangeResult{KVs: nil, Count: -1, Rev: 0}, ErrCompacted
|
||||
}
|
||||
|
||||
revpairs := tr.s.kvindex.Revisions(key, end, int64(rev))
|
||||
revpairs := tr.s.kvindex.Revisions(key, end, rev)
|
||||
if len(revpairs) == 0 {
|
||||
return &RangeResult{KVs: nil, Count: 0, Rev: curRev}, nil
|
||||
}
|
||||
|
|
@ -139,10 +140,25 @@ func (tr *storeTxnRead) rangeKeys(key, end []byte, curRev int64, ro RangeOptions
|
|||
revToBytes(revpair, revBytes)
|
||||
_, vs := tr.tx.UnsafeRange(keyBucketName, revBytes, nil, 0)
|
||||
if len(vs) != 1 {
|
||||
plog.Fatalf("range cannot find rev (%d,%d)", revpair.main, revpair.sub)
|
||||
if tr.s.lg != nil {
|
||||
tr.s.lg.Fatal(
|
||||
"range failed to find revision pair",
|
||||
zap.Int64("revision-main", revpair.main),
|
||||
zap.Int64("revision-sub", revpair.sub),
|
||||
)
|
||||
} else {
|
||||
plog.Fatalf("range cannot find rev (%d,%d)", revpair.main, revpair.sub)
|
||||
}
|
||||
}
|
||||
if err := kvs[i].Unmarshal(vs[0]); err != nil {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
if tr.s.lg != nil {
|
||||
tr.s.lg.Fatal(
|
||||
"failed to unmarshal mvccpb.KeyValue",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Fatalf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
return &RangeResult{KVs: kvs, Count: len(revpairs), Rev: curRev}, nil
|
||||
|
|
@ -177,7 +193,14 @@ func (tw *storeTxnWrite) put(key, value []byte, leaseID lease.LeaseID) {
|
|||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
if tw.storeTxnRead.s.lg != nil {
|
||||
tw.storeTxnRead.s.lg.Fatal(
|
||||
"failed to marshal mvccpb.KeyValue",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tw.tx.UnsafeSeqPut(keyBucketName, ibytes, d)
|
||||
|
|
@ -190,7 +213,14 @@ func (tw *storeTxnWrite) put(key, value []byte, leaseID lease.LeaseID) {
|
|||
}
|
||||
err = tw.s.le.Detach(oldLease, []lease.LeaseItem{{Key: string(key)}})
|
||||
if err != nil {
|
||||
plog.Errorf("unexpected error from lease detach: %v", err)
|
||||
if tw.storeTxnRead.s.lg != nil {
|
||||
tw.storeTxnRead.s.lg.Fatal(
|
||||
"failed to detach old lease from a key",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("unexpected error from lease detach: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
if leaseID != lease.NoLease {
|
||||
|
|
@ -209,33 +239,54 @@ func (tw *storeTxnWrite) deleteRange(key, end []byte) int64 {
|
|||
if len(tw.changes) > 0 {
|
||||
rrev += 1
|
||||
}
|
||||
keys, revs := tw.s.kvindex.Range(key, end, rrev)
|
||||
keys, _ := tw.s.kvindex.Range(key, end, rrev)
|
||||
if len(keys) == 0 {
|
||||
return 0
|
||||
}
|
||||
for i, key := range keys {
|
||||
tw.delete(key, revs[i])
|
||||
for _, key := range keys {
|
||||
tw.delete(key)
|
||||
}
|
||||
return int64(len(keys))
|
||||
}
|
||||
|
||||
func (tw *storeTxnWrite) delete(key []byte, rev revision) {
|
||||
func (tw *storeTxnWrite) delete(key []byte) {
|
||||
ibytes := newRevBytes()
|
||||
idxRev := revision{main: tw.beginRev + 1, sub: int64(len(tw.changes))}
|
||||
revToBytes(idxRev, ibytes)
|
||||
ibytes = appendMarkTombstone(ibytes)
|
||||
|
||||
if tw.storeTxnRead.s != nil && tw.storeTxnRead.s.lg != nil {
|
||||
ibytes = appendMarkTombstone(tw.storeTxnRead.s.lg, ibytes)
|
||||
} else {
|
||||
// TODO: remove this in v3.5
|
||||
ibytes = appendMarkTombstone(nil, ibytes)
|
||||
}
|
||||
|
||||
kv := mvccpb.KeyValue{Key: key}
|
||||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
if tw.storeTxnRead.s.lg != nil {
|
||||
tw.storeTxnRead.s.lg.Fatal(
|
||||
"failed to marshal mvccpb.KeyValue",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
tw.tx.UnsafeSeqPut(keyBucketName, ibytes, d)
|
||||
err = tw.s.kvindex.Tombstone(key, idxRev)
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot tombstone an existing key (%s): %v", string(key), err)
|
||||
if tw.storeTxnRead.s.lg != nil {
|
||||
tw.storeTxnRead.s.lg.Fatal(
|
||||
"failed to tombstone an existing key",
|
||||
zap.String("key", string(key)),
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Fatalf("cannot tombstone an existing key (%s): %v", string(key), err)
|
||||
}
|
||||
}
|
||||
tw.changes = append(tw.changes, kv)
|
||||
|
||||
|
|
@ -245,7 +296,14 @@ func (tw *storeTxnWrite) delete(key []byte, rev revision) {
|
|||
if leaseID != lease.NoLease {
|
||||
err = tw.s.le.Detach(leaseID, []lease.LeaseItem{item})
|
||||
if err != nil {
|
||||
plog.Errorf("cannot detach %v", err)
|
||||
if tw.storeTxnRead.s.lg != nil {
|
||||
tw.storeTxnRead.s.lg.Fatal(
|
||||
"failed to detach old lease from a key",
|
||||
zap.Error(err),
|
||||
)
|
||||
} else {
|
||||
plog.Errorf("cannot detach %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -143,7 +143,7 @@ var (
|
|||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "db_total_size_in_bytes",
|
||||
Help: "Total size of the underlying database in bytes.",
|
||||
Help: "Total size of the underlying database physically allocated in bytes.",
|
||||
},
|
||||
func() float64 {
|
||||
reportDbTotalSizeInBytesMu.RLock()
|
||||
|
|
@ -154,6 +154,22 @@ var (
|
|||
// overridden by mvcc initialization
|
||||
reportDbTotalSizeInBytesMu sync.RWMutex
|
||||
reportDbTotalSizeInBytes func() float64 = func() float64 { return 0 }
|
||||
|
||||
dbTotalSizeInUse = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
|
||||
Namespace: "etcd_debugging",
|
||||
Subsystem: "mvcc",
|
||||
Name: "db_total_size_in_use_in_bytes",
|
||||
Help: "Total size of the underlying database logically in use in bytes.",
|
||||
},
|
||||
func() float64 {
|
||||
reportDbTotalSizeInUseInBytesMu.RLock()
|
||||
defer reportDbTotalSizeInUseInBytesMu.RUnlock()
|
||||
return reportDbTotalSizeInUseInBytes()
|
||||
},
|
||||
)
|
||||
// overridden by mvcc initialization
|
||||
reportDbTotalSizeInUseInBytesMu sync.RWMutex
|
||||
reportDbTotalSizeInUseInBytes func() float64 = func() float64 { return 0 }
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
|
@ -172,6 +188,7 @@ func init() {
|
|||
prometheus.MustRegister(dbCompactionTotalDurations)
|
||||
prometheus.MustRegister(dbCompactionKeysCounter)
|
||||
prometheus.MustRegister(dbTotalSize)
|
||||
prometheus.MustRegister(dbTotalSizeInUse)
|
||||
}
|
||||
|
||||
// ReportEventReceived reports that an event is received.
|
||||
|
|
@ -14,9 +14,7 @@
|
|||
|
||||
package mvcc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
)
|
||||
import "github.com/coreos/etcd/lease"
|
||||
|
||||
type metricsTxnWrite struct {
|
||||
TxnWrite
|
||||
|
|
@ -16,9 +16,10 @@ package mvcc
|
|||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
func UpdateConsistentIndex(be backend.Backend, index uint64) {
|
||||
|
|
@ -47,7 +48,7 @@ func WriteKV(be backend.Backend, kv mvccpb.KeyValue) {
|
|||
|
||||
d, err := kv.Marshal()
|
||||
if err != nil {
|
||||
plog.Fatalf("cannot marshal event: %v", err)
|
||||
panic(fmt.Errorf("cannot marshal event: %v", err))
|
||||
}
|
||||
|
||||
be.BatchTx().Lock()
|
||||
|
|
@ -18,9 +18,10 @@ import (
|
|||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/internal/lease"
|
||||
"github.com/coreos/etcd/internal/mvcc/backend"
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/mvcc/backend"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
// non-const so modifiable by tests
|
||||
|
|
@ -67,13 +68,13 @@ type watchableStore struct {
|
|||
// cancel operations.
|
||||
type cancelFunc func()
|
||||
|
||||
func New(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) ConsistentWatchableKV {
|
||||
return newWatchableStore(b, le, ig)
|
||||
func New(lg *zap.Logger, b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) ConsistentWatchableKV {
|
||||
return newWatchableStore(lg, b, le, ig)
|
||||
}
|
||||
|
||||
func newWatchableStore(b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *watchableStore {
|
||||
func newWatchableStore(lg *zap.Logger, b backend.Backend, le lease.Lessor, ig ConsistentIndexGetter) *watchableStore {
|
||||
s := &watchableStore{
|
||||
store: NewStore(b, le, ig),
|
||||
store: NewStore(lg, b, le, ig),
|
||||
victimc: make(chan struct{}, 1),
|
||||
unsynced: newWatcherGroup(),
|
||||
synced: newWatcherGroup(),
|
||||
|
|
@ -192,7 +193,7 @@ func (s *watchableStore) Restore(b backend.Backend) error {
|
|||
}
|
||||
|
||||
for wa := range s.synced.watchers {
|
||||
s.unsynced.watchers.add(wa)
|
||||
s.unsynced.add(wa)
|
||||
}
|
||||
s.synced = newWatcherGroup()
|
||||
return nil
|
||||
|
|
@ -346,7 +347,13 @@ func (s *watchableStore) syncWatchers() int {
|
|||
tx := s.store.b.ReadTx()
|
||||
tx.Lock()
|
||||
revs, vs := tx.UnsafeRange(keyBucketName, minBytes, maxBytes, 0)
|
||||
evs := kvsToEvents(wg, revs, vs)
|
||||
var evs []mvccpb.Event
|
||||
if s.store != nil && s.store.lg != nil {
|
||||
evs = kvsToEvents(s.store.lg, wg, revs, vs)
|
||||
} else {
|
||||
// TODO: remove this in v3.5
|
||||
evs = kvsToEvents(nil, wg, revs, vs)
|
||||
}
|
||||
tx.Unlock()
|
||||
|
||||
var victims watcherBatch
|
||||
|
|
@ -398,11 +405,15 @@ func (s *watchableStore) syncWatchers() int {
|
|||
}
|
||||
|
||||
// kvsToEvents gets all events for the watchers from all key-value pairs
|
||||
func kvsToEvents(wg *watcherGroup, revs, vals [][]byte) (evs []mvccpb.Event) {
|
||||
func kvsToEvents(lg *zap.Logger, wg *watcherGroup, revs, vals [][]byte) (evs []mvccpb.Event) {
|
||||
for i, v := range vals {
|
||||
var kv mvccpb.KeyValue
|
||||
if err := kv.Unmarshal(v); err != nil {
|
||||
plog.Panicf("cannot unmarshal event: %v", err)
|
||||
if lg != nil {
|
||||
lg.Panic("failed to unmarshal mvccpb.KeyValue", zap.Error(err))
|
||||
} else {
|
||||
plog.Panicf("cannot unmarshal event: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if !wg.contains(string(kv.Key)) {
|
||||
|
|
@ -426,7 +437,14 @@ func (s *watchableStore) notify(rev int64, evs []mvccpb.Event) {
|
|||
var victim watcherBatch
|
||||
for w, eb := range newWatcherBatch(&s.synced, evs) {
|
||||
if eb.revs != 1 {
|
||||
plog.Panicf("unexpected multiple revisions in notification")
|
||||
if s.store != nil && s.store.lg != nil {
|
||||
s.store.lg.Panic(
|
||||
"unexpected multiple revisions in watch notification",
|
||||
zap.Int("number-of-revisions", eb.revs),
|
||||
)
|
||||
} else {
|
||||
plog.Panicf("unexpected multiple revisions in notification")
|
||||
}
|
||||
}
|
||||
if w.send(WatchResponse{WatchID: w.id, Events: eb.evs, Revision: rev}) {
|
||||
pendingEventsGauge.Add(float64(len(eb.evs)))
|
||||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
package mvcc
|
||||
|
||||
import "github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
import "github.com/coreos/etcd/mvcc/mvccpb"
|
||||
|
||||
func (tw *watchableStoreTxnWrite) End() {
|
||||
changes := tw.Changes()
|
||||
|
|
@ -19,7 +19,7 @@ import (
|
|||
"errors"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
)
|
||||
|
||||
// AutoWatchID is the watcher ID passed in WatchStream.Watch when no
|
||||
|
|
@ -17,7 +17,7 @@ package mvcc
|
|||
import (
|
||||
"math"
|
||||
|
||||
"github.com/coreos/etcd/internal/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/mvcc/mvccpb"
|
||||
"github.com/coreos/etcd/pkg/adt"
|
||||
)
|
||||
|
||||
|
|
@ -0,0 +1,46 @@
|
|||
// Copyright 2018 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 logutil
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// assert that "discardLogger" satisfy "Logger" interface
|
||||
var _ Logger = &discardLogger{}
|
||||
|
||||
// NewDiscardLogger returns a new Logger that discards everything except "fatal".
|
||||
func NewDiscardLogger() Logger { return &discardLogger{} }
|
||||
|
||||
type discardLogger struct{}
|
||||
|
||||
func (l *discardLogger) Info(args ...interface{}) {}
|
||||
func (l *discardLogger) Infoln(args ...interface{}) {}
|
||||
func (l *discardLogger) Infof(format string, args ...interface{}) {}
|
||||
func (l *discardLogger) Warning(args ...interface{}) {}
|
||||
func (l *discardLogger) Warningln(args ...interface{}) {}
|
||||
func (l *discardLogger) Warningf(format string, args ...interface{}) {}
|
||||
func (l *discardLogger) Error(args ...interface{}) {}
|
||||
func (l *discardLogger) Errorln(args ...interface{}) {}
|
||||
func (l *discardLogger) Errorf(format string, args ...interface{}) {}
|
||||
func (l *discardLogger) Fatal(args ...interface{}) { log.Fatal(args...) }
|
||||
func (l *discardLogger) Fatalln(args ...interface{}) { log.Fatalln(args...) }
|
||||
func (l *discardLogger) Fatalf(format string, args ...interface{}) { log.Fatalf(format, args...) }
|
||||
func (l *discardLogger) V(lvl int) bool {
|
||||
return false
|
||||
}
|
||||
func (l *discardLogger) Lvl(lvl int) grpclog.LoggerV2 { return l }
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2018 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 logutil includes utilities to facilitate logging.
|
||||
package logutil
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
// Copyright 2018 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 logutil
|
||||
|
||||
import "google.golang.org/grpc/grpclog"
|
||||
|
||||
// Logger defines logging interface.
|
||||
// TODO: deprecate in v3.5.
|
||||
type Logger interface {
|
||||
grpclog.LoggerV2
|
||||
|
||||
// Lvl returns logger if logger's verbosity level >= "lvl".
|
||||
// Otherwise, logger that discards everything.
|
||||
Lvl(lvl int) grpclog.LoggerV2
|
||||
}
|
||||
|
||||
// assert that "defaultLogger" satisfy "Logger" interface
|
||||
var _ Logger = &defaultLogger{}
|
||||
|
||||
// NewLogger wraps "grpclog.LoggerV2" that implements "Logger" interface.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var defaultLogger Logger
|
||||
// g := grpclog.NewLoggerV2WithVerbosity(os.Stderr, os.Stderr, os.Stderr, 4)
|
||||
// defaultLogger = NewLogger(g)
|
||||
//
|
||||
func NewLogger(g grpclog.LoggerV2) Logger { return &defaultLogger{g: g} }
|
||||
|
||||
type defaultLogger struct {
|
||||
g grpclog.LoggerV2
|
||||
}
|
||||
|
||||
func (l *defaultLogger) Info(args ...interface{}) { l.g.Info(args...) }
|
||||
func (l *defaultLogger) Infoln(args ...interface{}) { l.g.Info(args...) }
|
||||
func (l *defaultLogger) Infof(format string, args ...interface{}) { l.g.Infof(format, args...) }
|
||||
func (l *defaultLogger) Warning(args ...interface{}) { l.g.Warning(args...) }
|
||||
func (l *defaultLogger) Warningln(args ...interface{}) { l.g.Warning(args...) }
|
||||
func (l *defaultLogger) Warningf(format string, args ...interface{}) { l.g.Warningf(format, args...) }
|
||||
func (l *defaultLogger) Error(args ...interface{}) { l.g.Error(args...) }
|
||||
func (l *defaultLogger) Errorln(args ...interface{}) { l.g.Error(args...) }
|
||||
func (l *defaultLogger) Errorf(format string, args ...interface{}) { l.g.Errorf(format, args...) }
|
||||
func (l *defaultLogger) Fatal(args ...interface{}) { l.g.Fatal(args...) }
|
||||
func (l *defaultLogger) Fatalln(args ...interface{}) { l.g.Fatal(args...) }
|
||||
func (l *defaultLogger) Fatalf(format string, args ...interface{}) { l.g.Fatalf(format, args...) }
|
||||
func (l *defaultLogger) V(lvl int) bool { return l.g.V(lvl) }
|
||||
func (l *defaultLogger) Lvl(lvl int) grpclog.LoggerV2 {
|
||||
if l.g.V(lvl) {
|
||||
return l
|
||||
}
|
||||
return &discardLogger{}
|
||||
}
|
||||
|
|
@ -0,0 +1,194 @@
|
|||
// 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 logutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultMergePeriod = time.Second
|
||||
defaultTimeOutputScale = 10 * time.Millisecond
|
||||
|
||||
outputInterval = time.Second
|
||||
)
|
||||
|
||||
// line represents a log line that can be printed out
|
||||
// through capnslog.PackageLogger.
|
||||
type line struct {
|
||||
level capnslog.LogLevel
|
||||
str string
|
||||
}
|
||||
|
||||
func (l line) append(s string) line {
|
||||
return line{
|
||||
level: l.level,
|
||||
str: l.str + " " + s,
|
||||
}
|
||||
}
|
||||
|
||||
// status represents the merge status of a line.
|
||||
type status struct {
|
||||
period time.Duration
|
||||
|
||||
start time.Time // start time of latest merge period
|
||||
count int // number of merged lines from starting
|
||||
}
|
||||
|
||||
func (s *status) isInMergePeriod(now time.Time) bool {
|
||||
return s.period == 0 || s.start.Add(s.period).After(now)
|
||||
}
|
||||
|
||||
func (s *status) isEmpty() bool { return s.count == 0 }
|
||||
|
||||
func (s *status) summary(now time.Time) string {
|
||||
ts := s.start.Round(defaultTimeOutputScale)
|
||||
took := now.Round(defaultTimeOutputScale).Sub(ts)
|
||||
return fmt.Sprintf("[merged %d repeated lines in %s]", s.count, took)
|
||||
}
|
||||
|
||||
func (s *status) reset(now time.Time) {
|
||||
s.start = now
|
||||
s.count = 0
|
||||
}
|
||||
|
||||
// MergeLogger supports merge logging, which merges repeated log lines
|
||||
// and prints summary log lines instead.
|
||||
//
|
||||
// For merge logging, MergeLogger prints out the line when the line appears
|
||||
// at the first time. MergeLogger holds the same log line printed within
|
||||
// defaultMergePeriod, and prints out summary log line at the end of defaultMergePeriod.
|
||||
// It stops merging when the line doesn't appear within the
|
||||
// defaultMergePeriod.
|
||||
type MergeLogger struct {
|
||||
*capnslog.PackageLogger
|
||||
|
||||
mu sync.Mutex // protect statusm
|
||||
statusm map[line]*status
|
||||
}
|
||||
|
||||
func NewMergeLogger(logger *capnslog.PackageLogger) *MergeLogger {
|
||||
l := &MergeLogger{
|
||||
PackageLogger: logger,
|
||||
statusm: make(map[line]*status),
|
||||
}
|
||||
go l.outputLoop()
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeInfo(entries ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.INFO,
|
||||
str: fmt.Sprint(entries...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeInfof(format string, args ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.INFO,
|
||||
str: fmt.Sprintf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeNotice(entries ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.NOTICE,
|
||||
str: fmt.Sprint(entries...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeNoticef(format string, args ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.NOTICE,
|
||||
str: fmt.Sprintf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeWarning(entries ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.WARNING,
|
||||
str: fmt.Sprint(entries...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeWarningf(format string, args ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.WARNING,
|
||||
str: fmt.Sprintf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeError(entries ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.ERROR,
|
||||
str: fmt.Sprint(entries...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) MergeErrorf(format string, args ...interface{}) {
|
||||
l.merge(line{
|
||||
level: capnslog.ERROR,
|
||||
str: fmt.Sprintf(format, args...),
|
||||
})
|
||||
}
|
||||
|
||||
func (l *MergeLogger) merge(ln line) {
|
||||
l.mu.Lock()
|
||||
|
||||
// increase count if the logger is merging the line
|
||||
if status, ok := l.statusm[ln]; ok {
|
||||
status.count++
|
||||
l.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
// initialize status of the line
|
||||
l.statusm[ln] = &status{
|
||||
period: defaultMergePeriod,
|
||||
start: time.Now(),
|
||||
}
|
||||
// release the lock before IO operation
|
||||
l.mu.Unlock()
|
||||
// print out the line at its first time
|
||||
l.PackageLogger.Logf(ln.level, ln.str)
|
||||
}
|
||||
|
||||
func (l *MergeLogger) outputLoop() {
|
||||
for now := range time.Tick(outputInterval) {
|
||||
var outputs []line
|
||||
|
||||
l.mu.Lock()
|
||||
for ln, status := range l.statusm {
|
||||
if status.isInMergePeriod(now) {
|
||||
continue
|
||||
}
|
||||
if status.isEmpty() {
|
||||
delete(l.statusm, ln)
|
||||
continue
|
||||
}
|
||||
outputs = append(outputs, ln.append(status.summary(now)))
|
||||
status.reset(now)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
|
||||
for _, o := range outputs {
|
||||
l.PackageLogger.Logf(o.level, o.str)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
// Copyright 2018 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 logutil
|
||||
|
||||
import (
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// assert that "packageLogger" satisfy "Logger" interface
|
||||
var _ Logger = &packageLogger{}
|
||||
|
||||
// NewPackageLogger wraps "*capnslog.PackageLogger" that implements "Logger" interface.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// var defaultLogger Logger
|
||||
// defaultLogger = NewPackageLogger("github.com/coreos/etcd", "snapshot")
|
||||
//
|
||||
func NewPackageLogger(repo, pkg string) Logger {
|
||||
return &packageLogger{p: capnslog.NewPackageLogger(repo, pkg)}
|
||||
}
|
||||
|
||||
type packageLogger struct {
|
||||
p *capnslog.PackageLogger
|
||||
}
|
||||
|
||||
func (l *packageLogger) Info(args ...interface{}) { l.p.Info(args...) }
|
||||
func (l *packageLogger) Infoln(args ...interface{}) { l.p.Info(args...) }
|
||||
func (l *packageLogger) Infof(format string, args ...interface{}) { l.p.Infof(format, args...) }
|
||||
func (l *packageLogger) Warning(args ...interface{}) { l.p.Warning(args...) }
|
||||
func (l *packageLogger) Warningln(args ...interface{}) { l.p.Warning(args...) }
|
||||
func (l *packageLogger) Warningf(format string, args ...interface{}) { l.p.Warningf(format, args...) }
|
||||
func (l *packageLogger) Error(args ...interface{}) { l.p.Error(args...) }
|
||||
func (l *packageLogger) Errorln(args ...interface{}) { l.p.Error(args...) }
|
||||
func (l *packageLogger) Errorf(format string, args ...interface{}) { l.p.Errorf(format, args...) }
|
||||
func (l *packageLogger) Fatal(args ...interface{}) { l.p.Fatal(args...) }
|
||||
func (l *packageLogger) Fatalln(args ...interface{}) { l.p.Fatal(args...) }
|
||||
func (l *packageLogger) Fatalf(format string, args ...interface{}) { l.p.Fatalf(format, args...) }
|
||||
func (l *packageLogger) V(lvl int) bool {
|
||||
return l.p.LevelAt(capnslog.LogLevel(lvl))
|
||||
}
|
||||
func (l *packageLogger) Lvl(lvl int) grpclog.LoggerV2 {
|
||||
if l.p.LevelAt(capnslog.LogLevel(lvl)) {
|
||||
return l
|
||||
}
|
||||
return &discardLogger{}
|
||||
}
|
||||
|
|
@ -0,0 +1,111 @@
|
|||
// Copyright 2018 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 logutil
|
||||
|
||||
import (
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
// NewGRPCLoggerV2 converts "*zap.Logger" to "grpclog.LoggerV2".
|
||||
// It discards all INFO level logging in gRPC, if debug level
|
||||
// is not enabled in "*zap.Logger".
|
||||
func NewGRPCLoggerV2(lcfg zap.Config) (grpclog.LoggerV2, error) {
|
||||
lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &zapGRPCLogger{lg: lg, sugar: lg.Sugar()}, nil
|
||||
}
|
||||
|
||||
// NewGRPCLoggerV2FromZapCore creates "grpclog.LoggerV2" from "zap.Core"
|
||||
// and "zapcore.WriteSyncer". It discards all INFO level logging in gRPC,
|
||||
// if debug level is not enabled in "*zap.Logger".
|
||||
func NewGRPCLoggerV2FromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) grpclog.LoggerV2 {
|
||||
// "AddCallerSkip" to annotate caller outside of "logutil"
|
||||
lg := zap.New(cr, zap.AddCaller(), zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
|
||||
return &zapGRPCLogger{lg: lg, sugar: lg.Sugar()}
|
||||
}
|
||||
|
||||
type zapGRPCLogger struct {
|
||||
lg *zap.Logger
|
||||
sugar *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Info(args ...interface{}) {
|
||||
if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
|
||||
return
|
||||
}
|
||||
zl.sugar.Info(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Infoln(args ...interface{}) {
|
||||
if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
|
||||
return
|
||||
}
|
||||
zl.sugar.Info(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Infof(format string, args ...interface{}) {
|
||||
if !zl.lg.Core().Enabled(zapcore.DebugLevel) {
|
||||
return
|
||||
}
|
||||
zl.sugar.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Warning(args ...interface{}) {
|
||||
zl.sugar.Warn(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Warningln(args ...interface{}) {
|
||||
zl.sugar.Warn(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Warningf(format string, args ...interface{}) {
|
||||
zl.sugar.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Error(args ...interface{}) {
|
||||
zl.sugar.Error(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Errorln(args ...interface{}) {
|
||||
zl.sugar.Error(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Errorf(format string, args ...interface{}) {
|
||||
zl.sugar.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Fatal(args ...interface{}) {
|
||||
zl.sugar.Fatal(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Fatalln(args ...interface{}) {
|
||||
zl.sugar.Fatal(args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) Fatalf(format string, args ...interface{}) {
|
||||
zl.sugar.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapGRPCLogger) V(l int) bool {
|
||||
// infoLog == 0
|
||||
if l <= 0 { // debug level, then we ignore info level in gRPC
|
||||
return !zl.lg.Core().Enabled(zapcore.DebugLevel)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,89 @@
|
|||
// Copyright 2018 The etcd Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !windows
|
||||
|
||||
package logutil
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/coreos/go-systemd/journal"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// NewJournaldWriter wraps "io.Writer" to redirect log output
|
||||
// to the local systemd journal. If journald send fails, it fails
|
||||
// back to writing to the original writer.
|
||||
// The decode overhead is only <30µs per write.
|
||||
// Reference: https://github.com/coreos/pkg/blob/master/capnslog/journald_formatter.go
|
||||
func NewJournaldWriter(wr io.Writer) io.Writer {
|
||||
return &journaldWriter{Writer: wr}
|
||||
}
|
||||
|
||||
type journaldWriter struct {
|
||||
io.Writer
|
||||
}
|
||||
|
||||
// WARN: assume that etcd uses default field names in zap encoder config
|
||||
// make sure to keep this up-to-date!
|
||||
type logLine struct {
|
||||
Level string `json:"level"`
|
||||
Caller string `json:"caller"`
|
||||
}
|
||||
|
||||
func (w *journaldWriter) Write(p []byte) (int, error) {
|
||||
line := &logLine{}
|
||||
if err := json.NewDecoder(bytes.NewReader(p)).Decode(line); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
var pri journal.Priority
|
||||
switch line.Level {
|
||||
case zapcore.DebugLevel.String():
|
||||
pri = journal.PriDebug
|
||||
case zapcore.InfoLevel.String():
|
||||
pri = journal.PriInfo
|
||||
|
||||
case zapcore.WarnLevel.String():
|
||||
pri = journal.PriWarning
|
||||
case zapcore.ErrorLevel.String():
|
||||
pri = journal.PriErr
|
||||
|
||||
case zapcore.DPanicLevel.String():
|
||||
pri = journal.PriCrit
|
||||
case zapcore.PanicLevel.String():
|
||||
pri = journal.PriCrit
|
||||
case zapcore.FatalLevel.String():
|
||||
pri = journal.PriCrit
|
||||
|
||||
default:
|
||||
panic(fmt.Errorf("unknown log level: %q", line.Level))
|
||||
}
|
||||
|
||||
err := journal.Send(string(p), pri, map[string]string{
|
||||
"PACKAGE": filepath.Dir(line.Caller),
|
||||
"SYSLOG_IDENTIFIER": filepath.Base(os.Args[0]),
|
||||
})
|
||||
if err != nil {
|
||||
fmt.Println("FAILED TO WRITE TO JOURNALD", err, string(p))
|
||||
return w.Writer.Write(p)
|
||||
}
|
||||
return 0, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,97 @@
|
|||
// Copyright 2018 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 logutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/coreos/etcd/raft"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// NewRaftLogger converts "*zap.Logger" to "raft.Logger".
|
||||
func NewRaftLogger(lcfg *zap.Config) (raft.Logger, error) {
|
||||
if lcfg == nil {
|
||||
return nil, errors.New("nil zap.Config")
|
||||
}
|
||||
lg, err := lcfg.Build(zap.AddCallerSkip(1)) // to annotate caller outside of "logutil"
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}, nil
|
||||
}
|
||||
|
||||
// NewRaftLoggerFromZapCore creates "raft.Logger" from "zap.Core"
|
||||
// and "zapcore.WriteSyncer".
|
||||
func NewRaftLoggerFromZapCore(cr zapcore.Core, syncer zapcore.WriteSyncer) raft.Logger {
|
||||
// "AddCallerSkip" to annotate caller outside of "logutil"
|
||||
lg := zap.New(cr, zap.AddCaller(), zap.AddCallerSkip(1), zap.ErrorOutput(syncer))
|
||||
return &zapRaftLogger{lg: lg, sugar: lg.Sugar()}
|
||||
}
|
||||
|
||||
type zapRaftLogger struct {
|
||||
lg *zap.Logger
|
||||
sugar *zap.SugaredLogger
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Debug(args ...interface{}) {
|
||||
zl.sugar.Debug(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Debugf(format string, args ...interface{}) {
|
||||
zl.sugar.Debugf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Error(args ...interface{}) {
|
||||
zl.sugar.Error(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Errorf(format string, args ...interface{}) {
|
||||
zl.sugar.Errorf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Info(args ...interface{}) {
|
||||
zl.sugar.Info(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Infof(format string, args ...interface{}) {
|
||||
zl.sugar.Infof(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Warning(args ...interface{}) {
|
||||
zl.sugar.Warn(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Warningf(format string, args ...interface{}) {
|
||||
zl.sugar.Warnf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Fatal(args ...interface{}) {
|
||||
zl.sugar.Fatal(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Fatalf(format string, args ...interface{}) {
|
||||
zl.sugar.Fatalf(format, args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Panic(args ...interface{}) {
|
||||
zl.sugar.Panic(args...)
|
||||
}
|
||||
|
||||
func (zl *zapRaftLogger) Panicf(format string, args ...interface{}) {
|
||||
zl.sugar.Panicf(format, args...)
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// Copyright 2018 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 netutil implements network-related utility functions.
|
||||
package netutil
|
||||
|
|
@ -12,7 +12,6 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package netutil implements network-related utility functions.
|
||||
package netutil
|
||||
|
||||
import (
|
||||
|
|
@ -25,15 +24,12 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "pkg/netutil")
|
||||
|
||||
// indirection for testing
|
||||
resolveTCPAddr = resolveTCPAddrDefault
|
||||
)
|
||||
// indirection for testing
|
||||
var resolveTCPAddr = resolveTCPAddrDefault
|
||||
|
||||
const retryInterval = time.Second
|
||||
|
||||
|
|
@ -67,7 +63,7 @@ func resolveTCPAddrDefault(ctx context.Context, addr string) (*net.TCPAddr, erro
|
|||
// resolveTCPAddrs is a convenience wrapper for net.ResolveTCPAddr.
|
||||
// resolveTCPAddrs return a new set of url.URLs, in which all DNS hostnames
|
||||
// are resolved.
|
||||
func resolveTCPAddrs(ctx context.Context, urls [][]url.URL) ([][]url.URL, error) {
|
||||
func resolveTCPAddrs(ctx context.Context, lg *zap.Logger, urls [][]url.URL) ([][]url.URL, error) {
|
||||
newurls := make([][]url.URL, 0)
|
||||
for _, us := range urls {
|
||||
nus := make([]url.URL, len(us))
|
||||
|
|
@ -79,7 +75,7 @@ func resolveTCPAddrs(ctx context.Context, urls [][]url.URL) ([][]url.URL, error)
|
|||
nus[i] = *nu
|
||||
}
|
||||
for i, u := range nus {
|
||||
h, err := resolveURL(ctx, u)
|
||||
h, err := resolveURL(ctx, lg, u)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to resolve %q (%v)", u.String(), err)
|
||||
}
|
||||
|
|
@ -92,14 +88,19 @@ func resolveTCPAddrs(ctx context.Context, urls [][]url.URL) ([][]url.URL, error)
|
|||
return newurls, nil
|
||||
}
|
||||
|
||||
func resolveURL(ctx context.Context, u url.URL) (string, error) {
|
||||
func resolveURL(ctx context.Context, lg *zap.Logger, 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)
|
||||
lg.Warn(
|
||||
"failed to parse URL Host while resolving URL",
|
||||
zap.String("url", u.String()),
|
||||
zap.String("host", u.Host),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", err
|
||||
}
|
||||
if host == "localhost" || net.ParseIP(host) != nil {
|
||||
|
|
@ -108,13 +109,32 @@ func resolveURL(ctx context.Context, u url.URL) (string, error) {
|
|||
for ctx.Err() == nil {
|
||||
tcpAddr, err := resolveTCPAddr(ctx, u.Host)
|
||||
if err == nil {
|
||||
plog.Infof("resolving %s to %s", u.Host, tcpAddr.String())
|
||||
lg.Info(
|
||||
"resolved URL Host",
|
||||
zap.String("url", u.String()),
|
||||
zap.String("host", u.Host),
|
||||
zap.String("resolved-addr", tcpAddr.String()),
|
||||
)
|
||||
return tcpAddr.String(), nil
|
||||
}
|
||||
plog.Warningf("failed resolving host %s (%v); retrying in %v", u.Host, err, retryInterval)
|
||||
|
||||
lg.Warn(
|
||||
"failed to resolve URL Host",
|
||||
zap.String("url", u.String()),
|
||||
zap.String("host", u.Host),
|
||||
zap.Duration("retry-interval", retryInterval),
|
||||
zap.Error(err),
|
||||
)
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
plog.Errorf("could not resolve host %s", u.Host)
|
||||
lg.Warn(
|
||||
"failed to resolve URL Host; returning",
|
||||
zap.String("url", u.String()),
|
||||
zap.String("host", u.Host),
|
||||
zap.Duration("retry-interval", retryInterval),
|
||||
zap.Error(err),
|
||||
)
|
||||
return "", err
|
||||
case <-time.After(retryInterval):
|
||||
}
|
||||
|
|
@ -124,11 +144,11 @@ func resolveURL(ctx context.Context, u url.URL) (string, error) {
|
|||
|
||||
// urlsEqual checks equality of url.URLS between two arrays.
|
||||
// This check pass even if an URL is in hostname and opposite is in IP address.
|
||||
func urlsEqual(ctx context.Context, a []url.URL, b []url.URL) (bool, error) {
|
||||
func urlsEqual(ctx context.Context, lg *zap.Logger, a []url.URL, b []url.URL) (bool, error) {
|
||||
if len(a) != len(b) {
|
||||
return false, fmt.Errorf("len(%q) != len(%q)", urlsToStrings(a), urlsToStrings(b))
|
||||
}
|
||||
urls, err := resolveTCPAddrs(ctx, [][]url.URL{a, b})
|
||||
urls, err := resolveTCPAddrs(ctx, lg, [][]url.URL{a, b})
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
|
@ -150,7 +170,7 @@ func urlsEqual(ctx context.Context, a []url.URL, b []url.URL) (bool, error) {
|
|||
// URLStringsEqual returns "true" if given URLs are valid
|
||||
// and resolved to same IP addresses. Otherwise, return "false"
|
||||
// and error, if any.
|
||||
func URLStringsEqual(ctx context.Context, a []string, b []string) (bool, error) {
|
||||
func URLStringsEqual(ctx context.Context, lg *zap.Logger, a []string, b []string) (bool, error) {
|
||||
if len(a) != len(b) {
|
||||
return false, fmt.Errorf("len(%q) != len(%q)", a, b)
|
||||
}
|
||||
|
|
@ -170,7 +190,13 @@ func URLStringsEqual(ctx context.Context, a []string, b []string) (bool, error)
|
|||
}
|
||||
urlsB = append(urlsB, *u)
|
||||
}
|
||||
return urlsEqual(ctx, urlsA, urlsB)
|
||||
if lg == nil {
|
||||
lg, _ = zap.NewProduction()
|
||||
if lg == nil {
|
||||
lg = zap.NewExample()
|
||||
}
|
||||
}
|
||||
return urlsEqual(ctx, lg, urlsA, urlsB)
|
||||
}
|
||||
|
||||
func urlsToStrings(us []url.URL) []string {
|
||||
|
|
|
|||
|
|
@ -102,7 +102,7 @@ func (sp *secondPoints) getTimeSeries() TimeSeries {
|
|||
for k, v := range sp.tm {
|
||||
var lat time.Duration
|
||||
if v.count > 0 {
|
||||
lat = time.Duration(v.totalLatency) / time.Duration(v.count)
|
||||
lat = v.totalLatency / time.Duration(v.count)
|
||||
}
|
||||
tslice[i] = DataPoint{
|
||||
Timestamp: k,
|
||||
|
|
|
|||
|
|
@ -14,9 +14,7 @@
|
|||
|
||||
package types
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
)
|
||||
import "strconv"
|
||||
|
||||
// ID represents a generic identifier which is canonically
|
||||
// stored as a uint64 but is typically represented as a
|
||||
|
|
|
|||
|
|
@ -0,0 +1,300 @@
|
|||
// 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 raft sends and receives messages in the Protocol Buffer format
|
||||
defined in the raftpb package.
|
||||
|
||||
Raft is a protocol with which a cluster of nodes can maintain a replicated state machine.
|
||||
The state machine is kept in sync through the use of a replicated log.
|
||||
For more details on Raft, see "In Search of an Understandable Consensus Algorithm"
|
||||
(https://ramcloud.stanford.edu/raft.pdf) by Diego Ongaro and John Ousterhout.
|
||||
|
||||
A simple example application, _raftexample_, is also available to help illustrate
|
||||
how to use this package in practice:
|
||||
https://github.com/coreos/etcd/tree/master/contrib/raftexample
|
||||
|
||||
Usage
|
||||
|
||||
The primary object in raft is a Node. You either start a Node from scratch
|
||||
using raft.StartNode or start a Node from some initial state using raft.RestartNode.
|
||||
|
||||
To start a node from scratch:
|
||||
|
||||
storage := raft.NewMemoryStorage()
|
||||
c := &Config{
|
||||
ID: 0x01,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: storage,
|
||||
MaxSizePerMsg: 4096,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
n := raft.StartNode(c, []raft.Peer{{ID: 0x02}, {ID: 0x03}})
|
||||
|
||||
To restart a node from previous state:
|
||||
|
||||
storage := raft.NewMemoryStorage()
|
||||
|
||||
// recover the in-memory storage from persistent
|
||||
// snapshot, state and entries.
|
||||
storage.ApplySnapshot(snapshot)
|
||||
storage.SetHardState(state)
|
||||
storage.Append(entries)
|
||||
|
||||
c := &Config{
|
||||
ID: 0x01,
|
||||
ElectionTick: 10,
|
||||
HeartbeatTick: 1,
|
||||
Storage: storage,
|
||||
MaxSizePerMsg: 4096,
|
||||
MaxInflightMsgs: 256,
|
||||
}
|
||||
|
||||
// restart raft without peer information.
|
||||
// peer information is already included in the storage.
|
||||
n := raft.RestartNode(c)
|
||||
|
||||
Now that you are holding onto a Node you have a few responsibilities:
|
||||
|
||||
First, you must read from the Node.Ready() channel and process the updates
|
||||
it contains. These steps may be performed in parallel, except as noted in step
|
||||
2.
|
||||
|
||||
1. Write HardState, Entries, and Snapshot to persistent storage if they are
|
||||
not empty. Note that when writing an Entry with Index i, any
|
||||
previously-persisted entries with Index >= i must be discarded.
|
||||
|
||||
2. Send all Messages to the nodes named in the To field. It is important that
|
||||
no messages be sent until the latest HardState has been persisted to disk,
|
||||
and all Entries written by any previous Ready batch (Messages may be sent while
|
||||
entries from the same batch are being persisted). To reduce the I/O latency, an
|
||||
optimization can be applied to make leader write to disk in parallel with its
|
||||
followers (as explained at section 10.2.1 in Raft thesis). If any Message has type
|
||||
MsgSnap, call Node.ReportSnapshot() after it has been sent (these messages may be
|
||||
large).
|
||||
|
||||
Note: Marshalling messages is not thread-safe; it is important that you
|
||||
make sure that no new entries are persisted while marshalling.
|
||||
The easiest way to achieve this is to serialise the messages directly inside
|
||||
your main raft loop.
|
||||
|
||||
3. Apply Snapshot (if any) and CommittedEntries to the state machine.
|
||||
If any committed Entry has Type EntryConfChange, call Node.ApplyConfChange()
|
||||
to apply it to the node. The configuration change may be cancelled at this point
|
||||
by setting the NodeID field to zero before calling ApplyConfChange
|
||||
(but ApplyConfChange must be called one way or the other, and the decision to cancel
|
||||
must be based solely on the state machine and not external information such as
|
||||
the observed health of the node).
|
||||
|
||||
4. Call Node.Advance() to signal readiness for the next batch of updates.
|
||||
This may be done at any time after step 1, although all updates must be processed
|
||||
in the order they were returned by Ready.
|
||||
|
||||
Second, all persisted log entries must be made available via an
|
||||
implementation of the Storage interface. The provided MemoryStorage
|
||||
type can be used for this (if you repopulate its state upon a
|
||||
restart), or you can supply your own disk-backed implementation.
|
||||
|
||||
Third, when you receive a message from another node, pass it to Node.Step:
|
||||
|
||||
func recvRaftRPC(ctx context.Context, m raftpb.Message) {
|
||||
n.Step(ctx, m)
|
||||
}
|
||||
|
||||
Finally, you need to call Node.Tick() at regular intervals (probably
|
||||
via a time.Ticker). Raft has two important timeouts: heartbeat and the
|
||||
election timeout. However, internally to the raft package time is
|
||||
represented by an abstract "tick".
|
||||
|
||||
The total state machine handling loop will look something like this:
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-s.Ticker:
|
||||
n.Tick()
|
||||
case rd := <-s.Node.Ready():
|
||||
saveToStorage(rd.State, rd.Entries, rd.Snapshot)
|
||||
send(rd.Messages)
|
||||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
processSnapshot(rd.Snapshot)
|
||||
}
|
||||
for _, entry := range rd.CommittedEntries {
|
||||
process(entry)
|
||||
if entry.Type == raftpb.EntryConfChange {
|
||||
var cc raftpb.ConfChange
|
||||
cc.Unmarshal(entry.Data)
|
||||
s.Node.ApplyConfChange(cc)
|
||||
}
|
||||
}
|
||||
s.Node.Advance()
|
||||
case <-s.done:
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
To propose changes to the state machine from your node take your application
|
||||
data, serialize it into a byte slice and call:
|
||||
|
||||
n.Propose(ctx, data)
|
||||
|
||||
If the proposal is committed, data will appear in committed entries with type
|
||||
raftpb.EntryNormal. There is no guarantee that a proposed command will be
|
||||
committed; you may have to re-propose after a timeout.
|
||||
|
||||
To add or remove node in a cluster, build ConfChange struct 'cc' and call:
|
||||
|
||||
n.ProposeConfChange(ctx, cc)
|
||||
|
||||
After config change is committed, some committed entry with type
|
||||
raftpb.EntryConfChange will be returned. You must apply it to node through:
|
||||
|
||||
var cc raftpb.ConfChange
|
||||
cc.Unmarshal(data)
|
||||
n.ApplyConfChange(cc)
|
||||
|
||||
Note: An ID represents a unique node in a cluster for all time. A
|
||||
given ID MUST be used only once even if the old node has been removed.
|
||||
This means that for example IP addresses make poor node IDs since they
|
||||
may be reused. Node IDs must be non-zero.
|
||||
|
||||
Implementation notes
|
||||
|
||||
This implementation is up to date with the final Raft thesis
|
||||
(https://ramcloud.stanford.edu/~ongaro/thesis.pdf), although our
|
||||
implementation of the membership change protocol differs somewhat from
|
||||
that described in chapter 4. The key invariant that membership changes
|
||||
happen one node at a time is preserved, but in our implementation the
|
||||
membership change takes effect when its entry is applied, not when it
|
||||
is added to the log (so the entry is committed under the old
|
||||
membership instead of the new). This is equivalent in terms of safety,
|
||||
since the old and new configurations are guaranteed to overlap.
|
||||
|
||||
To ensure that we do not attempt to commit two membership changes at
|
||||
once by matching log positions (which would be unsafe since they
|
||||
should have different quorum requirements), we simply disallow any
|
||||
proposed membership change while any uncommitted change appears in
|
||||
the leader's log.
|
||||
|
||||
This approach introduces a problem when you try to remove a member
|
||||
from a two-member cluster: If one of the members dies before the
|
||||
other one receives the commit of the confchange entry, then the member
|
||||
cannot be removed any more since the cluster cannot make progress.
|
||||
For this reason it is highly recommended to use three or more nodes in
|
||||
every cluster.
|
||||
|
||||
MessageType
|
||||
|
||||
Package raft sends and receives message in Protocol Buffer format (defined
|
||||
in raftpb package). Each state (follower, candidate, leader) implements its
|
||||
own 'step' method ('stepFollower', 'stepCandidate', 'stepLeader') when
|
||||
advancing with the given raftpb.Message. Each step is determined by its
|
||||
raftpb.MessageType. Note that every step is checked by one common method
|
||||
'Step' that safety-checks the terms of node and incoming message to prevent
|
||||
stale log entries:
|
||||
|
||||
'MsgHup' is used for election. If a node is a follower or candidate, the
|
||||
'tick' function in 'raft' struct is set as 'tickElection'. If a follower or
|
||||
candidate has not received any heartbeat before the election timeout, it
|
||||
passes 'MsgHup' to its Step method and becomes (or remains) a candidate to
|
||||
start a new election.
|
||||
|
||||
'MsgBeat' is an internal type that signals the leader to send a heartbeat of
|
||||
the 'MsgHeartbeat' type. If a node is a leader, the 'tick' function in
|
||||
the 'raft' struct is set as 'tickHeartbeat', and triggers the leader to
|
||||
send periodic 'MsgHeartbeat' messages to its followers.
|
||||
|
||||
'MsgProp' proposes to append data to its log entries. This is a special
|
||||
type to redirect proposals to leader. Therefore, send method overwrites
|
||||
raftpb.Message's term with its HardState's term to avoid attaching its
|
||||
local term to 'MsgProp'. When 'MsgProp' is passed to the leader's 'Step'
|
||||
method, the leader first calls the 'appendEntry' method to append entries
|
||||
to its log, and then calls 'bcastAppend' method to send those entries to
|
||||
its peers. When passed to candidate, 'MsgProp' is dropped. When passed to
|
||||
follower, 'MsgProp' is stored in follower's mailbox(msgs) by the send
|
||||
method. It is stored with sender's ID and later forwarded to leader by
|
||||
rafthttp package.
|
||||
|
||||
'MsgApp' contains log entries to replicate. A leader calls bcastAppend,
|
||||
which calls sendAppend, which sends soon-to-be-replicated logs in 'MsgApp'
|
||||
type. When 'MsgApp' is passed to candidate's Step method, candidate reverts
|
||||
back to follower, because it indicates that there is a valid leader sending
|
||||
'MsgApp' messages. Candidate and follower respond to this message in
|
||||
'MsgAppResp' type.
|
||||
|
||||
'MsgAppResp' is response to log replication request('MsgApp'). When
|
||||
'MsgApp' is passed to candidate or follower's Step method, it responds by
|
||||
calling 'handleAppendEntries' method, which sends 'MsgAppResp' to raft
|
||||
mailbox.
|
||||
|
||||
'MsgVote' requests votes for election. When a node is a follower or
|
||||
candidate and 'MsgHup' is passed to its Step method, then the node calls
|
||||
'campaign' method to campaign itself to become a leader. Once 'campaign'
|
||||
method is called, the node becomes candidate and sends 'MsgVote' to peers
|
||||
in cluster to request votes. When passed to leader or candidate's Step
|
||||
method and the message's Term is lower than leader's or candidate's,
|
||||
'MsgVote' will be rejected ('MsgVoteResp' is returned with Reject true).
|
||||
If leader or candidate receives 'MsgVote' with higher term, it will revert
|
||||
back to follower. When 'MsgVote' is passed to follower, it votes for the
|
||||
sender only when sender's last term is greater than MsgVote's term or
|
||||
sender's last term is equal to MsgVote's term but sender's last committed
|
||||
index is greater than or equal to follower's.
|
||||
|
||||
'MsgVoteResp' contains responses from voting request. When 'MsgVoteResp' is
|
||||
passed to candidate, the candidate calculates how many votes it has won. If
|
||||
it's more than majority (quorum), it becomes leader and calls 'bcastAppend'.
|
||||
If candidate receives majority of votes of denials, it reverts back to
|
||||
follower.
|
||||
|
||||
'MsgPreVote' and 'MsgPreVoteResp' are used in an optional two-phase election
|
||||
protocol. When Config.PreVote is true, a pre-election is carried out first
|
||||
(using the same rules as a regular election), and no node increases its term
|
||||
number unless the pre-election indicates that the campaigining node would win.
|
||||
This minimizes disruption when a partitioned node rejoins the cluster.
|
||||
|
||||
'MsgSnap' requests to install a snapshot message. When a node has just
|
||||
become a leader or the leader receives 'MsgProp' message, it calls
|
||||
'bcastAppend' method, which then calls 'sendAppend' method to each
|
||||
follower. In 'sendAppend', if a leader fails to get term or entries,
|
||||
the leader requests snapshot by sending 'MsgSnap' type message.
|
||||
|
||||
'MsgSnapStatus' tells the result of snapshot install message. When a
|
||||
follower rejected 'MsgSnap', it indicates the snapshot request with
|
||||
'MsgSnap' had failed from network issues which causes the network layer
|
||||
to fail to send out snapshots to its followers. Then leader considers
|
||||
follower's progress as probe. When 'MsgSnap' were not rejected, it
|
||||
indicates that the snapshot succeeded and the leader sets follower's
|
||||
progress to probe and resumes its log replication.
|
||||
|
||||
'MsgHeartbeat' sends heartbeat from leader. When 'MsgHeartbeat' is passed
|
||||
to candidate and message's term is higher than candidate's, the candidate
|
||||
reverts back to follower and updates its committed index from the one in
|
||||
this heartbeat. And it sends the message to its mailbox. When
|
||||
'MsgHeartbeat' is passed to follower's Step method and message's term is
|
||||
higher than follower's, the follower updates its leaderID with the ID
|
||||
from the message.
|
||||
|
||||
'MsgHeartbeatResp' is a response to 'MsgHeartbeat'. When 'MsgHeartbeatResp'
|
||||
is passed to leader's Step method, the leader knows which follower
|
||||
responded. And only when the leader's last committed index is greater than
|
||||
follower's Match index, the leader runs 'sendAppend` method.
|
||||
|
||||
'MsgUnreachable' tells that request(message) wasn't delivered. When
|
||||
'MsgUnreachable' is passed to leader's Step method, the leader discovers
|
||||
that the follower that sent this 'MsgUnreachable' is not reachable, often
|
||||
indicating 'MsgApp' is lost. When follower's progress state is replicate,
|
||||
the leader sets it back to probe.
|
||||
|
||||
*/
|
||||
package raft
|
||||
|
|
@ -0,0 +1,358 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
type raftLog struct {
|
||||
// storage contains all stable entries since the last snapshot.
|
||||
storage Storage
|
||||
|
||||
// unstable contains all unstable entries and snapshot.
|
||||
// they will be saved into storage.
|
||||
unstable unstable
|
||||
|
||||
// committed is the highest log position that is known to be in
|
||||
// stable storage on a quorum of nodes.
|
||||
committed uint64
|
||||
// applied is the highest log position that the application has
|
||||
// been instructed to apply to its state machine.
|
||||
// Invariant: applied <= committed
|
||||
applied uint64
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// newLog returns log using the given storage. It recovers the log to the state
|
||||
// that it just commits and applies the latest snapshot.
|
||||
func newLog(storage Storage, logger Logger) *raftLog {
|
||||
if storage == nil {
|
||||
log.Panic("storage must not be nil")
|
||||
}
|
||||
log := &raftLog{
|
||||
storage: storage,
|
||||
logger: logger,
|
||||
}
|
||||
firstIndex, err := storage.FirstIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
lastIndex, err := storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
log.unstable.offset = lastIndex + 1
|
||||
log.unstable.logger = logger
|
||||
// Initialize our committed and applied pointers to the time of the last compaction.
|
||||
log.committed = firstIndex - 1
|
||||
log.applied = firstIndex - 1
|
||||
|
||||
return log
|
||||
}
|
||||
|
||||
func (l *raftLog) String() string {
|
||||
return fmt.Sprintf("committed=%d, applied=%d, unstable.offset=%d, len(unstable.Entries)=%d", l.committed, l.applied, l.unstable.offset, len(l.unstable.entries))
|
||||
}
|
||||
|
||||
// maybeAppend returns (0, false) if the entries cannot be appended. Otherwise,
|
||||
// it returns (last index of new entries, true).
|
||||
func (l *raftLog) maybeAppend(index, logTerm, committed uint64, ents ...pb.Entry) (lastnewi uint64, ok bool) {
|
||||
if l.matchTerm(index, logTerm) {
|
||||
lastnewi = index + uint64(len(ents))
|
||||
ci := l.findConflict(ents)
|
||||
switch {
|
||||
case ci == 0:
|
||||
case ci <= l.committed:
|
||||
l.logger.Panicf("entry %d conflict with committed entry [committed(%d)]", ci, l.committed)
|
||||
default:
|
||||
offset := index + 1
|
||||
l.append(ents[ci-offset:]...)
|
||||
}
|
||||
l.commitTo(min(committed, lastnewi))
|
||||
return lastnewi, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
func (l *raftLog) append(ents ...pb.Entry) uint64 {
|
||||
if len(ents) == 0 {
|
||||
return l.lastIndex()
|
||||
}
|
||||
if after := ents[0].Index - 1; after < l.committed {
|
||||
l.logger.Panicf("after(%d) is out of range [committed(%d)]", after, l.committed)
|
||||
}
|
||||
l.unstable.truncateAndAppend(ents)
|
||||
return l.lastIndex()
|
||||
}
|
||||
|
||||
// findConflict finds the index of the conflict.
|
||||
// It returns the first pair of conflicting entries between the existing
|
||||
// entries and the given entries, if there are any.
|
||||
// If there is no conflicting entries, and the existing entries contains
|
||||
// all the given entries, zero will be returned.
|
||||
// If there is no conflicting entries, but the given entries contains new
|
||||
// entries, the index of the first new entry will be returned.
|
||||
// An entry is considered to be conflicting if it has the same index but
|
||||
// a different term.
|
||||
// The first entry MUST have an index equal to the argument 'from'.
|
||||
// The index of the given entries MUST be continuously increasing.
|
||||
func (l *raftLog) findConflict(ents []pb.Entry) uint64 {
|
||||
for _, ne := range ents {
|
||||
if !l.matchTerm(ne.Index, ne.Term) {
|
||||
if ne.Index <= l.lastIndex() {
|
||||
l.logger.Infof("found conflict at index %d [existing term: %d, conflicting term: %d]",
|
||||
ne.Index, l.zeroTermOnErrCompacted(l.term(ne.Index)), ne.Term)
|
||||
}
|
||||
return ne.Index
|
||||
}
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (l *raftLog) unstableEntries() []pb.Entry {
|
||||
if len(l.unstable.entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
return l.unstable.entries
|
||||
}
|
||||
|
||||
// nextEnts returns all the available entries for execution.
|
||||
// If applied is smaller than the index of snapshot, it returns all committed
|
||||
// entries after the index of snapshot.
|
||||
func (l *raftLog) nextEnts() (ents []pb.Entry) {
|
||||
off := max(l.applied+1, l.firstIndex())
|
||||
if l.committed+1 > off {
|
||||
ents, err := l.slice(off, l.committed+1, noLimit)
|
||||
if err != nil {
|
||||
l.logger.Panicf("unexpected error when getting unapplied entries (%v)", err)
|
||||
}
|
||||
return ents
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// hasNextEnts returns if there is any available entries for execution. This
|
||||
// is a fast check without heavy raftLog.slice() in raftLog.nextEnts().
|
||||
func (l *raftLog) hasNextEnts() bool {
|
||||
off := max(l.applied+1, l.firstIndex())
|
||||
return l.committed+1 > off
|
||||
}
|
||||
|
||||
func (l *raftLog) snapshot() (pb.Snapshot, error) {
|
||||
if l.unstable.snapshot != nil {
|
||||
return *l.unstable.snapshot, nil
|
||||
}
|
||||
return l.storage.Snapshot()
|
||||
}
|
||||
|
||||
func (l *raftLog) firstIndex() uint64 {
|
||||
if i, ok := l.unstable.maybeFirstIndex(); ok {
|
||||
return i
|
||||
}
|
||||
index, err := l.storage.FirstIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
return index
|
||||
}
|
||||
|
||||
func (l *raftLog) lastIndex() uint64 {
|
||||
if i, ok := l.unstable.maybeLastIndex(); ok {
|
||||
return i
|
||||
}
|
||||
i, err := l.storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func (l *raftLog) commitTo(tocommit uint64) {
|
||||
// never decrease commit
|
||||
if l.committed < tocommit {
|
||||
if l.lastIndex() < tocommit {
|
||||
l.logger.Panicf("tocommit(%d) is out of range [lastIndex(%d)]. Was the raft log corrupted, truncated, or lost?", tocommit, l.lastIndex())
|
||||
}
|
||||
l.committed = tocommit
|
||||
}
|
||||
}
|
||||
|
||||
func (l *raftLog) appliedTo(i uint64) {
|
||||
if i == 0 {
|
||||
return
|
||||
}
|
||||
if l.committed < i || i < l.applied {
|
||||
l.logger.Panicf("applied(%d) is out of range [prevApplied(%d), committed(%d)]", i, l.applied, l.committed)
|
||||
}
|
||||
l.applied = i
|
||||
}
|
||||
|
||||
func (l *raftLog) stableTo(i, t uint64) { l.unstable.stableTo(i, t) }
|
||||
|
||||
func (l *raftLog) stableSnapTo(i uint64) { l.unstable.stableSnapTo(i) }
|
||||
|
||||
func (l *raftLog) lastTerm() uint64 {
|
||||
t, err := l.term(l.lastIndex())
|
||||
if err != nil {
|
||||
l.logger.Panicf("unexpected error when getting the last term (%v)", err)
|
||||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (l *raftLog) term(i uint64) (uint64, error) {
|
||||
// the valid term range is [index of dummy entry, last index]
|
||||
dummyIndex := l.firstIndex() - 1
|
||||
if i < dummyIndex || i > l.lastIndex() {
|
||||
// TODO: return an error instead?
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
if t, ok := l.unstable.maybeTerm(i); ok {
|
||||
return t, nil
|
||||
}
|
||||
|
||||
t, err := l.storage.Term(i)
|
||||
if err == nil {
|
||||
return t, nil
|
||||
}
|
||||
if err == ErrCompacted || err == ErrUnavailable {
|
||||
return 0, err
|
||||
}
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
|
||||
func (l *raftLog) entries(i, maxsize uint64) ([]pb.Entry, error) {
|
||||
if i > l.lastIndex() {
|
||||
return nil, nil
|
||||
}
|
||||
return l.slice(i, l.lastIndex()+1, maxsize)
|
||||
}
|
||||
|
||||
// allEntries returns all entries in the log.
|
||||
func (l *raftLog) allEntries() []pb.Entry {
|
||||
ents, err := l.entries(l.firstIndex(), noLimit)
|
||||
if err == nil {
|
||||
return ents
|
||||
}
|
||||
if err == ErrCompacted { // try again if there was a racing compaction
|
||||
return l.allEntries()
|
||||
}
|
||||
// TODO (xiangli): handle error?
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// isUpToDate determines if the given (lastIndex,term) log is more up-to-date
|
||||
// by comparing the index and term of the last entries in the existing logs.
|
||||
// If the logs have last entries with different terms, then the log with the
|
||||
// later term is more up-to-date. If the logs end with the same term, then
|
||||
// whichever log has the larger lastIndex is more up-to-date. If the logs are
|
||||
// the same, the given log is up-to-date.
|
||||
func (l *raftLog) isUpToDate(lasti, term uint64) bool {
|
||||
return term > l.lastTerm() || (term == l.lastTerm() && lasti >= l.lastIndex())
|
||||
}
|
||||
|
||||
func (l *raftLog) matchTerm(i, term uint64) bool {
|
||||
t, err := l.term(i)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return t == term
|
||||
}
|
||||
|
||||
func (l *raftLog) maybeCommit(maxIndex, term uint64) bool {
|
||||
if maxIndex > l.committed && l.zeroTermOnErrCompacted(l.term(maxIndex)) == term {
|
||||
l.commitTo(maxIndex)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *raftLog) restore(s pb.Snapshot) {
|
||||
l.logger.Infof("log [%s] starts to restore snapshot [index: %d, term: %d]", l, s.Metadata.Index, s.Metadata.Term)
|
||||
l.committed = s.Metadata.Index
|
||||
l.unstable.restore(s)
|
||||
}
|
||||
|
||||
// slice returns a slice of log entries from lo through hi-1, inclusive.
|
||||
func (l *raftLog) slice(lo, hi, maxSize uint64) ([]pb.Entry, error) {
|
||||
err := l.mustCheckOutOfBounds(lo, hi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if lo == hi {
|
||||
return nil, nil
|
||||
}
|
||||
var ents []pb.Entry
|
||||
if lo < l.unstable.offset {
|
||||
storedEnts, err := l.storage.Entries(lo, min(hi, l.unstable.offset), maxSize)
|
||||
if err == ErrCompacted {
|
||||
return nil, err
|
||||
} else if err == ErrUnavailable {
|
||||
l.logger.Panicf("entries[%d:%d) is unavailable from storage", lo, min(hi, l.unstable.offset))
|
||||
} else if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
|
||||
// check if ents has reached the size limitation
|
||||
if uint64(len(storedEnts)) < min(hi, l.unstable.offset)-lo {
|
||||
return storedEnts, nil
|
||||
}
|
||||
|
||||
ents = storedEnts
|
||||
}
|
||||
if hi > l.unstable.offset {
|
||||
unstable := l.unstable.slice(max(lo, l.unstable.offset), hi)
|
||||
if len(ents) > 0 {
|
||||
ents = append([]pb.Entry{}, ents...)
|
||||
ents = append(ents, unstable...)
|
||||
} else {
|
||||
ents = unstable
|
||||
}
|
||||
}
|
||||
return limitSize(ents, maxSize), nil
|
||||
}
|
||||
|
||||
// l.firstIndex <= lo <= hi <= l.firstIndex + len(l.entries)
|
||||
func (l *raftLog) mustCheckOutOfBounds(lo, hi uint64) error {
|
||||
if lo > hi {
|
||||
l.logger.Panicf("invalid slice %d > %d", lo, hi)
|
||||
}
|
||||
fi := l.firstIndex()
|
||||
if lo < fi {
|
||||
return ErrCompacted
|
||||
}
|
||||
|
||||
length := l.lastIndex() + 1 - fi
|
||||
if lo < fi || hi > fi+length {
|
||||
l.logger.Panicf("slice[%d,%d) out of bound [%d,%d]", lo, hi, fi, l.lastIndex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *raftLog) zeroTermOnErrCompacted(t uint64, err error) uint64 {
|
||||
if err == nil {
|
||||
return t
|
||||
}
|
||||
if err == ErrCompacted {
|
||||
return 0
|
||||
}
|
||||
l.logger.Panicf("unexpected error (%v)", err)
|
||||
return 0
|
||||
}
|
||||
|
|
@ -0,0 +1,159 @@
|
|||
// 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 raft
|
||||
|
||||
import pb "github.com/coreos/etcd/raft/raftpb"
|
||||
|
||||
// unstable.entries[i] has raft log position i+unstable.offset.
|
||||
// Note that unstable.offset may be less than the highest log
|
||||
// position in storage; this means that the next write to storage
|
||||
// might need to truncate the log before persisting unstable.entries.
|
||||
type unstable struct {
|
||||
// the incoming unstable snapshot, if any.
|
||||
snapshot *pb.Snapshot
|
||||
// all entries that have not yet been written to storage.
|
||||
entries []pb.Entry
|
||||
offset uint64
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
// maybeFirstIndex returns the index of the first possible entry in entries
|
||||
// if it has a snapshot.
|
||||
func (u *unstable) maybeFirstIndex() (uint64, bool) {
|
||||
if u.snapshot != nil {
|
||||
return u.snapshot.Metadata.Index + 1, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// maybeLastIndex returns the last index if it has at least one
|
||||
// unstable entry or snapshot.
|
||||
func (u *unstable) maybeLastIndex() (uint64, bool) {
|
||||
if l := len(u.entries); l != 0 {
|
||||
return u.offset + uint64(l) - 1, true
|
||||
}
|
||||
if u.snapshot != nil {
|
||||
return u.snapshot.Metadata.Index, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// maybeTerm returns the term of the entry at index i, if there
|
||||
// is any.
|
||||
func (u *unstable) maybeTerm(i uint64) (uint64, bool) {
|
||||
if i < u.offset {
|
||||
if u.snapshot == nil {
|
||||
return 0, false
|
||||
}
|
||||
if u.snapshot.Metadata.Index == i {
|
||||
return u.snapshot.Metadata.Term, true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
last, ok := u.maybeLastIndex()
|
||||
if !ok {
|
||||
return 0, false
|
||||
}
|
||||
if i > last {
|
||||
return 0, false
|
||||
}
|
||||
return u.entries[i-u.offset].Term, true
|
||||
}
|
||||
|
||||
func (u *unstable) stableTo(i, t uint64) {
|
||||
gt, ok := u.maybeTerm(i)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// if i < offset, term is matched with the snapshot
|
||||
// only update the unstable entries if term is matched with
|
||||
// an unstable entry.
|
||||
if gt == t && i >= u.offset {
|
||||
u.entries = u.entries[i+1-u.offset:]
|
||||
u.offset = i + 1
|
||||
u.shrinkEntriesArray()
|
||||
}
|
||||
}
|
||||
|
||||
// shrinkEntriesArray discards the underlying array used by the entries slice
|
||||
// if most of it isn't being used. This avoids holding references to a bunch of
|
||||
// potentially large entries that aren't needed anymore. Simply clearing the
|
||||
// entries wouldn't be safe because clients might still be using them.
|
||||
func (u *unstable) shrinkEntriesArray() {
|
||||
// We replace the array if we're using less than half of the space in
|
||||
// it. This number is fairly arbitrary, chosen as an attempt to balance
|
||||
// memory usage vs number of allocations. It could probably be improved
|
||||
// with some focused tuning.
|
||||
const lenMultiple = 2
|
||||
if len(u.entries) == 0 {
|
||||
u.entries = nil
|
||||
} else if len(u.entries)*lenMultiple < cap(u.entries) {
|
||||
newEntries := make([]pb.Entry, len(u.entries))
|
||||
copy(newEntries, u.entries)
|
||||
u.entries = newEntries
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) stableSnapTo(i uint64) {
|
||||
if u.snapshot != nil && u.snapshot.Metadata.Index == i {
|
||||
u.snapshot = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) restore(s pb.Snapshot) {
|
||||
u.offset = s.Metadata.Index + 1
|
||||
u.entries = nil
|
||||
u.snapshot = &s
|
||||
}
|
||||
|
||||
func (u *unstable) truncateAndAppend(ents []pb.Entry) {
|
||||
after := ents[0].Index
|
||||
switch {
|
||||
case after == u.offset+uint64(len(u.entries)):
|
||||
// after is the next index in the u.entries
|
||||
// directly append
|
||||
u.entries = append(u.entries, ents...)
|
||||
case after <= u.offset:
|
||||
u.logger.Infof("replace the unstable entries from index %d", after)
|
||||
// The log is being truncated to before our current offset
|
||||
// portion, so set the offset and replace the entries
|
||||
u.offset = after
|
||||
u.entries = ents
|
||||
default:
|
||||
// truncate to after and copy to u.entries
|
||||
// then append
|
||||
u.logger.Infof("truncate the unstable entries before index %d", after)
|
||||
u.entries = append([]pb.Entry{}, u.slice(u.offset, after)...)
|
||||
u.entries = append(u.entries, ents...)
|
||||
}
|
||||
}
|
||||
|
||||
func (u *unstable) slice(lo uint64, hi uint64) []pb.Entry {
|
||||
u.mustCheckOutOfBounds(lo, hi)
|
||||
return u.entries[lo-u.offset : hi-u.offset]
|
||||
}
|
||||
|
||||
// u.offset <= lo <= hi <= u.offset+len(u.entries)
|
||||
func (u *unstable) mustCheckOutOfBounds(lo, hi uint64) {
|
||||
if lo > hi {
|
||||
u.logger.Panicf("invalid unstable.slice %d > %d", lo, hi)
|
||||
}
|
||||
upper := u.offset + uint64(len(u.entries))
|
||||
if lo < u.offset || hi > upper {
|
||||
u.logger.Panicf("unstable.slice[%d,%d) out of bound [%d,%d]", lo, hi, u.offset, upper)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,126 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Logger interface {
|
||||
Debug(v ...interface{})
|
||||
Debugf(format string, v ...interface{})
|
||||
|
||||
Error(v ...interface{})
|
||||
Errorf(format string, v ...interface{})
|
||||
|
||||
Info(v ...interface{})
|
||||
Infof(format string, v ...interface{})
|
||||
|
||||
Warning(v ...interface{})
|
||||
Warningf(format string, v ...interface{})
|
||||
|
||||
Fatal(v ...interface{})
|
||||
Fatalf(format string, v ...interface{})
|
||||
|
||||
Panic(v ...interface{})
|
||||
Panicf(format string, v ...interface{})
|
||||
}
|
||||
|
||||
func SetLogger(l Logger) { raftLogger = l }
|
||||
|
||||
var (
|
||||
defaultLogger = &DefaultLogger{Logger: log.New(os.Stderr, "raft", log.LstdFlags)}
|
||||
discardLogger = &DefaultLogger{Logger: log.New(ioutil.Discard, "", 0)}
|
||||
raftLogger = Logger(defaultLogger)
|
||||
)
|
||||
|
||||
const (
|
||||
calldepth = 2
|
||||
)
|
||||
|
||||
// DefaultLogger is a default implementation of the Logger interface.
|
||||
type DefaultLogger struct {
|
||||
*log.Logger
|
||||
debug bool
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableTimestamps() {
|
||||
l.SetFlags(l.Flags() | log.Ldate | log.Ltime)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) EnableDebug() {
|
||||
l.debug = true
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debug(v ...interface{}) {
|
||||
if l.debug {
|
||||
l.Output(calldepth, header("DEBUG", fmt.Sprint(v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Debugf(format string, v ...interface{}) {
|
||||
if l.debug {
|
||||
l.Output(calldepth, header("DEBUG", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Info(v ...interface{}) {
|
||||
l.Output(calldepth, header("INFO", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Infof(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("INFO", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Error(v ...interface{}) {
|
||||
l.Output(calldepth, header("ERROR", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Errorf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("ERROR", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warning(v ...interface{}) {
|
||||
l.Output(calldepth, header("WARN", fmt.Sprint(v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Warningf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("WARN", fmt.Sprintf(format, v...)))
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatal(v ...interface{}) {
|
||||
l.Output(calldepth, header("FATAL", fmt.Sprint(v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Fatalf(format string, v ...interface{}) {
|
||||
l.Output(calldepth, header("FATAL", fmt.Sprintf(format, v...)))
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panic(v ...interface{}) {
|
||||
l.Logger.Panic(v)
|
||||
}
|
||||
|
||||
func (l *DefaultLogger) Panicf(format string, v ...interface{}) {
|
||||
l.Logger.Panicf(format, v...)
|
||||
}
|
||||
|
||||
func header(lvl, msg string) string {
|
||||
return fmt.Sprintf("%s: %s", lvl, msg)
|
||||
}
|
||||
|
|
@ -0,0 +1,582 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
type SnapshotStatus int
|
||||
|
||||
const (
|
||||
SnapshotFinish SnapshotStatus = 1
|
||||
SnapshotFailure SnapshotStatus = 2
|
||||
)
|
||||
|
||||
var (
|
||||
emptyState = pb.HardState{}
|
||||
|
||||
// ErrStopped is returned by methods on Nodes that have been stopped.
|
||||
ErrStopped = errors.New("raft: stopped")
|
||||
)
|
||||
|
||||
// SoftState provides state that is useful for logging and debugging.
|
||||
// The state is volatile and does not need to be persisted to the WAL.
|
||||
type SoftState struct {
|
||||
Lead uint64 // must use atomic operations to access; keep 64-bit aligned.
|
||||
RaftState StateType
|
||||
}
|
||||
|
||||
func (a *SoftState) equal(b *SoftState) bool {
|
||||
return a.Lead == b.Lead && a.RaftState == b.RaftState
|
||||
}
|
||||
|
||||
// Ready encapsulates the entries and messages that are ready to read,
|
||||
// be saved to stable storage, committed or sent to other peers.
|
||||
// All fields in Ready are read-only.
|
||||
type Ready struct {
|
||||
// The current volatile state of a Node.
|
||||
// SoftState will be nil if there is no update.
|
||||
// It is not required to consume or store SoftState.
|
||||
*SoftState
|
||||
|
||||
// The current state of a Node to be saved to stable storage BEFORE
|
||||
// Messages are sent.
|
||||
// HardState will be equal to empty state if there is no update.
|
||||
pb.HardState
|
||||
|
||||
// ReadStates can be used for node to serve linearizable read requests locally
|
||||
// when its applied index is greater than the index in ReadState.
|
||||
// Note that the readState will be returned when raft receives msgReadIndex.
|
||||
// The returned is only valid for the request that requested to read.
|
||||
ReadStates []ReadState
|
||||
|
||||
// Entries specifies entries to be saved to stable storage BEFORE
|
||||
// Messages are sent.
|
||||
Entries []pb.Entry
|
||||
|
||||
// Snapshot specifies the snapshot to be saved to stable storage.
|
||||
Snapshot pb.Snapshot
|
||||
|
||||
// CommittedEntries specifies entries to be committed to a
|
||||
// store/state-machine. These have previously been committed to stable
|
||||
// store.
|
||||
CommittedEntries []pb.Entry
|
||||
|
||||
// Messages specifies outbound messages to be sent AFTER Entries are
|
||||
// committed to stable storage.
|
||||
// If it contains a MsgSnap message, the application MUST report back to raft
|
||||
// when the snapshot has been received or has failed by calling ReportSnapshot.
|
||||
Messages []pb.Message
|
||||
|
||||
// MustSync indicates whether the HardState and Entries must be synchronously
|
||||
// written to disk or if an asynchronous write is permissible.
|
||||
MustSync bool
|
||||
}
|
||||
|
||||
func isHardStateEqual(a, b pb.HardState) bool {
|
||||
return a.Term == b.Term && a.Vote == b.Vote && a.Commit == b.Commit
|
||||
}
|
||||
|
||||
// IsEmptyHardState returns true if the given HardState is empty.
|
||||
func IsEmptyHardState(st pb.HardState) bool {
|
||||
return isHardStateEqual(st, emptyState)
|
||||
}
|
||||
|
||||
// IsEmptySnap returns true if the given Snapshot is empty.
|
||||
func IsEmptySnap(sp pb.Snapshot) bool {
|
||||
return sp.Metadata.Index == 0
|
||||
}
|
||||
|
||||
func (rd Ready) containsUpdates() bool {
|
||||
return rd.SoftState != nil || !IsEmptyHardState(rd.HardState) ||
|
||||
!IsEmptySnap(rd.Snapshot) || len(rd.Entries) > 0 ||
|
||||
len(rd.CommittedEntries) > 0 || len(rd.Messages) > 0 || len(rd.ReadStates) != 0
|
||||
}
|
||||
|
||||
// Node represents a node in a raft cluster.
|
||||
type Node interface {
|
||||
// Tick increments the internal logical clock for the Node by a single tick. Election
|
||||
// timeouts and heartbeat timeouts are in units of ticks.
|
||||
Tick()
|
||||
// Campaign causes the Node to transition to candidate state and start campaigning to become leader.
|
||||
Campaign(ctx context.Context) error
|
||||
// Propose proposes that data be appended to the log.
|
||||
Propose(ctx context.Context, data []byte) error
|
||||
// ProposeConfChange proposes config change.
|
||||
// At most one ConfChange can be in the process of going through consensus.
|
||||
// Application needs to call ApplyConfChange when applying EntryConfChange type entry.
|
||||
ProposeConfChange(ctx context.Context, cc pb.ConfChange) error
|
||||
// Step advances the state machine using the given message. ctx.Err() will be returned, if any.
|
||||
Step(ctx context.Context, msg pb.Message) error
|
||||
|
||||
// Ready returns a channel that returns the current point-in-time state.
|
||||
// Users of the Node must call Advance after retrieving the state returned by Ready.
|
||||
//
|
||||
// NOTE: No committed entries from the next Ready may be applied until all committed entries
|
||||
// and snapshots from the previous one have finished.
|
||||
Ready() <-chan Ready
|
||||
|
||||
// Advance notifies the Node that the application has saved progress up to the last Ready.
|
||||
// It prepares the node to return the next available Ready.
|
||||
//
|
||||
// The application should generally call Advance after it applies the entries in last Ready.
|
||||
//
|
||||
// However, as an optimization, the application may call Advance while it is applying the
|
||||
// commands. For example. when the last Ready contains a snapshot, the application might take
|
||||
// a long time to apply the snapshot data. To continue receiving Ready without blocking raft
|
||||
// progress, it can call Advance before finishing applying the last ready.
|
||||
Advance()
|
||||
// ApplyConfChange applies config change to the local node.
|
||||
// Returns an opaque ConfState protobuf which must be recorded
|
||||
// in snapshots. Will never return nil; it returns a pointer only
|
||||
// to match MemoryStorage.Compact.
|
||||
ApplyConfChange(cc pb.ConfChange) *pb.ConfState
|
||||
|
||||
// TransferLeadership attempts to transfer leadership to the given transferee.
|
||||
TransferLeadership(ctx context.Context, lead, transferee uint64)
|
||||
|
||||
// ReadIndex request a read state. The read state will be set in the ready.
|
||||
// Read state has a read index. Once the application advances further than the read
|
||||
// index, any linearizable read requests issued before the read request can be
|
||||
// processed safely. The read state will have the same rctx attached.
|
||||
ReadIndex(ctx context.Context, rctx []byte) error
|
||||
|
||||
// Status returns the current status of the raft state machine.
|
||||
Status() Status
|
||||
// ReportUnreachable reports the given node is not reachable for the last send.
|
||||
ReportUnreachable(id uint64)
|
||||
// ReportSnapshot reports the status of the sent snapshot.
|
||||
ReportSnapshot(id uint64, status SnapshotStatus)
|
||||
// Stop performs any necessary termination of the Node.
|
||||
Stop()
|
||||
}
|
||||
|
||||
type Peer struct {
|
||||
ID uint64
|
||||
Context []byte
|
||||
}
|
||||
|
||||
// StartNode returns a new Node given configuration and a list of raft peers.
|
||||
// It appends a ConfChangeAddNode entry for each given peer to the initial log.
|
||||
func StartNode(c *Config, peers []Peer) Node {
|
||||
r := newRaft(c)
|
||||
// become the follower at term 1 and apply initial configuration
|
||||
// entries of term 1
|
||||
r.becomeFollower(1, None)
|
||||
for _, peer := range peers {
|
||||
cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context}
|
||||
d, err := cc.Marshal()
|
||||
if err != nil {
|
||||
panic("unexpected marshal error")
|
||||
}
|
||||
e := pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: r.raftLog.lastIndex() + 1, Data: d}
|
||||
r.raftLog.append(e)
|
||||
}
|
||||
// Mark these initial entries as committed.
|
||||
// TODO(bdarnell): These entries are still unstable; do we need to preserve
|
||||
// the invariant that committed < unstable?
|
||||
r.raftLog.committed = r.raftLog.lastIndex()
|
||||
// Now apply them, mainly so that the application can call Campaign
|
||||
// immediately after StartNode in tests. Note that these nodes will
|
||||
// be added to raft twice: here and when the application's Ready
|
||||
// loop calls ApplyConfChange. The calls to addNode must come after
|
||||
// all calls to raftLog.append so progress.next is set after these
|
||||
// bootstrapping entries (it is an error if we try to append these
|
||||
// entries since they have already been committed).
|
||||
// We do not set raftLog.applied so the application will be able
|
||||
// to observe all conf changes via Ready.CommittedEntries.
|
||||
for _, peer := range peers {
|
||||
r.addNode(peer.ID)
|
||||
}
|
||||
|
||||
n := newNode()
|
||||
n.logger = c.Logger
|
||||
go n.run(r)
|
||||
return &n
|
||||
}
|
||||
|
||||
// RestartNode is similar to StartNode but does not take a list of peers.
|
||||
// The current membership of the cluster will be restored from the Storage.
|
||||
// If the caller has an existing state machine, pass in the last log index that
|
||||
// has been applied to it; otherwise use zero.
|
||||
func RestartNode(c *Config) Node {
|
||||
r := newRaft(c)
|
||||
|
||||
n := newNode()
|
||||
n.logger = c.Logger
|
||||
go n.run(r)
|
||||
return &n
|
||||
}
|
||||
|
||||
type msgWithResult struct {
|
||||
m pb.Message
|
||||
result chan error
|
||||
}
|
||||
|
||||
// node is the canonical implementation of the Node interface
|
||||
type node struct {
|
||||
propc chan msgWithResult
|
||||
recvc chan pb.Message
|
||||
confc chan pb.ConfChange
|
||||
confstatec chan pb.ConfState
|
||||
readyc chan Ready
|
||||
advancec chan struct{}
|
||||
tickc chan struct{}
|
||||
done chan struct{}
|
||||
stop chan struct{}
|
||||
status chan chan Status
|
||||
|
||||
logger Logger
|
||||
}
|
||||
|
||||
func newNode() node {
|
||||
return node{
|
||||
propc: make(chan msgWithResult),
|
||||
recvc: make(chan pb.Message),
|
||||
confc: make(chan pb.ConfChange),
|
||||
confstatec: make(chan pb.ConfState),
|
||||
readyc: make(chan Ready),
|
||||
advancec: make(chan struct{}),
|
||||
// make tickc a buffered chan, so raft node can buffer some ticks when the node
|
||||
// is busy processing raft messages. Raft node will resume process buffered
|
||||
// ticks when it becomes idle.
|
||||
tickc: make(chan struct{}, 128),
|
||||
done: make(chan struct{}),
|
||||
stop: make(chan struct{}),
|
||||
status: make(chan chan Status),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Stop() {
|
||||
select {
|
||||
case n.stop <- struct{}{}:
|
||||
// Not already stopped, so trigger it
|
||||
case <-n.done:
|
||||
// Node has already been stopped - no need to do anything
|
||||
return
|
||||
}
|
||||
// Block until the stop has been acknowledged by run()
|
||||
<-n.done
|
||||
}
|
||||
|
||||
func (n *node) run(r *raft) {
|
||||
var propc chan msgWithResult
|
||||
var readyc chan Ready
|
||||
var advancec chan struct{}
|
||||
var prevLastUnstablei, prevLastUnstablet uint64
|
||||
var havePrevLastUnstablei bool
|
||||
var prevSnapi uint64
|
||||
var rd Ready
|
||||
|
||||
lead := None
|
||||
prevSoftSt := r.softState()
|
||||
prevHardSt := emptyState
|
||||
|
||||
for {
|
||||
if advancec != nil {
|
||||
readyc = nil
|
||||
} else {
|
||||
rd = newReady(r, prevSoftSt, prevHardSt)
|
||||
if rd.containsUpdates() {
|
||||
readyc = n.readyc
|
||||
} else {
|
||||
readyc = nil
|
||||
}
|
||||
}
|
||||
|
||||
if lead != r.lead {
|
||||
if r.hasLeader() {
|
||||
if lead == None {
|
||||
r.logger.Infof("raft.node: %x elected leader %x at term %d", r.id, r.lead, r.Term)
|
||||
} else {
|
||||
r.logger.Infof("raft.node: %x changed leader from %x to %x at term %d", r.id, lead, r.lead, r.Term)
|
||||
}
|
||||
propc = n.propc
|
||||
} else {
|
||||
r.logger.Infof("raft.node: %x lost leader %x at term %d", r.id, lead, r.Term)
|
||||
propc = nil
|
||||
}
|
||||
lead = r.lead
|
||||
}
|
||||
|
||||
select {
|
||||
// TODO: maybe buffer the config propose if there exists one (the way
|
||||
// described in raft dissertation)
|
||||
// Currently it is dropped in Step silently.
|
||||
case pm := <-propc:
|
||||
m := pm.m
|
||||
m.From = r.id
|
||||
err := r.Step(m)
|
||||
if pm.result != nil {
|
||||
pm.result <- err
|
||||
close(pm.result)
|
||||
}
|
||||
case m := <-n.recvc:
|
||||
// filter out response message from unknown From.
|
||||
if pr := r.getProgress(m.From); pr != nil || !IsResponseMsg(m.Type) {
|
||||
r.Step(m)
|
||||
}
|
||||
case cc := <-n.confc:
|
||||
if cc.NodeID == None {
|
||||
select {
|
||||
case n.confstatec <- pb.ConfState{
|
||||
Nodes: r.nodes(),
|
||||
Learners: r.learnerNodes()}:
|
||||
case <-n.done:
|
||||
}
|
||||
break
|
||||
}
|
||||
switch cc.Type {
|
||||
case pb.ConfChangeAddNode:
|
||||
r.addNode(cc.NodeID)
|
||||
case pb.ConfChangeAddLearnerNode:
|
||||
r.addLearner(cc.NodeID)
|
||||
case pb.ConfChangeRemoveNode:
|
||||
// block incoming proposal when local node is
|
||||
// removed
|
||||
if cc.NodeID == r.id {
|
||||
propc = nil
|
||||
}
|
||||
r.removeNode(cc.NodeID)
|
||||
case pb.ConfChangeUpdateNode:
|
||||
default:
|
||||
panic("unexpected conf type")
|
||||
}
|
||||
select {
|
||||
case n.confstatec <- pb.ConfState{
|
||||
Nodes: r.nodes(),
|
||||
Learners: r.learnerNodes()}:
|
||||
case <-n.done:
|
||||
}
|
||||
case <-n.tickc:
|
||||
r.tick()
|
||||
case readyc <- rd:
|
||||
if rd.SoftState != nil {
|
||||
prevSoftSt = rd.SoftState
|
||||
}
|
||||
if len(rd.Entries) > 0 {
|
||||
prevLastUnstablei = rd.Entries[len(rd.Entries)-1].Index
|
||||
prevLastUnstablet = rd.Entries[len(rd.Entries)-1].Term
|
||||
havePrevLastUnstablei = true
|
||||
}
|
||||
if !IsEmptyHardState(rd.HardState) {
|
||||
prevHardSt = rd.HardState
|
||||
}
|
||||
if !IsEmptySnap(rd.Snapshot) {
|
||||
prevSnapi = rd.Snapshot.Metadata.Index
|
||||
}
|
||||
|
||||
r.msgs = nil
|
||||
r.readStates = nil
|
||||
advancec = n.advancec
|
||||
case <-advancec:
|
||||
if prevHardSt.Commit != 0 {
|
||||
r.raftLog.appliedTo(prevHardSt.Commit)
|
||||
}
|
||||
if havePrevLastUnstablei {
|
||||
r.raftLog.stableTo(prevLastUnstablei, prevLastUnstablet)
|
||||
havePrevLastUnstablei = false
|
||||
}
|
||||
r.raftLog.stableSnapTo(prevSnapi)
|
||||
advancec = nil
|
||||
case c := <-n.status:
|
||||
c <- getStatus(r)
|
||||
case <-n.stop:
|
||||
close(n.done)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Tick increments the internal logical clock for this Node. Election timeouts
|
||||
// and heartbeat timeouts are in units of ticks.
|
||||
func (n *node) Tick() {
|
||||
select {
|
||||
case n.tickc <- struct{}{}:
|
||||
case <-n.done:
|
||||
default:
|
||||
n.logger.Warningf("A tick missed to fire. Node blocks too long!")
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) Campaign(ctx context.Context) error { return n.step(ctx, pb.Message{Type: pb.MsgHup}) }
|
||||
|
||||
func (n *node) Propose(ctx context.Context, data []byte) error {
|
||||
return n.stepWait(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Data: data}}})
|
||||
}
|
||||
|
||||
func (n *node) Step(ctx context.Context, m pb.Message) error {
|
||||
// ignore unexpected local messages receiving over network
|
||||
if IsLocalMsg(m.Type) {
|
||||
// TODO: return an error?
|
||||
return nil
|
||||
}
|
||||
return n.step(ctx, m)
|
||||
}
|
||||
|
||||
func (n *node) ProposeConfChange(ctx context.Context, cc pb.ConfChange) error {
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return n.Step(ctx, pb.Message{Type: pb.MsgProp, Entries: []pb.Entry{{Type: pb.EntryConfChange, Data: data}}})
|
||||
}
|
||||
|
||||
func (n *node) step(ctx context.Context, m pb.Message) error {
|
||||
return n.stepWithWaitOption(ctx, m, false)
|
||||
}
|
||||
|
||||
func (n *node) stepWait(ctx context.Context, m pb.Message) error {
|
||||
return n.stepWithWaitOption(ctx, m, true)
|
||||
}
|
||||
|
||||
// Step advances the state machine using msgs. The ctx.Err() will be returned,
|
||||
// if any.
|
||||
func (n *node) stepWithWaitOption(ctx context.Context, m pb.Message, wait bool) error {
|
||||
if m.Type != pb.MsgProp {
|
||||
select {
|
||||
case n.recvc <- m:
|
||||
return nil
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-n.done:
|
||||
return ErrStopped
|
||||
}
|
||||
}
|
||||
ch := n.propc
|
||||
pm := msgWithResult{m: m}
|
||||
if wait {
|
||||
pm.result = make(chan error, 1)
|
||||
}
|
||||
select {
|
||||
case ch <- pm:
|
||||
if !wait {
|
||||
return nil
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-n.done:
|
||||
return ErrStopped
|
||||
}
|
||||
select {
|
||||
case rsp := <-pm.result:
|
||||
if rsp != nil {
|
||||
return rsp
|
||||
}
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-n.done:
|
||||
return ErrStopped
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (n *node) Ready() <-chan Ready { return n.readyc }
|
||||
|
||||
func (n *node) Advance() {
|
||||
select {
|
||||
case n.advancec <- struct{}{}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ApplyConfChange(cc pb.ConfChange) *pb.ConfState {
|
||||
var cs pb.ConfState
|
||||
select {
|
||||
case n.confc <- cc:
|
||||
case <-n.done:
|
||||
}
|
||||
select {
|
||||
case cs = <-n.confstatec:
|
||||
case <-n.done:
|
||||
}
|
||||
return &cs
|
||||
}
|
||||
|
||||
func (n *node) Status() Status {
|
||||
c := make(chan Status)
|
||||
select {
|
||||
case n.status <- c:
|
||||
return <-c
|
||||
case <-n.done:
|
||||
return Status{}
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ReportUnreachable(id uint64) {
|
||||
select {
|
||||
case n.recvc <- pb.Message{Type: pb.MsgUnreachable, From: id}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ReportSnapshot(id uint64, status SnapshotStatus) {
|
||||
rej := status == SnapshotFailure
|
||||
|
||||
select {
|
||||
case n.recvc <- pb.Message{Type: pb.MsgSnapStatus, From: id, Reject: rej}:
|
||||
case <-n.done:
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) TransferLeadership(ctx context.Context, lead, transferee uint64) {
|
||||
select {
|
||||
// manually set 'from' and 'to', so that leader can voluntarily transfers its leadership
|
||||
case n.recvc <- pb.Message{Type: pb.MsgTransferLeader, From: transferee, To: lead}:
|
||||
case <-n.done:
|
||||
case <-ctx.Done():
|
||||
}
|
||||
}
|
||||
|
||||
func (n *node) ReadIndex(ctx context.Context, rctx []byte) error {
|
||||
return n.step(ctx, pb.Message{Type: pb.MsgReadIndex, Entries: []pb.Entry{{Data: rctx}}})
|
||||
}
|
||||
|
||||
func newReady(r *raft, prevSoftSt *SoftState, prevHardSt pb.HardState) Ready {
|
||||
rd := Ready{
|
||||
Entries: r.raftLog.unstableEntries(),
|
||||
CommittedEntries: r.raftLog.nextEnts(),
|
||||
Messages: r.msgs,
|
||||
}
|
||||
if softSt := r.softState(); !softSt.equal(prevSoftSt) {
|
||||
rd.SoftState = softSt
|
||||
}
|
||||
if hardSt := r.hardState(); !isHardStateEqual(hardSt, prevHardSt) {
|
||||
rd.HardState = hardSt
|
||||
}
|
||||
if r.raftLog.unstable.snapshot != nil {
|
||||
rd.Snapshot = *r.raftLog.unstable.snapshot
|
||||
}
|
||||
if len(r.readStates) != 0 {
|
||||
rd.ReadStates = r.readStates
|
||||
}
|
||||
rd.MustSync = MustSync(rd.HardState, prevHardSt, len(rd.Entries))
|
||||
return rd
|
||||
}
|
||||
|
||||
// MustSync returns true if the hard state and count of Raft entries indicate
|
||||
// that a synchronous write to persistent storage is required.
|
||||
func MustSync(st, prevst pb.HardState, entsnum int) bool {
|
||||
// Persistent state on all servers:
|
||||
// (Updated on stable storage before responding to RPCs)
|
||||
// currentTerm
|
||||
// votedFor
|
||||
// log entries[]
|
||||
return entsnum != 0 || st.Vote != prevst.Vote || st.Term != prevst.Term
|
||||
}
|
||||
|
|
@ -0,0 +1,284 @@
|
|||
// 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 raft
|
||||
|
||||
import "fmt"
|
||||
|
||||
const (
|
||||
ProgressStateProbe ProgressStateType = iota
|
||||
ProgressStateReplicate
|
||||
ProgressStateSnapshot
|
||||
)
|
||||
|
||||
type ProgressStateType uint64
|
||||
|
||||
var prstmap = [...]string{
|
||||
"ProgressStateProbe",
|
||||
"ProgressStateReplicate",
|
||||
"ProgressStateSnapshot",
|
||||
}
|
||||
|
||||
func (st ProgressStateType) String() string { return prstmap[uint64(st)] }
|
||||
|
||||
// Progress represents a follower’s progress in the view of the leader. Leader maintains
|
||||
// progresses of all followers, and sends entries to the follower based on its progress.
|
||||
type Progress struct {
|
||||
Match, Next uint64
|
||||
// State defines how the leader should interact with the follower.
|
||||
//
|
||||
// When in ProgressStateProbe, leader sends at most one replication message
|
||||
// per heartbeat interval. It also probes actual progress of the follower.
|
||||
//
|
||||
// When in ProgressStateReplicate, leader optimistically increases next
|
||||
// to the latest entry sent after sending replication message. This is
|
||||
// an optimized state for fast replicating log entries to the follower.
|
||||
//
|
||||
// When in ProgressStateSnapshot, leader should have sent out snapshot
|
||||
// before and stops sending any replication message.
|
||||
State ProgressStateType
|
||||
|
||||
// Paused is used in ProgressStateProbe.
|
||||
// When Paused is true, raft should pause sending replication message to this peer.
|
||||
Paused bool
|
||||
// PendingSnapshot is used in ProgressStateSnapshot.
|
||||
// If there is a pending snapshot, the pendingSnapshot will be set to the
|
||||
// index of the snapshot. If pendingSnapshot is set, the replication process of
|
||||
// this Progress will be paused. raft will not resend snapshot until the pending one
|
||||
// is reported to be failed.
|
||||
PendingSnapshot uint64
|
||||
|
||||
// RecentActive is true if the progress is recently active. Receiving any messages
|
||||
// from the corresponding follower indicates the progress is active.
|
||||
// RecentActive can be reset to false after an election timeout.
|
||||
RecentActive bool
|
||||
|
||||
// inflights is a sliding window for the inflight messages.
|
||||
// Each inflight message contains one or more log entries.
|
||||
// The max number of entries per message is defined in raft config as MaxSizePerMsg.
|
||||
// Thus inflight effectively limits both the number of inflight messages
|
||||
// and the bandwidth each Progress can use.
|
||||
// When inflights is full, no more message should be sent.
|
||||
// When a leader sends out a message, the index of the last
|
||||
// entry should be added to inflights. The index MUST be added
|
||||
// into inflights in order.
|
||||
// When a leader receives a reply, the previous inflights should
|
||||
// be freed by calling inflights.freeTo with the index of the last
|
||||
// received entry.
|
||||
ins *inflights
|
||||
|
||||
// IsLearner is true if this progress is tracked for a learner.
|
||||
IsLearner bool
|
||||
}
|
||||
|
||||
func (pr *Progress) resetState(state ProgressStateType) {
|
||||
pr.Paused = false
|
||||
pr.PendingSnapshot = 0
|
||||
pr.State = state
|
||||
pr.ins.reset()
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeProbe() {
|
||||
// If the original state is ProgressStateSnapshot, progress knows that
|
||||
// the pending snapshot has been sent to this peer successfully, then
|
||||
// probes from pendingSnapshot + 1.
|
||||
if pr.State == ProgressStateSnapshot {
|
||||
pendingSnapshot := pr.PendingSnapshot
|
||||
pr.resetState(ProgressStateProbe)
|
||||
pr.Next = max(pr.Match+1, pendingSnapshot+1)
|
||||
} else {
|
||||
pr.resetState(ProgressStateProbe)
|
||||
pr.Next = pr.Match + 1
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeReplicate() {
|
||||
pr.resetState(ProgressStateReplicate)
|
||||
pr.Next = pr.Match + 1
|
||||
}
|
||||
|
||||
func (pr *Progress) becomeSnapshot(snapshoti uint64) {
|
||||
pr.resetState(ProgressStateSnapshot)
|
||||
pr.PendingSnapshot = snapshoti
|
||||
}
|
||||
|
||||
// maybeUpdate returns false if the given n index comes from an outdated message.
|
||||
// Otherwise it updates the progress and returns true.
|
||||
func (pr *Progress) maybeUpdate(n uint64) bool {
|
||||
var updated bool
|
||||
if pr.Match < n {
|
||||
pr.Match = n
|
||||
updated = true
|
||||
pr.resume()
|
||||
}
|
||||
if pr.Next < n+1 {
|
||||
pr.Next = n + 1
|
||||
}
|
||||
return updated
|
||||
}
|
||||
|
||||
func (pr *Progress) optimisticUpdate(n uint64) { pr.Next = n + 1 }
|
||||
|
||||
// maybeDecrTo returns false if the given to index comes from an out of order message.
|
||||
// Otherwise it decreases the progress next index to min(rejected, last) and returns true.
|
||||
func (pr *Progress) maybeDecrTo(rejected, last uint64) bool {
|
||||
if pr.State == ProgressStateReplicate {
|
||||
// the rejection must be stale if the progress has matched and "rejected"
|
||||
// is smaller than "match".
|
||||
if rejected <= pr.Match {
|
||||
return false
|
||||
}
|
||||
// directly decrease next to match + 1
|
||||
pr.Next = pr.Match + 1
|
||||
return true
|
||||
}
|
||||
|
||||
// the rejection must be stale if "rejected" does not match next - 1
|
||||
if pr.Next-1 != rejected {
|
||||
return false
|
||||
}
|
||||
|
||||
if pr.Next = min(rejected, last+1); pr.Next < 1 {
|
||||
pr.Next = 1
|
||||
}
|
||||
pr.resume()
|
||||
return true
|
||||
}
|
||||
|
||||
func (pr *Progress) pause() { pr.Paused = true }
|
||||
func (pr *Progress) resume() { pr.Paused = false }
|
||||
|
||||
// IsPaused returns whether sending log entries to this node has been
|
||||
// paused. A node may be paused because it has rejected recent
|
||||
// MsgApps, is currently waiting for a snapshot, or has reached the
|
||||
// MaxInflightMsgs limit.
|
||||
func (pr *Progress) IsPaused() bool {
|
||||
switch pr.State {
|
||||
case ProgressStateProbe:
|
||||
return pr.Paused
|
||||
case ProgressStateReplicate:
|
||||
return pr.ins.full()
|
||||
case ProgressStateSnapshot:
|
||||
return true
|
||||
default:
|
||||
panic("unexpected state")
|
||||
}
|
||||
}
|
||||
|
||||
func (pr *Progress) snapshotFailure() { pr.PendingSnapshot = 0 }
|
||||
|
||||
// needSnapshotAbort returns true if snapshot progress's Match
|
||||
// is equal or higher than the pendingSnapshot.
|
||||
func (pr *Progress) needSnapshotAbort() bool {
|
||||
return pr.State == ProgressStateSnapshot && pr.Match >= pr.PendingSnapshot
|
||||
}
|
||||
|
||||
func (pr *Progress) String() string {
|
||||
return fmt.Sprintf("next = %d, match = %d, state = %s, waiting = %v, pendingSnapshot = %d", pr.Next, pr.Match, pr.State, pr.IsPaused(), pr.PendingSnapshot)
|
||||
}
|
||||
|
||||
type inflights struct {
|
||||
// the starting index in the buffer
|
||||
start int
|
||||
// number of inflights in the buffer
|
||||
count int
|
||||
|
||||
// the size of the buffer
|
||||
size int
|
||||
|
||||
// buffer contains the index of the last entry
|
||||
// inside one message.
|
||||
buffer []uint64
|
||||
}
|
||||
|
||||
func newInflights(size int) *inflights {
|
||||
return &inflights{
|
||||
size: size,
|
||||
}
|
||||
}
|
||||
|
||||
// add adds an inflight into inflights
|
||||
func (in *inflights) add(inflight uint64) {
|
||||
if in.full() {
|
||||
panic("cannot add into a full inflights")
|
||||
}
|
||||
next := in.start + in.count
|
||||
size := in.size
|
||||
if next >= size {
|
||||
next -= size
|
||||
}
|
||||
if next >= len(in.buffer) {
|
||||
in.growBuf()
|
||||
}
|
||||
in.buffer[next] = inflight
|
||||
in.count++
|
||||
}
|
||||
|
||||
// grow the inflight buffer by doubling up to inflights.size. We grow on demand
|
||||
// instead of preallocating to inflights.size to handle systems which have
|
||||
// thousands of Raft groups per process.
|
||||
func (in *inflights) growBuf() {
|
||||
newSize := len(in.buffer) * 2
|
||||
if newSize == 0 {
|
||||
newSize = 1
|
||||
} else if newSize > in.size {
|
||||
newSize = in.size
|
||||
}
|
||||
newBuffer := make([]uint64, newSize)
|
||||
copy(newBuffer, in.buffer)
|
||||
in.buffer = newBuffer
|
||||
}
|
||||
|
||||
// freeTo frees the inflights smaller or equal to the given `to` flight.
|
||||
func (in *inflights) freeTo(to uint64) {
|
||||
if in.count == 0 || to < in.buffer[in.start] {
|
||||
// out of the left side of the window
|
||||
return
|
||||
}
|
||||
|
||||
idx := in.start
|
||||
var i int
|
||||
for i = 0; i < in.count; i++ {
|
||||
if to < in.buffer[idx] { // found the first large inflight
|
||||
break
|
||||
}
|
||||
|
||||
// increase index and maybe rotate
|
||||
size := in.size
|
||||
if idx++; idx >= size {
|
||||
idx -= size
|
||||
}
|
||||
}
|
||||
// free i inflights and set new start index
|
||||
in.count -= i
|
||||
in.start = idx
|
||||
if in.count == 0 {
|
||||
// inflights is empty, reset the start index so that we don't grow the
|
||||
// buffer unnecessarily.
|
||||
in.start = 0
|
||||
}
|
||||
}
|
||||
|
||||
func (in *inflights) freeFirstOne() { in.freeTo(in.buffer[in.start]) }
|
||||
|
||||
// full returns true if the inflights is full.
|
||||
func (in *inflights) full() bool {
|
||||
return in.count == in.size
|
||||
}
|
||||
|
||||
// resets frees all inflights.
|
||||
func (in *inflights) reset() {
|
||||
in.count = 0
|
||||
in.start = 0
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,264 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// ErrStepLocalMsg is returned when try to step a local raft message
|
||||
var ErrStepLocalMsg = errors.New("raft: cannot step raft local message")
|
||||
|
||||
// ErrStepPeerNotFound is returned when try to step a response message
|
||||
// but there is no peer found in raft.prs for that node.
|
||||
var ErrStepPeerNotFound = errors.New("raft: cannot step as peer not found")
|
||||
|
||||
// RawNode is a thread-unsafe Node.
|
||||
// The methods of this struct correspond to the methods of Node and are described
|
||||
// more fully there.
|
||||
type RawNode struct {
|
||||
raft *raft
|
||||
prevSoftSt *SoftState
|
||||
prevHardSt pb.HardState
|
||||
}
|
||||
|
||||
func (rn *RawNode) newReady() Ready {
|
||||
return newReady(rn.raft, rn.prevSoftSt, rn.prevHardSt)
|
||||
}
|
||||
|
||||
func (rn *RawNode) commitReady(rd Ready) {
|
||||
if rd.SoftState != nil {
|
||||
rn.prevSoftSt = rd.SoftState
|
||||
}
|
||||
if !IsEmptyHardState(rd.HardState) {
|
||||
rn.prevHardSt = rd.HardState
|
||||
}
|
||||
if rn.prevHardSt.Commit != 0 {
|
||||
// In most cases, prevHardSt and rd.HardState will be the same
|
||||
// because when there are new entries to apply we just sent a
|
||||
// HardState with an updated Commit value. However, on initial
|
||||
// startup the two are different because we don't send a HardState
|
||||
// until something changes, but we do send any un-applied but
|
||||
// committed entries (and previously-committed entries may be
|
||||
// incorporated into the snapshot, even if rd.CommittedEntries is
|
||||
// empty). Therefore we mark all committed entries as applied
|
||||
// whether they were included in rd.HardState or not.
|
||||
rn.raft.raftLog.appliedTo(rn.prevHardSt.Commit)
|
||||
}
|
||||
if len(rd.Entries) > 0 {
|
||||
e := rd.Entries[len(rd.Entries)-1]
|
||||
rn.raft.raftLog.stableTo(e.Index, e.Term)
|
||||
}
|
||||
if !IsEmptySnap(rd.Snapshot) {
|
||||
rn.raft.raftLog.stableSnapTo(rd.Snapshot.Metadata.Index)
|
||||
}
|
||||
if len(rd.ReadStates) != 0 {
|
||||
rn.raft.readStates = nil
|
||||
}
|
||||
}
|
||||
|
||||
// NewRawNode returns a new RawNode given configuration and a list of raft peers.
|
||||
func NewRawNode(config *Config, peers []Peer) (*RawNode, error) {
|
||||
if config.ID == 0 {
|
||||
panic("config.ID must not be zero")
|
||||
}
|
||||
r := newRaft(config)
|
||||
rn := &RawNode{
|
||||
raft: r,
|
||||
}
|
||||
lastIndex, err := config.Storage.LastIndex()
|
||||
if err != nil {
|
||||
panic(err) // TODO(bdarnell)
|
||||
}
|
||||
// If the log is empty, this is a new RawNode (like StartNode); otherwise it's
|
||||
// restoring an existing RawNode (like RestartNode).
|
||||
// TODO(bdarnell): rethink RawNode initialization and whether the application needs
|
||||
// to be able to tell us when it expects the RawNode to exist.
|
||||
if lastIndex == 0 {
|
||||
r.becomeFollower(1, None)
|
||||
ents := make([]pb.Entry, len(peers))
|
||||
for i, peer := range peers {
|
||||
cc := pb.ConfChange{Type: pb.ConfChangeAddNode, NodeID: peer.ID, Context: peer.Context}
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
panic("unexpected marshal error")
|
||||
}
|
||||
|
||||
ents[i] = pb.Entry{Type: pb.EntryConfChange, Term: 1, Index: uint64(i + 1), Data: data}
|
||||
}
|
||||
r.raftLog.append(ents...)
|
||||
r.raftLog.committed = uint64(len(ents))
|
||||
for _, peer := range peers {
|
||||
r.addNode(peer.ID)
|
||||
}
|
||||
}
|
||||
|
||||
// Set the initial hard and soft states after performing all initialization.
|
||||
rn.prevSoftSt = r.softState()
|
||||
if lastIndex == 0 {
|
||||
rn.prevHardSt = emptyState
|
||||
} else {
|
||||
rn.prevHardSt = r.hardState()
|
||||
}
|
||||
|
||||
return rn, nil
|
||||
}
|
||||
|
||||
// Tick advances the internal logical clock by a single tick.
|
||||
func (rn *RawNode) Tick() {
|
||||
rn.raft.tick()
|
||||
}
|
||||
|
||||
// TickQuiesced advances the internal logical clock by a single tick without
|
||||
// performing any other state machine processing. It allows the caller to avoid
|
||||
// periodic heartbeats and elections when all of the peers in a Raft group are
|
||||
// known to be at the same state. Expected usage is to periodically invoke Tick
|
||||
// or TickQuiesced depending on whether the group is "active" or "quiesced".
|
||||
//
|
||||
// WARNING: Be very careful about using this method as it subverts the Raft
|
||||
// state machine. You should probably be using Tick instead.
|
||||
func (rn *RawNode) TickQuiesced() {
|
||||
rn.raft.electionElapsed++
|
||||
}
|
||||
|
||||
// Campaign causes this RawNode to transition to candidate state.
|
||||
func (rn *RawNode) Campaign() error {
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgHup,
|
||||
})
|
||||
}
|
||||
|
||||
// Propose proposes data be appended to the raft log.
|
||||
func (rn *RawNode) Propose(data []byte) error {
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgProp,
|
||||
From: rn.raft.id,
|
||||
Entries: []pb.Entry{
|
||||
{Data: data},
|
||||
}})
|
||||
}
|
||||
|
||||
// ProposeConfChange proposes a config change.
|
||||
func (rn *RawNode) ProposeConfChange(cc pb.ConfChange) error {
|
||||
data, err := cc.Marshal()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return rn.raft.Step(pb.Message{
|
||||
Type: pb.MsgProp,
|
||||
Entries: []pb.Entry{
|
||||
{Type: pb.EntryConfChange, Data: data},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyConfChange applies a config change to the local node.
|
||||
func (rn *RawNode) ApplyConfChange(cc pb.ConfChange) *pb.ConfState {
|
||||
if cc.NodeID == None {
|
||||
return &pb.ConfState{Nodes: rn.raft.nodes(), Learners: rn.raft.learnerNodes()}
|
||||
}
|
||||
switch cc.Type {
|
||||
case pb.ConfChangeAddNode:
|
||||
rn.raft.addNode(cc.NodeID)
|
||||
case pb.ConfChangeAddLearnerNode:
|
||||
rn.raft.addLearner(cc.NodeID)
|
||||
case pb.ConfChangeRemoveNode:
|
||||
rn.raft.removeNode(cc.NodeID)
|
||||
case pb.ConfChangeUpdateNode:
|
||||
default:
|
||||
panic("unexpected conf type")
|
||||
}
|
||||
return &pb.ConfState{Nodes: rn.raft.nodes(), Learners: rn.raft.learnerNodes()}
|
||||
}
|
||||
|
||||
// Step advances the state machine using the given message.
|
||||
func (rn *RawNode) Step(m pb.Message) error {
|
||||
// ignore unexpected local messages receiving over network
|
||||
if IsLocalMsg(m.Type) {
|
||||
return ErrStepLocalMsg
|
||||
}
|
||||
if pr := rn.raft.getProgress(m.From); pr != nil || !IsResponseMsg(m.Type) {
|
||||
return rn.raft.Step(m)
|
||||
}
|
||||
return ErrStepPeerNotFound
|
||||
}
|
||||
|
||||
// Ready returns the current point-in-time state of this RawNode.
|
||||
func (rn *RawNode) Ready() Ready {
|
||||
rd := rn.newReady()
|
||||
rn.raft.msgs = nil
|
||||
return rd
|
||||
}
|
||||
|
||||
// HasReady called when RawNode user need to check if any Ready pending.
|
||||
// Checking logic in this method should be consistent with Ready.containsUpdates().
|
||||
func (rn *RawNode) HasReady() bool {
|
||||
r := rn.raft
|
||||
if !r.softState().equal(rn.prevSoftSt) {
|
||||
return true
|
||||
}
|
||||
if hardSt := r.hardState(); !IsEmptyHardState(hardSt) && !isHardStateEqual(hardSt, rn.prevHardSt) {
|
||||
return true
|
||||
}
|
||||
if r.raftLog.unstable.snapshot != nil && !IsEmptySnap(*r.raftLog.unstable.snapshot) {
|
||||
return true
|
||||
}
|
||||
if len(r.msgs) > 0 || len(r.raftLog.unstableEntries()) > 0 || r.raftLog.hasNextEnts() {
|
||||
return true
|
||||
}
|
||||
if len(r.readStates) != 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// Advance notifies the RawNode that the application has applied and saved progress in the
|
||||
// last Ready results.
|
||||
func (rn *RawNode) Advance(rd Ready) {
|
||||
rn.commitReady(rd)
|
||||
}
|
||||
|
||||
// Status returns the current status of the given group.
|
||||
func (rn *RawNode) Status() *Status {
|
||||
status := getStatus(rn.raft)
|
||||
return &status
|
||||
}
|
||||
|
||||
// ReportUnreachable reports the given node is not reachable for the last send.
|
||||
func (rn *RawNode) ReportUnreachable(id uint64) {
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgUnreachable, From: id})
|
||||
}
|
||||
|
||||
// ReportSnapshot reports the status of the sent snapshot.
|
||||
func (rn *RawNode) ReportSnapshot(id uint64, status SnapshotStatus) {
|
||||
rej := status == SnapshotFailure
|
||||
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgSnapStatus, From: id, Reject: rej})
|
||||
}
|
||||
|
||||
// TransferLeader tries to transfer leadership to the given transferee.
|
||||
func (rn *RawNode) TransferLeader(transferee uint64) {
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgTransferLeader, From: transferee})
|
||||
}
|
||||
|
||||
// ReadIndex requests a read state. The read state will be set in ready.
|
||||
// Read State has a read index. Once the application advances further than the read
|
||||
// index, any linearizable read requests issued before the read request can be
|
||||
// processed safely. The read state will have the same rctx attached.
|
||||
func (rn *RawNode) ReadIndex(rctx []byte) {
|
||||
_ = rn.raft.Step(pb.Message{Type: pb.MsgReadIndex, Entries: []pb.Entry{{Data: rctx}}})
|
||||
}
|
||||
|
|
@ -0,0 +1,118 @@
|
|||
// 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 raft
|
||||
|
||||
import pb "github.com/coreos/etcd/raft/raftpb"
|
||||
|
||||
// ReadState provides state for read only query.
|
||||
// It's caller's responsibility to call ReadIndex first before getting
|
||||
// this state from ready, it's also caller's duty to differentiate if this
|
||||
// state is what it requests through RequestCtx, eg. given a unique id as
|
||||
// RequestCtx
|
||||
type ReadState struct {
|
||||
Index uint64
|
||||
RequestCtx []byte
|
||||
}
|
||||
|
||||
type readIndexStatus struct {
|
||||
req pb.Message
|
||||
index uint64
|
||||
acks map[uint64]struct{}
|
||||
}
|
||||
|
||||
type readOnly struct {
|
||||
option ReadOnlyOption
|
||||
pendingReadIndex map[string]*readIndexStatus
|
||||
readIndexQueue []string
|
||||
}
|
||||
|
||||
func newReadOnly(option ReadOnlyOption) *readOnly {
|
||||
return &readOnly{
|
||||
option: option,
|
||||
pendingReadIndex: make(map[string]*readIndexStatus),
|
||||
}
|
||||
}
|
||||
|
||||
// addRequest adds a read only reuqest into readonly struct.
|
||||
// `index` is the commit index of the raft state machine when it received
|
||||
// the read only request.
|
||||
// `m` is the original read only request message from the local or remote node.
|
||||
func (ro *readOnly) addRequest(index uint64, m pb.Message) {
|
||||
ctx := string(m.Entries[0].Data)
|
||||
if _, ok := ro.pendingReadIndex[ctx]; ok {
|
||||
return
|
||||
}
|
||||
ro.pendingReadIndex[ctx] = &readIndexStatus{index: index, req: m, acks: make(map[uint64]struct{})}
|
||||
ro.readIndexQueue = append(ro.readIndexQueue, ctx)
|
||||
}
|
||||
|
||||
// recvAck notifies the readonly struct that the raft state machine received
|
||||
// an acknowledgment of the heartbeat that attached with the read only request
|
||||
// context.
|
||||
func (ro *readOnly) recvAck(m pb.Message) int {
|
||||
rs, ok := ro.pendingReadIndex[string(m.Context)]
|
||||
if !ok {
|
||||
return 0
|
||||
}
|
||||
|
||||
rs.acks[m.From] = struct{}{}
|
||||
// add one to include an ack from local node
|
||||
return len(rs.acks) + 1
|
||||
}
|
||||
|
||||
// advance advances the read only request queue kept by the readonly struct.
|
||||
// It dequeues the requests until it finds the read only request that has
|
||||
// the same context as the given `m`.
|
||||
func (ro *readOnly) advance(m pb.Message) []*readIndexStatus {
|
||||
var (
|
||||
i int
|
||||
found bool
|
||||
)
|
||||
|
||||
ctx := string(m.Context)
|
||||
rss := []*readIndexStatus{}
|
||||
|
||||
for _, okctx := range ro.readIndexQueue {
|
||||
i++
|
||||
rs, ok := ro.pendingReadIndex[okctx]
|
||||
if !ok {
|
||||
panic("cannot find corresponding read state from pending map")
|
||||
}
|
||||
rss = append(rss, rs)
|
||||
if okctx == ctx {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if found {
|
||||
ro.readIndexQueue = ro.readIndexQueue[i:]
|
||||
for _, rs := range rss {
|
||||
delete(ro.pendingReadIndex, string(rs.req.Entries[0].Data))
|
||||
}
|
||||
return rss
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// lastPendingRequestCtx returns the context of the last pending read only
|
||||
// request in readonly struct.
|
||||
func (ro *readOnly) lastPendingRequestCtx() string {
|
||||
if len(ro.readIndexQueue) == 0 {
|
||||
return ""
|
||||
}
|
||||
return ro.readIndexQueue[len(ro.readIndexQueue)-1]
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
type Status struct {
|
||||
ID uint64
|
||||
|
||||
pb.HardState
|
||||
SoftState
|
||||
|
||||
Applied uint64
|
||||
Progress map[uint64]Progress
|
||||
|
||||
LeadTransferee uint64
|
||||
}
|
||||
|
||||
// getStatus gets a copy of the current raft status.
|
||||
func getStatus(r *raft) Status {
|
||||
s := Status{
|
||||
ID: r.id,
|
||||
LeadTransferee: r.leadTransferee,
|
||||
}
|
||||
|
||||
s.HardState = r.hardState()
|
||||
s.SoftState = *r.softState()
|
||||
|
||||
s.Applied = r.raftLog.applied
|
||||
|
||||
if s.RaftState == StateLeader {
|
||||
s.Progress = make(map[uint64]Progress)
|
||||
for id, p := range r.prs {
|
||||
s.Progress[id] = *p
|
||||
}
|
||||
|
||||
for id, p := range r.learnerPrs {
|
||||
s.Progress[id] = *p
|
||||
}
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// MarshalJSON translates the raft status into JSON.
|
||||
// TODO: try to simplify this by introducing ID type into raft
|
||||
func (s Status) MarshalJSON() ([]byte, error) {
|
||||
j := fmt.Sprintf(`{"id":"%x","term":%d,"vote":"%x","commit":%d,"lead":"%x","raftState":%q,"applied":%d,"progress":{`,
|
||||
s.ID, s.Term, s.Vote, s.Commit, s.Lead, s.RaftState, s.Applied)
|
||||
|
||||
if len(s.Progress) == 0 {
|
||||
j += "},"
|
||||
} else {
|
||||
for k, v := range s.Progress {
|
||||
subj := fmt.Sprintf(`"%x":{"match":%d,"next":%d,"state":%q},`, k, v.Match, v.Next, v.State)
|
||||
j += subj
|
||||
}
|
||||
// remove the trailing ","
|
||||
j = j[:len(j)-1] + "},"
|
||||
}
|
||||
|
||||
j += fmt.Sprintf(`"leadtransferee":"%x"}`, s.LeadTransferee)
|
||||
return []byte(j), nil
|
||||
}
|
||||
|
||||
func (s Status) String() string {
|
||||
b, err := s.MarshalJSON()
|
||||
if err != nil {
|
||||
raftLogger.Panicf("unexpected error: %v", err)
|
||||
}
|
||||
return string(b)
|
||||
}
|
||||
|
|
@ -0,0 +1,271 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
// ErrCompacted is returned by Storage.Entries/Compact when a requested
|
||||
// index is unavailable because it predates the last snapshot.
|
||||
var ErrCompacted = errors.New("requested index is unavailable due to compaction")
|
||||
|
||||
// ErrSnapOutOfDate is returned by Storage.CreateSnapshot when a requested
|
||||
// index is older than the existing snapshot.
|
||||
var ErrSnapOutOfDate = errors.New("requested index is older than the existing snapshot")
|
||||
|
||||
// ErrUnavailable is returned by Storage interface when the requested log entries
|
||||
// are unavailable.
|
||||
var ErrUnavailable = errors.New("requested entry at index is unavailable")
|
||||
|
||||
// ErrSnapshotTemporarilyUnavailable is returned by the Storage interface when the required
|
||||
// snapshot is temporarily unavailable.
|
||||
var ErrSnapshotTemporarilyUnavailable = errors.New("snapshot is temporarily unavailable")
|
||||
|
||||
// Storage is an interface that may be implemented by the application
|
||||
// to retrieve log entries from storage.
|
||||
//
|
||||
// If any Storage method returns an error, the raft instance will
|
||||
// become inoperable and refuse to participate in elections; the
|
||||
// application is responsible for cleanup and recovery in this case.
|
||||
type Storage interface {
|
||||
// InitialState returns the saved HardState and ConfState information.
|
||||
InitialState() (pb.HardState, pb.ConfState, error)
|
||||
// Entries returns a slice of log entries in the range [lo,hi).
|
||||
// MaxSize limits the total size of the log entries returned, but
|
||||
// Entries returns at least one entry if any.
|
||||
Entries(lo, hi, maxSize uint64) ([]pb.Entry, error)
|
||||
// Term returns the term of entry i, which must be in the range
|
||||
// [FirstIndex()-1, LastIndex()]. The term of the entry before
|
||||
// FirstIndex is retained for matching purposes even though the
|
||||
// rest of that entry may not be available.
|
||||
Term(i uint64) (uint64, error)
|
||||
// LastIndex returns the index of the last entry in the log.
|
||||
LastIndex() (uint64, error)
|
||||
// FirstIndex returns the index of the first log entry that is
|
||||
// possibly available via Entries (older entries have been incorporated
|
||||
// into the latest Snapshot; if storage only contains the dummy entry the
|
||||
// first log entry is not available).
|
||||
FirstIndex() (uint64, error)
|
||||
// Snapshot returns the most recent snapshot.
|
||||
// If snapshot is temporarily unavailable, it should return ErrSnapshotTemporarilyUnavailable,
|
||||
// so raft state machine could know that Storage needs some time to prepare
|
||||
// snapshot and call Snapshot later.
|
||||
Snapshot() (pb.Snapshot, error)
|
||||
}
|
||||
|
||||
// MemoryStorage implements the Storage interface backed by an
|
||||
// in-memory array.
|
||||
type MemoryStorage struct {
|
||||
// Protects access to all fields. Most methods of MemoryStorage are
|
||||
// run on the raft goroutine, but Append() is run on an application
|
||||
// goroutine.
|
||||
sync.Mutex
|
||||
|
||||
hardState pb.HardState
|
||||
snapshot pb.Snapshot
|
||||
// ents[i] has raft log position i+snapshot.Metadata.Index
|
||||
ents []pb.Entry
|
||||
}
|
||||
|
||||
// NewMemoryStorage creates an empty MemoryStorage.
|
||||
func NewMemoryStorage() *MemoryStorage {
|
||||
return &MemoryStorage{
|
||||
// When starting from scratch populate the list with a dummy entry at term zero.
|
||||
ents: make([]pb.Entry, 1),
|
||||
}
|
||||
}
|
||||
|
||||
// InitialState implements the Storage interface.
|
||||
func (ms *MemoryStorage) InitialState() (pb.HardState, pb.ConfState, error) {
|
||||
return ms.hardState, ms.snapshot.Metadata.ConfState, nil
|
||||
}
|
||||
|
||||
// SetHardState saves the current HardState.
|
||||
func (ms *MemoryStorage) SetHardState(st pb.HardState) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
ms.hardState = st
|
||||
return nil
|
||||
}
|
||||
|
||||
// Entries implements the Storage interface.
|
||||
func (ms *MemoryStorage) Entries(lo, hi, maxSize uint64) ([]pb.Entry, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if lo <= offset {
|
||||
return nil, ErrCompacted
|
||||
}
|
||||
if hi > ms.lastIndex()+1 {
|
||||
raftLogger.Panicf("entries' hi(%d) is out of bound lastindex(%d)", hi, ms.lastIndex())
|
||||
}
|
||||
// only contains dummy entries.
|
||||
if len(ms.ents) == 1 {
|
||||
return nil, ErrUnavailable
|
||||
}
|
||||
|
||||
ents := ms.ents[lo-offset : hi-offset]
|
||||
return limitSize(ents, maxSize), nil
|
||||
}
|
||||
|
||||
// Term implements the Storage interface.
|
||||
func (ms *MemoryStorage) Term(i uint64) (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if i < offset {
|
||||
return 0, ErrCompacted
|
||||
}
|
||||
if int(i-offset) >= len(ms.ents) {
|
||||
return 0, ErrUnavailable
|
||||
}
|
||||
return ms.ents[i-offset].Term, nil
|
||||
}
|
||||
|
||||
// LastIndex implements the Storage interface.
|
||||
func (ms *MemoryStorage) LastIndex() (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.lastIndex(), nil
|
||||
}
|
||||
|
||||
func (ms *MemoryStorage) lastIndex() uint64 {
|
||||
return ms.ents[0].Index + uint64(len(ms.ents)) - 1
|
||||
}
|
||||
|
||||
// FirstIndex implements the Storage interface.
|
||||
func (ms *MemoryStorage) FirstIndex() (uint64, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.firstIndex(), nil
|
||||
}
|
||||
|
||||
func (ms *MemoryStorage) firstIndex() uint64 {
|
||||
return ms.ents[0].Index + 1
|
||||
}
|
||||
|
||||
// Snapshot implements the Storage interface.
|
||||
func (ms *MemoryStorage) Snapshot() (pb.Snapshot, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
return ms.snapshot, nil
|
||||
}
|
||||
|
||||
// ApplySnapshot overwrites the contents of this Storage object with
|
||||
// those of the given snapshot.
|
||||
func (ms *MemoryStorage) ApplySnapshot(snap pb.Snapshot) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
|
||||
//handle check for old snapshot being applied
|
||||
msIndex := ms.snapshot.Metadata.Index
|
||||
snapIndex := snap.Metadata.Index
|
||||
if msIndex >= snapIndex {
|
||||
return ErrSnapOutOfDate
|
||||
}
|
||||
|
||||
ms.snapshot = snap
|
||||
ms.ents = []pb.Entry{{Term: snap.Metadata.Term, Index: snap.Metadata.Index}}
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreateSnapshot makes a snapshot which can be retrieved with Snapshot() and
|
||||
// can be used to reconstruct the state at that point.
|
||||
// If any configuration changes have been made since the last compaction,
|
||||
// the result of the last ApplyConfChange must be passed in.
|
||||
func (ms *MemoryStorage) CreateSnapshot(i uint64, cs *pb.ConfState, data []byte) (pb.Snapshot, error) {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
if i <= ms.snapshot.Metadata.Index {
|
||||
return pb.Snapshot{}, ErrSnapOutOfDate
|
||||
}
|
||||
|
||||
offset := ms.ents[0].Index
|
||||
if i > ms.lastIndex() {
|
||||
raftLogger.Panicf("snapshot %d is out of bound lastindex(%d)", i, ms.lastIndex())
|
||||
}
|
||||
|
||||
ms.snapshot.Metadata.Index = i
|
||||
ms.snapshot.Metadata.Term = ms.ents[i-offset].Term
|
||||
if cs != nil {
|
||||
ms.snapshot.Metadata.ConfState = *cs
|
||||
}
|
||||
ms.snapshot.Data = data
|
||||
return ms.snapshot, nil
|
||||
}
|
||||
|
||||
// Compact discards all log entries prior to compactIndex.
|
||||
// It is the application's responsibility to not attempt to compact an index
|
||||
// greater than raftLog.applied.
|
||||
func (ms *MemoryStorage) Compact(compactIndex uint64) error {
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
offset := ms.ents[0].Index
|
||||
if compactIndex <= offset {
|
||||
return ErrCompacted
|
||||
}
|
||||
if compactIndex > ms.lastIndex() {
|
||||
raftLogger.Panicf("compact %d is out of bound lastindex(%d)", compactIndex, ms.lastIndex())
|
||||
}
|
||||
|
||||
i := compactIndex - offset
|
||||
ents := make([]pb.Entry, 1, 1+uint64(len(ms.ents))-i)
|
||||
ents[0].Index = ms.ents[i].Index
|
||||
ents[0].Term = ms.ents[i].Term
|
||||
ents = append(ents, ms.ents[i+1:]...)
|
||||
ms.ents = ents
|
||||
return nil
|
||||
}
|
||||
|
||||
// Append the new entries to storage.
|
||||
// TODO (xiangli): ensure the entries are continuous and
|
||||
// entries[0].Index > ms.entries[0].Index
|
||||
func (ms *MemoryStorage) Append(entries []pb.Entry) error {
|
||||
if len(entries) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
ms.Lock()
|
||||
defer ms.Unlock()
|
||||
|
||||
first := ms.firstIndex()
|
||||
last := entries[0].Index + uint64(len(entries)) - 1
|
||||
|
||||
// shortcut if there is no new entry.
|
||||
if last < first {
|
||||
return nil
|
||||
}
|
||||
// truncate compacted entries
|
||||
if first > entries[0].Index {
|
||||
entries = entries[first-entries[0].Index:]
|
||||
}
|
||||
|
||||
offset := entries[0].Index - ms.ents[0].Index
|
||||
switch {
|
||||
case uint64(len(ms.ents)) > offset:
|
||||
ms.ents = append([]pb.Entry{}, ms.ents[:offset]...)
|
||||
ms.ents = append(ms.ents, entries...)
|
||||
case uint64(len(ms.ents)) == offset:
|
||||
ms.ents = append(ms.ents, entries...)
|
||||
default:
|
||||
raftLogger.Panicf("missing log entry [last: %d, append at: %d]",
|
||||
ms.lastIndex(), entries[0].Index)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
// 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 raft
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
|
||||
pb "github.com/coreos/etcd/raft/raftpb"
|
||||
)
|
||||
|
||||
func (st StateType) MarshalJSON() ([]byte, error) {
|
||||
return []byte(fmt.Sprintf("%q", st.String())), nil
|
||||
}
|
||||
|
||||
// uint64Slice implements sort interface
|
||||
type uint64Slice []uint64
|
||||
|
||||
func (p uint64Slice) Len() int { return len(p) }
|
||||
func (p uint64Slice) Less(i, j int) bool { return p[i] < p[j] }
|
||||
func (p uint64Slice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
func min(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return b
|
||||
}
|
||||
return a
|
||||
}
|
||||
|
||||
func max(a, b uint64) uint64 {
|
||||
if a > b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func IsLocalMsg(msgt pb.MessageType) bool {
|
||||
return msgt == pb.MsgHup || msgt == pb.MsgBeat || msgt == pb.MsgUnreachable ||
|
||||
msgt == pb.MsgSnapStatus || msgt == pb.MsgCheckQuorum
|
||||
}
|
||||
|
||||
func IsResponseMsg(msgt pb.MessageType) bool {
|
||||
return msgt == pb.MsgAppResp || msgt == pb.MsgVoteResp || msgt == pb.MsgHeartbeatResp || msgt == pb.MsgUnreachable || msgt == pb.MsgPreVoteResp
|
||||
}
|
||||
|
||||
// voteResponseType maps vote and prevote message types to their corresponding responses.
|
||||
func voteRespMsgType(msgt pb.MessageType) pb.MessageType {
|
||||
switch msgt {
|
||||
case pb.MsgVote:
|
||||
return pb.MsgVoteResp
|
||||
case pb.MsgPreVote:
|
||||
return pb.MsgPreVoteResp
|
||||
default:
|
||||
panic(fmt.Sprintf("not a vote message: %s", msgt))
|
||||
}
|
||||
}
|
||||
|
||||
// EntryFormatter can be implemented by the application to provide human-readable formatting
|
||||
// of entry data. Nil is a valid EntryFormatter and will use a default format.
|
||||
type EntryFormatter func([]byte) string
|
||||
|
||||
// DescribeMessage returns a concise human-readable description of a
|
||||
// Message for debugging.
|
||||
func DescribeMessage(m pb.Message, f EntryFormatter) string {
|
||||
var buf bytes.Buffer
|
||||
fmt.Fprintf(&buf, "%x->%x %v Term:%d Log:%d/%d", m.From, m.To, m.Type, m.Term, m.LogTerm, m.Index)
|
||||
if m.Reject {
|
||||
fmt.Fprintf(&buf, " Rejected")
|
||||
if m.RejectHint != 0 {
|
||||
fmt.Fprintf(&buf, "(Hint:%d)", m.RejectHint)
|
||||
}
|
||||
}
|
||||
if m.Commit != 0 {
|
||||
fmt.Fprintf(&buf, " Commit:%d", m.Commit)
|
||||
}
|
||||
if len(m.Entries) > 0 {
|
||||
fmt.Fprintf(&buf, " Entries:[")
|
||||
for i, e := range m.Entries {
|
||||
if i != 0 {
|
||||
buf.WriteString(", ")
|
||||
}
|
||||
buf.WriteString(DescribeEntry(e, f))
|
||||
}
|
||||
fmt.Fprintf(&buf, "]")
|
||||
}
|
||||
if !IsEmptySnap(m.Snapshot) {
|
||||
fmt.Fprintf(&buf, " Snapshot:%v", m.Snapshot)
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
// DescribeEntry returns a concise human-readable description of an
|
||||
// Entry for debugging.
|
||||
func DescribeEntry(e pb.Entry, f EntryFormatter) string {
|
||||
var formatted string
|
||||
if e.Type == pb.EntryNormal && f != nil {
|
||||
formatted = f(e.Data)
|
||||
} else {
|
||||
formatted = fmt.Sprintf("%q", e.Data)
|
||||
}
|
||||
return fmt.Sprintf("%d/%d %s %s", e.Term, e.Index, e.Type, formatted)
|
||||
}
|
||||
|
||||
func limitSize(ents []pb.Entry, maxSize uint64) []pb.Entry {
|
||||
if len(ents) == 0 {
|
||||
return ents
|
||||
}
|
||||
size := ents[0].Size()
|
||||
var limit int
|
||||
for limit = 1; limit < len(ents); limit++ {
|
||||
size += ents[limit].Size()
|
||||
if uint64(size) > maxSize {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ents[:limit]
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2016 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,351 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package atomic provides simple wrappers around numerics to enforce atomic
|
||||
// access.
|
||||
package atomic
|
||||
|
||||
import (
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Int32 is an atomic wrapper around an int32.
|
||||
type Int32 struct{ v int32 }
|
||||
|
||||
// NewInt32 creates an Int32.
|
||||
func NewInt32(i int32) *Int32 {
|
||||
return &Int32{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int32) Load() int32 {
|
||||
return atomic.LoadInt32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Add(n int32) int32 {
|
||||
return atomic.AddInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Sub(n int32) int32 {
|
||||
return atomic.AddInt32(&i.v, -n)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Inc() int32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Int32) Dec() int32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Int32) CAS(old, new int32) bool {
|
||||
return atomic.CompareAndSwapInt32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int32) Store(n int32) {
|
||||
atomic.StoreInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int32 and returns the old value.
|
||||
func (i *Int32) Swap(n int32) int32 {
|
||||
return atomic.SwapInt32(&i.v, n)
|
||||
}
|
||||
|
||||
// Int64 is an atomic wrapper around an int64.
|
||||
type Int64 struct{ v int64 }
|
||||
|
||||
// NewInt64 creates an Int64.
|
||||
func NewInt64(i int64) *Int64 {
|
||||
return &Int64{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Int64) Load() int64 {
|
||||
return atomic.LoadInt64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Add(n int64) int64 {
|
||||
return atomic.AddInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Sub(n int64) int64 {
|
||||
return atomic.AddInt64(&i.v, -n)
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Inc() int64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int64 and returns the new value.
|
||||
func (i *Int64) Dec() int64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Int64) CAS(old, new int64) bool {
|
||||
return atomic.CompareAndSwapInt64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Int64) Store(n int64) {
|
||||
atomic.StoreInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped int64 and returns the old value.
|
||||
func (i *Int64) Swap(n int64) int64 {
|
||||
return atomic.SwapInt64(&i.v, n)
|
||||
}
|
||||
|
||||
// Uint32 is an atomic wrapper around an uint32.
|
||||
type Uint32 struct{ v uint32 }
|
||||
|
||||
// NewUint32 creates a Uint32.
|
||||
func NewUint32(i uint32) *Uint32 {
|
||||
return &Uint32{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint32) Load() uint32 {
|
||||
return atomic.LoadUint32(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Add(n uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Sub(n uint32) uint32 {
|
||||
return atomic.AddUint32(&i.v, ^(n - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint32 and returns the new value.
|
||||
func (i *Uint32) Inc() uint32 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped int32 and returns the new value.
|
||||
func (i *Uint32) Dec() uint32 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Uint32) CAS(old, new uint32) bool {
|
||||
return atomic.CompareAndSwapUint32(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint32) Store(n uint32) {
|
||||
atomic.StoreUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint32 and returns the old value.
|
||||
func (i *Uint32) Swap(n uint32) uint32 {
|
||||
return atomic.SwapUint32(&i.v, n)
|
||||
}
|
||||
|
||||
// Uint64 is an atomic wrapper around a uint64.
|
||||
type Uint64 struct{ v uint64 }
|
||||
|
||||
// NewUint64 creates a Uint64.
|
||||
func NewUint64(i uint64) *Uint64 {
|
||||
return &Uint64{i}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (i *Uint64) Load() uint64 {
|
||||
return atomic.LoadUint64(&i.v)
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Add(n uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Sub(n uint64) uint64 {
|
||||
return atomic.AddUint64(&i.v, ^(n - 1))
|
||||
}
|
||||
|
||||
// Inc atomically increments the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Inc() uint64 {
|
||||
return i.Add(1)
|
||||
}
|
||||
|
||||
// Dec atomically decrements the wrapped uint64 and returns the new value.
|
||||
func (i *Uint64) Dec() uint64 {
|
||||
return i.Sub(1)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (i *Uint64) CAS(old, new uint64) bool {
|
||||
return atomic.CompareAndSwapUint64(&i.v, old, new)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (i *Uint64) Store(n uint64) {
|
||||
atomic.StoreUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped uint64 and returns the old value.
|
||||
func (i *Uint64) Swap(n uint64) uint64 {
|
||||
return atomic.SwapUint64(&i.v, n)
|
||||
}
|
||||
|
||||
// Bool is an atomic Boolean.
|
||||
type Bool struct{ v uint32 }
|
||||
|
||||
// NewBool creates a Bool.
|
||||
func NewBool(initial bool) *Bool {
|
||||
return &Bool{boolToInt(initial)}
|
||||
}
|
||||
|
||||
// Load atomically loads the Boolean.
|
||||
func (b *Bool) Load() bool {
|
||||
return truthy(atomic.LoadUint32(&b.v))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (b *Bool) CAS(old, new bool) bool {
|
||||
return atomic.CompareAndSwapUint32(&b.v, boolToInt(old), boolToInt(new))
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (b *Bool) Store(new bool) {
|
||||
atomic.StoreUint32(&b.v, boolToInt(new))
|
||||
}
|
||||
|
||||
// Swap sets the given value and returns the previous value.
|
||||
func (b *Bool) Swap(new bool) bool {
|
||||
return truthy(atomic.SwapUint32(&b.v, boolToInt(new)))
|
||||
}
|
||||
|
||||
// Toggle atomically negates the Boolean and returns the previous value.
|
||||
func (b *Bool) Toggle() bool {
|
||||
return truthy(atomic.AddUint32(&b.v, 1) - 1)
|
||||
}
|
||||
|
||||
func truthy(n uint32) bool {
|
||||
return n&1 == 1
|
||||
}
|
||||
|
||||
func boolToInt(b bool) uint32 {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Float64 is an atomic wrapper around float64.
|
||||
type Float64 struct {
|
||||
v uint64
|
||||
}
|
||||
|
||||
// NewFloat64 creates a Float64.
|
||||
func NewFloat64(f float64) *Float64 {
|
||||
return &Float64{math.Float64bits(f)}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (f *Float64) Load() float64 {
|
||||
return math.Float64frombits(atomic.LoadUint64(&f.v))
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (f *Float64) Store(s float64) {
|
||||
atomic.StoreUint64(&f.v, math.Float64bits(s))
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Add(s float64) float64 {
|
||||
for {
|
||||
old := f.Load()
|
||||
new := old + s
|
||||
if f.CAS(old, new) {
|
||||
return new
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped float64 and returns the new value.
|
||||
func (f *Float64) Sub(s float64) float64 {
|
||||
return f.Add(-s)
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (f *Float64) CAS(old, new float64) bool {
|
||||
return atomic.CompareAndSwapUint64(&f.v, math.Float64bits(old), math.Float64bits(new))
|
||||
}
|
||||
|
||||
// Duration is an atomic wrapper around time.Duration
|
||||
// https://godoc.org/time#Duration
|
||||
type Duration struct {
|
||||
v Int64
|
||||
}
|
||||
|
||||
// NewDuration creates a Duration.
|
||||
func NewDuration(d time.Duration) *Duration {
|
||||
return &Duration{v: *NewInt64(int64(d))}
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped value.
|
||||
func (d *Duration) Load() time.Duration {
|
||||
return time.Duration(d.v.Load())
|
||||
}
|
||||
|
||||
// Store atomically stores the passed value.
|
||||
func (d *Duration) Store(n time.Duration) {
|
||||
d.v.Store(int64(n))
|
||||
}
|
||||
|
||||
// Add atomically adds to the wrapped time.Duration and returns the new value.
|
||||
func (d *Duration) Add(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Add(int64(n)))
|
||||
}
|
||||
|
||||
// Sub atomically subtracts from the wrapped time.Duration and returns the new value.
|
||||
func (d *Duration) Sub(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Sub(int64(n)))
|
||||
}
|
||||
|
||||
// Swap atomically swaps the wrapped time.Duration and returns the old value.
|
||||
func (d *Duration) Swap(n time.Duration) time.Duration {
|
||||
return time.Duration(d.v.Swap(int64(n)))
|
||||
}
|
||||
|
||||
// CAS is an atomic compare-and-swap.
|
||||
func (d *Duration) CAS(old, new time.Duration) bool {
|
||||
return d.v.CAS(int64(old), int64(new))
|
||||
}
|
||||
|
||||
// Value shadows the type of the same name from sync/atomic
|
||||
// https://godoc.org/sync/atomic#Value
|
||||
type Value struct{ atomic.Value }
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package atomic
|
||||
|
||||
// String is an atomic type-safe wrapper around Value for strings.
|
||||
type String struct{ v Value }
|
||||
|
||||
// NewString creates a String.
|
||||
func NewString(str string) *String {
|
||||
s := &String{}
|
||||
if str != "" {
|
||||
s.Store(str)
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// Load atomically loads the wrapped string.
|
||||
func (s *String) Load() string {
|
||||
v := s.v.Load()
|
||||
if v == nil {
|
||||
return ""
|
||||
}
|
||||
return v.(string)
|
||||
}
|
||||
|
||||
// Store atomically stores the passed string.
|
||||
// Note: Converting the string to an interface{} to store in the Value
|
||||
// requires an allocation.
|
||||
func (s *String) Store(str string) {
|
||||
s.v.Store(str)
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2017 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,401 @@
|
|||
// Copyright (c) 2017 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package multierr allows combining one or more errors together.
|
||||
//
|
||||
// Overview
|
||||
//
|
||||
// Errors can be combined with the use of the Combine function.
|
||||
//
|
||||
// multierr.Combine(
|
||||
// reader.Close(),
|
||||
// writer.Close(),
|
||||
// conn.Close(),
|
||||
// )
|
||||
//
|
||||
// If only two errors are being combined, the Append function may be used
|
||||
// instead.
|
||||
//
|
||||
// err = multierr.Combine(reader.Close(), writer.Close())
|
||||
//
|
||||
// This makes it possible to record resource cleanup failures from deferred
|
||||
// blocks with the help of named return values.
|
||||
//
|
||||
// func sendRequest(req Request) (err error) {
|
||||
// conn, err := openConnection()
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// defer func() {
|
||||
// err = multierr.Append(err, conn.Close())
|
||||
// }()
|
||||
// // ...
|
||||
// }
|
||||
//
|
||||
// The underlying list of errors for a returned error object may be retrieved
|
||||
// with the Errors function.
|
||||
//
|
||||
// errors := multierr.Errors(err)
|
||||
// if len(errors) > 0 {
|
||||
// fmt.Println("The following errors occurred:")
|
||||
// }
|
||||
//
|
||||
// Advanced Usage
|
||||
//
|
||||
// Errors returned by Combine and Append MAY implement the following
|
||||
// interface.
|
||||
//
|
||||
// type errorGroup interface {
|
||||
// // Returns a slice containing the underlying list of errors.
|
||||
// //
|
||||
// // This slice MUST NOT be modified by the caller.
|
||||
// Errors() []error
|
||||
// }
|
||||
//
|
||||
// Note that if you need access to list of errors behind a multierr error, you
|
||||
// should prefer using the Errors function. That said, if you need cheap
|
||||
// read-only access to the underlying errors slice, you can attempt to cast
|
||||
// the error to this interface. You MUST handle the failure case gracefully
|
||||
// because errors returned by Combine and Append are not guaranteed to
|
||||
// implement this interface.
|
||||
//
|
||||
// var errors []error
|
||||
// group, ok := err.(errorGroup)
|
||||
// if ok {
|
||||
// errors = group.Errors()
|
||||
// } else {
|
||||
// errors = []error{err}
|
||||
// }
|
||||
package multierr // import "go.uber.org/multierr"
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"go.uber.org/atomic"
|
||||
)
|
||||
|
||||
var (
|
||||
// Separator for single-line error messages.
|
||||
_singlelineSeparator = []byte("; ")
|
||||
|
||||
_newline = []byte("\n")
|
||||
|
||||
// Prefix for multi-line messages
|
||||
_multilinePrefix = []byte("the following errors occurred:")
|
||||
|
||||
// Prefix for the first and following lines of an item in a list of
|
||||
// multi-line error messages.
|
||||
//
|
||||
// For example, if a single item is:
|
||||
//
|
||||
// foo
|
||||
// bar
|
||||
//
|
||||
// It will become,
|
||||
//
|
||||
// - foo
|
||||
// bar
|
||||
_multilineSeparator = []byte("\n - ")
|
||||
_multilineIndent = []byte(" ")
|
||||
)
|
||||
|
||||
// _bufferPool is a pool of bytes.Buffers.
|
||||
var _bufferPool = sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &bytes.Buffer{}
|
||||
},
|
||||
}
|
||||
|
||||
type errorGroup interface {
|
||||
Errors() []error
|
||||
}
|
||||
|
||||
// Errors returns a slice containing zero or more errors that the supplied
|
||||
// error is composed of. If the error is nil, the returned slice is empty.
|
||||
//
|
||||
// err := multierr.Append(r.Close(), w.Close())
|
||||
// errors := multierr.Errors(err)
|
||||
//
|
||||
// If the error is not composed of other errors, the returned slice contains
|
||||
// just the error that was passed in.
|
||||
//
|
||||
// Callers of this function are free to modify the returned slice.
|
||||
func Errors(err error) []error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Note that we're casting to multiError, not errorGroup. Our contract is
|
||||
// that returned errors MAY implement errorGroup. Errors, however, only
|
||||
// has special behavior for multierr-specific error objects.
|
||||
//
|
||||
// This behavior can be expanded in the future but I think it's prudent to
|
||||
// start with as little as possible in terms of contract and possibility
|
||||
// of misuse.
|
||||
eg, ok := err.(*multiError)
|
||||
if !ok {
|
||||
return []error{err}
|
||||
}
|
||||
|
||||
errors := eg.Errors()
|
||||
result := make([]error, len(errors))
|
||||
copy(result, errors)
|
||||
return result
|
||||
}
|
||||
|
||||
// multiError is an error that holds one or more errors.
|
||||
//
|
||||
// An instance of this is guaranteed to be non-empty and flattened. That is,
|
||||
// none of the errors inside multiError are other multiErrors.
|
||||
//
|
||||
// multiError formats to a semi-colon delimited list of error messages with
|
||||
// %v and with a more readable multi-line format with %+v.
|
||||
type multiError struct {
|
||||
copyNeeded atomic.Bool
|
||||
errors []error
|
||||
}
|
||||
|
||||
var _ errorGroup = (*multiError)(nil)
|
||||
|
||||
// Errors returns the list of underlying errors.
|
||||
//
|
||||
// This slice MUST NOT be modified.
|
||||
func (merr *multiError) Errors() []error {
|
||||
if merr == nil {
|
||||
return nil
|
||||
}
|
||||
return merr.errors
|
||||
}
|
||||
|
||||
func (merr *multiError) Error() string {
|
||||
if merr == nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
buff := _bufferPool.Get().(*bytes.Buffer)
|
||||
buff.Reset()
|
||||
|
||||
merr.writeSingleline(buff)
|
||||
|
||||
result := buff.String()
|
||||
_bufferPool.Put(buff)
|
||||
return result
|
||||
}
|
||||
|
||||
func (merr *multiError) Format(f fmt.State, c rune) {
|
||||
if c == 'v' && f.Flag('+') {
|
||||
merr.writeMultiline(f)
|
||||
} else {
|
||||
merr.writeSingleline(f)
|
||||
}
|
||||
}
|
||||
|
||||
func (merr *multiError) writeSingleline(w io.Writer) {
|
||||
first := true
|
||||
for _, item := range merr.errors {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
w.Write(_singlelineSeparator)
|
||||
}
|
||||
io.WriteString(w, item.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func (merr *multiError) writeMultiline(w io.Writer) {
|
||||
w.Write(_multilinePrefix)
|
||||
for _, item := range merr.errors {
|
||||
w.Write(_multilineSeparator)
|
||||
writePrefixLine(w, _multilineIndent, fmt.Sprintf("%+v", item))
|
||||
}
|
||||
}
|
||||
|
||||
// Writes s to the writer with the given prefix added before each line after
|
||||
// the first.
|
||||
func writePrefixLine(w io.Writer, prefix []byte, s string) {
|
||||
first := true
|
||||
for len(s) > 0 {
|
||||
if first {
|
||||
first = false
|
||||
} else {
|
||||
w.Write(prefix)
|
||||
}
|
||||
|
||||
idx := strings.IndexByte(s, '\n')
|
||||
if idx < 0 {
|
||||
idx = len(s) - 1
|
||||
}
|
||||
|
||||
io.WriteString(w, s[:idx+1])
|
||||
s = s[idx+1:]
|
||||
}
|
||||
}
|
||||
|
||||
type inspectResult struct {
|
||||
// Number of top-level non-nil errors
|
||||
Count int
|
||||
|
||||
// Total number of errors including multiErrors
|
||||
Capacity int
|
||||
|
||||
// Index of the first non-nil error in the list. Value is meaningless if
|
||||
// Count is zero.
|
||||
FirstErrorIdx int
|
||||
|
||||
// Whether the list contains at least one multiError
|
||||
ContainsMultiError bool
|
||||
}
|
||||
|
||||
// Inspects the given slice of errors so that we can efficiently allocate
|
||||
// space for it.
|
||||
func inspect(errors []error) (res inspectResult) {
|
||||
first := true
|
||||
for i, err := range errors {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
res.Count++
|
||||
if first {
|
||||
first = false
|
||||
res.FirstErrorIdx = i
|
||||
}
|
||||
|
||||
if merr, ok := err.(*multiError); ok {
|
||||
res.Capacity += len(merr.errors)
|
||||
res.ContainsMultiError = true
|
||||
} else {
|
||||
res.Capacity++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// fromSlice converts the given list of errors into a single error.
|
||||
func fromSlice(errors []error) error {
|
||||
res := inspect(errors)
|
||||
switch res.Count {
|
||||
case 0:
|
||||
return nil
|
||||
case 1:
|
||||
// only one non-nil entry
|
||||
return errors[res.FirstErrorIdx]
|
||||
case len(errors):
|
||||
if !res.ContainsMultiError {
|
||||
// already flat
|
||||
return &multiError{errors: errors}
|
||||
}
|
||||
}
|
||||
|
||||
nonNilErrs := make([]error, 0, res.Capacity)
|
||||
for _, err := range errors[res.FirstErrorIdx:] {
|
||||
if err == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
if nested, ok := err.(*multiError); ok {
|
||||
nonNilErrs = append(nonNilErrs, nested.errors...)
|
||||
} else {
|
||||
nonNilErrs = append(nonNilErrs, err)
|
||||
}
|
||||
}
|
||||
|
||||
return &multiError{errors: nonNilErrs}
|
||||
}
|
||||
|
||||
// Combine combines the passed errors into a single error.
|
||||
//
|
||||
// If zero arguments were passed or if all items are nil, a nil error is
|
||||
// returned.
|
||||
//
|
||||
// Combine(nil, nil) // == nil
|
||||
//
|
||||
// If only a single error was passed, it is returned as-is.
|
||||
//
|
||||
// Combine(err) // == err
|
||||
//
|
||||
// Combine skips over nil arguments so this function may be used to combine
|
||||
// together errors from operations that fail independently of each other.
|
||||
//
|
||||
// multierr.Combine(
|
||||
// reader.Close(),
|
||||
// writer.Close(),
|
||||
// pipe.Close(),
|
||||
// )
|
||||
//
|
||||
// If any of the passed errors is a multierr error, it will be flattened along
|
||||
// with the other errors.
|
||||
//
|
||||
// multierr.Combine(multierr.Combine(err1, err2), err3)
|
||||
// // is the same as
|
||||
// multierr.Combine(err1, err2, err3)
|
||||
//
|
||||
// The returned error formats into a readable multi-line error message if
|
||||
// formatted with %+v.
|
||||
//
|
||||
// fmt.Sprintf("%+v", multierr.Combine(err1, err2))
|
||||
func Combine(errors ...error) error {
|
||||
return fromSlice(errors)
|
||||
}
|
||||
|
||||
// Append appends the given errors together. Either value may be nil.
|
||||
//
|
||||
// This function is a specialization of Combine for the common case where
|
||||
// there are only two errors.
|
||||
//
|
||||
// err = multierr.Append(reader.Close(), writer.Close())
|
||||
//
|
||||
// The following pattern may also be used to record failure of deferred
|
||||
// operations without losing information about the original error.
|
||||
//
|
||||
// func doSomething(..) (err error) {
|
||||
// f := acquireResource()
|
||||
// defer func() {
|
||||
// err = multierr.Append(err, f.Close())
|
||||
// }()
|
||||
func Append(left error, right error) error {
|
||||
switch {
|
||||
case left == nil:
|
||||
return right
|
||||
case right == nil:
|
||||
return left
|
||||
}
|
||||
|
||||
if _, ok := right.(*multiError); !ok {
|
||||
if l, ok := left.(*multiError); ok && !l.copyNeeded.Swap(true) {
|
||||
// Common case where the error on the left is constantly being
|
||||
// appended to.
|
||||
errs := append(l.errors, right)
|
||||
return &multiError{errors: errs}
|
||||
} else if !ok {
|
||||
// Both errors are single errors.
|
||||
return &multiError{errors: []error{left, right}}
|
||||
}
|
||||
}
|
||||
|
||||
// Either right or both, left and right, are multiErrors. Rely on usual
|
||||
// expensive logic.
|
||||
errors := [2]error{left, right}
|
||||
return fromSlice(errors[0:])
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
Copyright (c) 2016-2017 Uber Technologies, Inc.
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
|
@ -0,0 +1,320 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package zap
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// Array constructs a field with the given key and ArrayMarshaler. It provides
|
||||
// a flexible, but still type-safe and efficient, way to add array-like types
|
||||
// to the logging context. The struct's MarshalLogArray method is called lazily.
|
||||
func Array(key string, val zapcore.ArrayMarshaler) Field {
|
||||
return Field{Key: key, Type: zapcore.ArrayMarshalerType, Interface: val}
|
||||
}
|
||||
|
||||
// Bools constructs a field that carries a slice of bools.
|
||||
func Bools(key string, bs []bool) Field {
|
||||
return Array(key, bools(bs))
|
||||
}
|
||||
|
||||
// ByteStrings constructs a field that carries a slice of []byte, each of which
|
||||
// must be UTF-8 encoded text.
|
||||
func ByteStrings(key string, bss [][]byte) Field {
|
||||
return Array(key, byteStringsArray(bss))
|
||||
}
|
||||
|
||||
// Complex128s constructs a field that carries a slice of complex numbers.
|
||||
func Complex128s(key string, nums []complex128) Field {
|
||||
return Array(key, complex128s(nums))
|
||||
}
|
||||
|
||||
// Complex64s constructs a field that carries a slice of complex numbers.
|
||||
func Complex64s(key string, nums []complex64) Field {
|
||||
return Array(key, complex64s(nums))
|
||||
}
|
||||
|
||||
// Durations constructs a field that carries a slice of time.Durations.
|
||||
func Durations(key string, ds []time.Duration) Field {
|
||||
return Array(key, durations(ds))
|
||||
}
|
||||
|
||||
// Float64s constructs a field that carries a slice of floats.
|
||||
func Float64s(key string, nums []float64) Field {
|
||||
return Array(key, float64s(nums))
|
||||
}
|
||||
|
||||
// Float32s constructs a field that carries a slice of floats.
|
||||
func Float32s(key string, nums []float32) Field {
|
||||
return Array(key, float32s(nums))
|
||||
}
|
||||
|
||||
// Ints constructs a field that carries a slice of integers.
|
||||
func Ints(key string, nums []int) Field {
|
||||
return Array(key, ints(nums))
|
||||
}
|
||||
|
||||
// Int64s constructs a field that carries a slice of integers.
|
||||
func Int64s(key string, nums []int64) Field {
|
||||
return Array(key, int64s(nums))
|
||||
}
|
||||
|
||||
// Int32s constructs a field that carries a slice of integers.
|
||||
func Int32s(key string, nums []int32) Field {
|
||||
return Array(key, int32s(nums))
|
||||
}
|
||||
|
||||
// Int16s constructs a field that carries a slice of integers.
|
||||
func Int16s(key string, nums []int16) Field {
|
||||
return Array(key, int16s(nums))
|
||||
}
|
||||
|
||||
// Int8s constructs a field that carries a slice of integers.
|
||||
func Int8s(key string, nums []int8) Field {
|
||||
return Array(key, int8s(nums))
|
||||
}
|
||||
|
||||
// Strings constructs a field that carries a slice of strings.
|
||||
func Strings(key string, ss []string) Field {
|
||||
return Array(key, stringArray(ss))
|
||||
}
|
||||
|
||||
// Times constructs a field that carries a slice of time.Times.
|
||||
func Times(key string, ts []time.Time) Field {
|
||||
return Array(key, times(ts))
|
||||
}
|
||||
|
||||
// Uints constructs a field that carries a slice of unsigned integers.
|
||||
func Uints(key string, nums []uint) Field {
|
||||
return Array(key, uints(nums))
|
||||
}
|
||||
|
||||
// Uint64s constructs a field that carries a slice of unsigned integers.
|
||||
func Uint64s(key string, nums []uint64) Field {
|
||||
return Array(key, uint64s(nums))
|
||||
}
|
||||
|
||||
// Uint32s constructs a field that carries a slice of unsigned integers.
|
||||
func Uint32s(key string, nums []uint32) Field {
|
||||
return Array(key, uint32s(nums))
|
||||
}
|
||||
|
||||
// Uint16s constructs a field that carries a slice of unsigned integers.
|
||||
func Uint16s(key string, nums []uint16) Field {
|
||||
return Array(key, uint16s(nums))
|
||||
}
|
||||
|
||||
// Uint8s constructs a field that carries a slice of unsigned integers.
|
||||
func Uint8s(key string, nums []uint8) Field {
|
||||
return Array(key, uint8s(nums))
|
||||
}
|
||||
|
||||
// Uintptrs constructs a field that carries a slice of pointer addresses.
|
||||
func Uintptrs(key string, us []uintptr) Field {
|
||||
return Array(key, uintptrs(us))
|
||||
}
|
||||
|
||||
// Errors constructs a field that carries a slice of errors.
|
||||
func Errors(key string, errs []error) Field {
|
||||
return Array(key, errArray(errs))
|
||||
}
|
||||
|
||||
type bools []bool
|
||||
|
||||
func (bs bools) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range bs {
|
||||
arr.AppendBool(bs[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type byteStringsArray [][]byte
|
||||
|
||||
func (bss byteStringsArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range bss {
|
||||
arr.AppendByteString(bss[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type complex128s []complex128
|
||||
|
||||
func (nums complex128s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendComplex128(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type complex64s []complex64
|
||||
|
||||
func (nums complex64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendComplex64(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type durations []time.Duration
|
||||
|
||||
func (ds durations) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range ds {
|
||||
arr.AppendDuration(ds[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type float64s []float64
|
||||
|
||||
func (nums float64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendFloat64(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type float32s []float32
|
||||
|
||||
func (nums float32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendFloat32(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type ints []int
|
||||
|
||||
func (nums ints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendInt(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type int64s []int64
|
||||
|
||||
func (nums int64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendInt64(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type int32s []int32
|
||||
|
||||
func (nums int32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendInt32(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type int16s []int16
|
||||
|
||||
func (nums int16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendInt16(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type int8s []int8
|
||||
|
||||
func (nums int8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendInt8(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type stringArray []string
|
||||
|
||||
func (ss stringArray) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range ss {
|
||||
arr.AppendString(ss[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type times []time.Time
|
||||
|
||||
func (ts times) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range ts {
|
||||
arr.AppendTime(ts[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uints []uint
|
||||
|
||||
func (nums uints) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUint(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uint64s []uint64
|
||||
|
||||
func (nums uint64s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUint64(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uint32s []uint32
|
||||
|
||||
func (nums uint32s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUint32(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uint16s []uint16
|
||||
|
||||
func (nums uint16s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUint16(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uint8s []uint8
|
||||
|
||||
func (nums uint8s) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUint8(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type uintptrs []uintptr
|
||||
|
||||
func (nums uintptrs) MarshalLogArray(arr zapcore.ArrayEncoder) error {
|
||||
for i := range nums {
|
||||
arr.AppendUintptr(nums[i])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
// Package buffer provides a thin wrapper around a byte slice. Unlike the
|
||||
// standard library's bytes.Buffer, it supports a portion of the strconv
|
||||
// package's zero-allocation formatters.
|
||||
package buffer // import "go.uber.org/zap/buffer"
|
||||
|
||||
import "strconv"
|
||||
|
||||
const _size = 1024 // by default, create 1 KiB buffers
|
||||
|
||||
// Buffer is a thin wrapper around a byte slice. It's intended to be pooled, so
|
||||
// the only way to construct one is via a Pool.
|
||||
type Buffer struct {
|
||||
bs []byte
|
||||
pool Pool
|
||||
}
|
||||
|
||||
// AppendByte writes a single byte to the Buffer.
|
||||
func (b *Buffer) AppendByte(v byte) {
|
||||
b.bs = append(b.bs, v)
|
||||
}
|
||||
|
||||
// AppendString writes a string to the Buffer.
|
||||
func (b *Buffer) AppendString(s string) {
|
||||
b.bs = append(b.bs, s...)
|
||||
}
|
||||
|
||||
// AppendInt appends an integer to the underlying buffer (assuming base 10).
|
||||
func (b *Buffer) AppendInt(i int64) {
|
||||
b.bs = strconv.AppendInt(b.bs, i, 10)
|
||||
}
|
||||
|
||||
// AppendUint appends an unsigned integer to the underlying buffer (assuming
|
||||
// base 10).
|
||||
func (b *Buffer) AppendUint(i uint64) {
|
||||
b.bs = strconv.AppendUint(b.bs, i, 10)
|
||||
}
|
||||
|
||||
// AppendBool appends a bool to the underlying buffer.
|
||||
func (b *Buffer) AppendBool(v bool) {
|
||||
b.bs = strconv.AppendBool(b.bs, v)
|
||||
}
|
||||
|
||||
// AppendFloat appends a float to the underlying buffer. It doesn't quote NaN
|
||||
// or +/- Inf.
|
||||
func (b *Buffer) AppendFloat(f float64, bitSize int) {
|
||||
b.bs = strconv.AppendFloat(b.bs, f, 'f', -1, bitSize)
|
||||
}
|
||||
|
||||
// Len returns the length of the underlying byte slice.
|
||||
func (b *Buffer) Len() int {
|
||||
return len(b.bs)
|
||||
}
|
||||
|
||||
// Cap returns the capacity of the underlying byte slice.
|
||||
func (b *Buffer) Cap() int {
|
||||
return cap(b.bs)
|
||||
}
|
||||
|
||||
// Bytes returns a mutable reference to the underlying byte slice.
|
||||
func (b *Buffer) Bytes() []byte {
|
||||
return b.bs
|
||||
}
|
||||
|
||||
// String returns a string copy of the underlying byte slice.
|
||||
func (b *Buffer) String() string {
|
||||
return string(b.bs)
|
||||
}
|
||||
|
||||
// Reset resets the underlying byte slice. Subsequent writes re-use the slice's
|
||||
// backing array.
|
||||
func (b *Buffer) Reset() {
|
||||
b.bs = b.bs[:0]
|
||||
}
|
||||
|
||||
// Write implements io.Writer.
|
||||
func (b *Buffer) Write(bs []byte) (int, error) {
|
||||
b.bs = append(b.bs, bs...)
|
||||
return len(bs), nil
|
||||
}
|
||||
|
||||
// Free returns the Buffer to its Pool.
|
||||
//
|
||||
// Callers must not retain references to the Buffer after calling Free.
|
||||
func (b *Buffer) Free() {
|
||||
b.pool.put(b)
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package buffer
|
||||
|
||||
import "sync"
|
||||
|
||||
// A Pool is a type-safe wrapper around a sync.Pool.
|
||||
type Pool struct {
|
||||
p *sync.Pool
|
||||
}
|
||||
|
||||
// NewPool constructs a new Pool.
|
||||
func NewPool() Pool {
|
||||
return Pool{p: &sync.Pool{
|
||||
New: func() interface{} {
|
||||
return &Buffer{bs: make([]byte, 0, _size)}
|
||||
},
|
||||
}}
|
||||
}
|
||||
|
||||
// Get retrieves a Buffer from the pool, creating one if necessary.
|
||||
func (p Pool) Get() *Buffer {
|
||||
buf := p.p.Get().(*Buffer)
|
||||
buf.Reset()
|
||||
buf.pool = p
|
||||
return buf
|
||||
}
|
||||
|
||||
func (p Pool) put(buf *Buffer) {
|
||||
p.p.Put(buf)
|
||||
}
|
||||
|
|
@ -0,0 +1,243 @@
|
|||
// Copyright (c) 2016 Uber Technologies, Inc.
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
// THE SOFTWARE.
|
||||
|
||||
package zap
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// SamplingConfig sets a sampling strategy for the logger. Sampling caps the
|
||||
// global CPU and I/O load that logging puts on your process while attempting
|
||||
// to preserve a representative subset of your logs.
|
||||
//
|
||||
// Values configured here are per-second. See zapcore.NewSampler for details.
|
||||
type SamplingConfig struct {
|
||||
Initial int `json:"initial" yaml:"initial"`
|
||||
Thereafter int `json:"thereafter" yaml:"thereafter"`
|
||||
}
|
||||
|
||||
// Config offers a declarative way to construct a logger. It doesn't do
|
||||
// anything that can't be done with New, Options, and the various
|
||||
// zapcore.WriteSyncer and zapcore.Core wrappers, but it's a simpler way to
|
||||
// toggle common options.
|
||||
//
|
||||
// Note that Config intentionally supports only the most common options. More
|
||||
// unusual logging setups (logging to network connections or message queues,
|
||||
// splitting output between multiple files, etc.) are possible, but require
|
||||
// direct use of the zapcore package. For sample code, see the package-level
|
||||
// BasicConfiguration and AdvancedConfiguration examples.
|
||||
//
|
||||
// For an example showing runtime log level changes, see the documentation for
|
||||
// AtomicLevel.
|
||||
type Config struct {
|
||||
// Level is the minimum enabled logging level. Note that this is a dynamic
|
||||
// level, so calling Config.Level.SetLevel will atomically change the log
|
||||
// level of all loggers descended from this config.
|
||||
Level AtomicLevel `json:"level" yaml:"level"`
|
||||
// Development puts the logger in development mode, which changes the
|
||||
// behavior of DPanicLevel and takes stacktraces more liberally.
|
||||
Development bool `json:"development" yaml:"development"`
|
||||
// DisableCaller stops annotating logs with the calling function's file
|
||||
// name and line number. By default, all logs are annotated.
|
||||
DisableCaller bool `json:"disableCaller" yaml:"disableCaller"`
|
||||
// DisableStacktrace completely disables automatic stacktrace capturing. By
|
||||
// default, stacktraces are captured for WarnLevel and above logs in
|
||||
// development and ErrorLevel and above in production.
|
||||
DisableStacktrace bool `json:"disableStacktrace" yaml:"disableStacktrace"`
|
||||
// Sampling sets a sampling policy. A nil SamplingConfig disables sampling.
|
||||
Sampling *SamplingConfig `json:"sampling" yaml:"sampling"`
|
||||
// Encoding sets the logger's encoding. Valid values are "json" and
|
||||
// "console", as well as any third-party encodings registered via
|
||||
// RegisterEncoder.
|
||||
Encoding string `json:"encoding" yaml:"encoding"`
|
||||
// EncoderConfig sets options for the chosen encoder. See
|
||||
// zapcore.EncoderConfig for details.
|
||||
EncoderConfig zapcore.EncoderConfig `json:"encoderConfig" yaml:"encoderConfig"`
|
||||
// OutputPaths is a list of paths to write logging output to. See Open for
|
||||
// details.
|
||||
OutputPaths []string `json:"outputPaths" yaml:"outputPaths"`
|
||||
// ErrorOutputPaths is a list of paths to write internal logger errors to.
|
||||
// The default is standard error.
|
||||
//
|
||||
// Note that this setting only affects internal errors; for sample code that
|
||||
// sends error-level logs to a different location from info- and debug-level
|
||||
// logs, see the package-level AdvancedConfiguration example.
|
||||
ErrorOutputPaths []string `json:"errorOutputPaths" yaml:"errorOutputPaths"`
|
||||
// InitialFields is a collection of fields to add to the root logger.
|
||||
InitialFields map[string]interface{} `json:"initialFields" yaml:"initialFields"`
|
||||
}
|
||||
|
||||
// NewProductionEncoderConfig returns an opinionated EncoderConfig for
|
||||
// production environments.
|
||||
func NewProductionEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
TimeKey: "ts",
|
||||
LevelKey: "level",
|
||||
NameKey: "logger",
|
||||
CallerKey: "caller",
|
||||
MessageKey: "msg",
|
||||
StacktraceKey: "stacktrace",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.LowercaseLevelEncoder,
|
||||
EncodeTime: zapcore.EpochTimeEncoder,
|
||||
EncodeDuration: zapcore.SecondsDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
}
|
||||
|
||||
// NewProductionConfig is a reasonable production logging configuration.
|
||||
// Logging is enabled at InfoLevel and above.
|
||||
//
|
||||
// It uses a JSON encoder, writes to standard error, and enables sampling.
|
||||
// Stacktraces are automatically included on logs of ErrorLevel and above.
|
||||
func NewProductionConfig() Config {
|
||||
return Config{
|
||||
Level: NewAtomicLevelAt(InfoLevel),
|
||||
Development: false,
|
||||
Sampling: &SamplingConfig{
|
||||
Initial: 100,
|
||||
Thereafter: 100,
|
||||
},
|
||||
Encoding: "json",
|
||||
EncoderConfig: NewProductionEncoderConfig(),
|
||||
OutputPaths: []string{"stderr"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
}
|
||||
}
|
||||
|
||||
// NewDevelopmentEncoderConfig returns an opinionated EncoderConfig for
|
||||
// development environments.
|
||||
func NewDevelopmentEncoderConfig() zapcore.EncoderConfig {
|
||||
return zapcore.EncoderConfig{
|
||||
// Keys can be anything except the empty string.
|
||||
TimeKey: "T",
|
||||
LevelKey: "L",
|
||||
NameKey: "N",
|
||||
CallerKey: "C",
|
||||
MessageKey: "M",
|
||||
StacktraceKey: "S",
|
||||
LineEnding: zapcore.DefaultLineEnding,
|
||||
EncodeLevel: zapcore.CapitalLevelEncoder,
|
||||
EncodeTime: zapcore.ISO8601TimeEncoder,
|
||||
EncodeDuration: zapcore.StringDurationEncoder,
|
||||
EncodeCaller: zapcore.ShortCallerEncoder,
|
||||
}
|
||||
}
|
||||
|
||||
// NewDevelopmentConfig is a reasonable development logging configuration.
|
||||
// Logging is enabled at DebugLevel and above.
|
||||
//
|
||||
// It enables development mode (which makes DPanicLevel logs panic), uses a
|
||||
// console encoder, writes to standard error, and disables sampling.
|
||||
// Stacktraces are automatically included on logs of WarnLevel and above.
|
||||
func NewDevelopmentConfig() Config {
|
||||
return Config{
|
||||
Level: NewAtomicLevelAt(DebugLevel),
|
||||
Development: true,
|
||||
Encoding: "console",
|
||||
EncoderConfig: NewDevelopmentEncoderConfig(),
|
||||
OutputPaths: []string{"stderr"},
|
||||
ErrorOutputPaths: []string{"stderr"},
|
||||
}
|
||||
}
|
||||
|
||||
// Build constructs a logger from the Config and Options.
|
||||
func (cfg Config) Build(opts ...Option) (*Logger, error) {
|
||||
enc, err := cfg.buildEncoder()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sink, errSink, err := cfg.openSinks()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
log := New(
|
||||
zapcore.NewCore(enc, sink, cfg.Level),
|
||||
cfg.buildOptions(errSink)...,
|
||||
)
|
||||
if len(opts) > 0 {
|
||||
log = log.WithOptions(opts...)
|
||||
}
|
||||
return log, nil
|
||||
}
|
||||
|
||||
func (cfg Config) buildOptions(errSink zapcore.WriteSyncer) []Option {
|
||||
opts := []Option{ErrorOutput(errSink)}
|
||||
|
||||
if cfg.Development {
|
||||
opts = append(opts, Development())
|
||||
}
|
||||
|
||||
if !cfg.DisableCaller {
|
||||
opts = append(opts, AddCaller())
|
||||
}
|
||||
|
||||
stackLevel := ErrorLevel
|
||||
if cfg.Development {
|
||||
stackLevel = WarnLevel
|
||||
}
|
||||
if !cfg.DisableStacktrace {
|
||||
opts = append(opts, AddStacktrace(stackLevel))
|
||||
}
|
||||
|
||||
if cfg.Sampling != nil {
|
||||
opts = append(opts, WrapCore(func(core zapcore.Core) zapcore.Core {
|
||||
return zapcore.NewSampler(core, time.Second, int(cfg.Sampling.Initial), int(cfg.Sampling.Thereafter))
|
||||
}))
|
||||
}
|
||||
|
||||
if len(cfg.InitialFields) > 0 {
|
||||
fs := make([]Field, 0, len(cfg.InitialFields))
|
||||
keys := make([]string, 0, len(cfg.InitialFields))
|
||||
for k := range cfg.InitialFields {
|
||||
keys = append(keys, k)
|
||||
}
|
||||
sort.Strings(keys)
|
||||
for _, k := range keys {
|
||||
fs = append(fs, Any(k, cfg.InitialFields[k]))
|
||||
}
|
||||
opts = append(opts, Fields(fs...))
|
||||
}
|
||||
|
||||
return opts
|
||||
}
|
||||
|
||||
func (cfg Config) openSinks() (zapcore.WriteSyncer, zapcore.WriteSyncer, error) {
|
||||
sink, closeOut, err := Open(cfg.OutputPaths...)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
errSink, _, err := Open(cfg.ErrorOutputPaths...)
|
||||
if err != nil {
|
||||
closeOut()
|
||||
return nil, nil, err
|
||||
}
|
||||
return sink, errSink, nil
|
||||
}
|
||||
|
||||
func (cfg Config) buildEncoder() (zapcore.Encoder, error) {
|
||||
return newEncoder(cfg.Encoding, cfg.EncoderConfig)
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue