Merge pull request #23194 from hongchaodeng/dep
Auto commit by PR queue bot
This commit is contained in:
134
Godeps/Godeps.json
generated
134
Godeps/Godeps.json
generated
@@ -144,125 +144,160 @@
|
||||
"Comment": "v0.1-62-g8d75e11",
|
||||
"Rev": "8d75e11374a1928608c906fe745b538483e7aeb2"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/auth",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/client",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/compactor",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/discovery",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/error",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/etcdserver",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/lease",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/adt",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/contention",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/crc",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/fileutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/httputil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/idutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/ioutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/logutil",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/netutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/pathutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/pbutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/runtime",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/timeutil",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/schedule",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/testutil",
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/transport",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/types",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/pkg/wait",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/raft",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/rafthttp",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/snap",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/storage",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/store",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/version",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/etcd/wal",
|
||||
"Comment": "v2.2.5",
|
||||
"Rev": "bc9ddf260115d2680191c46977ae72b837785472"
|
||||
"Comment": "v2.3.0",
|
||||
"Rev": "5e6eb7e19d6385adfabb1f1caea03e732f9348ad"
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/coreos/go-etcd/etcd",
|
||||
@@ -696,6 +731,7 @@
|
||||
},
|
||||
{
|
||||
"ImportPath": "github.com/hawkular/hawkular-client-go/metrics",
|
||||
"Comment": "v0.5.1-1-g1d46ce7",
|
||||
"Rev": "1d46ce7e1eca635f372357a8ccbf1fa7cc28b7d2"
|
||||
},
|
||||
{
|
||||
|
||||
1479
Godeps/LICENSES
generated
1479
Godeps/LICENSES
generated
File diff suppressed because it is too large
Load Diff
66
Godeps/_workspace/src/github.com/coreos/etcd/auth/store.go
generated
vendored
Normal file
66
Godeps/_workspace/src/github.com/coreos/etcd/auth/store.go
generated
vendored
Normal file
@@ -0,0 +1,66 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// 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 (
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
type backendGetter interface {
|
||||
Backend() backend.Backend
|
||||
}
|
||||
|
||||
var (
|
||||
enableFlagKey = []byte("authEnabled")
|
||||
authBucketName = []byte("auth")
|
||||
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "auth")
|
||||
)
|
||||
|
||||
type AuthStore interface {
|
||||
// AuthEnable() turns on the authentication feature
|
||||
AuthEnable()
|
||||
}
|
||||
|
||||
type authStore struct {
|
||||
bgetter backendGetter
|
||||
}
|
||||
|
||||
func (as *authStore) AuthEnable() {
|
||||
value := []byte{1}
|
||||
|
||||
b := as.bgetter.Backend()
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafePut(authBucketName, enableFlagKey, value)
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
plog.Noticef("Authentication enabled")
|
||||
}
|
||||
|
||||
func NewAuthStore(bgetter backendGetter) *authStore {
|
||||
b := bgetter.Backend()
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket(authBucketName)
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
return &authStore{
|
||||
bgetter: bgetter,
|
||||
}
|
||||
}
|
||||
20
Godeps/_workspace/src/github.com/coreos/etcd/client/README.md
generated
vendored
20
Godeps/_workspace/src/github.com/coreos/etcd/client/README.md
generated
vendored
@@ -35,9 +35,25 @@ func main() {
|
||||
log.Fatal(err)
|
||||
}
|
||||
kapi := client.NewKeysAPI(c)
|
||||
resp, err := kapi.Set(context.Background(), "foo", "bar", nil)
|
||||
// set "/foo" key with "bar" value
|
||||
log.Print("Setting '/foo' key with 'bar' value")
|
||||
resp, err := kapi.Set(context.Background(), "/foo", "bar", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Set is done. Metadata is %q\n", resp)
|
||||
}
|
||||
// get "/foo" key's value
|
||||
log.Print("Getting '/foo' key value")
|
||||
resp, err = kapi.Get(context.Background(), "/foo", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Get is done. Metadata is %q\n", resp)
|
||||
// print value
|
||||
log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value)
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -61,7 +77,7 @@ If the response gets from the cluster is invalid, a plain string error will be r
|
||||
Here is the example code to handle client errors:
|
||||
|
||||
```go
|
||||
cfg := client.Config{Endpoints: []string{"http://etcd1:2379,http://etcd2:2379,http://etcd3:2379"}}
|
||||
cfg := client.Config{Endpoints: []string{"http://etcd1:2379","http://etcd2:2379","http://etcd3:2379"}}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
|
||||
34
Godeps/_workspace/src/github.com/coreos/etcd/client/auth_role.go
generated
vendored
34
Godeps/_workspace/src/github.com/coreos/etcd/client/auth_role.go
generated
vendored
@@ -56,22 +56,22 @@ func NewAuthRoleAPI(c Client) AuthRoleAPI {
|
||||
}
|
||||
|
||||
type AuthRoleAPI interface {
|
||||
// Add a role.
|
||||
// AddRole adds a role.
|
||||
AddRole(ctx context.Context, role string) error
|
||||
|
||||
// Remove a role.
|
||||
// RemoveRole removes a role.
|
||||
RemoveRole(ctx context.Context, role string) error
|
||||
|
||||
// Get role details.
|
||||
// GetRole retrieves role details.
|
||||
GetRole(ctx context.Context, role string) (*Role, error)
|
||||
|
||||
// Grant a role some permission prefixes for the KV store.
|
||||
// GrantRoleKV grants a role some permission prefixes for the KV store.
|
||||
GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// Revoke some some permission prefixes for a role on the KV store.
|
||||
// RevokeRoleKV revokes some permission prefixes for a role on the KV store.
|
||||
RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// List roles.
|
||||
// ListRoles lists roles.
|
||||
ListRoles(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
@@ -115,17 +115,20 @@ func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var userList struct {
|
||||
Roles []string `json:"roles"`
|
||||
var roleList struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
err = json.Unmarshal(body, &userList)
|
||||
if err != nil {
|
||||
if err = json.Unmarshal(body, &roleList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userList.Roles, nil
|
||||
ret := make([]string, 0, len(roleList.Roles))
|
||||
for _, r := range roleList.Roles {
|
||||
ret = append(ret, r.Role)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error {
|
||||
@@ -218,17 +221,16 @@ func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var role Role
|
||||
err = json.Unmarshal(body, &role)
|
||||
if err != nil {
|
||||
if err = json.Unmarshal(body, &role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
|
||||
65
Godeps/_workspace/src/github.com/coreos/etcd/client/auth_user.go
generated
vendored
65
Godeps/_workspace/src/github.com/coreos/etcd/client/auth_user.go
generated
vendored
@@ -36,6 +36,15 @@ type User struct {
|
||||
Revoke []string `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type UserRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
type userName struct {
|
||||
User string `json:"user"`
|
||||
}
|
||||
|
||||
func v2AuthURL(ep url.URL, action string, name string) *url.URL {
|
||||
if name != "" {
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
|
||||
@@ -78,9 +87,9 @@ func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -117,25 +126,25 @@ func NewAuthUserAPI(c Client) AuthUserAPI {
|
||||
}
|
||||
|
||||
type AuthUserAPI interface {
|
||||
// Add a user.
|
||||
// AddUser adds a user.
|
||||
AddUser(ctx context.Context, username string, password string) error
|
||||
|
||||
// Remove a user.
|
||||
// RemoveUser removes a user.
|
||||
RemoveUser(ctx context.Context, username string) error
|
||||
|
||||
// Get user details.
|
||||
// GetUser retrieves user details.
|
||||
GetUser(ctx context.Context, username string) (*User, error)
|
||||
|
||||
// Grant a user some permission roles.
|
||||
// GrantUser grants a user some permission roles.
|
||||
GrantUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// Revoke some permission roles from a user.
|
||||
// RevokeUser revokes some permission roles from a user.
|
||||
RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// Change the user's password.
|
||||
// ChangePassword changes the user's password.
|
||||
ChangePassword(ctx context.Context, username string, password string) (*User, error)
|
||||
|
||||
// List users.
|
||||
// ListUsers lists the users.
|
||||
ListUsers(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
@@ -179,22 +188,28 @@ func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
|
||||
var userList struct {
|
||||
Users []string `json:"users"`
|
||||
Users []User `json:"users"`
|
||||
}
|
||||
err = json.Unmarshal(body, &userList)
|
||||
if err != nil {
|
||||
|
||||
if err = json.Unmarshal(body, &userList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return userList.Users, nil
|
||||
|
||||
ret := make([]string, 0, len(userList.Users))
|
||||
for _, u := range userList.Users {
|
||||
ret = append(ret, u.User)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
|
||||
@@ -221,9 +236,9 @@ func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAct
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -280,18 +295,24 @@ func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var user User
|
||||
err = json.Unmarshal(body, &user)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err = json.Unmarshal(body, &user); err != nil {
|
||||
var userR UserRoles
|
||||
if urerr := json.Unmarshal(body, &userR); urerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.User = userR.User
|
||||
for _, r := range userR.Roles {
|
||||
user.Roles = append(user.Roles, r.Role)
|
||||
}
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
|
||||
69
Godeps/_workspace/src/github.com/coreos/etcd/client/client.go
generated
vendored
69
Godeps/_workspace/src/github.com/coreos/etcd/client/client.go
generated
vendored
@@ -35,6 +35,7 @@ var (
|
||||
ErrNoEndpoints = errors.New("client: no endpoints available")
|
||||
ErrTooManyRedirects = errors.New("client: too many redirects")
|
||||
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
|
||||
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
|
||||
errTooManyRedirectChecks = errors.New("client: too many redirect checks")
|
||||
)
|
||||
|
||||
@@ -49,6 +50,29 @@ var DefaultTransport CancelableTransport = &http.Transport{
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type EndpointSelectionMode int
|
||||
|
||||
const (
|
||||
// EndpointSelectionRandom is the default value of the 'SelectionMode'.
|
||||
// As the name implies, the client object will pick a node from the members
|
||||
// of the cluster in a random fashion. If the cluster has three members, A, B,
|
||||
// and C, the client picks any node from its three members as its request
|
||||
// destination.
|
||||
EndpointSelectionRandom EndpointSelectionMode = iota
|
||||
|
||||
// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader',
|
||||
// requests are sent directly to the cluster leader. This reduces
|
||||
// forwarding roundtrips compared to making requests to etcd followers
|
||||
// who then forward them to the cluster leader. In the event of a leader
|
||||
// failure, however, clients configured this way cannot prioritize among
|
||||
// the remaining etcd followers. Therefore, when a client sets 'SelectionMode'
|
||||
// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to
|
||||
// maintain its knowledge of current cluster state.
|
||||
//
|
||||
// This mode should be used with Client.AutoSync().
|
||||
EndpointSelectionPrioritizeLeader
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Endpoints defines a set of URLs (schemes, hosts and ports only)
|
||||
// that can be used to communicate with a logical etcd cluster. For
|
||||
@@ -74,7 +98,7 @@ type Config struct {
|
||||
// CheckRedirect specifies the policy for handling HTTP redirects.
|
||||
// If CheckRedirect is not nil, the Client calls it before
|
||||
// following an HTTP redirect. The sole argument is the number of
|
||||
// requests that have alrady been made. If CheckRedirect returns
|
||||
// requests that have already been made. If CheckRedirect returns
|
||||
// an error, Client.Do will not make any further requests and return
|
||||
// the error back it to the caller.
|
||||
//
|
||||
@@ -107,6 +131,10 @@ type Config struct {
|
||||
//
|
||||
// A HeaderTimeoutPerRequest of zero means no timeout.
|
||||
HeaderTimeoutPerRequest time.Duration
|
||||
|
||||
// SelectionMode is an EndpointSelectionMode enum that specifies the
|
||||
// policy for choosing the etcd cluster node to which requests are sent.
|
||||
SelectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (cfg *Config) transport() CancelableTransport {
|
||||
@@ -177,6 +205,7 @@ func New(cfg Config) (Client, error) {
|
||||
c := &httpClusterClient{
|
||||
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
|
||||
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
||||
selectionMode: cfg.SelectionMode,
|
||||
}
|
||||
if cfg.Username != "" {
|
||||
c.credentials = &credentials{
|
||||
@@ -224,7 +253,18 @@ type httpClusterClient struct {
|
||||
pinned int
|
||||
credentials *credentials
|
||||
sync.RWMutex
|
||||
rand *rand.Rand
|
||||
rand *rand.Rand
|
||||
selectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) getLeaderEndpoint() (string, error) {
|
||||
mAPI := NewMembersAPI(c)
|
||||
leader, err := mAPI.Leader(context.Background())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs?
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
||||
@@ -241,9 +281,28 @@ func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
||||
neps[i] = *u
|
||||
}
|
||||
|
||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
||||
// TODO: pin old endpoint if possible, and rebalance when new endpoint appears
|
||||
c.pinned = 0
|
||||
switch c.selectionMode {
|
||||
case EndpointSelectionRandom:
|
||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
||||
c.pinned = 0
|
||||
case EndpointSelectionPrioritizeLeader:
|
||||
c.endpoints = neps
|
||||
lep, err := c.getLeaderEndpoint()
|
||||
if err != nil {
|
||||
return ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
for i := range c.endpoints {
|
||||
if c.endpoints[i].String() == lep {
|
||||
c.pinned = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// If endpoints doesn't have the lu, just keep c.pinned = 0.
|
||||
// Forwarding between follower and leader would be required but it works.
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("invalid endpoint selection mode: %d", c.selectionMode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/client/discover.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/client/discover.go
generated
vendored
@@ -16,6 +16,6 @@ package client
|
||||
|
||||
// Discoverer is an interface that wraps the Discover method.
|
||||
type Discoverer interface {
|
||||
// Dicover looks up the etcd servers for the domain.
|
||||
// Discover looks up the etcd servers for the domain.
|
||||
Discover(domain string) ([]string, error)
|
||||
}
|
||||
|
||||
16
Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go
generated
vendored
16
Godeps/_workspace/src/github.com/coreos/etcd/client/keys.go
generated
vendored
@@ -106,7 +106,7 @@ type KeysAPI interface {
|
||||
|
||||
// Set assigns a new value to a Node identified by a given key. The caller
|
||||
// may define a set of conditions in the SetOptions. If SetOptions.Dir=true
|
||||
// than value is ignored.
|
||||
// then value is ignored.
|
||||
Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
|
||||
|
||||
// Delete removes a Node identified by the given key, optionally destroying
|
||||
@@ -184,6 +184,11 @@ type SetOptions struct {
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
|
||||
// Refresh set to true means a TTL value can be updated
|
||||
// without firing a watch or changing the node value. A
|
||||
// value must not be provided when refreshing a key.
|
||||
Refresh bool
|
||||
|
||||
// Dir specifies whether or not this Node should be created as a directory.
|
||||
Dir bool
|
||||
}
|
||||
@@ -234,7 +239,7 @@ type DeleteOptions struct {
|
||||
|
||||
type Watcher interface {
|
||||
// Next blocks until an etcd event occurs, then returns a Response
|
||||
// represeting that event. The behavior of Next depends on the
|
||||
// representing that event. The behavior of Next depends on the
|
||||
// WatcherOptions used to construct the Watcher. Next is designed to
|
||||
// be called repeatedly, each time blocking until a subsequent event
|
||||
// is available.
|
||||
@@ -306,6 +311,7 @@ func (n *Node) TTLDuration() time.Duration {
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
|
||||
func (ns Nodes) Len() int { return len(ns) }
|
||||
func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
|
||||
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
@@ -327,6 +333,7 @@ func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.PrevExist = opts.PrevExist
|
||||
act.TTL = opts.TTL
|
||||
act.Refresh = opts.Refresh
|
||||
act.Dir = opts.Dir
|
||||
}
|
||||
|
||||
@@ -518,6 +525,7 @@ type setAction struct {
|
||||
PrevIndex uint64
|
||||
PrevExist PrevExistType
|
||||
TTL time.Duration
|
||||
Refresh bool
|
||||
Dir bool
|
||||
}
|
||||
|
||||
@@ -549,6 +557,10 @@ func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
|
||||
if a.Refresh {
|
||||
form.Add("refresh", "true")
|
||||
}
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
|
||||
32
Godeps/_workspace/src/github.com/coreos/etcd/client/members.go
generated
vendored
32
Godeps/_workspace/src/github.com/coreos/etcd/client/members.go
generated
vendored
@@ -29,6 +29,7 @@ import (
|
||||
|
||||
var (
|
||||
defaultV2MembersPrefix = "/v2/members"
|
||||
defaultLeaderSuffix = "/leader"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
@@ -105,6 +106,9 @@ type MembersAPI interface {
|
||||
|
||||
// Update instructs etcd to update an existing Member in the cluster.
|
||||
Update(ctx context.Context, mID string, peerURLs []string) error
|
||||
|
||||
// Leader gets current leader of the cluster
|
||||
Leader(ctx context.Context) (*Member, error)
|
||||
}
|
||||
|
||||
type httpMembersAPI struct {
|
||||
@@ -199,6 +203,25 @@ func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
|
||||
return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
|
||||
req := &membersAPIActionLeader{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var leader Member
|
||||
if err := json.Unmarshal(body, &leader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &leader, nil
|
||||
}
|
||||
|
||||
type membersAPIActionList struct{}
|
||||
|
||||
func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
|
||||
@@ -255,6 +278,15 @@ func assertStatusCode(got int, want ...int) (err error) {
|
||||
return fmt.Errorf("unexpected status code %d", got)
|
||||
}
|
||||
|
||||
type membersAPIActionLeader struct{}
|
||||
|
||||
func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, defaultLeaderSuffix)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
// v2MembersURL add the necessary path to the provided endpoint
|
||||
// to route requests to the default v2 members API.
|
||||
func v2MembersURL(ep url.URL) *url.URL {
|
||||
|
||||
6
Godeps/_workspace/src/github.com/coreos/etcd/client/srv.go
generated
vendored
6
Godeps/_workspace/src/github.com/coreos/etcd/client/srv.go
generated
vendored
@@ -27,7 +27,7 @@ var (
|
||||
|
||||
type srvDiscover struct{}
|
||||
|
||||
// NewSRVDiscover constructs a new Dicoverer that uses the stdlib to lookup SRV records.
|
||||
// NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records.
|
||||
func NewSRVDiscover() Discoverer {
|
||||
return &srvDiscover{}
|
||||
}
|
||||
@@ -50,8 +50,8 @@ func (d *srvDiscover) Discover(domain string) ([]string, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
errHTTPS := updateURLs("etcd-server-ssl", "https")
|
||||
errHTTP := updateURLs("etcd-server", "http")
|
||||
errHTTPS := updateURLs("etcd-client-ssl", "https")
|
||||
errHTTP := updateURLs("etcd-client", "http")
|
||||
|
||||
if errHTTPS != nil && errHTTP != nil {
|
||||
return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
|
||||
|
||||
23
Godeps/_workspace/src/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
23
Godeps/_workspace/src/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package client
|
||||
|
||||
// IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound.
|
||||
func IsKeyNotFound(err error) bool {
|
||||
if cErr, ok := err.(Error); ok {
|
||||
return cErr.Code == ErrorCodeKeyNotFound
|
||||
}
|
||||
return false
|
||||
}
|
||||
133
Godeps/_workspace/src/github.com/coreos/etcd/compactor/compactor.go
generated
vendored
Normal file
133
Godeps/_workspace/src/github.com/coreos/etcd/compactor/compactor.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package compactor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdserver")
|
||||
)
|
||||
|
||||
const (
|
||||
checkCompactionInterval = 5 * time.Minute
|
||||
)
|
||||
|
||||
type Compactable interface {
|
||||
Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
|
||||
}
|
||||
|
||||
type RevGetter interface {
|
||||
Rev() int64
|
||||
}
|
||||
|
||||
type Periodic struct {
|
||||
clock clockwork.Clock
|
||||
periodInHour int
|
||||
|
||||
rg RevGetter
|
||||
c Compactable
|
||||
|
||||
revs []int64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
}
|
||||
|
||||
func NewPeriodic(h int, rg RevGetter, c Compactable) *Periodic {
|
||||
return &Periodic{
|
||||
clock: clockwork.NewRealClock(),
|
||||
periodInHour: h,
|
||||
rg: rg,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Periodic) Run() {
|
||||
t.ctx, t.cancel = context.WithCancel(context.Background())
|
||||
t.revs = make([]int64, 0)
|
||||
clock := t.clock
|
||||
|
||||
go func() {
|
||||
last := clock.Now()
|
||||
for {
|
||||
t.revs = append(t.revs, t.rg.Rev())
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case <-clock.After(checkCompactionInterval):
|
||||
t.mu.Lock()
|
||||
p := t.paused
|
||||
t.mu.Unlock()
|
||||
if p {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if clock.Now().Sub(last) < time.Duration(t.periodInHour)*time.Hour {
|
||||
continue
|
||||
}
|
||||
|
||||
rev := t.getRev(t.periodInHour)
|
||||
if rev < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
plog.Noticef("Starting auto-compaction at revision %d", rev)
|
||||
_, err := t.c.Compact(t.ctx, &pb.CompactionRequest{Revision: rev})
|
||||
if err == nil || err == storage.ErrCompacted {
|
||||
t.revs = make([]int64, 0)
|
||||
last = clock.Now()
|
||||
plog.Noticef("Finished auto-compaction at revision %d", rev)
|
||||
} else {
|
||||
plog.Noticef("Failed auto-compaction at revision %d (%v)", err, rev)
|
||||
plog.Noticef("Retry after %v", checkCompactionInterval)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *Periodic) Stop() {
|
||||
t.cancel()
|
||||
}
|
||||
|
||||
func (t *Periodic) Pause() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.paused = true
|
||||
}
|
||||
|
||||
func (t *Periodic) Resume() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.paused = false
|
||||
}
|
||||
|
||||
func (t *Periodic) getRev(h int) int64 {
|
||||
i := len(t.revs) - int(time.Duration(h)*time.Hour/checkCompactionInterval)
|
||||
if i < 0 {
|
||||
return -1
|
||||
}
|
||||
return t.revs[i]
|
||||
}
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/discovery/discovery.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/discovery/discovery.go
generated
vendored
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package discovery provides an implementation of the cluster discovery that
|
||||
// is used by etcd.
|
||||
package discovery
|
||||
|
||||
import (
|
||||
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/discovery/srv.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/discovery/srv.go
generated
vendored
@@ -28,6 +28,7 @@ var (
|
||||
resolveTCPAddr = net.ResolveTCPAddr
|
||||
)
|
||||
|
||||
// SRVGetCluster gets the cluster information via DNS discovery.
|
||||
// TODO(barakmich): Currently ignores priority and weight (as they don't make as much sense for a bootstrap)
|
||||
// Also doesn't do any lookups for the token (though it could)
|
||||
// Also sees each entry as a separate instance.
|
||||
|
||||
15
Godeps/_workspace/src/github.com/coreos/etcd/error/error.go
generated
vendored
15
Godeps/_workspace/src/github.com/coreos/etcd/error/error.go
generated
vendored
@@ -12,9 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// error package describes errors in etcd project.
|
||||
// When any change happens, Documentation/errorcode.md needs to be updated
|
||||
// correspondingly.
|
||||
// error package describes errors in etcd project. When any change happens,
|
||||
// Documentation/errorcode.md needs to be updated correspondingly.
|
||||
package error
|
||||
|
||||
import (
|
||||
@@ -49,6 +48,8 @@ var errors = map[int]string{
|
||||
ecodeIndexValueMutex: "Index and value cannot both be specified",
|
||||
EcodeInvalidField: "Invalid field",
|
||||
EcodeInvalidForm: "Invalid POST form",
|
||||
EcodeRefreshValue: "Value provided on refresh",
|
||||
EcodeRefreshTTLRequired: "A TTL must be provided on refresh",
|
||||
|
||||
// raft related errors
|
||||
EcodeRaftInternal: "Raft Internal Error",
|
||||
@@ -100,6 +101,8 @@ const (
|
||||
ecodeIndexValueMutex = 208
|
||||
EcodeInvalidField = 209
|
||||
EcodeInvalidForm = 210
|
||||
EcodeRefreshValue = 211
|
||||
EcodeRefreshTTLRequired = 212
|
||||
|
||||
EcodeRaftInternal = 300
|
||||
EcodeLeaderElect = 301
|
||||
@@ -133,7 +136,7 @@ func NewError(errorCode int, cause string, index uint64) *Error {
|
||||
}
|
||||
}
|
||||
|
||||
// Only for error interface
|
||||
// Error is for the error interface
|
||||
func (e Error) Error() string {
|
||||
return e.Message + " (" + e.Cause + ")"
|
||||
}
|
||||
@@ -143,7 +146,7 @@ func (e Error) toJsonString() string {
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (e Error) statusCode() int {
|
||||
func (e Error) StatusCode() int {
|
||||
status, ok := errorStatus[e.ErrorCode]
|
||||
if !ok {
|
||||
status = http.StatusBadRequest
|
||||
@@ -154,6 +157,6 @@ func (e Error) statusCode() int {
|
||||
func (e Error) WriteTo(w http.ResponseWriter) {
|
||||
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(e.statusCode())
|
||||
w.WriteHeader(e.StatusCode())
|
||||
fmt.Fprintln(w, e.toJsonString())
|
||||
}
|
||||
|
||||
98
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/auth.go
generated
vendored
Normal file
98
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/auth.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type AuthServer struct {
|
||||
authenticator etcdserver.Authenticator
|
||||
}
|
||||
|
||||
func NewAuthServer(s *etcdserver.EtcdServer) *AuthServer {
|
||||
return &AuthServer{authenticator: s}
|
||||
}
|
||||
|
||||
func (as *AuthServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
|
||||
return as.authenticator.AuthEnable(ctx, r)
|
||||
}
|
||||
|
||||
func (as *AuthServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.RoleAddRequest) (*pb.RoleAddResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleDelete(ctx context.Context, r *pb.RoleDeleteRequest) (*pb.RoleDeleteResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleGet(ctx context.Context, r *pb.RoleGetRequest) (*pb.RoleGetResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleRevoke(ctx context.Context, r *pb.RoleRevokeRequest) (*pb.RoleRevokeResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleGrant(ctx context.Context, r *pb.RoleGrantRequest) (*pb.RoleGrantResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserAdd(ctx context.Context, r *pb.UserAddRequest) (*pb.UserAddResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserDelete(ctx context.Context, r *pb.UserDeleteRequest) (*pb.UserDeleteResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserGet(ctx context.Context, r *pb.UserGetRequest) (*pb.UserGetResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserGrant(ctx context.Context, r *pb.UserGrantRequest) (*pb.UserGrantResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.UserRevokeRequest) (*pb.UserRevokeResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserChangePassword(ctx context.Context, r *pb.UserChangePasswordRequest) (*pb.UserChangePasswordResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
42
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/grpc.go
generated
vendored
Normal file
42
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/grpc.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func Server(s *etcdserver.EtcdServer, tls *transport.TLSInfo) (*grpc.Server, error) {
|
||||
var opts []grpc.ServerOption
|
||||
if tls != nil {
|
||||
creds, err := credentials.NewServerTLSFromFile(tls.CertFile, tls.KeyFile)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
opts = append(opts, grpc.Creds(creds))
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer(opts...)
|
||||
pb.RegisterKVServer(grpcServer, NewKVServer(s))
|
||||
pb.RegisterWatchServer(grpcServer, NewWatchServer(s))
|
||||
pb.RegisterLeaseServer(grpcServer, NewLeaseServer(s))
|
||||
pb.RegisterClusterServer(grpcServer, NewClusterServer(s))
|
||||
pb.RegisterAuthServer(grpcServer, NewAuthServer(s))
|
||||
pb.RegisterMaintenanceServer(grpcServer, NewMaintenanceServer(s))
|
||||
return grpcServer, nil
|
||||
}
|
||||
286
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/key.go
generated
vendored
286
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/key.go
generated
vendored
@@ -12,42 +12,292 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package v3rpc implements etcd v3 RPC system based on gRPC.
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type handler struct {
|
||||
server etcdserver.V3DemoServer
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver/api", "v3rpc")
|
||||
|
||||
// Max operations per txn list. For example, Txn.Success can have at most 128 operations,
|
||||
// and Txn.Failure can have at most 128 operations.
|
||||
MaxOpsPerTxn = 128
|
||||
)
|
||||
|
||||
type kvServer struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
|
||||
kv etcdserver.RaftKV
|
||||
}
|
||||
|
||||
func New(s etcdserver.V3DemoServer) pb.EtcdServer {
|
||||
return &handler{s}
|
||||
func NewKVServer(s *etcdserver.EtcdServer) pb.KVServer {
|
||||
return &kvServer{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
kv: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *handler) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
resp := h.server.V3DemoDo(ctx, pb.InternalRaftRequest{Range: r})
|
||||
return resp.(*pb.RangeResponse), nil
|
||||
func (s *kvServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
if err := checkRangeRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Range(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *handler) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
resp := h.server.V3DemoDo(ctx, pb.InternalRaftRequest{Put: r})
|
||||
return resp.(*pb.PutResponse), nil
|
||||
func (s *kvServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
if err := checkPutRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Put(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *handler) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
resp := h.server.V3DemoDo(ctx, pb.InternalRaftRequest{DeleteRange: r})
|
||||
return resp.(*pb.DeleteRangeResponse), nil
|
||||
func (s *kvServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
if err := checkDeleteRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.DeleteRange(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *handler) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
resp := h.server.V3DemoDo(ctx, pb.InternalRaftRequest{Txn: r})
|
||||
return resp.(*pb.TxnResponse), nil
|
||||
func (s *kvServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
if err := checkTxnRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Txn(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (h *handler) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
panic("not implemented")
|
||||
func (s *kvServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
resp, err := s.kv.Compact(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (s *kvServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
|
||||
resp, err := s.kv.Hash(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
s.fillInHeader(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// fillInHeader populates pb.ResponseHeader from kvServer, except Revision.
|
||||
func (s *kvServer) fillInHeader(h *pb.ResponseHeader) {
|
||||
h.ClusterId = uint64(s.clusterID)
|
||||
h.MemberId = uint64(s.memberID)
|
||||
h.RaftTerm = s.raftTimer.Term()
|
||||
}
|
||||
|
||||
func checkRangeRequest(r *pb.RangeRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPutRequest(r *pb.PutRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDeleteRequest(r *pb.DeleteRangeRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTxnRequest(r *pb.TxnRequest) error {
|
||||
if len(r.Compare) > MaxOpsPerTxn || len(r.Success) > MaxOpsPerTxn || len(r.Failure) > MaxOpsPerTxn {
|
||||
return rpctypes.ErrTooManyOps
|
||||
}
|
||||
|
||||
for _, c := range r.Compare {
|
||||
if len(c.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range r.Success {
|
||||
if err := checkRequestUnion(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := checkRequestDupKeys(r.Success); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, u := range r.Failure {
|
||||
if err := checkRequestUnion(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := checkRequestDupKeys(r.Failure); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRequestDupKeys gives rpctypes.ErrDuplicateKey if the same key is modified twice
|
||||
func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
|
||||
// check put overlap
|
||||
keys := make(map[string]struct{})
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestPut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
preq := tv.RequestPut
|
||||
if preq == nil {
|
||||
continue
|
||||
}
|
||||
key := string(preq.Key)
|
||||
if _, ok := keys[key]; ok {
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
keys[key] = struct{}{}
|
||||
}
|
||||
|
||||
// no need to check deletes if no puts; delete overlaps are permitted
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sort keys for range checking
|
||||
sortedKeys := []string{}
|
||||
for k := range keys {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
// check put overlap with deletes
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestDeleteRange)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dreq := tv.RequestDeleteRange
|
||||
if dreq == nil {
|
||||
continue
|
||||
}
|
||||
key := string(dreq.Key)
|
||||
if dreq.RangeEnd == nil {
|
||||
if _, found := keys[key]; found {
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
} else {
|
||||
lo := sort.SearchStrings(sortedKeys, key)
|
||||
hi := sort.SearchStrings(sortedKeys, string(dreq.RangeEnd))
|
||||
if lo != hi {
|
||||
// element between lo and hi => overlap
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequestUnion(u *pb.RequestUnion) error {
|
||||
// TODO: ensure only one of the field is set.
|
||||
switch uv := u.Request.(type) {
|
||||
case *pb.RequestUnion_RequestRange:
|
||||
if uv.RequestRange != nil {
|
||||
return checkRangeRequest(uv.RequestRange)
|
||||
}
|
||||
case *pb.RequestUnion_RequestPut:
|
||||
if uv.RequestPut != nil {
|
||||
return checkPutRequest(uv.RequestPut)
|
||||
}
|
||||
case *pb.RequestUnion_RequestDeleteRange:
|
||||
if uv.RequestDeleteRange != nil {
|
||||
return checkDeleteRequest(uv.RequestDeleteRange)
|
||||
}
|
||||
default:
|
||||
// empty union
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func togRPCError(err error) error {
|
||||
switch err {
|
||||
case storage.ErrCompacted:
|
||||
return rpctypes.ErrCompacted
|
||||
case storage.ErrFutureRev:
|
||||
return rpctypes.ErrFutureRev
|
||||
case lease.ErrLeaseNotFound:
|
||||
return rpctypes.ErrLeaseNotFound
|
||||
// TODO: handle error from raft and timeout
|
||||
case etcdserver.ErrRequestTooLarge:
|
||||
return rpctypes.ErrRequestTooLarge
|
||||
default:
|
||||
return grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
76
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/lease.go
generated
vendored
Normal file
76
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/lease.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type LeaseServer struct {
|
||||
le etcdserver.Lessor
|
||||
}
|
||||
|
||||
func NewLeaseServer(le etcdserver.Lessor) pb.LeaseServer {
|
||||
return &LeaseServer{le: le}
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseCreate(ctx context.Context, cr *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
|
||||
resp, err := ls.le.LeaseCreate(ctx, cr)
|
||||
if err == lease.ErrLeaseExists {
|
||||
return nil, rpctypes.ErrLeaseExist
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
r, err := ls.le.LeaseRevoke(ctx, rr)
|
||||
if err != nil {
|
||||
return nil, rpctypes.ErrLeaseNotFound
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ttl, err := ls.le.LeaseRenew(lease.LeaseID(req.ID))
|
||||
if err == lease.ErrLeaseNotFound {
|
||||
return rpctypes.ErrLeaseNotFound
|
||||
}
|
||||
|
||||
if err != nil && err != lease.ErrLeaseNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := &pb.LeaseKeepAliveResponse{ID: req.ID, TTL: ttl}
|
||||
err = stream.Send(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
45
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/maintenance.go
generated
vendored
Normal file
45
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/maintenance.go
generated
vendored
Normal file
@@ -0,0 +1,45 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type BackendGetter interface {
|
||||
Backend() backend.Backend
|
||||
}
|
||||
|
||||
type maintenanceServer struct {
|
||||
bg BackendGetter
|
||||
}
|
||||
|
||||
func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
|
||||
return &maintenanceServer{bg: s}
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
|
||||
plog.Noticef("starting to defragment the storage backend...")
|
||||
err := ms.bg.Backend().Defrag()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to deframent the storage backend (%v)", err)
|
||||
return nil, err
|
||||
}
|
||||
plog.Noticef("finished defragmenting the storage backend")
|
||||
return &pb.DefragmentResponse{}, nil
|
||||
}
|
||||
118
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/member.go
generated
vendored
Normal file
118
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/member.go
generated
vendored
Normal file
@@ -0,0 +1,118 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type ClusterServer struct {
|
||||
cluster etcdserver.Cluster
|
||||
server etcdserver.Server
|
||||
raftTimer etcdserver.RaftTimer
|
||||
}
|
||||
|
||||
func NewClusterServer(s *etcdserver.EtcdServer) *ClusterServer {
|
||||
return &ClusterServer{
|
||||
cluster: s.Cluster(),
|
||||
server: s,
|
||||
raftTimer: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
|
||||
urls, err := types.NewURLs(r.PeerURLs)
|
||||
if err != nil {
|
||||
return nil, rpctypes.ErrMemberBadURLs
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
m := etcdserver.NewMember("", urls, "", &now)
|
||||
err = cs.server.AddMember(ctx, *m)
|
||||
switch {
|
||||
case err == etcdserver.ErrIDExists:
|
||||
return nil, rpctypes.ErrMemberExist
|
||||
case err == etcdserver.ErrPeerURLexists:
|
||||
return nil, rpctypes.ErrPeerURLExist
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberAddResponse{
|
||||
Header: cs.header(),
|
||||
Member: &pb.Member{ID: uint64(m.ID), IsLeader: m.ID == cs.server.Leader(), PeerURLs: m.PeerURLs},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest) (*pb.MemberRemoveResponse, error) {
|
||||
err := cs.server.RemoveMember(ctx, r.ID)
|
||||
switch {
|
||||
case err == etcdserver.ErrIDRemoved:
|
||||
fallthrough
|
||||
case err == etcdserver.ErrIDNotFound:
|
||||
return nil, rpctypes.ErrMemberNotFound
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberRemoveResponse{Header: cs.header()}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest) (*pb.MemberUpdateResponse, error) {
|
||||
m := etcdserver.Member{
|
||||
ID: types.ID(r.ID),
|
||||
RaftAttributes: etcdserver.RaftAttributes{PeerURLs: r.PeerURLs},
|
||||
}
|
||||
err := cs.server.UpdateMember(ctx, m)
|
||||
switch {
|
||||
case err == etcdserver.ErrPeerURLexists:
|
||||
return nil, rpctypes.ErrPeerURLExist
|
||||
case err == etcdserver.ErrIDNotFound:
|
||||
return nil, rpctypes.ErrMemberNotFound
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberUpdateResponse{Header: cs.header()}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest) (*pb.MemberListResponse, error) {
|
||||
membs := cs.cluster.Members()
|
||||
|
||||
protoMembs := make([]*pb.Member, len(membs))
|
||||
for i := range membs {
|
||||
protoMembs[i] = &pb.Member{
|
||||
Name: membs[i].Name,
|
||||
ID: uint64(membs[i].ID),
|
||||
IsLeader: membs[i].ID == cs.server.Leader(),
|
||||
PeerURLs: membs[i].PeerURLs,
|
||||
ClientURLs: membs[i].ClientURLs,
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.MemberListResponse{Header: cs.header(), Members: protoMembs}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) header() *pb.ResponseHeader {
|
||||
return &pb.ResponseHeader{ClusterId: uint64(cs.cluster.ID()), MemberId: uint64(cs.server.ID()), RaftTerm: cs.raftTimer.Term()}
|
||||
}
|
||||
38
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/error.go
generated
vendored
Normal file
38
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/error.go
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package rpctypes
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided")
|
||||
ErrTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request")
|
||||
ErrDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request")
|
||||
ErrCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision has been compacted")
|
||||
ErrFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision is a future revision")
|
||||
|
||||
ErrLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found")
|
||||
ErrLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists")
|
||||
|
||||
ErrMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist")
|
||||
ErrPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists")
|
||||
ErrMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid")
|
||||
ErrMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found")
|
||||
|
||||
ErrRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large")
|
||||
)
|
||||
265
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/watch.go
generated
vendored
Normal file
265
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/api/v3rpc/watch.go
generated
vendored
Normal file
@@ -0,0 +1,265 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
)
|
||||
|
||||
type watchServer struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
watchable storage.Watchable
|
||||
}
|
||||
|
||||
func NewWatchServer(s *etcdserver.EtcdServer) pb.WatchServer {
|
||||
return &watchServer{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
watchable: s.Watchable(),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// expose for testing purpose. External test can change this to a
|
||||
// small value to finish fast.
|
||||
ProgressReportInterval = 10 * time.Minute
|
||||
)
|
||||
|
||||
const (
|
||||
// We send ctrl response inside the read loop. We do not want
|
||||
// send to block read, but we still want ctrl response we sent to
|
||||
// be serialized. Thus we use a buffered chan to solve the problem.
|
||||
// A small buffer should be OK for most cases, since we expect the
|
||||
// ctrl requests are infrequent.
|
||||
ctrlStreamBufLen = 16
|
||||
)
|
||||
|
||||
// serverWatchStream is an etcd server side stream. It receives requests
|
||||
// from client side gRPC stream. It receives watch events from storage.WatchStream,
|
||||
// and creates responses that forwarded to gRPC stream.
|
||||
// It also forwards control message like watch created and canceled.
|
||||
type serverWatchStream struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
|
||||
gRPCStream pb.Watch_WatchServer
|
||||
watchStream storage.WatchStream
|
||||
ctrlStream chan *pb.WatchResponse
|
||||
|
||||
// progress tracks the watchID that stream might need to send
|
||||
// progress to.
|
||||
progress map[storage.WatchID]bool
|
||||
|
||||
// closec indicates the stream is closed.
|
||||
closec chan struct{}
|
||||
}
|
||||
|
||||
func (ws *watchServer) Watch(stream pb.Watch_WatchServer) error {
|
||||
sws := serverWatchStream{
|
||||
clusterID: ws.clusterID,
|
||||
memberID: ws.memberID,
|
||||
raftTimer: ws.raftTimer,
|
||||
gRPCStream: stream,
|
||||
watchStream: ws.watchable.NewWatchStream(),
|
||||
// chan for sending control response like watcher created and canceled.
|
||||
ctrlStream: make(chan *pb.WatchResponse, ctrlStreamBufLen),
|
||||
progress: make(map[storage.WatchID]bool),
|
||||
closec: make(chan struct{}),
|
||||
}
|
||||
defer sws.close()
|
||||
|
||||
go sws.sendLoop()
|
||||
return sws.recvLoop()
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) recvLoop() error {
|
||||
for {
|
||||
req, err := sws.gRPCStream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch uv := req.RequestUnion.(type) {
|
||||
case *pb.WatchRequest_CreateRequest:
|
||||
if uv.CreateRequest == nil {
|
||||
break
|
||||
}
|
||||
|
||||
creq := uv.CreateRequest
|
||||
if len(creq.Key) == 0 {
|
||||
// \x00 is the smallest key
|
||||
creq.Key = []byte{0}
|
||||
}
|
||||
if len(creq.RangeEnd) == 1 && creq.RangeEnd[0] == 0 {
|
||||
// support >= key queries
|
||||
creq.RangeEnd = []byte{}
|
||||
}
|
||||
wsrev := sws.watchStream.Rev()
|
||||
rev := creq.StartRevision
|
||||
if rev == 0 {
|
||||
rev = wsrev + 1
|
||||
}
|
||||
id := sws.watchStream.Watch(creq.Key, creq.RangeEnd, rev)
|
||||
if id != -1 && creq.ProgressNotify {
|
||||
sws.progress[id] = true
|
||||
}
|
||||
sws.ctrlStream <- &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(wsrev),
|
||||
WatchId: int64(id),
|
||||
Created: true,
|
||||
Canceled: id == -1,
|
||||
}
|
||||
case *pb.WatchRequest_CancelRequest:
|
||||
if uv.CancelRequest != nil {
|
||||
id := uv.CancelRequest.WatchId
|
||||
err := sws.watchStream.Cancel(storage.WatchID(id))
|
||||
if err == nil {
|
||||
sws.ctrlStream <- &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(sws.watchStream.Rev()),
|
||||
WatchId: id,
|
||||
Canceled: true,
|
||||
}
|
||||
delete(sws.progress, storage.WatchID(id))
|
||||
}
|
||||
}
|
||||
// TODO: do we need to return error back to client?
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) sendLoop() {
|
||||
// watch ids that are currently active
|
||||
ids := make(map[storage.WatchID]struct{})
|
||||
// watch responses pending on a watch id creation message
|
||||
pending := make(map[storage.WatchID][]*pb.WatchResponse)
|
||||
|
||||
progressTicker := time.NewTicker(ProgressReportInterval)
|
||||
defer progressTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case wresp, ok := <-sws.watchStream.Chan():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: evs is []storagepb.Event type
|
||||
// either return []*storagepb.Event from storage package
|
||||
// or define protocol buffer with []storagepb.Event.
|
||||
evs := wresp.Events
|
||||
events := make([]*storagepb.Event, len(evs))
|
||||
for i := range evs {
|
||||
events[i] = &evs[i]
|
||||
}
|
||||
|
||||
wr := &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(wresp.Revision),
|
||||
WatchId: int64(wresp.WatchID),
|
||||
Events: events,
|
||||
CompactRevision: wresp.CompactRevision,
|
||||
}
|
||||
|
||||
if _, hasId := ids[wresp.WatchID]; !hasId {
|
||||
// buffer if id not yet announced
|
||||
wrs := append(pending[wresp.WatchID], wr)
|
||||
pending[wresp.WatchID] = wrs
|
||||
continue
|
||||
}
|
||||
|
||||
storage.ReportEventReceived()
|
||||
if err := sws.gRPCStream.Send(wr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if _, ok := sws.progress[wresp.WatchID]; ok {
|
||||
sws.progress[wresp.WatchID] = false
|
||||
}
|
||||
|
||||
case c, ok := <-sws.ctrlStream:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sws.gRPCStream.Send(c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// track id creation
|
||||
wid := storage.WatchID(c.WatchId)
|
||||
if c.Canceled {
|
||||
delete(ids, wid)
|
||||
continue
|
||||
}
|
||||
if c.Created {
|
||||
// flush buffered events
|
||||
ids[wid] = struct{}{}
|
||||
for _, v := range pending[wid] {
|
||||
storage.ReportEventReceived()
|
||||
if err := sws.gRPCStream.Send(v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
delete(pending, wid)
|
||||
}
|
||||
case <-progressTicker.C:
|
||||
for id, ok := range sws.progress {
|
||||
if ok {
|
||||
sws.watchStream.RequestProgress(id)
|
||||
}
|
||||
sws.progress[id] = true
|
||||
}
|
||||
case <-sws.closec:
|
||||
// drain the chan to clean up pending events
|
||||
for range sws.watchStream.Chan() {
|
||||
storage.ReportEventReceived()
|
||||
}
|
||||
for _, wrs := range pending {
|
||||
for range wrs {
|
||||
storage.ReportEventReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) close() {
|
||||
sws.watchStream.Close()
|
||||
close(sws.closec)
|
||||
close(sws.ctrlStream)
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) newResponseHeader(rev int64) *pb.ResponseHeader {
|
||||
return &pb.ResponseHeader{
|
||||
ClusterId: uint64(sws.clusterID),
|
||||
MemberId: uint64(sws.memberID),
|
||||
Revision: rev,
|
||||
RaftTerm: sws.raftTimer.Term(),
|
||||
}
|
||||
}
|
||||
79
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/auth/auth.go
generated
vendored
79
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/auth/auth.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package auth implements etcd authentication.
|
||||
package auth
|
||||
|
||||
import (
|
||||
@@ -22,7 +23,6 @@ import (
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
etcderr "github.com/coreos/etcd/error"
|
||||
@@ -88,6 +88,12 @@ type Store interface {
|
||||
AuthEnabled() bool
|
||||
EnableAuth() error
|
||||
DisableAuth() error
|
||||
PasswordStore
|
||||
}
|
||||
|
||||
type PasswordStore interface {
|
||||
CheckPassword(user User, password string) bool
|
||||
HashPassword(password string) (string, error)
|
||||
}
|
||||
|
||||
type store struct {
|
||||
@@ -95,7 +101,7 @@ type store struct {
|
||||
timeout time.Duration
|
||||
ensuredOnce bool
|
||||
|
||||
mu sync.Mutex
|
||||
PasswordStore
|
||||
}
|
||||
|
||||
type User struct {
|
||||
@@ -140,12 +146,26 @@ func authErr(hs int, s string, v ...interface{}) Error {
|
||||
|
||||
func NewStore(server doer, timeout time.Duration) Store {
|
||||
s := &store{
|
||||
server: server,
|
||||
timeout: timeout,
|
||||
server: server,
|
||||
timeout: timeout,
|
||||
PasswordStore: passwordStore{},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// passwordStore implements PasswordStore using bcrypt to hash user passwords
|
||||
type passwordStore struct{}
|
||||
|
||||
func (_ passwordStore) CheckPassword(user User, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (_ passwordStore) HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(hash), err
|
||||
}
|
||||
|
||||
func (s *store) AllUsers() ([]string, error) {
|
||||
resp, err := s.requestResource("/users/", false)
|
||||
if err != nil {
|
||||
@@ -216,11 +236,11 @@ func (s *store) createUserInternal(user User) (User, error) {
|
||||
if user.Password == "" {
|
||||
return user, authErr(http.StatusBadRequest, "Cannot create user %s with an empty password", user.User)
|
||||
}
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(user.Password), bcrypt.DefaultCost)
|
||||
hash, err := s.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
user.Password = string(hash)
|
||||
user.Password = hash
|
||||
|
||||
_, err = s.createResource("/users/"+user.User, user)
|
||||
if err != nil {
|
||||
@@ -260,6 +280,13 @@ func (s *store) UpdateUser(user User) (User, error) {
|
||||
}
|
||||
return old, err
|
||||
}
|
||||
|
||||
hash, err := s.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
return old, err
|
||||
}
|
||||
user.Password = hash
|
||||
|
||||
newUser, err := old.merge(user)
|
||||
if err != nil {
|
||||
return old, err
|
||||
@@ -379,9 +406,6 @@ func (s *store) UpdateRole(role Role) (Role, error) {
|
||||
}
|
||||
|
||||
func (s *store) AuthEnabled() bool {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
return s.detectAuth()
|
||||
}
|
||||
|
||||
@@ -390,29 +414,24 @@ func (s *store) EnableAuth() error {
|
||||
return authErr(http.StatusConflict, "already enabled")
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
_, err := s.GetUser("root")
|
||||
if err != nil {
|
||||
if _, err := s.GetUser("root"); err != nil {
|
||||
return authErr(http.StatusConflict, "No root user available, please create one")
|
||||
}
|
||||
_, err = s.GetRole(GuestRoleName)
|
||||
if err != nil {
|
||||
if _, err := s.GetRole(GuestRoleName); err != nil {
|
||||
plog.Printf("no guest role access found, creating default")
|
||||
err := s.CreateRole(guestRole)
|
||||
if err != nil {
|
||||
if err := s.CreateRole(guestRole); err != nil {
|
||||
plog.Errorf("error creating guest role. aborting auth enable.")
|
||||
return err
|
||||
}
|
||||
}
|
||||
err = s.enableAuth()
|
||||
if err == nil {
|
||||
plog.Noticef("auth: enabled auth")
|
||||
} else {
|
||||
|
||||
if err := s.enableAuth(); err != nil {
|
||||
plog.Errorf("error enabling auth (%v)", err)
|
||||
return err
|
||||
}
|
||||
return err
|
||||
|
||||
plog.Noticef("auth: enabled auth")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) DisableAuth() error {
|
||||
@@ -420,9 +439,6 @@ func (s *store) DisableAuth() error {
|
||||
return authErr(http.StatusConflict, "already disabled")
|
||||
}
|
||||
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
err := s.disableAuth()
|
||||
if err == nil {
|
||||
plog.Noticef("auth: disabled auth")
|
||||
@@ -443,11 +459,7 @@ func (u User) merge(n User) (User, error) {
|
||||
}
|
||||
out.User = u.User
|
||||
if n.Password != "" {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(n.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
return User{}, err
|
||||
}
|
||||
out.Password = string(hash)
|
||||
out.Password = n.Password
|
||||
} else {
|
||||
out.Password = u.Password
|
||||
}
|
||||
@@ -471,11 +483,6 @@ func (u User) merge(n User) (User, error) {
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (u User) CheckPassword(password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(u.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
// merge for a role works the same as User above -- atomic Role application to
|
||||
// each of the substructures.
|
||||
func (r Role) merge(n Role) (Role, error) {
|
||||
|
||||
74
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/cluster.go
generated
vendored
74
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/cluster.go
generated
vendored
@@ -53,7 +53,7 @@ type Cluster interface {
|
||||
// IsIDRemoved checks whether the given ID has been removed from this
|
||||
// cluster at some point in the past
|
||||
IsIDRemoved(id types.ID) bool
|
||||
// ClusterVersion is the cluster-wide minimum major.minor version.
|
||||
// Version is the cluster-wide minimum major.minor version.
|
||||
Version() *semver.Version
|
||||
}
|
||||
|
||||
@@ -226,6 +226,13 @@ func (c *cluster) Recover() {
|
||||
c.members, c.removed = membersFromStore(c.store)
|
||||
c.version = clusterVersionFromStore(c.store)
|
||||
MustDetectDowngrade(c.version)
|
||||
|
||||
for _, m := range c.members {
|
||||
plog.Infof("added member %s %v to cluster %s from store", m.ID, m.PeerURLs, c.id)
|
||||
}
|
||||
if c.version != nil {
|
||||
plog.Infof("set the cluster version to %v from store", version.Cluster(c.version.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfigurationChange takes a proposed ConfChange and
|
||||
@@ -299,7 +306,7 @@ func (c *cluster) AddMember(m *Member) {
|
||||
plog.Panicf("marshal raftAttributes should never fail: %v", err)
|
||||
}
|
||||
p := path.Join(memberStoreKey(m.ID), raftAttributesSuffix)
|
||||
if _, err := c.store.Create(p, false, string(b), false, store.Permanent); err != nil {
|
||||
if _, err := c.store.Create(p, false, string(b), false, store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("create raftAttributes should never fail: %v", err)
|
||||
}
|
||||
c.members[m.ID] = m
|
||||
@@ -314,26 +321,27 @@ func (c *cluster) RemoveMember(id types.ID) {
|
||||
plog.Panicf("delete member should never fail: %v", err)
|
||||
}
|
||||
delete(c.members, id)
|
||||
if _, err := c.store.Create(removedMemberStoreKey(id), false, "", false, store.Permanent); err != nil {
|
||||
if _, err := c.store.Create(removedMemberStoreKey(id), false, "", false, store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("create removedMember should never fail: %v", err)
|
||||
}
|
||||
c.removed[id] = true
|
||||
}
|
||||
|
||||
func (c *cluster) UpdateAttributes(id types.ID, attr Attributes) {
|
||||
func (c *cluster) UpdateAttributes(id types.ID, attr Attributes) bool {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if m, ok := c.members[id]; ok {
|
||||
m.Attributes = attr
|
||||
return
|
||||
return true
|
||||
}
|
||||
_, ok := c.removed[id]
|
||||
if ok {
|
||||
plog.Debugf("skipped updating attributes of removed member %s", id)
|
||||
plog.Warningf("skipped updating attributes of removed member %s", id)
|
||||
} else {
|
||||
plog.Panicf("error updating attributes of unknown member %s", id)
|
||||
}
|
||||
// TODO: update store in this function
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *cluster) UpdateRaftAttributes(id types.ID, raftAttr RaftAttributes) {
|
||||
@@ -344,7 +352,7 @@ func (c *cluster) UpdateRaftAttributes(id types.ID, raftAttr RaftAttributes) {
|
||||
plog.Panicf("marshal raftAttributes should never fail: %v", err)
|
||||
}
|
||||
p := path.Join(memberStoreKey(id), raftAttributesSuffix)
|
||||
if _, err := c.store.Update(p, string(b), store.Permanent); err != nil {
|
||||
if _, err := c.store.Update(p, string(b), store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("update raftAttributes should never fail: %v", err)
|
||||
}
|
||||
c.members[id].RaftAttributes = raftAttr
|
||||
@@ -371,6 +379,58 @@ func (c *cluster) SetVersion(ver *semver.Version) {
|
||||
MustDetectDowngrade(c.version)
|
||||
}
|
||||
|
||||
func (c *cluster) isReadyToAddNewMember() bool {
|
||||
nmembers := 1
|
||||
nstarted := 0
|
||||
|
||||
for _, member := range c.members {
|
||||
if member.IsStarted() {
|
||||
nstarted++
|
||||
}
|
||||
nmembers++
|
||||
}
|
||||
|
||||
if nstarted == 1 && nmembers == 2 {
|
||||
// a case of adding a new node to 1-member cluster for restoring cluster data
|
||||
// https://github.com/coreos/etcd/blob/master/Documentation/admin_guide.md#restoring-the-cluster
|
||||
|
||||
plog.Debugf("The number of started member is 1. This cluster can accept add member request.")
|
||||
return true
|
||||
}
|
||||
|
||||
nquorum := nmembers/2 + 1
|
||||
if nstarted < nquorum {
|
||||
plog.Warningf("Reject add member request: the number of started member (%d) will be less than the quorum number of the cluster (%d)", nstarted, nquorum)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *cluster) isReadyToRemoveMember(id uint64) bool {
|
||||
nmembers := 0
|
||||
nstarted := 0
|
||||
|
||||
for _, member := range c.members {
|
||||
if uint64(member.ID) == id {
|
||||
continue
|
||||
}
|
||||
|
||||
if member.IsStarted() {
|
||||
nstarted++
|
||||
}
|
||||
nmembers++
|
||||
}
|
||||
|
||||
nquorum := nmembers/2 + 1
|
||||
if nstarted < nquorum {
|
||||
plog.Warningf("Reject remove member request: the number of started member (%d) will be less than the quorum number of the cluster (%d)", nstarted, nquorum)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func membersFromStore(st store.Store) (map[types.ID]*Member, map[types.ID]bool) {
|
||||
members := make(map[types.ID]*Member)
|
||||
removed := make(map[types.ID]bool)
|
||||
|
||||
34
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/cluster_util.go
generated
vendored
34
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/cluster_util.go
generated
vendored
@@ -29,8 +29,8 @@ import (
|
||||
|
||||
// isMemberBootstrapped tries to check if the given member has been bootstrapped
|
||||
// in the given cluster.
|
||||
func isMemberBootstrapped(cl *cluster, member string, tr *http.Transport) bool {
|
||||
rcl, err := getClusterFromRemotePeers(getRemotePeerURLs(cl, member), time.Second, false, tr)
|
||||
func isMemberBootstrapped(cl *cluster, member string, rt http.RoundTripper, timeout time.Duration) bool {
|
||||
rcl, err := getClusterFromRemotePeers(getRemotePeerURLs(cl, member), timeout, false, rt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
@@ -52,14 +52,14 @@ func isMemberBootstrapped(cl *cluster, member string, tr *http.Transport) bool {
|
||||
// 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, tr *http.Transport) (*cluster, error) {
|
||||
return getClusterFromRemotePeers(urls, 10*time.Second, true, tr)
|
||||
func GetClusterFromRemotePeers(urls []string, rt http.RoundTripper) (*cluster, error) {
|
||||
return getClusterFromRemotePeers(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, tr *http.Transport) (*cluster, error) {
|
||||
func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool, rt http.RoundTripper) (*cluster, error) {
|
||||
cc := &http.Client{
|
||||
Transport: tr,
|
||||
Transport: rt,
|
||||
Timeout: timeout,
|
||||
}
|
||||
for _, u := range urls {
|
||||
@@ -78,7 +78,7 @@ func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool
|
||||
continue
|
||||
}
|
||||
var membs []*Member
|
||||
if err := json.Unmarshal(b, &membs); err != nil {
|
||||
if err = json.Unmarshal(b, &membs); err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not unmarshal cluster response: %v", err)
|
||||
}
|
||||
@@ -114,7 +114,7 @@ func getRemotePeerURLs(cl Cluster, 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 Cluster, local types.ID, tr *http.Transport) map[string]*version.Versions {
|
||||
func getVersions(cl Cluster, local types.ID, rt http.RoundTripper) map[string]*version.Versions {
|
||||
members := cl.Members()
|
||||
vers := make(map[string]*version.Versions)
|
||||
for _, m := range members {
|
||||
@@ -126,7 +126,7 @@ func getVersions(cl Cluster, local types.ID, tr *http.Transport) map[string]*ver
|
||||
vers[m.ID.String()] = &version.Versions{Server: version.Version, Cluster: cv}
|
||||
continue
|
||||
}
|
||||
ver, err := getVersion(m, tr)
|
||||
ver, err := getVersion(m, rt)
|
||||
if err != nil {
|
||||
plog.Warningf("cannot get the version of member %s (%v)", m.ID, err)
|
||||
vers[m.ID.String()] = nil
|
||||
@@ -166,14 +166,14 @@ func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
||||
return cv
|
||||
}
|
||||
|
||||
// isCompatibleWithCluster return true if the local member has a compitable version with
|
||||
// isCompatibleWithCluster return true if the local member has a compatible version with
|
||||
// the current running cluster.
|
||||
// The version is considered as compitable when at least one of the other members in the cluster has a
|
||||
// The version is considered as compatible when at least one of the other members in the cluster has a
|
||||
// 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 Cluster, local types.ID, tr *http.Transport) bool {
|
||||
vers := getVersions(cl, local, tr)
|
||||
func isCompatibleWithCluster(cl Cluster, local types.ID, rt http.RoundTripper) bool {
|
||||
vers := getVersions(cl, local, rt)
|
||||
minV := semver.Must(semver.NewVersion(version.MinClusterVersion))
|
||||
maxV := semver.Must(semver.NewVersion(version.Version))
|
||||
maxV = &semver.Version{
|
||||
@@ -187,7 +187,7 @@ func isCompatibleWithCluster(cl Cluster, local types.ID, tr *http.Transport) boo
|
||||
func isCompatibleWithVers(vers map[string]*version.Versions, local types.ID, minV, maxV *semver.Version) bool {
|
||||
var ok bool
|
||||
for id, v := range vers {
|
||||
// ignore comparasion with local version
|
||||
// ignore comparison with local version
|
||||
if id == local.String() {
|
||||
continue
|
||||
}
|
||||
@@ -214,9 +214,9 @@ 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 *Member, tr *http.Transport) (*version.Versions, error) {
|
||||
func getVersion(m *Member, rt http.RoundTripper) (*version.Versions, error) {
|
||||
cc := &http.Client{
|
||||
Transport: tr,
|
||||
Transport: rt,
|
||||
}
|
||||
var (
|
||||
err error
|
||||
@@ -246,7 +246,7 @@ func getVersion(m *Member, tr *http.Transport) (*version.Versions, error) {
|
||||
continue
|
||||
}
|
||||
var vers version.Versions
|
||||
if err := json.Unmarshal(b, &vers); err != nil {
|
||||
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)
|
||||
continue
|
||||
}
|
||||
|
||||
35
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/config.go
generated
vendored
35
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/config.go
generated
vendored
@@ -16,13 +16,13 @@ package etcdserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/netutil"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
@@ -44,15 +44,21 @@ type ServerConfig struct {
|
||||
InitialClusterToken string
|
||||
NewCluster bool
|
||||
ForceNewCluster bool
|
||||
Transport *http.Transport
|
||||
PeerTLSInfo transport.TLSInfo
|
||||
|
||||
TickMs uint
|
||||
ElectionTicks int
|
||||
TickMs uint
|
||||
ElectionTicks int
|
||||
BootstrapTimeout time.Duration
|
||||
|
||||
V3demo bool
|
||||
V3demo bool
|
||||
AutoCompactionRetention int
|
||||
|
||||
StrictReconfigCheck bool
|
||||
|
||||
EnablePprof bool
|
||||
}
|
||||
|
||||
// VerifyBootstrapConfig sanity-checks the initial config for bootstrap case
|
||||
// VerifyBootstrap sanity-checks the initial config for bootstrap case
|
||||
// and returns an error for things that should never happen.
|
||||
func (c *ServerConfig) VerifyBootstrap() error {
|
||||
if err := c.verifyLocalMember(true); err != nil {
|
||||
@@ -128,6 +134,16 @@ func (c *ServerConfig) ReqTimeout() time.Duration {
|
||||
return 5*time.Second + 2*time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond
|
||||
}
|
||||
|
||||
func (c *ServerConfig) electionTimeout() time.Duration {
|
||||
return time.Duration(c.ElectionTicks) * time.Duration(c.TickMs) * time.Millisecond
|
||||
}
|
||||
|
||||
func (c *ServerConfig) peerDialTimeout() time.Duration {
|
||||
// 1s for queue wait and system delay
|
||||
// + one RTT, which is smaller than 1/5 election timeout
|
||||
return time.Second + time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond/5
|
||||
}
|
||||
|
||||
func (c *ServerConfig) PrintWithInitial() { c.print(true) }
|
||||
|
||||
func (c *ServerConfig) Print() { c.print(false) }
|
||||
@@ -171,3 +187,10 @@ func checkDuplicateURL(urlsmap types.URLsMap) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ServerConfig) bootstrapTimeout() time.Duration {
|
||||
if c.BootstrapTimeout != 0 {
|
||||
return c.BootstrapTimeout
|
||||
}
|
||||
return time.Second
|
||||
}
|
||||
|
||||
25
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/consistent_index.go
generated
vendored
Normal file
25
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/consistent_index.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdserver
|
||||
|
||||
// consistentIndex represents the offset of an entry in a consistent replica log.
|
||||
// It implements the storage.ConsistentIndexGetter interface.
|
||||
// It is always set to the offset of current entry before executing the entry,
|
||||
// so ConsistentWatchableKV could get the consistent index from it.
|
||||
type consistentIndex uint64
|
||||
|
||||
func (i *consistentIndex) setConsistentIndex(v uint64) { *i = consistentIndex(v) }
|
||||
|
||||
func (i *consistentIndex) ConsistentIndex() uint64 { return uint64(*i) }
|
||||
16
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/doc.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package etcdserver defines how etcd servers interact and store their states.
|
||||
package etcdserver
|
||||
3
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/errors.go
generated
vendored
3
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/errors.go
generated
vendored
@@ -32,6 +32,9 @@ var (
|
||||
ErrTimeout = errors.New("etcdserver: request timed out")
|
||||
ErrTimeoutDueToLeaderFail = errors.New("etcdserver: request timed out, possibly due to previous leader failure")
|
||||
ErrTimeoutDueToConnectionLost = errors.New("etcdserver: request timed out, possibly due to connection lost")
|
||||
ErrNotEnoughStartedMembers = errors.New("etcdserver: re-configuration failed due to not enough started members")
|
||||
ErrNoLeader = errors.New("etcdserver: no leader")
|
||||
ErrRequestTooLarge = errors.New("etcdserver: request is too large")
|
||||
)
|
||||
|
||||
func isKeyNotFound(err error) bool {
|
||||
|
||||
29
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/capability.go
generated
vendored
29
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/capability.go
generated
vendored
@@ -1,3 +1,17 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdhttp
|
||||
|
||||
import (
|
||||
@@ -18,20 +32,21 @@ const (
|
||||
)
|
||||
|
||||
var (
|
||||
// capabilityMap is a static map of version to capability map.
|
||||
// capabilityMaps is a static map of version to capability map.
|
||||
// the base capabilities is the set of capability 2.0 supports.
|
||||
capabilityMaps = map[string]map[capability]bool{
|
||||
"2.1.0": {authCapability: true},
|
||||
"2.2.0": {authCapability: true},
|
||||
"2.3.0": {authCapability: true},
|
||||
}
|
||||
|
||||
enableMapMu sync.Mutex
|
||||
// enabled points to a map in cpapbilityMaps
|
||||
// enabledMap points to a map in capabilityMaps
|
||||
enabledMap map[capability]bool
|
||||
)
|
||||
|
||||
// capabilityLoop checks the cluster version every 500ms and updates
|
||||
// the enabledCapability when the cluster version increased.
|
||||
// the enabledMap when the cluster version increased.
|
||||
// capabilityLoop MUST be ran in a goroutine before checking capability
|
||||
// or using capabilityHandler.
|
||||
func capabilityLoop(s *etcdserver.EtcdServer) {
|
||||
@@ -70,14 +85,16 @@ func isCapabilityEnabled(c capability) bool {
|
||||
func capabilityHandler(c capability, fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !isCapabilityEnabled(c) {
|
||||
notCapable(w, c)
|
||||
notCapable(w, r, c)
|
||||
return
|
||||
}
|
||||
fn(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func notCapable(w http.ResponseWriter, c capability) {
|
||||
func notCapable(w http.ResponseWriter, r *http.Request, c capability) {
|
||||
herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c))
|
||||
herr.WriteTo(w)
|
||||
if err := herr.WriteTo(w); err != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
108
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/client.go
generated
vendored
108
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/client.go
generated
vendored
@@ -21,6 +21,7 @@ import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
@@ -54,6 +55,7 @@ const (
|
||||
healthPath = "/health"
|
||||
versionPath = "/version"
|
||||
configPath = "/config"
|
||||
pprofPrefix = "/debug/pprof"
|
||||
)
|
||||
|
||||
// NewClientHandler generates a muxed http.Handler with the given parameters to serve etcd client requests.
|
||||
@@ -108,6 +110,23 @@ func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http
|
||||
mux.Handle(deprecatedMachinesPrefix, dmh)
|
||||
handleAuth(mux, sech)
|
||||
|
||||
if server.IsPprofEnabled() {
|
||||
plog.Infof("pprof is enabled under %s", pprofPrefix)
|
||||
|
||||
mux.HandleFunc(pprofPrefix, pprof.Index)
|
||||
mux.HandleFunc(pprofPrefix+"/profile", pprof.Profile)
|
||||
mux.HandleFunc(pprofPrefix+"/symbol", pprof.Symbol)
|
||||
mux.HandleFunc(pprofPrefix+"/cmdline", pprof.Cmdline)
|
||||
// TODO: currently, we don't create an entry for pprof.Trace,
|
||||
// because go 1.4 doesn't provide it. After support of go 1.4 is dropped,
|
||||
// we should add the entry.
|
||||
|
||||
mux.Handle(pprofPrefix+"/heap", pprof.Handler("heap"))
|
||||
mux.Handle(pprofPrefix+"/goroutine", pprof.Handler("goroutine"))
|
||||
mux.Handle(pprofPrefix+"/threadcreate", pprof.Handler("threadcreate"))
|
||||
mux.Handle(pprofPrefix+"/block", pprof.Handler("block"))
|
||||
}
|
||||
|
||||
return requestLogger(mux)
|
||||
}
|
||||
|
||||
@@ -128,8 +147,9 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
|
||||
defer cancel()
|
||||
|
||||
rr, err := parseKeyRequest(r, clockwork.NewRealClock())
|
||||
clock := clockwork.NewRealClock()
|
||||
startTime := clock.Now()
|
||||
rr, err := parseKeyRequest(r, clock)
|
||||
if err != nil {
|
||||
writeKeyError(w, err)
|
||||
return
|
||||
@@ -139,11 +159,14 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
writeKeyNoAuth(w)
|
||||
return
|
||||
}
|
||||
|
||||
if !rr.Wait {
|
||||
reportRequestReceived(rr)
|
||||
}
|
||||
resp, err := h.server.Do(ctx, rr)
|
||||
if err != nil {
|
||||
err = trimErrorPrefix(err, etcdserver.StoreKeysPrefix)
|
||||
writeKeyError(w, err)
|
||||
reportRequestFailed(rr, err)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
@@ -152,6 +175,7 @@ func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
// Should never be reached
|
||||
plog.Errorf("error writing event (%v)", err)
|
||||
}
|
||||
reportRequestCompleted(rr, resp, startTime)
|
||||
case resp.Watcher != nil:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultWatchTimeout)
|
||||
defer cancel()
|
||||
@@ -186,7 +210,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !hasWriteRootAccess(h.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
|
||||
@@ -206,7 +230,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
case "leader":
|
||||
id := h.server.Leader()
|
||||
if id == 0 {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election"))
|
||||
return
|
||||
}
|
||||
m := newMember(h.cluster.Member(id))
|
||||
@@ -215,7 +239,7 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
plog.Warningf("failed to encode members response (%v)", err)
|
||||
}
|
||||
default:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusNotFound, "Not found"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, "Not found"))
|
||||
}
|
||||
case "POST":
|
||||
req := httptypes.MemberCreateRequest{}
|
||||
@@ -227,11 +251,11 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
err := h.server.AddMember(ctx, *m)
|
||||
switch {
|
||||
case err == etcdserver.ErrIDExists || err == etcdserver.ErrPeerURLexists:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
return
|
||||
case err != nil:
|
||||
plog.Errorf("error adding member %s (%v)", m.ID, err)
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
res := newMember(m)
|
||||
@@ -248,12 +272,12 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
err := h.server.RemoveMember(ctx, uint64(id))
|
||||
switch {
|
||||
case err == etcdserver.ErrIDRemoved:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id)))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id)))
|
||||
case err == etcdserver.ErrIDNotFound:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
case err != nil:
|
||||
plog.Errorf("error removing member %s (%v)", id, err)
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -273,12 +297,12 @@ func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
err := h.server.UpdateMember(ctx, m)
|
||||
switch {
|
||||
case err == etcdserver.ErrPeerURLexists:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
case err == etcdserver.ErrIDNotFound:
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
case err != nil:
|
||||
plog.Errorf("error updating member %s (%v)", m.ID, err)
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
@@ -311,7 +335,7 @@ func (h *statsHandler) serveLeader(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
stats := h.stats.LeaderStats()
|
||||
if stats == nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusForbidden, "not current leader"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusForbidden, "not current leader"))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
@@ -319,6 +343,10 @@ func (h *statsHandler) serveLeader(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
func serveVars(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
first := true
|
||||
@@ -333,7 +361,7 @@ func serveVars(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
// TODO: change etcdserver to raft interface when we have it.
|
||||
// add test for healthHeadler when we have the interface ready.
|
||||
// add test for healthHandler when we have the interface ready.
|
||||
func healthHandler(server *etcdserver.EtcdServer) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
@@ -398,13 +426,13 @@ func logHandleFunc(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&in); err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body"))
|
||||
return
|
||||
}
|
||||
|
||||
logl, err := capnslog.ParseLevel(strings.ToUpper(in.Level))
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid log level "+in.Level))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid log level "+in.Level))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -530,6 +558,34 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
||||
pe = &bv
|
||||
}
|
||||
|
||||
// refresh is nullable, so leave it null if not specified
|
||||
var refresh *bool
|
||||
if _, ok := r.Form["refresh"]; ok {
|
||||
bv, err := getBool(r.Form, "refresh")
|
||||
if err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
"invalid value for refresh",
|
||||
)
|
||||
}
|
||||
refresh = &bv
|
||||
if refresh != nil && *refresh {
|
||||
val := r.FormValue("value")
|
||||
if _, ok := r.Form["value"]; ok && val != "" {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeRefreshValue,
|
||||
`A value was provided on a refresh`,
|
||||
)
|
||||
}
|
||||
if ttl == nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeRefreshTTLRequired,
|
||||
`No TTL value set`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rr := etcdserverpb.Request{
|
||||
Method: r.Method,
|
||||
Path: p,
|
||||
@@ -550,6 +606,10 @@ func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Reque
|
||||
rr.PrevExist = pe
|
||||
}
|
||||
|
||||
if refresh != nil {
|
||||
rr.Refresh = refresh
|
||||
}
|
||||
|
||||
// Null TTL is equivalent to unset Expiration
|
||||
if ttl != nil {
|
||||
expr := time.Duration(*ttl) * time.Second
|
||||
@@ -596,9 +656,9 @@ func writeKeyError(w http.ResponseWriter, err error) {
|
||||
default:
|
||||
switch err {
|
||||
case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost:
|
||||
plog.Error(err)
|
||||
mlog.MergeError(err)
|
||||
default:
|
||||
plog.Errorf("got unexpected response error (%v)", err)
|
||||
mlog.MergeErrorf("got unexpected response error (%v)", err)
|
||||
}
|
||||
ee := etcdErr.NewError(etcdErr.EcodeRaftInternal, err.Error(), 0)
|
||||
ee.WriteTo(w)
|
||||
@@ -684,16 +744,16 @@ func trimErrorPrefix(err error, prefix string) error {
|
||||
func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool {
|
||||
ctype := r.Header.Get("Content-Type")
|
||||
if ctype != "application/json" {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
||||
return false
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return false
|
||||
}
|
||||
if err := req.UnmarshalJSON(b); err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
@@ -707,7 +767,7 @@ func getID(p string, w http.ResponseWriter) (types.ID, bool) {
|
||||
}
|
||||
id, err := types.IDFromString(idStr)
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
||||
writeError(w, nil, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
|
||||
156
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/client_auth.go
generated
vendored
156
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/client_auth.go
generated
vendored
@@ -23,7 +23,6 @@ import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/auth"
|
||||
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
|
||||
"github.com/coreos/etcd/pkg/netutil"
|
||||
)
|
||||
|
||||
type authHandler struct {
|
||||
@@ -46,7 +45,7 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool {
|
||||
if !sec.AuthEnabled() {
|
||||
return true
|
||||
}
|
||||
username, password, ok := netutil.BasicAuth(r)
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
@@ -54,7 +53,8 @@ func hasRootAccess(sec auth.Store, r *http.Request) bool {
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ok = rootUser.CheckPassword(password)
|
||||
|
||||
ok = sec.CheckPassword(rootUser, password)
|
||||
if !ok {
|
||||
plog.Warningf("auth: wrong password for user %s", username)
|
||||
return false
|
||||
@@ -80,7 +80,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
|
||||
plog.Warningf("auth: no authorization provided, checking guest access")
|
||||
return hasGuestAccess(sec, r, key)
|
||||
}
|
||||
username, password, ok := netutil.BasicAuth(r)
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
plog.Warningf("auth: malformed basic auth encoding")
|
||||
return false
|
||||
@@ -90,7 +90,7 @@ func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive b
|
||||
plog.Warningf("auth: no such user: %s.", username)
|
||||
return false
|
||||
}
|
||||
authAsUser := user.CheckPassword(password)
|
||||
authAsUser := sec.CheckPassword(user, password)
|
||||
if !authAsUser {
|
||||
plog.Warningf("auth: incorrect password for user: %s.", username)
|
||||
return false
|
||||
@@ -126,9 +126,11 @@ func hasGuestAccess(sec auth.Store, r *http.Request, key string) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
func writeNoAuth(w http.ResponseWriter) {
|
||||
func writeNoAuth(w http.ResponseWriter, r *http.Request) {
|
||||
herr := httptypes.NewHTTPError(http.StatusUnauthorized, "Insufficient credentials")
|
||||
herr.WriteTo(w)
|
||||
if err := herr.WriteTo(w); err != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func handleAuth(mux *http.ServeMux, sh *authHandler) {
|
||||
@@ -144,27 +146,46 @@ func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var rolesCollections struct {
|
||||
Roles []string `json:"roles"`
|
||||
}
|
||||
|
||||
roles, err := sh.sec.AllRoles()
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
if roles == nil {
|
||||
roles = make([]string, 0)
|
||||
}
|
||||
|
||||
rolesCollections.Roles = roles
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
var rolesCollections struct {
|
||||
Roles []auth.Role `json:"roles"`
|
||||
}
|
||||
for _, roleName := range roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
rolesCollections.Roles = append(rolesCollections.Roles, role)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(rolesCollections)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("baseRoles error encoding on %s", r.URL)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -178,7 +199,7 @@ func (sh *authHandler) handleRoles(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if len(pieces) != 3 {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
return
|
||||
}
|
||||
sh.forRole(w, r, pieces[2])
|
||||
@@ -189,7 +210,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
@@ -199,7 +220,7 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
||||
case "GET":
|
||||
data, err := sh.sec.GetRole(role)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(data)
|
||||
@@ -212,11 +233,11 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
||||
var in auth.Role
|
||||
err := json.NewDecoder(r.Body).Decode(&in)
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
return
|
||||
}
|
||||
if in.Role != role {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON name does not match the name in the URL"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON name does not match the name in the URL"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -226,19 +247,19 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
||||
if in.Grant.IsEmpty() && in.Revoke.IsEmpty() {
|
||||
err = sh.sec.CreateRole(in)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
out = in
|
||||
} else {
|
||||
if !in.Permissions.IsEmpty() {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON contains both permissions and grant/revoke"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON contains both permissions and grant/revoke"))
|
||||
return
|
||||
}
|
||||
out, err = sh.sec.UpdateRole(in)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
@@ -253,38 +274,73 @@ func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role stri
|
||||
case "DELETE":
|
||||
err := sh.sec.DeleteRole(role)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type userWithRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []auth.Role `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
var usersCollections struct {
|
||||
Users []string `json:"users"`
|
||||
}
|
||||
|
||||
users, err := sh.sec.AllUsers()
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
if users == nil {
|
||||
users = make([]string, 0)
|
||||
}
|
||||
|
||||
usersCollections.Users = users
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
var usersCollections struct {
|
||||
Users []userWithRoles `json:"users"`
|
||||
}
|
||||
for _, userName := range users {
|
||||
var user auth.User
|
||||
user, err = sh.sec.GetUser(userName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
uwr := userWithRoles{User: user.User}
|
||||
for _, roleName := range user.Roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
uwr.Roles = append(uwr.Roles, role)
|
||||
}
|
||||
|
||||
usersCollections.Users = append(usersCollections.Users, uwr)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(usersCollections)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("baseUsers error encoding on %s", r.URL)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,7 +354,7 @@ func (sh *authHandler) handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if len(pieces) != 3 {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
return
|
||||
}
|
||||
sh.forUser(w, r, pieces[2])
|
||||
@@ -309,7 +365,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
@@ -319,12 +375,28 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
||||
case "GET":
|
||||
u, err := sh.sec.GetUser(user)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
u.Password = ""
|
||||
|
||||
err = json.NewEncoder(w).Encode(u)
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
uwr := userWithRoles{User: u.User}
|
||||
for _, roleName := range u.Roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
uwr.Roles = append(uwr.Roles, role)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(uwr)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("forUser error encoding on %s", r.URL)
|
||||
return
|
||||
@@ -334,11 +406,11 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
||||
var u auth.User
|
||||
err := json.NewDecoder(r.Body).Decode(&u)
|
||||
if err != nil {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
return
|
||||
}
|
||||
if u.User != user {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON name does not match the name in the URL"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON name does not match the name in the URL"))
|
||||
return
|
||||
}
|
||||
|
||||
@@ -358,18 +430,18 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// update case
|
||||
if len(u.Roles) != 0 {
|
||||
writeError(w, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON contains both roles and grant/revoke"))
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON contains both roles and grant/revoke"))
|
||||
return
|
||||
}
|
||||
out, err = sh.sec.UpdateUser(u)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -391,7 +463,7 @@ func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user stri
|
||||
case "DELETE":
|
||||
err := sh.sec.DeleteUser(user)
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -406,7 +478,7 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
if !hasWriteRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w)
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
@@ -422,13 +494,13 @@ func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
|
||||
case "PUT":
|
||||
err := sh.sec.EnableAuth()
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
case "DELETE":
|
||||
err := sh.sec.DisableAuth()
|
||||
if err != nil {
|
||||
writeError(w, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
16
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/doc.go
generated
vendored
Normal file
16
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package etcdhttp provides etcd client and server implementations.
|
||||
package etcdhttp
|
||||
24
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/http.go
generated
vendored
24
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/http.go
generated
vendored
@@ -25,6 +25,7 @@ import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/auth"
|
||||
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
|
||||
"github.com/coreos/etcd/pkg/logutil"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
@@ -35,13 +36,14 @@ const (
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdhttp")
|
||||
mlog = logutil.NewMergeLogger(plog)
|
||||
errClosed = errors.New("etcdhttp: client closed connection")
|
||||
)
|
||||
|
||||
// writeError logs and writes the given Error to the ResponseWriter
|
||||
// If Error is an etcdErr, it is rendered to the ResponseWriter
|
||||
// Otherwise, it is assumed to be an InternalServerError
|
||||
func writeError(w http.ResponseWriter, err error) {
|
||||
// Otherwise, it is assumed to be a StatusInternalServerError
|
||||
func writeError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
@@ -49,19 +51,25 @@ func writeError(w http.ResponseWriter, err error) {
|
||||
case *etcdErr.Error:
|
||||
e.WriteTo(w)
|
||||
case *httptypes.HTTPError:
|
||||
e.WriteTo(w)
|
||||
if et := e.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
case auth.Error:
|
||||
herr := httptypes.NewHTTPError(e.HTTPStatus(), e.Error())
|
||||
herr.WriteTo(w)
|
||||
if et := herr.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
default:
|
||||
switch err {
|
||||
case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost:
|
||||
plog.Error(err)
|
||||
case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost, etcdserver.ErrNotEnoughStartedMembers:
|
||||
mlog.MergeError(err)
|
||||
default:
|
||||
plog.Errorf("got unexpected response error (%v)", err)
|
||||
mlog.MergeErrorf("got unexpected response error (%v)", err)
|
||||
}
|
||||
herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error")
|
||||
herr.WriteTo(w)
|
||||
if et := herr.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
10
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go
generated
vendored
10
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/errors.go
generated
vendored
@@ -27,7 +27,7 @@ var (
|
||||
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
// HTTP return code
|
||||
// Code is the HTTP status code
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
@@ -35,15 +35,17 @@ func (e HTTPError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// TODO(xiangli): handle http write errors
|
||||
func (e HTTPError) WriteTo(w http.ResponseWriter) {
|
||||
func (e HTTPError) WriteTo(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(e.Code)
|
||||
b, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal HTTPError should never fail (%v)", err)
|
||||
}
|
||||
w.Write(b)
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHTTPError(code int, m string) *HTTPError {
|
||||
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/httptypes/member.go
generated
vendored
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package httptypes defines how etcd's HTTP API entities are serialized to and
|
||||
// deserialized from JSON.
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
|
||||
96
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/metrics.go
generated
vendored
Normal file
96
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdhttp
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
incomingEvents = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "received_total",
|
||||
Help: "Counter of requests received into the system (successfully parsed and authd).",
|
||||
}, []string{"method"})
|
||||
|
||||
failedEvents = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "failed_total",
|
||||
Help: "Counter of handle failures of requests (non-watches), by method (GET/PUT etc.) and code (400, 500 etc.).",
|
||||
}, []string{"method", "code"})
|
||||
|
||||
successfulEventsHandlingTime = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "successful_duration_second",
|
||||
Help: "Bucketed histogram of processing time (s) of successfully handled requests (non-watches), by method (GET/PUT etc.).",
|
||||
Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13),
|
||||
}, []string{"method"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(incomingEvents)
|
||||
prometheus.MustRegister(failedEvents)
|
||||
prometheus.MustRegister(successfulEventsHandlingTime)
|
||||
}
|
||||
|
||||
func reportRequestReceived(request etcdserverpb.Request) {
|
||||
incomingEvents.WithLabelValues(methodFromRequest(request)).Inc()
|
||||
}
|
||||
|
||||
func reportRequestCompleted(request etcdserverpb.Request, response etcdserver.Response, startTime time.Time) {
|
||||
method := methodFromRequest(request)
|
||||
successfulEventsHandlingTime.WithLabelValues(method).Observe(time.Since(startTime).Seconds())
|
||||
}
|
||||
|
||||
func reportRequestFailed(request etcdserverpb.Request, err error) {
|
||||
method := methodFromRequest(request)
|
||||
failedEvents.WithLabelValues(method, strconv.Itoa(codeFromError(err))).Inc()
|
||||
}
|
||||
|
||||
func methodFromRequest(request etcdserverpb.Request) string {
|
||||
if request.Method == "GET" && request.Quorum {
|
||||
return "QGET"
|
||||
}
|
||||
return request.Method
|
||||
}
|
||||
|
||||
func codeFromError(err error) int {
|
||||
if err == nil {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *etcdErr.Error:
|
||||
return (*etcdErr.Error)(e).StatusCode()
|
||||
case *httptypes.HTTPError:
|
||||
return (*httptypes.HTTPError)(e).Code
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
17
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/peer.go
generated
vendored
17
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdhttp/peer.go
generated
vendored
@@ -19,15 +19,25 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/lease/leasehttp"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
)
|
||||
|
||||
const (
|
||||
peerMembersPrefix = "/members"
|
||||
leasesPrefix = "/leases"
|
||||
)
|
||||
|
||||
// NewPeerHandler generates an http.Handler to handle etcd peer (raft) requests.
|
||||
func NewPeerHandler(cluster etcdserver.Cluster, raftHandler http.Handler) http.Handler {
|
||||
// NewPeerHandler generates an http.Handler to handle etcd peer requests.
|
||||
func NewPeerHandler(s *etcdserver.EtcdServer) http.Handler {
|
||||
var lh http.Handler
|
||||
if l := s.Lessor(); l != nil {
|
||||
lh = leasehttp.NewHandler(l)
|
||||
}
|
||||
return newPeerHandler(s.Cluster(), s.RaftHandler(), lh)
|
||||
}
|
||||
|
||||
func newPeerHandler(cluster etcdserver.Cluster, raftHandler http.Handler, leaseHandler http.Handler) http.Handler {
|
||||
mh := &peerMembersHandler{
|
||||
cluster: cluster,
|
||||
}
|
||||
@@ -37,6 +47,9 @@ func NewPeerHandler(cluster etcdserver.Cluster, raftHandler http.Handler) http.H
|
||||
mux.Handle(rafthttp.RaftPrefix, raftHandler)
|
||||
mux.Handle(rafthttp.RaftPrefix+"/", raftHandler)
|
||||
mux.Handle(peerMembersPrefix, mh)
|
||||
if leaseHandler != nil {
|
||||
mux.Handle(leasesPrefix, leaseHandler)
|
||||
}
|
||||
mux.HandleFunc(versionPath, versionHandler(cluster, serveVersion))
|
||||
return mux
|
||||
}
|
||||
|
||||
89
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.pb.go
generated
vendored
89
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.pb.go
generated
vendored
@@ -14,6 +14,7 @@
|
||||
Request
|
||||
Metadata
|
||||
InternalRaftRequest
|
||||
EmptyResponse
|
||||
ResponseHeader
|
||||
RangeRequest
|
||||
RangeResponse
|
||||
@@ -28,6 +29,57 @@
|
||||
TxnResponse
|
||||
CompactionRequest
|
||||
CompactionResponse
|
||||
HashRequest
|
||||
HashResponse
|
||||
WatchRequest
|
||||
WatchCreateRequest
|
||||
WatchCancelRequest
|
||||
WatchResponse
|
||||
LeaseCreateRequest
|
||||
LeaseCreateResponse
|
||||
LeaseRevokeRequest
|
||||
LeaseRevokeResponse
|
||||
LeaseKeepAliveRequest
|
||||
LeaseKeepAliveResponse
|
||||
Member
|
||||
MemberAddRequest
|
||||
MemberAddResponse
|
||||
MemberRemoveRequest
|
||||
MemberRemoveResponse
|
||||
MemberUpdateRequest
|
||||
MemberUpdateResponse
|
||||
MemberListRequest
|
||||
MemberListResponse
|
||||
DefragmentRequest
|
||||
DefragmentResponse
|
||||
AuthEnableRequest
|
||||
AuthDisableRequest
|
||||
AuthenticateRequest
|
||||
UserAddRequest
|
||||
UserGetRequest
|
||||
UserDeleteRequest
|
||||
UserChangePasswordRequest
|
||||
UserGrantRequest
|
||||
UserRevokeRequest
|
||||
RoleAddRequest
|
||||
RoleGetRequest
|
||||
RoleDeleteRequest
|
||||
RoleGrantRequest
|
||||
RoleRevokeRequest
|
||||
AuthEnableResponse
|
||||
AuthDisableResponse
|
||||
AuthenticateResponse
|
||||
UserAddResponse
|
||||
UserGetResponse
|
||||
UserDeleteResponse
|
||||
UserChangePasswordResponse
|
||||
UserGrantResponse
|
||||
UserRevokeResponse
|
||||
RoleAddResponse
|
||||
RoleGetResponse
|
||||
RoleDeleteResponse
|
||||
RoleGrantResponse
|
||||
RoleRevokeResponse
|
||||
*/
|
||||
package etcdserverpb
|
||||
|
||||
@@ -63,6 +115,7 @@ type Request struct {
|
||||
Quorum bool `protobuf:"varint,14,opt,name=Quorum" json:"Quorum"`
|
||||
Time int64 `protobuf:"varint,15,opt,name=Time" json:"Time"`
|
||||
Stream bool `protobuf:"varint,16,opt,name=Stream" json:"Stream"`
|
||||
Refresh *bool `protobuf:"varint,17,opt,name=Refresh" json:"Refresh,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
@@ -190,6 +243,18 @@ func (m *Request) MarshalTo(data []byte) (int, error) {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
if m.Refresh != nil {
|
||||
data[i] = 0x88
|
||||
i++
|
||||
data[i] = 0x1
|
||||
i++
|
||||
if *m.Refresh {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
@@ -275,6 +340,9 @@ func (m *Request) Size() (n int) {
|
||||
n += 2
|
||||
n += 1 + sovEtcdserver(uint64(m.Time))
|
||||
n += 3
|
||||
if m.Refresh != nil {
|
||||
n += 3
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
@@ -686,6 +754,27 @@ func (m *Request) Unmarshal(data []byte) error {
|
||||
}
|
||||
}
|
||||
m.Stream = bool(v != 0)
|
||||
case 17:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Refresh", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
b := bool(v != 0)
|
||||
m.Refresh = &b
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipEtcdserver(data[iNdEx:])
|
||||
|
||||
@@ -25,6 +25,7 @@ message Request {
|
||||
optional bool Quorum = 14 [(gogoproto.nullable) = false];
|
||||
optional int64 Time = 15 [(gogoproto.nullable) = false];
|
||||
optional bool Stream = 16 [(gogoproto.nullable) = false];
|
||||
optional bool Refresh = 17 [(gogoproto.nullable) = true];
|
||||
}
|
||||
|
||||
message Metadata {
|
||||
|
||||
330
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.pb.go
generated
vendored
330
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.pb.go
generated
vendored
@@ -22,19 +22,32 @@ var _ = math.Inf
|
||||
// An InternalRaftRequest is the union of all requests which can be
|
||||
// sent via raft.
|
||||
type InternalRaftRequest struct {
|
||||
V2 *Request `protobuf:"bytes,1,opt,name=v2" json:"v2,omitempty"`
|
||||
Range *RangeRequest `protobuf:"bytes,2,opt,name=range" json:"range,omitempty"`
|
||||
Put *PutRequest `protobuf:"bytes,3,opt,name=put" json:"put,omitempty"`
|
||||
DeleteRange *DeleteRangeRequest `protobuf:"bytes,4,opt,name=delete_range" json:"delete_range,omitempty"`
|
||||
Txn *TxnRequest `protobuf:"bytes,5,opt,name=txn" json:"txn,omitempty"`
|
||||
ID uint64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
V2 *Request `protobuf:"bytes,2,opt,name=v2" json:"v2,omitempty"`
|
||||
Range *RangeRequest `protobuf:"bytes,3,opt,name=range" json:"range,omitempty"`
|
||||
Put *PutRequest `protobuf:"bytes,4,opt,name=put" json:"put,omitempty"`
|
||||
DeleteRange *DeleteRangeRequest `protobuf:"bytes,5,opt,name=delete_range" json:"delete_range,omitempty"`
|
||||
Txn *TxnRequest `protobuf:"bytes,6,opt,name=txn" json:"txn,omitempty"`
|
||||
Compaction *CompactionRequest `protobuf:"bytes,7,opt,name=compaction" json:"compaction,omitempty"`
|
||||
LeaseCreate *LeaseCreateRequest `protobuf:"bytes,8,opt,name=lease_create" json:"lease_create,omitempty"`
|
||||
LeaseRevoke *LeaseRevokeRequest `protobuf:"bytes,9,opt,name=lease_revoke" json:"lease_revoke,omitempty"`
|
||||
AuthEnable *AuthEnableRequest `protobuf:"bytes,10,opt,name=auth_enable" json:"auth_enable,omitempty"`
|
||||
}
|
||||
|
||||
func (m *InternalRaftRequest) Reset() { *m = InternalRaftRequest{} }
|
||||
func (m *InternalRaftRequest) String() string { return proto.CompactTextString(m) }
|
||||
func (*InternalRaftRequest) ProtoMessage() {}
|
||||
|
||||
type EmptyResponse struct {
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) Reset() { *m = EmptyResponse{} }
|
||||
func (m *EmptyResponse) String() string { return proto.CompactTextString(m) }
|
||||
func (*EmptyResponse) ProtoMessage() {}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*InternalRaftRequest)(nil), "etcdserverpb.InternalRaftRequest")
|
||||
proto.RegisterType((*EmptyResponse)(nil), "etcdserverpb.EmptyResponse")
|
||||
}
|
||||
func (m *InternalRaftRequest) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
@@ -51,8 +64,13 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.ID))
|
||||
}
|
||||
if m.V2 != nil {
|
||||
data[i] = 0xa
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.V2.Size()))
|
||||
n1, err := m.V2.MarshalTo(data[i:])
|
||||
@@ -62,7 +80,7 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
i += n1
|
||||
}
|
||||
if m.Range != nil {
|
||||
data[i] = 0x12
|
||||
data[i] = 0x1a
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.Range.Size()))
|
||||
n2, err := m.Range.MarshalTo(data[i:])
|
||||
@@ -72,7 +90,7 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
i += n2
|
||||
}
|
||||
if m.Put != nil {
|
||||
data[i] = 0x1a
|
||||
data[i] = 0x22
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.Put.Size()))
|
||||
n3, err := m.Put.MarshalTo(data[i:])
|
||||
@@ -82,7 +100,7 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
i += n3
|
||||
}
|
||||
if m.DeleteRange != nil {
|
||||
data[i] = 0x22
|
||||
data[i] = 0x2a
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.DeleteRange.Size()))
|
||||
n4, err := m.DeleteRange.MarshalTo(data[i:])
|
||||
@@ -92,7 +110,7 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
i += n4
|
||||
}
|
||||
if m.Txn != nil {
|
||||
data[i] = 0x2a
|
||||
data[i] = 0x32
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.Txn.Size()))
|
||||
n5, err := m.Txn.MarshalTo(data[i:])
|
||||
@@ -101,6 +119,64 @@ func (m *InternalRaftRequest) MarshalTo(data []byte) (int, error) {
|
||||
}
|
||||
i += n5
|
||||
}
|
||||
if m.Compaction != nil {
|
||||
data[i] = 0x3a
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.Compaction.Size()))
|
||||
n6, err := m.Compaction.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n6
|
||||
}
|
||||
if m.LeaseCreate != nil {
|
||||
data[i] = 0x42
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.LeaseCreate.Size()))
|
||||
n7, err := m.LeaseCreate.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n7
|
||||
}
|
||||
if m.LeaseRevoke != nil {
|
||||
data[i] = 0x4a
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.LeaseRevoke.Size()))
|
||||
n8, err := m.LeaseRevoke.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n8
|
||||
}
|
||||
if m.AuthEnable != nil {
|
||||
data[i] = 0x52
|
||||
i++
|
||||
i = encodeVarintRaftInternal(data, i, uint64(m.AuthEnable.Size()))
|
||||
n9, err := m.AuthEnable.MarshalTo(data[i:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i += n9
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
return i, nil
|
||||
}
|
||||
|
||||
@@ -134,6 +210,9 @@ func encodeVarintRaftInternal(data []byte, offset int, v uint64) int {
|
||||
func (m *InternalRaftRequest) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
n += 1 + sovRaftInternal(uint64(m.ID))
|
||||
}
|
||||
if m.V2 != nil {
|
||||
l = m.V2.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
@@ -154,6 +233,28 @@ func (m *InternalRaftRequest) Size() (n int) {
|
||||
l = m.Txn.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
}
|
||||
if m.Compaction != nil {
|
||||
l = m.Compaction.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
}
|
||||
if m.LeaseCreate != nil {
|
||||
l = m.LeaseCreate.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
}
|
||||
if m.LeaseRevoke != nil {
|
||||
l = m.LeaseRevoke.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
}
|
||||
if m.AuthEnable != nil {
|
||||
l = m.AuthEnable.Size()
|
||||
n += 1 + l + sovRaftInternal(uint64(l))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *EmptyResponse) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
return n
|
||||
}
|
||||
|
||||
@@ -200,6 +301,25 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
|
||||
}
|
||||
m.ID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.ID |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field V2", wireType)
|
||||
}
|
||||
@@ -232,7 +352,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Range", wireType)
|
||||
}
|
||||
@@ -265,7 +385,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Put", wireType)
|
||||
}
|
||||
@@ -298,7 +418,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
case 5:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field DeleteRange", wireType)
|
||||
}
|
||||
@@ -331,7 +451,7 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
case 6:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Txn", wireType)
|
||||
}
|
||||
@@ -364,6 +484,188 @@ func (m *InternalRaftRequest) Unmarshal(data []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 7:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Compaction", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthRaftInternal
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.Compaction == nil {
|
||||
m.Compaction = &CompactionRequest{}
|
||||
}
|
||||
if err := m.Compaction.Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 8:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field LeaseCreate", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthRaftInternal
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.LeaseCreate == nil {
|
||||
m.LeaseCreate = &LeaseCreateRequest{}
|
||||
}
|
||||
if err := m.LeaseCreate.Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 9:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field LeaseRevoke", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthRaftInternal
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.LeaseRevoke == nil {
|
||||
m.LeaseRevoke = &LeaseRevokeRequest{}
|
||||
}
|
||||
if err := m.LeaseRevoke.Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 10:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field AuthEnable", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthRaftInternal
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
if m.AuthEnable == nil {
|
||||
m.AuthEnable = &AuthEnableRequest{}
|
||||
}
|
||||
if err := m.AuthEnable.Unmarshal(data[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRaftInternal(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthRaftInternal
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *EmptyResponse) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowRaftInternal
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: EmptyResponse: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: EmptyResponse: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipRaftInternal(data[iNdEx:])
|
||||
|
||||
21
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.proto
generated
vendored
21
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.proto
generated
vendored
@@ -13,9 +13,20 @@ option (gogoproto.goproto_getters_all) = false;
|
||||
// An InternalRaftRequest is the union of all requests which can be
|
||||
// sent via raft.
|
||||
message InternalRaftRequest {
|
||||
Request v2 = 1;
|
||||
RangeRequest range = 2;
|
||||
PutRequest put = 3;
|
||||
DeleteRangeRequest delete_range = 4;
|
||||
TxnRequest txn = 5;
|
||||
uint64 ID = 1;
|
||||
Request v2 = 2;
|
||||
|
||||
RangeRequest range = 3;
|
||||
PutRequest put = 4;
|
||||
DeleteRangeRequest delete_range = 5;
|
||||
TxnRequest txn = 6;
|
||||
CompactionRequest compaction = 7;
|
||||
|
||||
LeaseCreateRequest lease_create = 8;
|
||||
LeaseRevokeRequest lease_revoke = 9;
|
||||
|
||||
AuthEnableRequest auth_enable = 10;
|
||||
}
|
||||
|
||||
message EmptyResponse {
|
||||
}
|
||||
|
||||
8163
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.pb.go
generated
vendored
8163
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.pb.go
generated
vendored
File diff suppressed because it is too large
Load Diff
382
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.proto
generated
vendored
382
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.proto
generated
vendored
@@ -7,8 +7,7 @@ import "etcd/storage/storagepb/kv.proto";
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
|
||||
// Interface exported by the server.
|
||||
service etcd {
|
||||
service KV {
|
||||
// Range gets the keys in the range from the store.
|
||||
rpc Range(RangeRequest) returns (RangeResponse) {}
|
||||
|
||||
@@ -25,28 +24,134 @@ service etcd {
|
||||
// Txn processes all the requests in one transaction.
|
||||
// A txn request increases the revision of the store,
|
||||
// and generates events with the same revision in the event history.
|
||||
// It is not allowed to modify the same key several times within one txn.
|
||||
rpc Txn(TxnRequest) returns (TxnResponse) {}
|
||||
|
||||
// Compact compacts the event history in etcd. User should compact the
|
||||
// event history periodically, or it will grow infinitely.
|
||||
rpc Compact(CompactionRequest) returns (CompactionResponse) {}
|
||||
|
||||
// Hash returns the hash of local KV state for consistency checking purpose.
|
||||
// This is designed for testing purpose. Do not use this in production when there
|
||||
// are ongoing transactions.
|
||||
rpc Hash(HashRequest) returns (HashResponse) {}
|
||||
}
|
||||
|
||||
service Watch {
|
||||
// Watch watches the events happening or happened. Both input and output
|
||||
// are stream. One watch rpc can watch for multiple keys or prefixs and
|
||||
// get a stream of events. The whole events history can be watched unless
|
||||
// compacted.
|
||||
rpc Watch(stream WatchRequest) returns (stream WatchResponse) {}
|
||||
}
|
||||
|
||||
service Lease {
|
||||
// LeaseCreate creates a lease. A lease has a TTL. The lease will expire if the
|
||||
// server does not receive a keepAlive within TTL from the lease holder.
|
||||
// All keys attached to the lease will be expired and deleted if the lease expires.
|
||||
// The key expiration generates an event in event history.
|
||||
rpc LeaseCreate(LeaseCreateRequest) returns (LeaseCreateResponse) {}
|
||||
|
||||
// LeaseRevoke revokes a lease. All the key attached to the lease will be expired and deleted.
|
||||
rpc LeaseRevoke(LeaseRevokeRequest) returns (LeaseRevokeResponse) {}
|
||||
|
||||
// KeepAlive keeps the lease alive.
|
||||
rpc LeaseKeepAlive(stream LeaseKeepAliveRequest) returns (stream LeaseKeepAliveResponse) {}
|
||||
|
||||
// TODO(xiangli) List all existing Leases?
|
||||
// TODO(xiangli) Get details information (expirations, leased keys, etc.) of a lease?
|
||||
}
|
||||
|
||||
service Cluster {
|
||||
// MemberAdd adds a member into the cluster.
|
||||
rpc MemberAdd(MemberAddRequest) returns (MemberAddResponse) {}
|
||||
|
||||
// MemberRemove removes an existing member from the cluster.
|
||||
rpc MemberRemove(MemberRemoveRequest) returns (MemberRemoveResponse) {}
|
||||
|
||||
// MemberUpdate updates the member configuration.
|
||||
rpc MemberUpdate(MemberUpdateRequest) returns (MemberUpdateResponse) {}
|
||||
|
||||
// MemberList lists all the members in the cluster.
|
||||
rpc MemberList(MemberListRequest) returns (MemberListResponse) {}
|
||||
}
|
||||
|
||||
service Maintenance {
|
||||
// TODO: move Hash from kv to Maintenance
|
||||
rpc Defragment(DefragmentRequest) returns (DefragmentResponse) {}
|
||||
}
|
||||
|
||||
service Auth {
|
||||
// AuthEnable enables authentication.
|
||||
rpc AuthEnable(AuthEnableRequest) returns (AuthEnableResponse) {}
|
||||
|
||||
// AuthDisable disables authentication.
|
||||
rpc AuthDisable(AuthDisableRequest) returns (AuthDisableResponse) {}
|
||||
|
||||
// Authenticate processes authenticate request.
|
||||
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {}
|
||||
|
||||
// UserAdd adds a new user.
|
||||
rpc UserAdd(UserAddRequest) returns (UserAddResponse) {}
|
||||
|
||||
// UserGet gets a detailed information of a user or lists entire users.
|
||||
rpc UserGet(UserGetRequest) returns (UserGetResponse) {}
|
||||
|
||||
// UserDelete deletes a specified user.
|
||||
rpc UserDelete(UserDeleteRequest) returns (UserDeleteResponse) {}
|
||||
|
||||
// UserChangePassword changes password of a specified user.
|
||||
rpc UserChangePassword(UserChangePasswordRequest) returns (UserChangePasswordResponse) {}
|
||||
|
||||
// UserGrant grants a role to a specified user.
|
||||
rpc UserGrant(UserGrantRequest) returns (UserGrantResponse) {}
|
||||
|
||||
// UserRevoke revokes a role of specified user.
|
||||
rpc UserRevoke(UserRevokeRequest) returns (UserRevokeResponse) {}
|
||||
|
||||
// RoleAdd adds a new role.
|
||||
rpc RoleAdd(RoleAddRequest) returns (RoleAddResponse) {}
|
||||
|
||||
// RoleGet gets a detailed information of a role or lists entire roles.
|
||||
rpc RoleGet(RoleGetRequest) returns (RoleGetResponse) {}
|
||||
|
||||
// RoleDelete deletes a specified role.
|
||||
rpc RoleDelete(RoleDeleteRequest) returns (RoleDeleteResponse) {}
|
||||
|
||||
// RoleGrant grants a permission of a specified key or range to a specified role.
|
||||
rpc RoleGrant(RoleGrantRequest) returns (RoleGrantResponse) {}
|
||||
|
||||
// RoleRevoke revokes a key or range permission of a specified role.
|
||||
rpc RoleRevoke(RoleRevokeRequest) returns (RoleRevokeResponse) {}
|
||||
}
|
||||
|
||||
message ResponseHeader {
|
||||
// an error type message?
|
||||
string error = 1;
|
||||
uint64 cluster_id = 2;
|
||||
uint64 member_id = 3;
|
||||
uint64 cluster_id = 1;
|
||||
uint64 member_id = 2;
|
||||
// revision of the store when the request was applied.
|
||||
int64 revision = 4;
|
||||
int64 revision = 3;
|
||||
// term of raft when the request was applied.
|
||||
uint64 raft_term = 5;
|
||||
uint64 raft_term = 4;
|
||||
}
|
||||
|
||||
message RangeRequest {
|
||||
enum SortOrder {
|
||||
NONE = 0; // default, no sorting
|
||||
ASCEND = 1; // lowest target value first
|
||||
DESCEND = 2; // highest target value first
|
||||
}
|
||||
enum SortTarget {
|
||||
KEY = 0;
|
||||
VERSION = 1;
|
||||
CREATE = 2;
|
||||
MOD = 3;
|
||||
VALUE = 4;
|
||||
}
|
||||
|
||||
// if the range_end is not given, the request returns the key.
|
||||
bytes key = 1;
|
||||
// if the range_end is given, it gets the keys in range [key, range_end).
|
||||
// if the range_end is given, it gets the keys in range [key, range_end)
|
||||
// if range_end is nonempty, otherwise it returns all keys >= key.
|
||||
bytes range_end = 2;
|
||||
// limit the number of keys returned.
|
||||
int64 limit = 3;
|
||||
@@ -55,6 +160,19 @@ message RangeRequest {
|
||||
// if the revision has been compacted, ErrCompaction will be returned in
|
||||
// response.
|
||||
int64 revision = 4;
|
||||
|
||||
// sort_order is the requested order for returned the results
|
||||
SortOrder sort_order = 5;
|
||||
|
||||
// sort_target is the kv field to use for sorting
|
||||
SortTarget sort_target = 6;
|
||||
|
||||
// range request is linearizable by default. Linearizable requests has a higher
|
||||
// latency and lower throughput than serializable request.
|
||||
// To reduce latency, serializable can be set. If serializable is set, range request
|
||||
// will be serializable, but not linearizable with other requests.
|
||||
// Serializable range can be served locally without waiting for other nodes in the cluster.
|
||||
bool serializable = 7;
|
||||
}
|
||||
|
||||
message RangeResponse {
|
||||
@@ -67,6 +185,7 @@ message RangeResponse {
|
||||
message PutRequest {
|
||||
bytes key = 1;
|
||||
bytes value = 2;
|
||||
int64 lease = 3;
|
||||
}
|
||||
|
||||
message PutResponse {
|
||||
@@ -82,6 +201,8 @@ message DeleteRangeRequest {
|
||||
|
||||
message DeleteRangeResponse {
|
||||
ResponseHeader header = 1;
|
||||
// Deleted is the number of keys that got deleted.
|
||||
int64 deleted = 2;
|
||||
}
|
||||
|
||||
message RequestUnion {
|
||||
@@ -171,3 +292,246 @@ message CompactionRequest {
|
||||
message CompactionResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message HashRequest {
|
||||
}
|
||||
|
||||
message HashResponse {
|
||||
ResponseHeader header = 1;
|
||||
uint32 hash = 2;
|
||||
}
|
||||
|
||||
message WatchRequest {
|
||||
oneof request_union {
|
||||
WatchCreateRequest create_request = 1;
|
||||
WatchCancelRequest cancel_request = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message WatchCreateRequest {
|
||||
// the key to be watched
|
||||
bytes key = 1;
|
||||
// if the range_end is given, keys in [key, range_end) are watched
|
||||
// NOTE: only range_end == prefixEnd(key) is accepted now
|
||||
bytes range_end = 2;
|
||||
// start_revision is an optional revision (including) to watch from. No start_revision is "now".
|
||||
int64 start_revision = 3;
|
||||
// if progress_notify is set, etcd server sends WatchResponse with empty events to the
|
||||
// created watcher when there are no recent events. It is useful when clients want always to be
|
||||
// able to recover a disconnected watcher from a recent known revision.
|
||||
// etcdsever can decide how long it should send a notification based on current load.
|
||||
bool progress_notify = 4;
|
||||
}
|
||||
|
||||
message WatchCancelRequest {
|
||||
int64 watch_id = 1;
|
||||
}
|
||||
|
||||
message WatchResponse {
|
||||
ResponseHeader header = 1;
|
||||
// watch_id is the ID of the watching the response sent to.
|
||||
int64 watch_id = 2;
|
||||
// If the response is for a create watch request, created is set to true.
|
||||
// Client should record the watch_id and prepare for receiving events for
|
||||
// that watching from the same stream.
|
||||
// All events sent to the created watching will attach with the same watch_id.
|
||||
bool created = 3;
|
||||
// If the response is for a cancel watch request, cancel is set to true.
|
||||
// No further events will be sent to the canceled watching.
|
||||
bool canceled = 4;
|
||||
// CompactRevision is set to the minimum index if a watching tries to watch
|
||||
// at a compacted index.
|
||||
//
|
||||
// This happens when creating a watching at a compacted revision or the watching cannot
|
||||
// catch up with the progress of the KV.
|
||||
//
|
||||
// Client should treat the watching as canceled and should not try to create any
|
||||
// watching with same start_revision again.
|
||||
int64 compact_revision = 5;
|
||||
|
||||
repeated storagepb.Event events = 11;
|
||||
}
|
||||
|
||||
message LeaseCreateRequest {
|
||||
// advisory ttl in seconds
|
||||
int64 TTL = 1;
|
||||
// requested ID to create; 0 lets lessor choose
|
||||
int64 ID = 2;
|
||||
}
|
||||
|
||||
message LeaseCreateResponse {
|
||||
ResponseHeader header = 1;
|
||||
int64 ID = 2;
|
||||
// server decided ttl in second
|
||||
int64 TTL = 3;
|
||||
string error = 4;
|
||||
}
|
||||
|
||||
message LeaseRevokeRequest {
|
||||
int64 ID = 1;
|
||||
}
|
||||
|
||||
message LeaseRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message LeaseKeepAliveRequest {
|
||||
int64 ID = 1;
|
||||
}
|
||||
|
||||
message LeaseKeepAliveResponse {
|
||||
ResponseHeader header = 1;
|
||||
int64 ID = 2;
|
||||
int64 TTL = 3;
|
||||
}
|
||||
|
||||
message Member {
|
||||
uint64 ID = 1;
|
||||
// If the member is not started, name will be an empty string.
|
||||
string name = 2;
|
||||
bool IsLeader = 3;
|
||||
repeated string peerURLs = 4;
|
||||
// If the member is not started, client_URLs will be an zero length
|
||||
// string array.
|
||||
repeated string clientURLs = 5;
|
||||
}
|
||||
|
||||
message MemberAddRequest {
|
||||
repeated string peerURLs = 1;
|
||||
}
|
||||
|
||||
message MemberAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
Member member = 2;
|
||||
}
|
||||
|
||||
message MemberRemoveRequest {
|
||||
uint64 ID = 1;
|
||||
}
|
||||
|
||||
message MemberRemoveResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message MemberUpdateRequest {
|
||||
uint64 ID = 1;
|
||||
repeated string peerURLs = 2;
|
||||
}
|
||||
|
||||
message MemberUpdateResponse{
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message MemberListRequest {
|
||||
}
|
||||
|
||||
message MemberListResponse {
|
||||
ResponseHeader header = 1;
|
||||
repeated Member members = 2;
|
||||
}
|
||||
|
||||
message DefragmentRequest {
|
||||
|
||||
}
|
||||
|
||||
message DefragmentResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthEnableRequest {
|
||||
}
|
||||
|
||||
message AuthDisableRequest {
|
||||
}
|
||||
|
||||
message AuthenticateRequest {
|
||||
}
|
||||
|
||||
message UserAddRequest {
|
||||
}
|
||||
|
||||
message UserGetRequest {
|
||||
}
|
||||
|
||||
message UserDeleteRequest {
|
||||
}
|
||||
|
||||
message UserChangePasswordRequest {
|
||||
}
|
||||
|
||||
message UserGrantRequest {
|
||||
}
|
||||
|
||||
message UserRevokeRequest {
|
||||
}
|
||||
|
||||
message RoleAddRequest {
|
||||
}
|
||||
|
||||
message RoleGetRequest {
|
||||
}
|
||||
|
||||
message RoleDeleteRequest {
|
||||
}
|
||||
|
||||
message RoleGrantRequest {
|
||||
}
|
||||
|
||||
message RoleRevokeRequest {
|
||||
}
|
||||
|
||||
message AuthEnableResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthDisableResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthenticateResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserGetResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserDeleteResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserChangePasswordResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserGrantResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message UserRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message RoleAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message RoleGetResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message RoleDeleteResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message RoleGrantResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message RoleRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
11
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/member.go
generated
vendored
11
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/member.go
generated
vendored
@@ -35,6 +35,7 @@ var (
|
||||
|
||||
// RaftAttributes represents the raft related attributes of an etcd member.
|
||||
type RaftAttributes struct {
|
||||
// PeerURLs is the list of peers in the raft cluster.
|
||||
// TODO(philips): ensure these are URLs
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}
|
||||
@@ -52,7 +53,7 @@ type Member struct {
|
||||
}
|
||||
|
||||
// NewMember creates a Member without an ID and generates one based on the
|
||||
// name, peer URLs. This is used for bootstrapping/adding new member.
|
||||
// cluster name, peer URLs, and time. This is used for bootstrapping/adding new member.
|
||||
func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member {
|
||||
m := &Member{
|
||||
RaftAttributes: RaftAttributes{PeerURLs: peerURLs.StringSlice()},
|
||||
@@ -105,6 +106,10 @@ func (m *Member) Clone() *Member {
|
||||
return mm
|
||||
}
|
||||
|
||||
func (m *Member) IsStarted() bool {
|
||||
return len(m.Name) != 0
|
||||
}
|
||||
|
||||
func memberStoreKey(id types.ID) string {
|
||||
return path.Join(storeMembersPrefix, id.String())
|
||||
}
|
||||
@@ -153,14 +158,14 @@ func nodeToMember(n *store.NodeExtern) (*Member, error) {
|
||||
return m, nil
|
||||
}
|
||||
|
||||
// implement sort by ID interface
|
||||
// MembersByID implements sort by ID interface
|
||||
type MembersByID []*Member
|
||||
|
||||
func (ms MembersByID) Len() int { return len(ms) }
|
||||
func (ms MembersByID) Less(i, j int) bool { return ms[i].ID < ms[j].ID }
|
||||
func (ms MembersByID) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
|
||||
|
||||
// implement sort by peer urls interface
|
||||
// MembersByPeerURLs implements sort by peer urls interface
|
||||
type MembersByPeerURLs []*Member
|
||||
|
||||
func (ms MembersByPeerURLs) Len() int { return len(ms) }
|
||||
|
||||
5
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/metrics.go
generated
vendored
5
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/metrics.go
generated
vendored
@@ -23,11 +23,12 @@ import (
|
||||
|
||||
var (
|
||||
// TODO: with label in v3?
|
||||
proposeDurations = prometheus.NewSummary(prometheus.SummaryOpts{
|
||||
proposeDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "proposal_durations_milliseconds",
|
||||
Name: "proposal_durations_seconds",
|
||||
Help: "The latency distributions of committing proposal.",
|
||||
Buckets: prometheus.ExponentialBuckets(0.001, 2, 14),
|
||||
})
|
||||
proposePending = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "etcd",
|
||||
|
||||
77
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/raft.go
generated
vendored
77
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/raft.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/contention"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft"
|
||||
@@ -31,7 +32,6 @@ import (
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
@@ -76,13 +76,14 @@ type RaftTimer interface {
|
||||
Term() uint64
|
||||
}
|
||||
|
||||
// apply contains entries, snapshot be applied.
|
||||
// After applied all the items, the application needs
|
||||
// to send notification to done chan.
|
||||
// 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
|
||||
// raftDone before assuming the raft messages are stable.
|
||||
type apply struct {
|
||||
entries []raftpb.Entry
|
||||
snapshot raftpb.Snapshot
|
||||
done chan struct{}
|
||||
raftDone <-chan struct{} // rx {} after raft has persisted messages
|
||||
}
|
||||
|
||||
type raftNode struct {
|
||||
@@ -117,6 +118,8 @@ type raftNode struct {
|
||||
// If transport is nil, server will panic.
|
||||
transport rafthttp.Transporter
|
||||
|
||||
td *contention.TimeoutDetector
|
||||
|
||||
stopped chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
@@ -130,10 +133,20 @@ func (r *raftNode) start(s *EtcdServer) {
|
||||
r.stopped = make(chan struct{})
|
||||
r.done = make(chan struct{})
|
||||
|
||||
heartbeat := 200 * time.Millisecond
|
||||
if s.cfg != nil {
|
||||
heartbeat = time.Duration(s.cfg.TickMs) * time.Millisecond
|
||||
}
|
||||
// set up contention detectors for raft heartbeat message.
|
||||
// expect to send a heartbeat within 2 heartbeat intervals.
|
||||
r.td = contention.NewTimeoutDetector(2 * heartbeat)
|
||||
|
||||
go func() {
|
||||
var syncC <-chan time.Time
|
||||
|
||||
defer r.onStop()
|
||||
islead := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-r.ticker:
|
||||
@@ -147,29 +160,54 @@ func (r *raftNode) start(s *EtcdServer) {
|
||||
}
|
||||
atomic.StoreUint64(&r.lead, rd.SoftState.Lead)
|
||||
if rd.RaftState == raft.StateLeader {
|
||||
islead = true
|
||||
// TODO: raft should send server a notification through chan when
|
||||
// it promotes or demotes instead of modifying server directly.
|
||||
syncC = r.s.SyncTicker
|
||||
if r.s.lessor != nil {
|
||||
r.s.lessor.Promote(r.s.cfg.electionTimeout())
|
||||
}
|
||||
// TODO: remove the nil checking
|
||||
// current test utility does not provide the stats
|
||||
if r.s.stats != nil {
|
||||
r.s.stats.BecomeLeader()
|
||||
}
|
||||
if r.s.compactor != nil {
|
||||
r.s.compactor.Resume()
|
||||
}
|
||||
r.td.Reset()
|
||||
} else {
|
||||
islead = false
|
||||
if r.s.lessor != nil {
|
||||
r.s.lessor.Demote()
|
||||
}
|
||||
if r.s.compactor != nil {
|
||||
r.s.compactor.Pause()
|
||||
}
|
||||
syncC = nil
|
||||
}
|
||||
}
|
||||
|
||||
apply := apply{
|
||||
raftDone := make(chan struct{}, 1)
|
||||
ap := apply{
|
||||
entries: rd.CommittedEntries,
|
||||
snapshot: rd.Snapshot,
|
||||
done: make(chan struct{}),
|
||||
raftDone: raftDone,
|
||||
}
|
||||
|
||||
select {
|
||||
case r.applyc <- apply:
|
||||
case r.applyc <- ap:
|
||||
case <-r.stopped:
|
||||
return
|
||||
}
|
||||
|
||||
// the leader can write to its disk in parallel with replicating to the followers and them
|
||||
// writing to their disks.
|
||||
// For more details, check raft thesis 10.2.1
|
||||
if islead {
|
||||
r.s.send(rd.Messages)
|
||||
}
|
||||
|
||||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
if err := r.storage.SaveSnap(rd.Snapshot); err != nil {
|
||||
plog.Fatalf("raft save snapshot error: %v", err)
|
||||
@@ -182,13 +220,10 @@ func (r *raftNode) start(s *EtcdServer) {
|
||||
}
|
||||
r.raftStorage.Append(rd.Entries)
|
||||
|
||||
r.s.send(rd.Messages)
|
||||
|
||||
select {
|
||||
case <-apply.done:
|
||||
case <-r.stopped:
|
||||
return
|
||||
if !islead {
|
||||
r.s.send(rd.Messages)
|
||||
}
|
||||
raftDone <- struct{}{}
|
||||
r.Advance()
|
||||
case <-syncC:
|
||||
r.s.sync(r.s.cfg.ReqTimeout())
|
||||
@@ -253,7 +288,7 @@ func startNode(cfg *ServerConfig, cl *cluster, ids []types.ID) (id types.ID, n r
|
||||
ClusterID: uint64(cl.ID()),
|
||||
},
|
||||
)
|
||||
if err := os.MkdirAll(cfg.SnapDir(), privateDirMode); err != nil {
|
||||
if err = os.MkdirAll(cfg.SnapDir(), privateDirMode); err != nil {
|
||||
plog.Fatalf("create snapshot directory error: %v", err)
|
||||
}
|
||||
if w, err = wal.Create(cfg.WALDir(), metadata); err != nil {
|
||||
@@ -278,6 +313,11 @@ func startNode(cfg *ServerConfig, cl *cluster, ids []types.ID) (id types.ID, n r
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
}
|
||||
|
||||
if cfg.V3demo {
|
||||
c.CheckQuorum = true
|
||||
}
|
||||
|
||||
n = raft.StartNode(c, peers)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
@@ -310,6 +350,11 @@ func restartNode(cfg *ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *clust
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
}
|
||||
|
||||
if cfg.V3demo {
|
||||
c.CheckQuorum = true
|
||||
}
|
||||
|
||||
n := raft.RestartNode(c)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
@@ -373,7 +418,7 @@ func restartAsStandaloneNode(cfg *ServerConfig, snapshot *raftpb.Snapshot) (type
|
||||
// the entries. The given snapshot/entries can contain two kinds of
|
||||
// ID-related entry:
|
||||
// - ConfChangeAddNode, in which case the contained ID will be added into the set.
|
||||
// - ConfChangeAddRemove, in which case the contained ID will be removed from the set.
|
||||
// - ConfChangeRemoveNode, in which case the contained ID will be removed from the set.
|
||||
func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
||||
ids := make(map[uint64]bool)
|
||||
if snap != nil {
|
||||
|
||||
491
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/server.go
generated
vendored
491
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/server.go
generated
vendored
@@ -16,6 +16,7 @@ package etcdserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
@@ -23,18 +24,22 @@ import (
|
||||
"os"
|
||||
"path"
|
||||
"regexp"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/auth"
|
||||
"github.com/coreos/etcd/compactor"
|
||||
"github.com/coreos/etcd/discovery"
|
||||
"github.com/coreos/etcd/etcdserver/etcdhttp/httptypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/stats"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/pkg/fileutil"
|
||||
"github.com/coreos/etcd/pkg/idutil"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/runtime"
|
||||
"github.com/coreos/etcd/pkg/timeutil"
|
||||
"github.com/coreos/etcd/pkg/schedule"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/pkg/wait"
|
||||
"github.com/coreos/etcd/raft"
|
||||
@@ -42,6 +47,7 @@ import (
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
"github.com/coreos/etcd/snap"
|
||||
dstorage "github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/version"
|
||||
"github.com/coreos/etcd/wal"
|
||||
@@ -59,8 +65,18 @@ const (
|
||||
StoreClusterPrefix = "/0"
|
||||
StoreKeysPrefix = "/1"
|
||||
|
||||
purgeFileInterval = 30 * time.Second
|
||||
monitorVersionInterval = 5 * time.Second
|
||||
purgeFileInterval = 30 * time.Second
|
||||
// monitorVersionInterval should be smaller than the timeout
|
||||
// on the connection. Or we will not be able to reuse the connection
|
||||
// (since it will timeout).
|
||||
monitorVersionInterval = rafthttp.ConnWriteTimeout - time.Second
|
||||
|
||||
databaseFilename = "db"
|
||||
// max number of in-flight snapshot messages etcdserver allows to have
|
||||
// This number is more than enough for most clusters with 5 machines.
|
||||
maxInFlightMsgSnap = 16
|
||||
|
||||
releaseDelayAfterSnapshot = 30 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -116,12 +132,12 @@ type Server interface {
|
||||
// ErrIDNotFound if member ID is not in the cluster.
|
||||
RemoveMember(ctx context.Context, id uint64) error
|
||||
|
||||
// UpdateMember attempts to update a existing member in the cluster. It will
|
||||
// UpdateMember attempts to update an existing member in the cluster. It will
|
||||
// return ErrIDNotFound if the member ID does not exist.
|
||||
UpdateMember(ctx context.Context, updateMemb Member) error
|
||||
|
||||
// ClusterVersion is the cluster-wide minimum major.minor version.
|
||||
// Cluster version is set to the min version that a etcd member is
|
||||
// Cluster version is set to the min version that an etcd member is
|
||||
// compatible with when first bootstrap.
|
||||
//
|
||||
// ClusterVersion is nil until the cluster is bootstrapped (has a quorum).
|
||||
@@ -156,35 +172,59 @@ type EtcdServer struct {
|
||||
cluster *cluster
|
||||
|
||||
store store.Store
|
||||
kv dstorage.KV
|
||||
|
||||
kv dstorage.ConsistentWatchableKV
|
||||
lessor lease.Lessor
|
||||
bemu sync.Mutex
|
||||
be backend.Backend
|
||||
authStore auth.AuthStore
|
||||
|
||||
stats *stats.ServerStats
|
||||
lstats *stats.LeaderStats
|
||||
|
||||
SyncTicker <-chan time.Time
|
||||
// compactor is used to auto-compact the KV.
|
||||
compactor *compactor.Periodic
|
||||
|
||||
// consistent index used to hold the offset of current executing entry
|
||||
// It is initialized to 0 before executing any entry.
|
||||
consistIndex consistentIndex
|
||||
|
||||
// peerRt used to send requests (version, lease) to peers.
|
||||
peerRt http.RoundTripper
|
||||
reqIDGen *idutil.Generator
|
||||
|
||||
// forceVersionC is used to force the version monitor loop
|
||||
// to detect the cluster version immediately.
|
||||
forceVersionC chan struct{}
|
||||
|
||||
msgSnapC chan raftpb.Message
|
||||
|
||||
// count the number of inflight snapshots.
|
||||
// MUST use atomic operation to access this field.
|
||||
inflightSnapshots int64
|
||||
}
|
||||
|
||||
// NewServer creates a new EtcdServer from the supplied configuration. The
|
||||
// configuration is considered static for the lifetime of the EtcdServer.
|
||||
func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
st := store.New(StoreClusterPrefix, StoreKeysPrefix)
|
||||
|
||||
var w *wal.WAL
|
||||
var n raft.Node
|
||||
var s *raft.MemoryStorage
|
||||
var id types.ID
|
||||
var cl *cluster
|
||||
var (
|
||||
w *wal.WAL
|
||||
n raft.Node
|
||||
s *raft.MemoryStorage
|
||||
id types.ID
|
||||
cl *cluster
|
||||
)
|
||||
|
||||
if terr := fileutil.TouchDirAll(cfg.DataDir); terr != nil {
|
||||
return nil, fmt.Errorf("cannot access data directory: %v", terr)
|
||||
}
|
||||
|
||||
if !cfg.V3demo && fileutil.Exist(path.Join(cfg.SnapDir(), databaseFilename)) {
|
||||
return nil, errors.New("experimental-v3demo cannot be disabled once it is enabled")
|
||||
}
|
||||
|
||||
// Run the migrations.
|
||||
dataVer, err := version.DetectDataDir(cfg.DataDir)
|
||||
if err != nil {
|
||||
@@ -197,6 +237,10 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
haveWAL := wal.Exist(cfg.WALDir())
|
||||
ss := snap.New(cfg.SnapDir())
|
||||
|
||||
prt, err := rafthttp.NewRoundTripper(cfg.PeerTLSInfo, cfg.peerDialTimeout())
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var remotes []*Member
|
||||
switch {
|
||||
case !haveWAL && !cfg.NewCluster:
|
||||
@@ -207,14 +251,14 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
existingCluster, err := GetClusterFromRemotePeers(getRemotePeerURLs(cl, cfg.Name), cfg.Transport)
|
||||
existingCluster, err := GetClusterFromRemotePeers(getRemotePeerURLs(cl, cfg.Name), prt)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("cannot fetch cluster info from peer urls: %v", err)
|
||||
}
|
||||
if err := ValidateClusterAndAssignIDs(cl, existingCluster); err != nil {
|
||||
return nil, fmt.Errorf("error validating peerURLs %s: %v", existingCluster, err)
|
||||
}
|
||||
if !isCompatibleWithCluster(cl, cl.MemberByName(cfg.Name).ID, cfg.Transport) {
|
||||
if !isCompatibleWithCluster(cl, cl.MemberByName(cfg.Name).ID, prt) {
|
||||
return nil, fmt.Errorf("incomptible with current running cluster")
|
||||
}
|
||||
|
||||
@@ -232,11 +276,13 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
return nil, err
|
||||
}
|
||||
m := cl.MemberByName(cfg.Name)
|
||||
if isMemberBootstrapped(cl, cfg.Name, cfg.Transport) {
|
||||
if isMemberBootstrapped(cl, cfg.Name, prt, cfg.bootstrapTimeout()) {
|
||||
return nil, fmt.Errorf("member %s has already been bootstrapped", m.ID)
|
||||
}
|
||||
if cfg.ShouldDiscover() {
|
||||
str, err := discovery.JoinCluster(cfg.DiscoveryURL, cfg.DiscoveryProxy, m.ID, cfg.InitialPeerURLsMap.String())
|
||||
var str string
|
||||
var err error
|
||||
str, err = discovery.JoinCluster(cfg.DiscoveryURL, cfg.DiscoveryProxy, m.ID, cfg.InitialPeerURLsMap.String())
|
||||
if err != nil {
|
||||
return nil, &DiscoveryError{Op: "join", Err: err}
|
||||
}
|
||||
@@ -266,7 +312,9 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
if cfg.ShouldDiscover() {
|
||||
plog.Warningf("discovery token ignored since a cluster has already been initialized. Valid log found at %q", cfg.WALDir())
|
||||
}
|
||||
snapshot, err := ss.Load()
|
||||
var snapshot *raftpb.Snapshot
|
||||
var err error
|
||||
snapshot, err = ss.Load()
|
||||
if err != nil && err != snap.ErrNoSnapshot {
|
||||
return nil, err
|
||||
}
|
||||
@@ -277,9 +325,6 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
plog.Infof("recovered store from snapshot at index %d", snapshot.Metadata.Index)
|
||||
}
|
||||
cfg.Print()
|
||||
if snapshot != nil {
|
||||
plog.Infof("loaded cluster information from store: %s", cl)
|
||||
}
|
||||
if !cfg.ForceNewCluster {
|
||||
id, cl, n, s, w = restartNode(cfg, snapshot)
|
||||
} else {
|
||||
@@ -319,19 +364,40 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
stats: sstats,
|
||||
lstats: lstats,
|
||||
SyncTicker: time.Tick(500 * time.Millisecond),
|
||||
reqIDGen: idutil.NewGenerator(uint8(id), time.Now()),
|
||||
peerRt: prt,
|
||||
reqIDGen: idutil.NewGenerator(uint16(id), time.Now()),
|
||||
forceVersionC: make(chan struct{}),
|
||||
msgSnapC: make(chan raftpb.Message, maxInFlightMsgSnap),
|
||||
}
|
||||
|
||||
if cfg.V3demo {
|
||||
srv.kv = dstorage.New(path.Join(cfg.DataDir, "member", "v3demo"))
|
||||
} else {
|
||||
// we do not care about the error of the removal
|
||||
os.RemoveAll(path.Join(cfg.DataDir, "member", "v3demo"))
|
||||
srv.be = backend.NewDefaultBackend(path.Join(cfg.SnapDir(), databaseFilename))
|
||||
srv.lessor = lease.NewLessor(srv.be)
|
||||
srv.kv = dstorage.New(srv.be, srv.lessor, &srv.consistIndex)
|
||||
srv.authStore = auth.NewAuthStore(srv)
|
||||
if h := cfg.AutoCompactionRetention; h != 0 {
|
||||
srv.compactor = compactor.NewPeriodic(h, srv.kv, srv)
|
||||
srv.compactor.Run()
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move transport initialization near the definition of remote
|
||||
tr := rafthttp.NewTransporter(cfg.Transport, id, cl.ID(), srv, srv.errorc, sstats, lstats)
|
||||
tr := &rafthttp.Transport{
|
||||
TLSInfo: cfg.PeerTLSInfo,
|
||||
DialTimeout: cfg.peerDialTimeout(),
|
||||
ID: id,
|
||||
URLs: cfg.PeerURLs,
|
||||
ClusterID: cl.ID(),
|
||||
Raft: srv,
|
||||
Snapshotter: ss,
|
||||
ServerStats: sstats,
|
||||
LeaderStats: lstats,
|
||||
ErrorC: srv.errorc,
|
||||
V3demo: cfg.V3demo,
|
||||
}
|
||||
if err := tr.Start(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// add all remotes into transport
|
||||
for _, m := range remotes {
|
||||
if m.ID != id {
|
||||
@@ -344,6 +410,7 @@ func NewServer(cfg *ServerConfig) (*EtcdServer, error) {
|
||||
}
|
||||
}
|
||||
srv.r.transport = tr
|
||||
|
||||
return srv, nil
|
||||
}
|
||||
|
||||
@@ -403,6 +470,8 @@ func (s *EtcdServer) Cluster() Cluster { return s.cluster }
|
||||
|
||||
func (s *EtcdServer) RaftHandler() http.Handler { return s.r.transport.Handler() }
|
||||
|
||||
func (s *EtcdServer) Lessor() lease.Lessor { return s.lessor }
|
||||
|
||||
func (s *EtcdServer) Process(ctx context.Context, m raftpb.Message) error {
|
||||
if s.cluster.IsIDRemoved(types.ID(m.From)) {
|
||||
plog.Warningf("reject message from removed member %s", types.ID(m.From).String())
|
||||
@@ -418,81 +487,70 @@ func (s *EtcdServer) IsIDRemoved(id uint64) bool { return s.cluster.IsIDRemoved(
|
||||
|
||||
func (s *EtcdServer) ReportUnreachable(id uint64) { s.r.ReportUnreachable(id) }
|
||||
|
||||
// ReportSnapshot reports snapshot sent status to the raft state machine,
|
||||
// and clears the used snapshot from the snapshot store.
|
||||
func (s *EtcdServer) ReportSnapshot(id uint64, status raft.SnapshotStatus) {
|
||||
s.r.ReportSnapshot(id, status)
|
||||
}
|
||||
|
||||
type etcdProgress struct {
|
||||
confState raftpb.ConfState
|
||||
snapi uint64
|
||||
appliedi uint64
|
||||
}
|
||||
|
||||
func (s *EtcdServer) run() {
|
||||
snap, err := s.r.raftStorage.Snapshot()
|
||||
if err != nil {
|
||||
plog.Panicf("get snapshot from raft storage error: %v", err)
|
||||
}
|
||||
confState := snap.Metadata.ConfState
|
||||
snapi := snap.Metadata.Index
|
||||
appliedi := snapi
|
||||
s.r.start(s)
|
||||
|
||||
// asynchronously accept apply packets, dispatch progress in-order
|
||||
sched := schedule.NewFIFOScheduler()
|
||||
ep := etcdProgress{
|
||||
confState: snap.Metadata.ConfState,
|
||||
snapi: snap.Metadata.Index,
|
||||
appliedi: snap.Metadata.Index,
|
||||
}
|
||||
|
||||
defer func() {
|
||||
s.r.stop()
|
||||
sched.Stop()
|
||||
|
||||
// kv, lessor and backend can be nil if running without v3 enabled
|
||||
// or running unit tests.
|
||||
if s.lessor != nil {
|
||||
s.lessor.Stop()
|
||||
}
|
||||
if s.kv != nil {
|
||||
s.kv.Close()
|
||||
}
|
||||
if s.be != nil {
|
||||
s.be.Close()
|
||||
}
|
||||
if s.compactor != nil {
|
||||
s.compactor.Stop()
|
||||
}
|
||||
close(s.done)
|
||||
}()
|
||||
|
||||
var shouldstop bool
|
||||
var expiredLeaseC <-chan []*lease.Lease
|
||||
if s.lessor != nil {
|
||||
expiredLeaseC = s.lessor.ExpiredLeasesC()
|
||||
}
|
||||
|
||||
for {
|
||||
select {
|
||||
case apply := <-s.r.apply():
|
||||
// apply snapshot
|
||||
if !raft.IsEmptySnap(apply.snapshot) {
|
||||
if apply.snapshot.Metadata.Index <= appliedi {
|
||||
plog.Panicf("snapshot index [%d] should > appliedi[%d] + 1",
|
||||
apply.snapshot.Metadata.Index, appliedi)
|
||||
case ap := <-s.r.apply():
|
||||
f := func(context.Context) { s.applyAll(&ep, &ap) }
|
||||
sched.Schedule(f)
|
||||
case leases := <-expiredLeaseC:
|
||||
go func() {
|
||||
for _, l := range leases {
|
||||
s.LeaseRevoke(context.TODO(), &pb.LeaseRevokeRequest{ID: int64(l.ID)})
|
||||
}
|
||||
|
||||
if err := s.store.Recovery(apply.snapshot.Data); err != nil {
|
||||
plog.Panicf("recovery store error: %v", err)
|
||||
}
|
||||
s.cluster.Recover()
|
||||
|
||||
// recover raft transport
|
||||
s.r.transport.RemoveAllPeers()
|
||||
for _, m := range s.cluster.Members() {
|
||||
if m.ID == s.ID() {
|
||||
continue
|
||||
}
|
||||
s.r.transport.AddPeer(m.ID, m.PeerURLs)
|
||||
}
|
||||
|
||||
appliedi = apply.snapshot.Metadata.Index
|
||||
snapi = appliedi
|
||||
confState = apply.snapshot.Metadata.ConfState
|
||||
plog.Infof("recovered from incoming snapshot at index %d", snapi)
|
||||
}
|
||||
|
||||
// apply entries
|
||||
if len(apply.entries) != 0 {
|
||||
firsti := apply.entries[0].Index
|
||||
if firsti > appliedi+1 {
|
||||
plog.Panicf("first index of committed entry[%d] should <= appliedi[%d] + 1", firsti, appliedi)
|
||||
}
|
||||
var ents []raftpb.Entry
|
||||
if appliedi+1-firsti < uint64(len(apply.entries)) {
|
||||
ents = apply.entries[appliedi+1-firsti:]
|
||||
}
|
||||
if appliedi, shouldstop = s.apply(ents, &confState); shouldstop {
|
||||
go s.stopWithDelay(10*100*time.Millisecond, fmt.Errorf("the member has been permanently removed from the cluster"))
|
||||
}
|
||||
}
|
||||
|
||||
// wait for the raft routine to finish the disk writes before triggering a
|
||||
// snapshot. or applied index might be greater than the last index in raft
|
||||
// storage, since the raft routine might be slower than apply routine.
|
||||
apply.done <- struct{}{}
|
||||
|
||||
// trigger snapshot
|
||||
if appliedi-snapi > s.snapCount {
|
||||
plog.Infof("start to snapshot (applied: %d, lastsnap: %d)", appliedi, snapi)
|
||||
s.snapshot(appliedi, confState)
|
||||
snapi = appliedi
|
||||
}
|
||||
}()
|
||||
case err := <-s.errorc:
|
||||
plog.Errorf("%s", err)
|
||||
plog.Infof("the data-dir used by this member must be removed.")
|
||||
@@ -503,6 +561,127 @@ func (s *EtcdServer) run() {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EtcdServer) applyAll(ep *etcdProgress, apply *apply) {
|
||||
s.applySnapshot(ep, apply)
|
||||
s.applyEntries(ep, apply)
|
||||
// wait for the raft routine to finish the disk writes before triggering a
|
||||
// snapshot. or applied index might be greater than the last index in raft
|
||||
// storage, since the raft routine might be slower than apply routine.
|
||||
<-apply.raftDone
|
||||
s.triggerSnapshot(ep)
|
||||
select {
|
||||
// snapshot requested via send()
|
||||
case m := <-s.msgSnapC:
|
||||
merged := s.createMergedSnapshotMessage(m, ep.appliedi, ep.confState)
|
||||
s.sendMergedSnap(merged)
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EtcdServer) applySnapshot(ep *etcdProgress, apply *apply) {
|
||||
if raft.IsEmptySnap(apply.snapshot) {
|
||||
return
|
||||
}
|
||||
|
||||
if apply.snapshot.Metadata.Index <= ep.appliedi {
|
||||
plog.Panicf("snapshot index [%d] should > appliedi[%d] + 1",
|
||||
apply.snapshot.Metadata.Index, ep.appliedi)
|
||||
}
|
||||
|
||||
if s.cfg.V3demo {
|
||||
snapfn, err := s.r.storage.DBFilePath(apply.snapshot.Metadata.Index)
|
||||
if err != nil {
|
||||
plog.Panicf("get database snapshot file path error: %v", err)
|
||||
}
|
||||
|
||||
fn := path.Join(s.cfg.SnapDir(), databaseFilename)
|
||||
if err := os.Rename(snapfn, fn); err != nil {
|
||||
plog.Panicf("rename snapshot file error: %v", err)
|
||||
}
|
||||
|
||||
newbe := backend.NewDefaultBackend(fn)
|
||||
if err := s.kv.Restore(newbe); err != nil {
|
||||
plog.Panicf("restore KV error: %v", err)
|
||||
}
|
||||
|
||||
// Closing old backend might block until all the txns
|
||||
// on the backend are finished.
|
||||
// We do not want to wait on closing the old backend.
|
||||
s.bemu.Lock()
|
||||
oldbe := s.be
|
||||
go func() {
|
||||
if err := oldbe.Close(); err != nil {
|
||||
plog.Panicf("close backend error: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
s.be = newbe
|
||||
s.bemu.Unlock()
|
||||
|
||||
if s.lessor != nil {
|
||||
s.lessor.Recover(newbe, s.kv)
|
||||
}
|
||||
}
|
||||
if err := s.store.Recovery(apply.snapshot.Data); err != nil {
|
||||
plog.Panicf("recovery store error: %v", err)
|
||||
}
|
||||
s.cluster.Recover()
|
||||
|
||||
// recover raft transport
|
||||
s.r.transport.RemoveAllPeers()
|
||||
for _, m := range s.cluster.Members() {
|
||||
if m.ID == s.ID() {
|
||||
continue
|
||||
}
|
||||
s.r.transport.AddPeer(m.ID, m.PeerURLs)
|
||||
}
|
||||
|
||||
ep.appliedi = apply.snapshot.Metadata.Index
|
||||
ep.snapi = ep.appliedi
|
||||
ep.confState = apply.snapshot.Metadata.ConfState
|
||||
plog.Infof("recovered from incoming snapshot at index %d", ep.snapi)
|
||||
}
|
||||
|
||||
func (s *EtcdServer) applyEntries(ep *etcdProgress, apply *apply) {
|
||||
if len(apply.entries) == 0 {
|
||||
return
|
||||
}
|
||||
firsti := apply.entries[0].Index
|
||||
if firsti > ep.appliedi+1 {
|
||||
plog.Panicf("first index of committed entry[%d] should <= appliedi[%d] + 1", firsti, ep.appliedi)
|
||||
}
|
||||
var ents []raftpb.Entry
|
||||
if ep.appliedi+1-firsti < uint64(len(apply.entries)) {
|
||||
ents = apply.entries[ep.appliedi+1-firsti:]
|
||||
}
|
||||
if len(ents) == 0 {
|
||||
return
|
||||
}
|
||||
var shouldstop bool
|
||||
if ep.appliedi, shouldstop = s.apply(ents, &ep.confState); shouldstop {
|
||||
go s.stopWithDelay(10*100*time.Millisecond, fmt.Errorf("the member has been permanently removed from the cluster"))
|
||||
}
|
||||
}
|
||||
|
||||
func (s *EtcdServer) triggerSnapshot(ep *etcdProgress) {
|
||||
if ep.appliedi-ep.snapi <= s.snapCount {
|
||||
return
|
||||
}
|
||||
|
||||
// When sending a snapshot, etcd will pause compaction.
|
||||
// After receives a snapshot, the slow follower needs to get all the entries right after
|
||||
// the snapshot sent to catch up. If we do not pause compaction, the log entries right after
|
||||
// the snapshot sent might already be compacted. It happens when the snapshot takes long time
|
||||
// to send and save. Pausing compaction avoids triggering a snapshot sending cycle.
|
||||
if atomic.LoadInt64(&s.inflightSnapshots) != 0 {
|
||||
return
|
||||
}
|
||||
|
||||
plog.Infof("start to snapshot (applied: %d, lastsnap: %d)", ep.appliedi, ep.snapi)
|
||||
s.snapshot(ep.appliedi, ep.confState)
|
||||
ep.snapi = ep.appliedi
|
||||
}
|
||||
|
||||
// Stop stops the server gracefully, and shuts down the running goroutine.
|
||||
// Stop should be called after a Start(s), otherwise it will block forever.
|
||||
func (s *EtcdServer) Stop() {
|
||||
@@ -515,7 +694,10 @@ func (s *EtcdServer) Stop() {
|
||||
}
|
||||
|
||||
func (s *EtcdServer) stopWithDelay(d time.Duration, err error) {
|
||||
time.Sleep(d)
|
||||
select {
|
||||
case <-time.After(d):
|
||||
case <-s.done:
|
||||
}
|
||||
select {
|
||||
case s.errorc <- err:
|
||||
default:
|
||||
@@ -554,7 +736,7 @@ func (s *EtcdServer) Do(ctx context.Context, r pb.Request) (Response, error) {
|
||||
|
||||
select {
|
||||
case x := <-ch:
|
||||
proposeDurations.Observe(float64(time.Since(start).Nanoseconds() / int64(time.Millisecond)))
|
||||
proposeDurations.Observe(float64(time.Since(start)) / float64(time.Second))
|
||||
resp := x.(Response)
|
||||
return resp, resp.err
|
||||
case <-ctx.Done():
|
||||
@@ -603,6 +785,12 @@ func (s *EtcdServer) LeaderStats() []byte {
|
||||
func (s *EtcdServer) StoreStats() []byte { return s.store.JsonStats() }
|
||||
|
||||
func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error {
|
||||
if s.cfg.StrictReconfigCheck && !s.cluster.isReadyToAddNewMember() {
|
||||
// If s.cfg.StrictReconfigCheck is false, it means the option -strict-reconfig-check isn't passed to etcd.
|
||||
// In such a case adding a new member is allowed unconditionally
|
||||
return ErrNotEnoughStartedMembers
|
||||
}
|
||||
|
||||
// TODO: move Member to protobuf type
|
||||
b, err := json.Marshal(memb)
|
||||
if err != nil {
|
||||
@@ -617,6 +805,12 @@ func (s *EtcdServer) AddMember(ctx context.Context, memb Member) error {
|
||||
}
|
||||
|
||||
func (s *EtcdServer) RemoveMember(ctx context.Context, id uint64) error {
|
||||
if s.cfg.StrictReconfigCheck && !s.cluster.isReadyToRemoveMember(id) {
|
||||
// If s.cfg.StrictReconfigCheck is false, it means the option -strict-reconfig-check isn't passed to etcd.
|
||||
// In such a case removing a member is allowed unconditionally
|
||||
return ErrNotEnoughStartedMembers
|
||||
}
|
||||
|
||||
cc := raftpb.ConfChange{
|
||||
Type: raftpb.ConfChangeRemoveNode,
|
||||
NodeID: id,
|
||||
@@ -638,17 +832,20 @@ func (s *EtcdServer) UpdateMember(ctx context.Context, memb Member) error {
|
||||
}
|
||||
|
||||
// Implement the RaftTimer interface
|
||||
|
||||
func (s *EtcdServer) Index() uint64 { return atomic.LoadUint64(&s.r.index) }
|
||||
|
||||
func (s *EtcdServer) Term() uint64 { return atomic.LoadUint64(&s.r.term) }
|
||||
|
||||
// Only for testing purpose
|
||||
// Lead is only for testing purposes.
|
||||
// TODO: add Raft server interface to expose raft related info:
|
||||
// Index, Term, Lead, Committed, Applied, LastIndex, etc.
|
||||
func (s *EtcdServer) Lead() uint64 { return atomic.LoadUint64(&s.r.lead) }
|
||||
|
||||
func (s *EtcdServer) Leader() types.ID { return types.ID(s.Lead()) }
|
||||
|
||||
func (s *EtcdServer) IsPprofEnabled() bool { return s.cfg.EnablePprof }
|
||||
|
||||
// configure sends a configuration change through consensus and
|
||||
// then waits for it to be applied to the server. It
|
||||
// will block until the change is performed or there is an error.
|
||||
@@ -679,7 +876,7 @@ func (s *EtcdServer) configure(ctx context.Context, cc raftpb.ConfChange) error
|
||||
|
||||
// sync proposes a SYNC request and is non-blocking.
|
||||
// This makes no guarantee that the request will be proposed or performed.
|
||||
// The request will be cancelled after the given timeout.
|
||||
// The request will be canceled after the given timeout.
|
||||
func (s *EtcdServer) sync(timeout time.Duration) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
req := pb.Request{
|
||||
@@ -730,24 +927,75 @@ func (s *EtcdServer) publish(timeout time.Duration) {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: move this function into raft.go
|
||||
func (s *EtcdServer) send(ms []raftpb.Message) {
|
||||
for i := range ms {
|
||||
if s.cluster.IsIDRemoved(types.ID(ms[i].To)) {
|
||||
ms[i].To = 0
|
||||
}
|
||||
|
||||
if s.cfg.V3demo {
|
||||
if ms[i].Type == raftpb.MsgSnap {
|
||||
// There are two separate data store when v3 demo is enabled: the store for v2,
|
||||
// and the KV for v3.
|
||||
// The msgSnap only contains the most recent snapshot of store without KV.
|
||||
// So we need to redirect the msgSnap to etcd server main loop for merging in the
|
||||
// current store snapshot and KV snapshot.
|
||||
select {
|
||||
case s.msgSnapC <- ms[i]:
|
||||
default:
|
||||
// drop msgSnap if the inflight chan if full.
|
||||
}
|
||||
ms[i].To = 0
|
||||
}
|
||||
}
|
||||
if ms[i].Type == raftpb.MsgHeartbeat {
|
||||
ok, exceed := s.r.td.Observe(ms[i].To)
|
||||
if !ok {
|
||||
// TODO: limit request rate.
|
||||
plog.Warningf("failed to send out heartbeat on time (deadline exceeded for %v)", exceed)
|
||||
plog.Warningf("server is likely overloaded")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
s.r.transport.Send(ms)
|
||||
}
|
||||
|
||||
func (s *EtcdServer) sendMergedSnap(merged snap.Message) {
|
||||
atomic.AddInt64(&s.inflightSnapshots, 1)
|
||||
|
||||
s.r.transport.SendSnapshot(merged)
|
||||
go func() {
|
||||
select {
|
||||
case ok := <-merged.CloseNotify():
|
||||
// delay releasing inflight snapshot for another 30 seconds to
|
||||
// block log compaction.
|
||||
// If the follower still fails to catch up, it is probably just too slow
|
||||
// to catch up. We cannot avoid the snapshot cycle anyway.
|
||||
if ok {
|
||||
select {
|
||||
case <-time.After(releaseDelayAfterSnapshot):
|
||||
case <-s.done:
|
||||
}
|
||||
}
|
||||
atomic.AddInt64(&s.inflightSnapshots, -1)
|
||||
case <-s.done:
|
||||
return
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// apply takes entries received from Raft (after it has been committed) and
|
||||
// applies them to the current state of the EtcdServer.
|
||||
// The given entries should not be empty.
|
||||
func (s *EtcdServer) apply(es []raftpb.Entry, confState *raftpb.ConfState) (uint64, bool) {
|
||||
var applied uint64
|
||||
var shouldstop bool
|
||||
var err error
|
||||
for i := range es {
|
||||
e := es[i]
|
||||
// set the consistent index of current executing entry
|
||||
s.consistIndex.setConsistentIndex(e.Index)
|
||||
switch e.Type {
|
||||
case raftpb.EntryNormal:
|
||||
// raft state machine may generate noop entry when leader confirmation.
|
||||
@@ -770,12 +1018,15 @@ func (s *EtcdServer) apply(es []raftpb.Entry, confState *raftpb.ConfState) (uint
|
||||
case raftReq.V2 != nil:
|
||||
req := raftReq.V2
|
||||
s.w.Trigger(req.ID, s.applyRequest(*req))
|
||||
default:
|
||||
s.w.Trigger(raftReq.ID, s.applyV3Request(&raftReq))
|
||||
}
|
||||
}
|
||||
case raftpb.EntryConfChange:
|
||||
var cc raftpb.ConfChange
|
||||
pbutil.MustUnmarshal(&cc, e.Data)
|
||||
shouldstop, err = s.applyConfChange(cc, confState)
|
||||
removedSelf, err := s.applyConfChange(cc, confState)
|
||||
shouldstop = shouldstop || removedSelf
|
||||
s.w.Trigger(cc.ID, err)
|
||||
default:
|
||||
plog.Panicf("entry type should be either EntryNormal or EntryConfChange")
|
||||
@@ -793,24 +1044,30 @@ func (s *EtcdServer) applyRequest(r pb.Request) Response {
|
||||
f := func(ev *store.Event, err error) Response {
|
||||
return Response{Event: ev, err: err}
|
||||
}
|
||||
expr := timeutil.UnixNanoToTime(r.Expiration)
|
||||
|
||||
refresh, _ := pbutil.GetBool(r.Refresh)
|
||||
ttlOptions := store.TTLOptionSet{Refresh: refresh}
|
||||
if r.Expiration != 0 {
|
||||
ttlOptions.ExpireTime = time.Unix(0, r.Expiration)
|
||||
}
|
||||
|
||||
switch r.Method {
|
||||
case "POST":
|
||||
return f(s.store.Create(r.Path, r.Dir, r.Val, true, expr))
|
||||
return f(s.store.Create(r.Path, r.Dir, r.Val, true, ttlOptions))
|
||||
case "PUT":
|
||||
exists, existsSet := pbutil.GetBool(r.PrevExist)
|
||||
switch {
|
||||
case existsSet:
|
||||
if exists {
|
||||
if r.PrevIndex == 0 && r.PrevValue == "" {
|
||||
return f(s.store.Update(r.Path, r.Val, expr))
|
||||
return f(s.store.Update(r.Path, r.Val, ttlOptions))
|
||||
} else {
|
||||
return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, expr))
|
||||
return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, ttlOptions))
|
||||
}
|
||||
}
|
||||
return f(s.store.Create(r.Path, r.Dir, r.Val, false, expr))
|
||||
return f(s.store.Create(r.Path, r.Dir, r.Val, false, ttlOptions))
|
||||
case r.PrevIndex > 0 || r.PrevValue != "":
|
||||
return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, expr))
|
||||
return f(s.store.CompareAndSwap(r.Path, r.PrevValue, r.PrevIndex, r.Val, ttlOptions))
|
||||
default:
|
||||
// TODO (yicheng): cluster should be the owner of cluster prefix store
|
||||
// we should not modify cluster store here.
|
||||
@@ -820,12 +1077,15 @@ func (s *EtcdServer) applyRequest(r pb.Request) Response {
|
||||
if err := json.Unmarshal([]byte(r.Val), &attr); err != nil {
|
||||
plog.Panicf("unmarshal %s should never fail: %v", r.Val, err)
|
||||
}
|
||||
s.cluster.UpdateAttributes(id, attr)
|
||||
ok := s.cluster.UpdateAttributes(id, attr)
|
||||
if !ok {
|
||||
return Response{}
|
||||
}
|
||||
}
|
||||
if r.Path == path.Join(StoreClusterPrefix, "version") {
|
||||
s.cluster.SetVersion(semver.Must(semver.NewVersion(r.Val)))
|
||||
}
|
||||
return f(s.store.Set(r.Path, r.Dir, r.Val, expr))
|
||||
return f(s.store.Set(r.Path, r.Dir, r.Val, ttlOptions))
|
||||
}
|
||||
case "DELETE":
|
||||
switch {
|
||||
@@ -918,7 +1178,14 @@ func (s *EtcdServer) snapshot(snapi uint64, confState raftpb.ConfState) {
|
||||
}
|
||||
plog.Panicf("unexpected create snapshot error %v", err)
|
||||
}
|
||||
if err := s.r.storage.SaveSnap(snap); err != nil {
|
||||
if s.cfg.V3demo {
|
||||
// commit v3 storage because WAL file before snapshot index
|
||||
// could be removed after SaveSnap.
|
||||
s.getKV().Commit()
|
||||
}
|
||||
// SaveSnap saves the snapshot and releases the locked wal files
|
||||
// to the snapshot index.
|
||||
if err = s.r.storage.SaveSnap(snap); err != nil {
|
||||
plog.Fatalf("save snapshot error: %v", err)
|
||||
}
|
||||
plog.Infof("saved snapshot at index %d", snap.Metadata.Index)
|
||||
@@ -952,7 +1219,7 @@ func (s *EtcdServer) ClusterVersion() *semver.Version {
|
||||
return s.cluster.Version()
|
||||
}
|
||||
|
||||
// monitorVersions checks the member's version every monitorVersion interval.
|
||||
// monitorVersions checks the member's version every monitorVersionInterval.
|
||||
// It updates the cluster version if all members agrees on a higher one.
|
||||
// It prints out log if there is a member with a higher version than the
|
||||
// local version.
|
||||
@@ -969,9 +1236,9 @@ func (s *EtcdServer) monitorVersions() {
|
||||
continue
|
||||
}
|
||||
|
||||
v := decideClusterVersion(getVersions(s.cluster, s.id, s.cfg.Transport))
|
||||
v := decideClusterVersion(getVersions(s.cluster, s.id, s.peerRt))
|
||||
if v != nil {
|
||||
// only keep major.minor version for comparasion
|
||||
// only keep major.minor version for comparison
|
||||
v = &semver.Version{
|
||||
Major: v.Major,
|
||||
Minor: v.Minor,
|
||||
@@ -1054,21 +1321,11 @@ func (s *EtcdServer) parseProposeCtxErr(err error, start time.Time) error {
|
||||
}
|
||||
}
|
||||
|
||||
// isConnectedToQuorumSince checks whether the local member is connected to the
|
||||
// quorum of the cluster since the given time.
|
||||
func isConnectedToQuorumSince(transport rafthttp.Transporter, since time.Time, self types.ID, members []*Member) bool {
|
||||
var connectedNum int
|
||||
for _, m := range members {
|
||||
if m.ID == self || isConnectedSince(transport, since, m.ID) {
|
||||
connectedNum++
|
||||
}
|
||||
}
|
||||
return connectedNum >= (len(members)+1)/2
|
||||
func (s *EtcdServer) getKV() dstorage.ConsistentWatchableKV { return s.kv }
|
||||
func (s *EtcdServer) Backend() backend.Backend {
|
||||
s.bemu.Lock()
|
||||
defer s.bemu.Unlock()
|
||||
return s.be
|
||||
}
|
||||
|
||||
// isConnectedSince checks whether the local member is connected to the
|
||||
// remote member since the given time.
|
||||
func isConnectedSince(transport rafthttp.Transporter, since time.Time, remote types.ID) bool {
|
||||
t := transport.ActiveSince(remote)
|
||||
return !t.IsZero() && t.Before(since)
|
||||
}
|
||||
func (s *EtcdServer) AuthStore() auth.AuthStore { return s.authStore }
|
||||
|
||||
71
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/snapshot_merge.go
generated
vendored
Normal file
71
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/snapshot_merge.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/snap"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
)
|
||||
|
||||
// createMergedSnapshotMessage creates a snapshot message that contains: raft status (term, conf),
|
||||
// a snapshot of v2 store inside raft.Snapshot as []byte, a snapshot of v3 KV in the top level message
|
||||
// as ReadCloser.
|
||||
func (s *EtcdServer) createMergedSnapshotMessage(m raftpb.Message, snapi uint64, confState raftpb.ConfState) snap.Message {
|
||||
snapt, err := s.r.raftStorage.Term(snapi)
|
||||
if err != nil {
|
||||
log.Panicf("get term should never fail: %v", err)
|
||||
}
|
||||
|
||||
// get a snapshot of v2 store as []byte
|
||||
clone := s.store.Clone()
|
||||
d, err := clone.SaveNoCopy()
|
||||
if err != nil {
|
||||
plog.Panicf("store save should never fail: %v", err)
|
||||
}
|
||||
|
||||
// get a snapshot of v3 KV as readCloser
|
||||
rc := newSnapshotReaderCloser(s.be.Snapshot())
|
||||
|
||||
// put the []byte snapshot of store into raft snapshot and return the merged snapshot with
|
||||
// KV readCloser snapshot.
|
||||
snapshot := raftpb.Snapshot{
|
||||
Metadata: raftpb.SnapshotMetadata{
|
||||
Index: snapi,
|
||||
Term: snapt,
|
||||
ConfState: confState,
|
||||
},
|
||||
Data: d,
|
||||
}
|
||||
m.Snapshot = snapshot
|
||||
|
||||
return *snap.NewMessage(m, rc)
|
||||
}
|
||||
|
||||
func newSnapshotReaderCloser(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)
|
||||
}
|
||||
pw.CloseWithError(err)
|
||||
snapshot.Close()
|
||||
}()
|
||||
return pr
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/leader.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/leader.go
generated
vendored
@@ -24,6 +24,7 @@ import (
|
||||
// LeaderStats is used by the leader in an etcd cluster, and encapsulates
|
||||
// statistics about communication with its followers
|
||||
type LeaderStats struct {
|
||||
// Leader is the ID of the leader in the etcd cluster.
|
||||
// TODO(jonboulle): clarify that these are IDs, not names
|
||||
Leader string `json:"leader"`
|
||||
Followers map[string]*FollowerStats `json:"followers"`
|
||||
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/server.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/server.go
generated
vendored
@@ -27,6 +27,7 @@ import (
|
||||
// communication with other members of the cluster
|
||||
type ServerStats struct {
|
||||
Name string `json:"name"`
|
||||
// ID is the raft ID of the node.
|
||||
// TODO(jonboulle): use ID instead of name?
|
||||
ID string `json:"id"`
|
||||
State raft.StateType `json:"state"`
|
||||
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/stats.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/stats/stats.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package stats defines a standard interface for etcd cluster statistics.
|
||||
package stats
|
||||
|
||||
import "github.com/coreos/pkg/capnslog"
|
||||
|
||||
5
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/storage.go
generated
vendored
5
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/storage.go
generated
vendored
@@ -35,6 +35,9 @@ type Storage interface {
|
||||
Save(st raftpb.HardState, ents []raftpb.Entry) error
|
||||
// SaveSnap function saves snapshot to the underlying stable storage.
|
||||
SaveSnap(snap raftpb.Snapshot) error
|
||||
// DBFilePath returns the file path of database snapshot saved with given
|
||||
// id.
|
||||
DBFilePath(id uint64) (string, error)
|
||||
// Close closes the Storage and performs finalization.
|
||||
Close() error
|
||||
}
|
||||
@@ -104,7 +107,7 @@ func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID,
|
||||
return
|
||||
}
|
||||
|
||||
// upgradeWAL converts an older version of the etcdServer data to the newest version.
|
||||
// upgradeDataDir converts an older version of the etcdServer data to the newest version.
|
||||
// It must ensure that, after upgrading, the most recent version is present.
|
||||
func upgradeDataDir(baseDataDir string, name string, ver version.DataDirVersion) error {
|
||||
switch ver {
|
||||
|
||||
41
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/util.go
generated
vendored
Normal file
41
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/util.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package etcdserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
)
|
||||
|
||||
// isConnectedToQuorumSince checks whether the local member is connected to the
|
||||
// quorum of the cluster since the given time.
|
||||
func isConnectedToQuorumSince(transport rafthttp.Transporter, since time.Time, self types.ID, members []*Member) bool {
|
||||
var connectedNum int
|
||||
for _, m := range members {
|
||||
if m.ID == self || isConnectedSince(transport, since, m.ID) {
|
||||
connectedNum++
|
||||
}
|
||||
}
|
||||
return connectedNum >= (len(members)+1)/2
|
||||
}
|
||||
|
||||
// isConnectedSince checks whether the local member is connected to the
|
||||
// remote member since the given time.
|
||||
func isConnectedSince(transport rafthttp.Transporter, since time.Time, remote types.ID) bool {
|
||||
t := transport.ActiveSince(remote)
|
||||
return !t.IsZero() && t.Before(since)
|
||||
}
|
||||
544
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/v3demo_server.go
generated
vendored
544
Godeps/_workspace/src/github.com/coreos/etcd/etcdserver/v3demo_server.go
generated
vendored
@@ -16,70 +16,455 @@ package etcdserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/lease/leasehttp"
|
||||
dstorage "github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type V3DemoServer interface {
|
||||
V3DemoDo(ctx context.Context, r pb.InternalRaftRequest) proto.Message
|
||||
const (
|
||||
// the max request size that raft accepts.
|
||||
// TODO: make this a flag? But we probably do not want to
|
||||
// accept large request which might block raft stream. User
|
||||
// specify a large value might end up with shooting in the foot.
|
||||
maxRequestBytes = 1.5 * 1024 * 1024
|
||||
)
|
||||
|
||||
type RaftKV interface {
|
||||
Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error)
|
||||
Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error)
|
||||
DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error)
|
||||
Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error)
|
||||
Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
|
||||
Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error)
|
||||
}
|
||||
|
||||
func (s *EtcdServer) V3DemoDo(ctx context.Context, r pb.InternalRaftRequest) proto.Message {
|
||||
switch {
|
||||
case r.Range != nil:
|
||||
return doRange(s.kv, r.Range)
|
||||
case r.Put != nil:
|
||||
return doPut(s.kv, r.Put)
|
||||
case r.DeleteRange != nil:
|
||||
return doDeleteRange(s.kv, r.DeleteRange)
|
||||
case r.Txn != nil:
|
||||
return doTxn(s.kv, r.Txn)
|
||||
default:
|
||||
panic("not implemented")
|
||||
type Lessor interface {
|
||||
// LeaseCreate sends LeaseCreate request to raft and apply it after committed.
|
||||
LeaseCreate(ctx context.Context, r *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error)
|
||||
// LeaseRevoke sends LeaseRevoke request to raft and apply it after committed.
|
||||
LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
|
||||
|
||||
// LeaseRenew renews the lease with given ID. The renewed TTL is returned. Or an error
|
||||
// is returned.
|
||||
LeaseRenew(id lease.LeaseID) (int64, error)
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error)
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
if r.Serializable {
|
||||
return applyRange(noTxn, s.kv, r)
|
||||
}
|
||||
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.RangeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Put: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.PutResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{DeleteRange: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.DeleteRangeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Txn: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.TxnResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Compaction: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := result.resp.(*pb.CompactionResponse)
|
||||
if resp == nil {
|
||||
resp = &pb.CompactionResponse{}
|
||||
}
|
||||
if resp.Header == nil {
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
}
|
||||
resp.Header.Revision = s.kv.Rev()
|
||||
return resp, result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
|
||||
h, err := s.be.Hash()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pb.HashResponse{Header: &pb.ResponseHeader{Revision: s.kv.Rev()}, Hash: h}, nil
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseCreate(ctx context.Context, r *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
|
||||
// no id given? choose one
|
||||
for r.ID == int64(lease.NoLease) {
|
||||
// only use positive int64 id's
|
||||
r.ID = int64(s.reqIDGen.Next() & ((1 << 63) - 1))
|
||||
}
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseCreate: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.LeaseCreateResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseRevoke: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.LeaseRevokeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseRenew(id lease.LeaseID) (int64, error) {
|
||||
ttl, err := s.lessor.Renew(id)
|
||||
if err == nil {
|
||||
return ttl, nil
|
||||
}
|
||||
if err != lease.ErrNotPrimary {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// renewals don't go through raft; forward to leader manually
|
||||
leader := s.cluster.Member(s.Leader())
|
||||
for i := 0; i < 5 && leader == nil; i++ {
|
||||
// wait an election
|
||||
dur := time.Duration(s.cfg.ElectionTicks) * time.Duration(s.cfg.TickMs) * time.Millisecond
|
||||
select {
|
||||
case <-time.After(dur):
|
||||
leader = s.cluster.Member(s.Leader())
|
||||
case <-s.done:
|
||||
return -1, ErrStopped
|
||||
}
|
||||
}
|
||||
if leader == nil || len(leader.PeerURLs) == 0 {
|
||||
return -1, ErrNoLeader
|
||||
}
|
||||
|
||||
for _, url := range leader.PeerURLs {
|
||||
lurl := url + "/leases"
|
||||
ttl, err = leasehttp.RenewHTTP(id, lurl, s.peerRt, s.cfg.peerDialTimeout())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ttl, err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthEnable: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthEnableResponse), result.err
|
||||
}
|
||||
|
||||
type applyResult struct {
|
||||
resp proto.Message
|
||||
err error
|
||||
}
|
||||
|
||||
func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
|
||||
r.ID = s.reqIDGen.Next()
|
||||
|
||||
data, err := r.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) > maxRequestBytes {
|
||||
return nil, ErrRequestTooLarge
|
||||
}
|
||||
|
||||
ch := s.w.Register(r.ID)
|
||||
|
||||
s.r.Propose(ctx, data)
|
||||
|
||||
select {
|
||||
case x := <-ch:
|
||||
return x.(*applyResult), nil
|
||||
case <-ctx.Done():
|
||||
s.w.Trigger(r.ID, nil) // GC wait
|
||||
return nil, ctx.Err()
|
||||
case <-s.done:
|
||||
return nil, ErrStopped
|
||||
}
|
||||
}
|
||||
|
||||
func doPut(kv dstorage.KV, p *pb.PutRequest) *pb.PutResponse {
|
||||
resp := &pb.PutResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
rev := kv.Put(p.Key, p.Value)
|
||||
resp.Header.Revision = rev
|
||||
return resp
|
||||
// Watchable returns a watchable interface attached to the etcdserver.
|
||||
func (s *EtcdServer) Watchable() dstorage.Watchable {
|
||||
return s.getKV()
|
||||
}
|
||||
|
||||
func doRange(kv dstorage.KV, r *pb.RangeRequest) *pb.RangeResponse {
|
||||
const (
|
||||
// noTxn is an invalid txn ID.
|
||||
// To apply with independent Range, Put, Delete, you can pass noTxn
|
||||
// to apply functions instead of a valid txn ID.
|
||||
noTxn = -1
|
||||
)
|
||||
|
||||
func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) interface{} {
|
||||
kv := s.getKV()
|
||||
le := s.lessor
|
||||
|
||||
ar := &applyResult{}
|
||||
|
||||
switch {
|
||||
case r.Range != nil:
|
||||
ar.resp, ar.err = applyRange(noTxn, kv, r.Range)
|
||||
case r.Put != nil:
|
||||
ar.resp, ar.err = applyPut(noTxn, kv, le, r.Put)
|
||||
case r.DeleteRange != nil:
|
||||
ar.resp, ar.err = applyDeleteRange(noTxn, kv, r.DeleteRange)
|
||||
case r.Txn != nil:
|
||||
ar.resp, ar.err = applyTxn(kv, le, r.Txn)
|
||||
case r.Compaction != nil:
|
||||
ar.resp, ar.err = applyCompaction(kv, r.Compaction)
|
||||
case r.LeaseCreate != nil:
|
||||
ar.resp, ar.err = applyLeaseCreate(le, r.LeaseCreate)
|
||||
case r.LeaseRevoke != nil:
|
||||
ar.resp, ar.err = applyLeaseRevoke(le, r.LeaseRevoke)
|
||||
case r.AuthEnable != nil:
|
||||
ar.resp, ar.err = applyAuthEnable(s)
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
|
||||
return ar
|
||||
}
|
||||
|
||||
func applyPut(txnID int64, kv dstorage.KV, le lease.Lessor, p *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
resp := &pb.PutResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
var (
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
if txnID != noTxn {
|
||||
rev, err = kv.TxnPut(txnID, p.Key, p.Value, lease.LeaseID(p.Lease))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
leaseID := lease.LeaseID(p.Lease)
|
||||
if leaseID != lease.NoLease {
|
||||
if l := le.Lookup(leaseID); l == nil {
|
||||
return nil, lease.ErrLeaseNotFound
|
||||
}
|
||||
}
|
||||
rev = kv.Put(p.Key, p.Value, leaseID)
|
||||
}
|
||||
resp.Header.Revision = rev
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type kvSort struct{ kvs []storagepb.KeyValue }
|
||||
|
||||
func (s *kvSort) Swap(i, j int) {
|
||||
t := s.kvs[i]
|
||||
s.kvs[i] = s.kvs[j]
|
||||
s.kvs[j] = t
|
||||
}
|
||||
func (s *kvSort) Len() int { return len(s.kvs) }
|
||||
|
||||
type kvSortByKey struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByKey) Less(i, j int) bool {
|
||||
return bytes.Compare(s.kvs[i].Key, s.kvs[j].Key) < 0
|
||||
}
|
||||
|
||||
type kvSortByVersion struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByVersion) Less(i, j int) bool {
|
||||
return (s.kvs[i].Version - s.kvs[j].Version) < 0
|
||||
}
|
||||
|
||||
type kvSortByCreate struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByCreate) Less(i, j int) bool {
|
||||
return (s.kvs[i].CreateRevision - s.kvs[j].CreateRevision) < 0
|
||||
}
|
||||
|
||||
type kvSortByMod struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByMod) Less(i, j int) bool {
|
||||
return (s.kvs[i].ModRevision - s.kvs[j].ModRevision) < 0
|
||||
}
|
||||
|
||||
type kvSortByValue struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByValue) Less(i, j int) bool {
|
||||
return bytes.Compare(s.kvs[i].Value, s.kvs[j].Value) < 0
|
||||
}
|
||||
|
||||
func applyRange(txnID int64, kv dstorage.KV, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
resp := &pb.RangeResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
kvs, rev, err := kv.Range(r.Key, r.RangeEnd, r.Limit, 0)
|
||||
if err != nil {
|
||||
panic("not handled error")
|
||||
|
||||
var (
|
||||
kvs []storagepb.KeyValue
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
|
||||
if isGteRange(r.RangeEnd) {
|
||||
r.RangeEnd = []byte{}
|
||||
}
|
||||
|
||||
limit := r.Limit
|
||||
if r.SortOrder != pb.RangeRequest_NONE {
|
||||
// fetch everything; sort and truncate afterwards
|
||||
limit = 0
|
||||
}
|
||||
if limit > 0 {
|
||||
// fetch one extra for 'more' flag
|
||||
limit = limit + 1
|
||||
}
|
||||
|
||||
if txnID != noTxn {
|
||||
kvs, rev, err = kv.TxnRange(txnID, r.Key, r.RangeEnd, limit, r.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
kvs, rev, err = kv.Range(r.Key, r.RangeEnd, limit, r.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if r.SortOrder != pb.RangeRequest_NONE {
|
||||
var sorter sort.Interface
|
||||
switch {
|
||||
case r.SortTarget == pb.RangeRequest_KEY:
|
||||
sorter = &kvSortByKey{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_VERSION:
|
||||
sorter = &kvSortByVersion{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_CREATE:
|
||||
sorter = &kvSortByCreate{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_MOD:
|
||||
sorter = &kvSortByMod{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_VALUE:
|
||||
sorter = &kvSortByValue{&kvSort{kvs}}
|
||||
}
|
||||
switch {
|
||||
case r.SortOrder == pb.RangeRequest_ASCEND:
|
||||
sort.Sort(sorter)
|
||||
case r.SortOrder == pb.RangeRequest_DESCEND:
|
||||
sort.Sort(sort.Reverse(sorter))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Limit > 0 && len(kvs) > int(r.Limit) {
|
||||
kvs = kvs[:r.Limit]
|
||||
resp.More = true
|
||||
}
|
||||
|
||||
resp.Header.Revision = rev
|
||||
for i := range kvs {
|
||||
resp.Kvs = append(resp.Kvs, &kvs[i])
|
||||
}
|
||||
return resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func doDeleteRange(kv dstorage.KV, dr *pb.DeleteRangeRequest) *pb.DeleteRangeResponse {
|
||||
func applyDeleteRange(txnID int64, kv dstorage.KV, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
resp := &pb.DeleteRangeResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
_, rev := kv.DeleteRange(dr.Key, dr.RangeEnd)
|
||||
|
||||
var (
|
||||
n int64
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
|
||||
if isGteRange(dr.RangeEnd) {
|
||||
dr.RangeEnd = []byte{}
|
||||
}
|
||||
|
||||
if txnID != noTxn {
|
||||
n, rev, err = kv.TxnDeleteRange(txnID, dr.Key, dr.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
n, rev = kv.DeleteRange(dr.Key, dr.RangeEnd)
|
||||
}
|
||||
|
||||
resp.Deleted = n
|
||||
resp.Header.Revision = rev
|
||||
return resp
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func doTxn(kv dstorage.KV, rt *pb.TxnRequest) *pb.TxnResponse {
|
||||
func checkRequestLeases(le lease.Lessor, reqs []*pb.RequestUnion) error {
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestPut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
preq := tv.RequestPut
|
||||
if preq == nil || lease.LeaseID(preq.Lease) == lease.NoLease {
|
||||
continue
|
||||
}
|
||||
if l := le.Lookup(lease.LeaseID(preq.Lease)); l == nil {
|
||||
return lease.ErrLeaseNotFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequestRange(kv dstorage.KV, reqs []*pb.RequestUnion) error {
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestRange)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
greq := tv.RequestRange
|
||||
if greq == nil || greq.Revision == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if greq.Revision > kv.Rev() {
|
||||
return dstorage.ErrFutureRev
|
||||
}
|
||||
if greq.Revision < kv.FirstRev() {
|
||||
return dstorage.ErrCompacted
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func applyTxn(kv dstorage.KV, le lease.Lessor, rt *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
var revision int64
|
||||
|
||||
ok := true
|
||||
for _, c := range rt.Compare {
|
||||
if revision, ok = doCompare(kv, c); !ok {
|
||||
if revision, ok = applyCompare(kv, c); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
@@ -90,10 +475,29 @@ func doTxn(kv dstorage.KV, rt *pb.TxnRequest) *pb.TxnResponse {
|
||||
} else {
|
||||
reqs = rt.Failure
|
||||
}
|
||||
|
||||
if err := checkRequestLeases(le, reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := checkRequestRange(kv, reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// When executing the operations of txn, we need to hold the txn lock.
|
||||
// So the reader will not see any intermediate results.
|
||||
txnID := kv.TxnBegin()
|
||||
defer func() {
|
||||
err := kv.TxnEnd(txnID)
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("unexpected error when closing txn", txnID))
|
||||
}
|
||||
}()
|
||||
|
||||
resps := make([]*pb.ResponseUnion, len(reqs))
|
||||
for i := range reqs {
|
||||
resps[i] = doUnion(kv, reqs[i])
|
||||
resps[i] = applyUnion(txnID, kv, reqs[i])
|
||||
}
|
||||
|
||||
if len(resps) != 0 {
|
||||
revision += 1
|
||||
}
|
||||
@@ -103,22 +507,46 @@ func doTxn(kv dstorage.KV, rt *pb.TxnRequest) *pb.TxnResponse {
|
||||
txnResp.Header.Revision = revision
|
||||
txnResp.Responses = resps
|
||||
txnResp.Succeeded = ok
|
||||
return txnResp
|
||||
return txnResp, nil
|
||||
}
|
||||
|
||||
func doUnion(kv dstorage.KV, union *pb.RequestUnion) *pb.ResponseUnion {
|
||||
func applyCompaction(kv dstorage.KV, compaction *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
resp := &pb.CompactionResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
err := kv.Compact(compaction.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// get the current revision. which key to get is not important.
|
||||
_, resp.Header.Revision, _ = kv.Range([]byte("compaction"), nil, 1, 0)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func applyUnion(txnID int64, kv dstorage.KV, union *pb.RequestUnion) *pb.ResponseUnion {
|
||||
switch tv := union.Request.(type) {
|
||||
case *pb.RequestUnion_RequestRange:
|
||||
if tv.RequestRange != nil {
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseRange{ResponseRange: doRange(kv, tv.RequestRange)}}
|
||||
resp, err := applyRange(txnID, kv, tv.RequestRange)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseRange{ResponseRange: resp}}
|
||||
}
|
||||
case *pb.RequestUnion_RequestPut:
|
||||
if tv.RequestPut != nil {
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponsePut{ResponsePut: doPut(kv, tv.RequestPut)}}
|
||||
resp, err := applyPut(txnID, kv, nil, tv.RequestPut)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponsePut{ResponsePut: resp}}
|
||||
}
|
||||
case *pb.RequestUnion_RequestDeleteRange:
|
||||
if tv.RequestDeleteRange != nil {
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseDeleteRange{ResponseDeleteRange: doDeleteRange(kv, tv.RequestDeleteRange)}}
|
||||
resp, err := applyDeleteRange(txnID, kv, tv.RequestDeleteRange)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseDeleteRange{ResponseDeleteRange: resp}}
|
||||
}
|
||||
default:
|
||||
// empty union
|
||||
@@ -127,14 +555,29 @@ func doUnion(kv dstorage.KV, union *pb.RequestUnion) *pb.ResponseUnion {
|
||||
return nil
|
||||
}
|
||||
|
||||
func doCompare(kv dstorage.KV, c *pb.Compare) (int64, bool) {
|
||||
// applyCompare applies the compare request.
|
||||
// It returns the revision at which the comparison happens. If the comparison
|
||||
// succeeds, the it returns true. Otherwise it returns false.
|
||||
func applyCompare(kv dstorage.KV, c *pb.Compare) (int64, bool) {
|
||||
ckvs, rev, err := kv.Range(c.Key, nil, 1, 0)
|
||||
if err != nil {
|
||||
if err == dstorage.ErrTxnIDMismatch {
|
||||
panic("unexpected txn ID mismatch error")
|
||||
}
|
||||
return rev, false
|
||||
}
|
||||
var ckv storagepb.KeyValue
|
||||
if len(ckvs) != 0 {
|
||||
ckv = ckvs[0]
|
||||
} else {
|
||||
// Use the zero value of ckv normally. However...
|
||||
if c.Target == pb.Compare_VALUE {
|
||||
// Always fail if we're comparing a value on a key that doesn't exist.
|
||||
// We can treat non-existence as the empty set explicitly, such that
|
||||
// even a key with a value of length 0 bytes is still a real key
|
||||
// that was written that way
|
||||
return rev, false
|
||||
}
|
||||
}
|
||||
|
||||
// -1 is less, 0 is equal, 1 is greater
|
||||
@@ -180,6 +623,22 @@ func doCompare(kv dstorage.KV, c *pb.Compare) (int64, bool) {
|
||||
return rev, true
|
||||
}
|
||||
|
||||
func applyLeaseCreate(le lease.Lessor, lc *pb.LeaseCreateRequest) (*pb.LeaseCreateResponse, error) {
|
||||
l, err := le.Grant(lease.LeaseID(lc.ID), lc.TTL)
|
||||
resp := &pb.LeaseCreateResponse{}
|
||||
if err == nil {
|
||||
resp.ID = int64(l.ID)
|
||||
resp.TTL = l.TTL
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func applyLeaseRevoke(le lease.Lessor, lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
err := le.Revoke(lease.LeaseID(lc.ID))
|
||||
|
||||
return &pb.LeaseRevokeResponse{}, err
|
||||
}
|
||||
|
||||
func compareInt64(a, b int64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
@@ -190,3 +649,14 @@ func compareInt64(a, b int64) int {
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// isGteRange determines if the range end is a >= range. This works around grpc
|
||||
// sending empty byte strings as nil; >= is encoded in the range end as '\0'.
|
||||
func isGteRange(rangeEnd []byte) bool {
|
||||
return len(rangeEnd) == 1 && rangeEnd[0] == 0
|
||||
}
|
||||
|
||||
func applyAuthEnable(s *EtcdServer) (*pb.AuthEnableResponse, error) {
|
||||
s.AuthStore().AuthEnable()
|
||||
return &pb.AuthEnableResponse{}, nil
|
||||
}
|
||||
|
||||
112
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasehttp/http.go
generated
vendored
Normal file
112
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasehttp/http.go
generated
vendored
Normal file
@@ -0,0 +1,112 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package leasehttp
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
)
|
||||
|
||||
// NewHandler returns an http Handler for lease renewals
|
||||
func NewHandler(l lease.Lessor) http.Handler {
|
||||
return &leaseHandler{l}
|
||||
}
|
||||
|
||||
type leaseHandler struct{ l lease.Lessor }
|
||||
|
||||
func (h *leaseHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if r.Method != "POST" {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, "error reading body", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
lreq := pb.LeaseKeepAliveRequest{}
|
||||
if err := lreq.Unmarshal(b); err != nil {
|
||||
http.Error(w, "error unmarshalling request", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
ttl, err := h.l.Renew(lease.LeaseID(lreq.ID))
|
||||
if err != nil {
|
||||
if err == lease.ErrLeaseNotFound {
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
|
||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: fill out ResponseHeader
|
||||
resp := &pb.LeaseKeepAliveResponse{ID: lreq.ID, TTL: ttl}
|
||||
v, err := resp.Marshal()
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/protobuf")
|
||||
w.Write(v)
|
||||
}
|
||||
|
||||
// RenewHTTP renews a lease at a given primary server.
|
||||
// TODO: Batch request in future?
|
||||
func RenewHTTP(id lease.LeaseID, url string, rt http.RoundTripper, timeout time.Duration) (int64, error) {
|
||||
// will post lreq protobuf to leader
|
||||
lreq, err := (&pb.LeaseKeepAliveRequest{ID: int64(id)}).Marshal()
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
cc := &http.Client{Transport: rt, Timeout: timeout}
|
||||
resp, err := cc.Post(url, "application/protobuf", bytes.NewReader(lreq))
|
||||
if err != nil {
|
||||
// TODO detect if leader failed and retry?
|
||||
return -1, err
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
return -1, lease.ErrLeaseNotFound
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return -1, fmt.Errorf("lease: unknown error(%s)", string(b))
|
||||
}
|
||||
|
||||
lresp := &pb.LeaseKeepAliveResponse{}
|
||||
if err := lresp.Unmarshal(b); err != nil {
|
||||
return -1, fmt.Errorf(`lease: %v. data = "%s"`, err, string(b))
|
||||
}
|
||||
if lresp.ID != int64(id) {
|
||||
return -1, fmt.Errorf("lease: renew id mismatch")
|
||||
}
|
||||
return lresp.TTL, nil
|
||||
}
|
||||
314
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasepb/lease.pb.go
generated
vendored
Normal file
314
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasepb/lease.pb.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: lease.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package leasepb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
lease.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Lease
|
||||
*/
|
||||
package leasepb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
import math "math"
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type Lease struct {
|
||||
ID int64 `protobuf:"varint,1,opt,name=ID,proto3" json:"ID,omitempty"`
|
||||
TTL int64 `protobuf:"varint,2,opt,name=TTL,proto3" json:"TTL,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Lease) Reset() { *m = Lease{} }
|
||||
func (m *Lease) String() string { return proto.CompactTextString(m) }
|
||||
func (*Lease) ProtoMessage() {}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Lease)(nil), "leasepb.Lease")
|
||||
}
|
||||
func (m *Lease) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *Lease) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintLease(data, i, uint64(m.ID))
|
||||
}
|
||||
if m.TTL != 0 {
|
||||
data[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintLease(data, i, uint64(m.TTL))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Lease(data []byte, offset int, v uint64) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
data[offset+4] = uint8(v >> 32)
|
||||
data[offset+5] = uint8(v >> 40)
|
||||
data[offset+6] = uint8(v >> 48)
|
||||
data[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Lease(data []byte, offset int, v uint32) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintLease(data []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
data[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
data[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *Lease) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.ID != 0 {
|
||||
n += 1 + sovLease(uint64(m.ID))
|
||||
}
|
||||
if m.TTL != 0 {
|
||||
n += 1 + sovLease(uint64(m.TTL))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovLease(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozLease(x uint64) (n int) {
|
||||
return sovLease(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *Lease) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Lease: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Lease: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
|
||||
}
|
||||
m.ID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.ID |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field TTL", wireType)
|
||||
}
|
||||
m.TTL = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.TTL |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipLease(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthLease
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipLease(data []byte) (n int, err error) {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if data[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthLease
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowLease
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipLease(data[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthLease = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowLease = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
15
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasepb/lease.proto
generated
vendored
Normal file
15
Godeps/_workspace/src/github.com/coreos/etcd/lease/leasepb/lease.proto
generated
vendored
Normal file
@@ -0,0 +1,15 @@
|
||||
syntax = "proto3";
|
||||
package leasepb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
option (gogoproto.goproto_enum_prefix_all) = false;
|
||||
|
||||
message Lease {
|
||||
int64 ID = 1;
|
||||
int64 TTL = 2;
|
||||
}
|
||||
507
Godeps/_workspace/src/github.com/coreos/etcd/lease/lessor.go
generated
vendored
Normal file
507
Godeps/_workspace/src/github.com/coreos/etcd/lease/lessor.go
generated
vendored
Normal file
@@ -0,0 +1,507 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package lease
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/lease/leasepb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
)
|
||||
|
||||
const (
|
||||
// NoLease is a special LeaseID representing the absence of a lease.
|
||||
NoLease = LeaseID(0)
|
||||
)
|
||||
|
||||
var (
|
||||
minLeaseTTL = int64(5)
|
||||
|
||||
leaseBucketName = []byte("lease")
|
||||
// do not use maxInt64 since it can overflow time which will add
|
||||
// the offset of unix time (1970yr to seconds).
|
||||
forever = time.Unix(math.MaxInt64>>1, 0)
|
||||
|
||||
ErrNotPrimary = errors.New("not a primary lessor")
|
||||
ErrLeaseNotFound = errors.New("lease not found")
|
||||
ErrLeaseExists = errors.New("lease already exists")
|
||||
)
|
||||
|
||||
type LeaseID int64
|
||||
|
||||
// RangeDeleter defines an interface with DeleteRange method.
|
||||
// We define this interface only for lessor to limit the number
|
||||
// of methods of storage.KV to what lessor actually needs.
|
||||
//
|
||||
// Having a minimum interface makes testing easy.
|
||||
type RangeDeleter interface {
|
||||
DeleteRange(key, end []byte) (int64, int64)
|
||||
}
|
||||
|
||||
// A Lessor is the owner of leases. It can grant, revoke, renew and modify leases for lessee.
|
||||
type Lessor interface {
|
||||
// SetRangeDeleter sets the RangeDeleter to the Lessor.
|
||||
// Lessor deletes the items in the revoked or expired lease from the
|
||||
// the set RangeDeleter.
|
||||
SetRangeDeleter(dr RangeDeleter)
|
||||
|
||||
// Grant grants a lease that expires at least after TTL seconds.
|
||||
Grant(id LeaseID, ttl int64) (*Lease, error)
|
||||
// Revoke revokes a lease with given ID. The item attached to the
|
||||
// given lease will be removed. If the ID does not exist, an error
|
||||
// will be returned.
|
||||
Revoke(id LeaseID) error
|
||||
|
||||
// Attach attaches given leaseItem to the lease with given LeaseID.
|
||||
// If the lease does not exist, an error will be returned.
|
||||
Attach(id LeaseID, items []LeaseItem) error
|
||||
|
||||
// Detach detaches given leaseItem from the lease with given LeaseID.
|
||||
// If the lease does not exist, an error will be returned.
|
||||
Detach(id LeaseID, items []LeaseItem) error
|
||||
|
||||
// Promote promotes the lessor to be the primary lessor. Primary lessor manages
|
||||
// the expiration and renew of leases.
|
||||
// Newly promoted lessor renew the TTL of all lease to extend + previous TTL.
|
||||
Promote(extend time.Duration)
|
||||
|
||||
// Demote demotes the lessor from being the primary lessor.
|
||||
Demote()
|
||||
|
||||
// Renew renews a lease with given ID. It returns the renewed TTL. If the ID does not exist,
|
||||
// an error will be returned.
|
||||
Renew(id LeaseID) (int64, error)
|
||||
|
||||
// Lookup gives the lease at a given lease id, if any
|
||||
Lookup(id LeaseID) *Lease
|
||||
|
||||
// ExpiredLeasesC returns a chan that is used to receive expired leases.
|
||||
ExpiredLeasesC() <-chan []*Lease
|
||||
|
||||
// Recover recovers the lessor state from the given backend and RangeDeleter.
|
||||
Recover(b backend.Backend, rd RangeDeleter)
|
||||
|
||||
// Stop stops the lessor for managing leases. The behavior of calling Stop multiple
|
||||
// times is undefined.
|
||||
Stop()
|
||||
}
|
||||
|
||||
// lessor implements Lessor interface.
|
||||
// TODO: use clockwork for testability.
|
||||
type lessor struct {
|
||||
mu sync.Mutex
|
||||
|
||||
// primary indicates if this lessor is the primary lessor. The primary
|
||||
// lessor manages lease expiration and renew.
|
||||
//
|
||||
// in etcd, raft leader is the primary. Thus there might be two primary
|
||||
// leaders at the same time (raft allows concurrent leader but with different term)
|
||||
// for at most a leader election timeout.
|
||||
// The old primary leader cannot affect the correctness since its proposal has a
|
||||
// smaller term and will not be committed.
|
||||
//
|
||||
// TODO: raft follower do not forward lease management proposals. There might be a
|
||||
// very small window (within second normally which depends on go scheduling) that
|
||||
// a raft follow is the primary between the raft leader demotion and lessor demotion.
|
||||
// Usually this should not be a problem. Lease should not be that sensitive to timing.
|
||||
primary bool
|
||||
|
||||
// TODO: probably this should be a heap with a secondary
|
||||
// id index.
|
||||
// Now it is O(N) to loop over the leases to find expired ones.
|
||||
// We want to make Grant, Revoke, and findExpiredLeases all O(logN) and
|
||||
// Renew O(1).
|
||||
// findExpiredLeases and Renew should be the most frequent operations.
|
||||
leaseMap map[LeaseID]*Lease
|
||||
|
||||
// When a lease expires, the lessor will delete the
|
||||
// leased range (or key) by the RangeDeleter.
|
||||
rd RangeDeleter
|
||||
|
||||
// backend to persist leases. We only persist lease ID and expiry for now.
|
||||
// The leased items can be recovered by iterating all the keys in kv.
|
||||
b backend.Backend
|
||||
|
||||
expiredC chan []*Lease
|
||||
// stopC is a channel whose closure indicates that the lessor should be stopped.
|
||||
stopC chan struct{}
|
||||
// doneC is a channel whose closure indicates that the lessor is stopped.
|
||||
doneC chan struct{}
|
||||
}
|
||||
|
||||
func NewLessor(b backend.Backend) Lessor {
|
||||
return newLessor(b)
|
||||
}
|
||||
|
||||
func newLessor(b backend.Backend) *lessor {
|
||||
l := &lessor{
|
||||
leaseMap: make(map[LeaseID]*Lease),
|
||||
b: b,
|
||||
// expiredC is a small buffered chan to avoid unnecessary blocking.
|
||||
expiredC: make(chan []*Lease, 16),
|
||||
stopC: make(chan struct{}),
|
||||
doneC: make(chan struct{}),
|
||||
}
|
||||
l.initAndRecover()
|
||||
|
||||
go l.runLoop()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (le *lessor) SetRangeDeleter(rd RangeDeleter) {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
le.rd = rd
|
||||
}
|
||||
|
||||
// TODO: when lessor is under high load, it should give out lease
|
||||
// with longer TTL to reduce renew load.
|
||||
func (le *lessor) Grant(id LeaseID, ttl int64) (*Lease, error) {
|
||||
if id == NoLease {
|
||||
return nil, ErrLeaseNotFound
|
||||
}
|
||||
|
||||
l := &Lease{ID: id, TTL: ttl, itemSet: make(map[LeaseItem]struct{})}
|
||||
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
if _, ok := le.leaseMap[id]; ok {
|
||||
return nil, ErrLeaseExists
|
||||
}
|
||||
|
||||
if le.primary {
|
||||
l.refresh(0)
|
||||
} else {
|
||||
l.forever()
|
||||
}
|
||||
|
||||
le.leaseMap[id] = l
|
||||
l.persistTo(le.b)
|
||||
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func (le *lessor) Revoke(id LeaseID) error {
|
||||
le.mu.Lock()
|
||||
|
||||
l := le.leaseMap[id]
|
||||
if l == nil {
|
||||
le.mu.Unlock()
|
||||
return ErrLeaseNotFound
|
||||
}
|
||||
// unlock before doing external work
|
||||
le.mu.Unlock()
|
||||
|
||||
if le.rd != nil {
|
||||
for item := range l.itemSet {
|
||||
le.rd.DeleteRange([]byte(item.Key), nil)
|
||||
}
|
||||
}
|
||||
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
delete(le.leaseMap, l.ID)
|
||||
l.removeFrom(le.b)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Renew renews an existing lease. If the given lease does not exist or
|
||||
// has expired, an error will be returned.
|
||||
func (le *lessor) Renew(id LeaseID) (int64, error) {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
if !le.primary {
|
||||
// forward renew request to primary instead of returning error.
|
||||
return -1, ErrNotPrimary
|
||||
}
|
||||
|
||||
l := le.leaseMap[id]
|
||||
if l == nil {
|
||||
return -1, ErrLeaseNotFound
|
||||
}
|
||||
|
||||
l.refresh(0)
|
||||
return l.TTL, nil
|
||||
}
|
||||
|
||||
func (le *lessor) Lookup(id LeaseID) *Lease {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
if l, ok := le.leaseMap[id]; ok {
|
||||
return l
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (le *lessor) Promote(extend time.Duration) {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
le.primary = true
|
||||
|
||||
// refresh the expiries of all leases.
|
||||
for _, l := range le.leaseMap {
|
||||
l.refresh(extend)
|
||||
}
|
||||
}
|
||||
|
||||
func (le *lessor) Demote() {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
// set the expiries of all leases to forever
|
||||
for _, l := range le.leaseMap {
|
||||
l.forever()
|
||||
}
|
||||
|
||||
le.primary = false
|
||||
}
|
||||
|
||||
// Attach attaches items to the lease with given ID. When the lease
|
||||
// expires, the attached items will be automatically removed.
|
||||
// If the given lease does not exist, an error will be returned.
|
||||
func (le *lessor) Attach(id LeaseID, items []LeaseItem) error {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
l := le.leaseMap[id]
|
||||
if l == nil {
|
||||
return ErrLeaseNotFound
|
||||
}
|
||||
|
||||
for _, it := range items {
|
||||
l.itemSet[it] = struct{}{}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detach detaches items from the lease with given ID.
|
||||
// If the given lease does not exist, an error will be returned.
|
||||
func (le *lessor) Detach(id LeaseID, items []LeaseItem) error {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
l := le.leaseMap[id]
|
||||
if l == nil {
|
||||
return ErrLeaseNotFound
|
||||
}
|
||||
|
||||
for _, it := range items {
|
||||
delete(l.itemSet, it)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (le *lessor) Recover(b backend.Backend, rd RangeDeleter) {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
le.b = b
|
||||
le.rd = rd
|
||||
le.leaseMap = make(map[LeaseID]*Lease)
|
||||
|
||||
le.initAndRecover()
|
||||
}
|
||||
|
||||
func (le *lessor) ExpiredLeasesC() <-chan []*Lease {
|
||||
return le.expiredC
|
||||
}
|
||||
|
||||
func (le *lessor) Stop() {
|
||||
close(le.stopC)
|
||||
<-le.doneC
|
||||
}
|
||||
|
||||
func (le *lessor) runLoop() {
|
||||
defer close(le.doneC)
|
||||
|
||||
for {
|
||||
var ls []*Lease
|
||||
|
||||
le.mu.Lock()
|
||||
if le.primary {
|
||||
ls = le.findExpiredLeases()
|
||||
}
|
||||
le.mu.Unlock()
|
||||
|
||||
if len(ls) != 0 {
|
||||
select {
|
||||
case <-le.stopC:
|
||||
return
|
||||
case le.expiredC <- ls:
|
||||
default:
|
||||
// the receiver of expiredC is probably busy handling
|
||||
// other stuff
|
||||
// let's try this next time after 500ms
|
||||
}
|
||||
}
|
||||
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
case <-le.stopC:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// findExpiredLeases loops all the leases in the leaseMap and returns the expired
|
||||
// leases that needed to be revoked.
|
||||
func (le *lessor) findExpiredLeases() []*Lease {
|
||||
leases := make([]*Lease, 0, 16)
|
||||
now := time.Now()
|
||||
|
||||
for _, l := range le.leaseMap {
|
||||
// TODO: probably should change to <= 100-500 millisecond to
|
||||
// make up committing latency.
|
||||
if l.expiry.Sub(now) <= 0 {
|
||||
leases = append(leases, l)
|
||||
}
|
||||
}
|
||||
|
||||
return leases
|
||||
}
|
||||
|
||||
// get gets the lease with given id.
|
||||
// get is a helper function for testing, at least for now.
|
||||
func (le *lessor) get(id LeaseID) *Lease {
|
||||
le.mu.Lock()
|
||||
defer le.mu.Unlock()
|
||||
|
||||
return le.leaseMap[id]
|
||||
}
|
||||
|
||||
func (le *lessor) initAndRecover() {
|
||||
tx := le.b.BatchTx()
|
||||
tx.Lock()
|
||||
|
||||
tx.UnsafeCreateBucket(leaseBucketName)
|
||||
_, vs := tx.UnsafeRange(leaseBucketName, int64ToBytes(0), int64ToBytes(math.MaxInt64), 0)
|
||||
// TODO: copy vs and do decoding outside tx lock if lock contention becomes an issue.
|
||||
for i := range vs {
|
||||
var lpb leasepb.Lease
|
||||
err := lpb.Unmarshal(vs[i])
|
||||
if err != nil {
|
||||
tx.Unlock()
|
||||
panic("failed to unmarshal lease proto item")
|
||||
}
|
||||
ID := LeaseID(lpb.ID)
|
||||
le.leaseMap[ID] = &Lease{
|
||||
ID: ID,
|
||||
TTL: lpb.TTL,
|
||||
// itemSet will be filled in when recover key-value pairs
|
||||
// set expiry to forever, refresh when promoted
|
||||
itemSet: make(map[LeaseItem]struct{}),
|
||||
expiry: forever,
|
||||
}
|
||||
}
|
||||
tx.Unlock()
|
||||
|
||||
le.b.ForceCommit()
|
||||
}
|
||||
|
||||
type Lease struct {
|
||||
ID LeaseID
|
||||
TTL int64 // time to live in seconds
|
||||
|
||||
itemSet map[LeaseItem]struct{}
|
||||
// expiry time in unixnano
|
||||
expiry time.Time
|
||||
}
|
||||
|
||||
func (l Lease) persistTo(b backend.Backend) {
|
||||
key := int64ToBytes(int64(l.ID))
|
||||
|
||||
lpb := leasepb.Lease{ID: int64(l.ID), TTL: int64(l.TTL)}
|
||||
val, err := lpb.Marshal()
|
||||
if err != nil {
|
||||
panic("failed to marshal lease proto item")
|
||||
}
|
||||
|
||||
b.BatchTx().Lock()
|
||||
b.BatchTx().UnsafePut(leaseBucketName, key, val)
|
||||
b.BatchTx().Unlock()
|
||||
}
|
||||
|
||||
func (l Lease) removeFrom(b backend.Backend) {
|
||||
key := int64ToBytes(int64(l.ID))
|
||||
|
||||
b.BatchTx().Lock()
|
||||
b.BatchTx().UnsafeDelete(leaseBucketName, key)
|
||||
b.BatchTx().Unlock()
|
||||
}
|
||||
|
||||
// refresh refreshes the expiry of the lease. It extends the expiry at least
|
||||
// minLeaseTTL second.
|
||||
func (l *Lease) refresh(extend time.Duration) {
|
||||
if l.TTL < minLeaseTTL {
|
||||
l.TTL = minLeaseTTL
|
||||
}
|
||||
l.expiry = time.Now().Add(extend + time.Second*time.Duration(l.TTL))
|
||||
}
|
||||
|
||||
// forever sets the expiry of lease to be forever.
|
||||
func (l *Lease) forever() {
|
||||
if l.TTL < minLeaseTTL {
|
||||
l.TTL = minLeaseTTL
|
||||
}
|
||||
l.expiry = forever
|
||||
}
|
||||
|
||||
type LeaseItem struct {
|
||||
Key string
|
||||
}
|
||||
|
||||
func int64ToBytes(n int64) []byte {
|
||||
bytes := make([]byte, 8)
|
||||
binary.BigEndian.PutUint64(bytes, uint64(n))
|
||||
return bytes
|
||||
}
|
||||
|
||||
// FakeLessor is a fake implementation of Lessor interface.
|
||||
// Used for testing only.
|
||||
type FakeLessor struct{}
|
||||
|
||||
func (fl *FakeLessor) SetRangeDeleter(dr RangeDeleter) {}
|
||||
|
||||
func (fl *FakeLessor) Grant(id LeaseID, ttl int64) (*Lease, error) { return nil, nil }
|
||||
|
||||
func (fl *FakeLessor) Revoke(id LeaseID) error { return nil }
|
||||
|
||||
func (fl *FakeLessor) Attach(id LeaseID, items []LeaseItem) error { return nil }
|
||||
|
||||
func (fl *FakeLessor) Detach(id LeaseID, items []LeaseItem) error { return nil }
|
||||
|
||||
func (fl *FakeLessor) Promote(extend time.Duration) {}
|
||||
|
||||
func (fl *FakeLessor) Demote() {}
|
||||
|
||||
func (fl *FakeLessor) Renew(id LeaseID) (int64, error) { return 10, nil }
|
||||
|
||||
func (le *FakeLessor) Lookup(id LeaseID) *Lease { return nil }
|
||||
|
||||
func (fl *FakeLessor) ExpiredLeasesC() <-chan []*Lease { return nil }
|
||||
|
||||
func (fl *FakeLessor) Recover(b backend.Backend, rd RangeDeleter) {}
|
||||
|
||||
func (fl *FakeLessor) Stop() {}
|
||||
526
Godeps/_workspace/src/github.com/coreos/etcd/pkg/adt/interval_tree.go
generated
vendored
Normal file
526
Godeps/_workspace/src/github.com/coreos/etcd/pkg/adt/interval_tree.go
generated
vendored
Normal file
@@ -0,0 +1,526 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package adt
|
||||
|
||||
import (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Comparable is an interface for trichotomic comparisons.
|
||||
type Comparable interface {
|
||||
// Compare gives the result of a 3-way comparison
|
||||
// a.Compare(b) = 1 => a > b
|
||||
// a.Compare(b) = 0 => a == b
|
||||
// a.Compare(b) = -1 => a < b
|
||||
Compare(c Comparable) int
|
||||
}
|
||||
|
||||
type rbcolor bool
|
||||
|
||||
const black = true
|
||||
const red = false
|
||||
|
||||
// Interval implements a Comparable interval [begin, end)
|
||||
// TODO: support different sorts of intervals: (a,b), [a,b], (a, b]
|
||||
type Interval struct {
|
||||
Begin Comparable
|
||||
End Comparable
|
||||
}
|
||||
|
||||
// Compare on an interval gives == if the interval overlaps.
|
||||
func (ivl *Interval) Compare(c Comparable) int {
|
||||
ivl2 := c.(*Interval)
|
||||
ivbCmpBegin := ivl.Begin.Compare(ivl2.Begin)
|
||||
ivbCmpEnd := ivl.Begin.Compare(ivl2.End)
|
||||
iveCmpBegin := ivl.End.Compare(ivl2.Begin)
|
||||
|
||||
// ivl is left of ivl2
|
||||
if ivbCmpBegin < 0 && iveCmpBegin <= 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
// iv is right of iv2
|
||||
if ivbCmpEnd >= 0 {
|
||||
return 1
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
type intervalNode struct {
|
||||
// iv is the interval-value pair entry.
|
||||
iv IntervalValue
|
||||
// max endpoint of all descendent nodes.
|
||||
max Comparable
|
||||
// left and right are sorted by low endpoint of key interval
|
||||
left, right *intervalNode
|
||||
// parent is the direct ancestor of the node
|
||||
parent *intervalNode
|
||||
c rbcolor
|
||||
}
|
||||
|
||||
func (x *intervalNode) color() rbcolor {
|
||||
if x == nil {
|
||||
return black
|
||||
}
|
||||
return x.c
|
||||
}
|
||||
|
||||
func (n *intervalNode) height() int {
|
||||
if n == nil {
|
||||
return 0
|
||||
}
|
||||
ld := n.left.height()
|
||||
rd := n.right.height()
|
||||
if ld < rd {
|
||||
return rd + 1
|
||||
}
|
||||
return ld + 1
|
||||
}
|
||||
|
||||
func (x *intervalNode) min() *intervalNode {
|
||||
for x.left != nil {
|
||||
x = x.left
|
||||
}
|
||||
return x
|
||||
}
|
||||
|
||||
// successor is the next in-order node in the tree
|
||||
func (x *intervalNode) successor() *intervalNode {
|
||||
if x.right != nil {
|
||||
return x.right.min()
|
||||
}
|
||||
y := x.parent
|
||||
for y != nil && x == y.right {
|
||||
x = y
|
||||
y = y.parent
|
||||
}
|
||||
return y
|
||||
}
|
||||
|
||||
// updateMax updates the maximum values for a node and its ancestors
|
||||
func (x *intervalNode) updateMax() {
|
||||
for x != nil {
|
||||
oldmax := x.max
|
||||
max := x.iv.Ivl.End
|
||||
if x.left != nil && x.left.max.Compare(max) > 0 {
|
||||
max = x.left.max
|
||||
}
|
||||
if x.right != nil && x.right.max.Compare(max) > 0 {
|
||||
max = x.right.max
|
||||
}
|
||||
if oldmax.Compare(max) == 0 {
|
||||
break
|
||||
}
|
||||
x.max = max
|
||||
x = x.parent
|
||||
}
|
||||
}
|
||||
|
||||
type nodeVisitor func(n *intervalNode) bool
|
||||
|
||||
// visit will call a node visitor on each node that overlaps the given interval
|
||||
func (x *intervalNode) visit(iv *Interval, nv nodeVisitor) {
|
||||
if x == nil {
|
||||
return
|
||||
}
|
||||
v := iv.Compare(&x.iv.Ivl)
|
||||
switch {
|
||||
case v < 0:
|
||||
x.left.visit(iv, nv)
|
||||
case v > 0:
|
||||
maxiv := Interval{x.iv.Ivl.Begin, x.max}
|
||||
if maxiv.Compare(iv) == 0 {
|
||||
x.left.visit(iv, nv)
|
||||
x.right.visit(iv, nv)
|
||||
}
|
||||
default:
|
||||
nv(x)
|
||||
x.left.visit(iv, nv)
|
||||
x.right.visit(iv, nv)
|
||||
}
|
||||
}
|
||||
|
||||
type IntervalValue struct {
|
||||
Ivl Interval
|
||||
Val interface{}
|
||||
}
|
||||
|
||||
// IntervalTree represents a (mostly) textbook implementation of the
|
||||
// "Introduction to Algorithms" (Cormen et al, 2nd ed.) chapter 13 red-black tree
|
||||
// and chapter 14.3 interval tree with search supporting "stabbing queries".
|
||||
type IntervalTree struct {
|
||||
root *intervalNode
|
||||
count int
|
||||
}
|
||||
|
||||
// Delete removes the node with the given interval from the tree, returning
|
||||
// true if a node is in fact removed.
|
||||
func (ivt *IntervalTree) Delete(ivl Interval) bool {
|
||||
z := ivt.find(ivl)
|
||||
if z == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
y := z
|
||||
if z.left != nil && z.right != nil {
|
||||
y = z.successor()
|
||||
}
|
||||
|
||||
x := y.left
|
||||
if x == nil {
|
||||
x = y.right
|
||||
}
|
||||
if x != nil {
|
||||
x.parent = y.parent
|
||||
}
|
||||
|
||||
if y.parent == nil {
|
||||
ivt.root = x
|
||||
} else {
|
||||
if y == y.parent.left {
|
||||
y.parent.left = x
|
||||
} else {
|
||||
y.parent.right = x
|
||||
}
|
||||
y.parent.updateMax()
|
||||
}
|
||||
if y != z {
|
||||
z.iv = y.iv
|
||||
z.updateMax()
|
||||
}
|
||||
|
||||
if y.color() == black && x != nil {
|
||||
ivt.deleteFixup(x)
|
||||
}
|
||||
|
||||
ivt.count--
|
||||
return true
|
||||
}
|
||||
|
||||
func (ivt *IntervalTree) deleteFixup(x *intervalNode) {
|
||||
for x != ivt.root && x.color() == black && x.parent != nil {
|
||||
if x == x.parent.left {
|
||||
w := x.parent.right
|
||||
if w.color() == red {
|
||||
w.c = black
|
||||
x.parent.c = red
|
||||
ivt.rotateLeft(x.parent)
|
||||
w = x.parent.right
|
||||
}
|
||||
if w == nil {
|
||||
break
|
||||
}
|
||||
if w.left.color() == black && w.right.color() == black {
|
||||
w.c = red
|
||||
x = x.parent
|
||||
} else {
|
||||
if w.right.color() == black {
|
||||
w.left.c = black
|
||||
w.c = red
|
||||
ivt.rotateRight(w)
|
||||
w = x.parent.right
|
||||
}
|
||||
w.c = x.parent.color()
|
||||
x.parent.c = black
|
||||
w.right.c = black
|
||||
ivt.rotateLeft(x.parent)
|
||||
x = ivt.root
|
||||
}
|
||||
} else {
|
||||
// same as above but with left and right exchanged
|
||||
w := x.parent.left
|
||||
if w.color() == red {
|
||||
w.c = black
|
||||
x.parent.c = red
|
||||
ivt.rotateRight(x.parent)
|
||||
w = x.parent.left
|
||||
}
|
||||
if w == nil {
|
||||
break
|
||||
}
|
||||
if w.left.color() == black && w.right.color() == black {
|
||||
w.c = red
|
||||
x = x.parent
|
||||
} else {
|
||||
if w.left.color() == black {
|
||||
w.right.c = black
|
||||
w.c = red
|
||||
ivt.rotateLeft(w)
|
||||
w = x.parent.left
|
||||
}
|
||||
w.c = x.parent.color()
|
||||
x.parent.c = black
|
||||
w.left.c = black
|
||||
ivt.rotateRight(x.parent)
|
||||
x = ivt.root
|
||||
}
|
||||
}
|
||||
}
|
||||
if x != nil {
|
||||
x.c = black
|
||||
}
|
||||
}
|
||||
|
||||
// Insert adds a node with the given interval into the tree.
|
||||
func (ivt *IntervalTree) Insert(ivl Interval, val interface{}) {
|
||||
var y *intervalNode
|
||||
z := &intervalNode{iv: IntervalValue{ivl, val}, max: ivl.End, c: red}
|
||||
x := ivt.root
|
||||
for x != nil {
|
||||
y = x
|
||||
if z.iv.Ivl.Begin.Compare(x.iv.Ivl.Begin) < 0 {
|
||||
x = x.left
|
||||
} else {
|
||||
x = x.right
|
||||
}
|
||||
}
|
||||
|
||||
z.parent = y
|
||||
if y == nil {
|
||||
ivt.root = z
|
||||
} else {
|
||||
if z.iv.Ivl.Begin.Compare(y.iv.Ivl.Begin) < 0 {
|
||||
y.left = z
|
||||
} else {
|
||||
y.right = z
|
||||
}
|
||||
y.updateMax()
|
||||
}
|
||||
z.c = red
|
||||
ivt.insertFixup(z)
|
||||
ivt.count++
|
||||
}
|
||||
|
||||
func (ivt *IntervalTree) insertFixup(z *intervalNode) {
|
||||
for z.parent != nil && z.parent.parent != nil && z.parent.color() == red {
|
||||
if z.parent == z.parent.parent.left {
|
||||
y := z.parent.parent.right
|
||||
if y.color() == red {
|
||||
y.c = black
|
||||
z.parent.c = black
|
||||
z.parent.parent.c = red
|
||||
z = z.parent.parent
|
||||
} else {
|
||||
if z == z.parent.right {
|
||||
z = z.parent
|
||||
ivt.rotateLeft(z)
|
||||
}
|
||||
z.parent.c = black
|
||||
z.parent.parent.c = red
|
||||
ivt.rotateRight(z.parent.parent)
|
||||
}
|
||||
} else {
|
||||
// same as then with left/right exchanged
|
||||
y := z.parent.parent.left
|
||||
if y.color() == red {
|
||||
y.c = black
|
||||
z.parent.c = black
|
||||
z.parent.parent.c = red
|
||||
z = z.parent.parent
|
||||
} else {
|
||||
if z == z.parent.left {
|
||||
z = z.parent
|
||||
ivt.rotateRight(z)
|
||||
}
|
||||
z.parent.c = black
|
||||
z.parent.parent.c = red
|
||||
ivt.rotateLeft(z.parent.parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
ivt.root.c = black
|
||||
}
|
||||
|
||||
// rotateLeft moves x so it is left of its right child
|
||||
func (ivt *IntervalTree) rotateLeft(x *intervalNode) {
|
||||
y := x.right
|
||||
x.right = y.left
|
||||
if y.left != nil {
|
||||
y.left.parent = x
|
||||
}
|
||||
x.updateMax()
|
||||
ivt.replaceParent(x, y)
|
||||
y.left = x
|
||||
y.updateMax()
|
||||
}
|
||||
|
||||
// rotateLeft moves x so it is right of its left child
|
||||
func (ivt *IntervalTree) rotateRight(x *intervalNode) {
|
||||
if x == nil {
|
||||
return
|
||||
}
|
||||
y := x.left
|
||||
x.left = y.right
|
||||
if y.right != nil {
|
||||
y.right.parent = x
|
||||
}
|
||||
x.updateMax()
|
||||
ivt.replaceParent(x, y)
|
||||
y.right = x
|
||||
y.updateMax()
|
||||
}
|
||||
|
||||
// replaceParent replaces x's parent with y
|
||||
func (ivt *IntervalTree) replaceParent(x *intervalNode, y *intervalNode) {
|
||||
y.parent = x.parent
|
||||
if x.parent == nil {
|
||||
ivt.root = y
|
||||
} else {
|
||||
if x == x.parent.left {
|
||||
x.parent.left = y
|
||||
} else {
|
||||
x.parent.right = y
|
||||
}
|
||||
x.parent.updateMax()
|
||||
}
|
||||
x.parent = y
|
||||
}
|
||||
|
||||
// Len gives the number of elements in the tree
|
||||
func (ivt *IntervalTree) Len() int { return ivt.count }
|
||||
|
||||
// Height is the number of levels in the tree; one node has height 1.
|
||||
func (ivt *IntervalTree) Height() int { return ivt.root.height() }
|
||||
|
||||
// MaxHeight is the expected maximum tree height given the number of nodes
|
||||
func (ivt *IntervalTree) MaxHeight() int {
|
||||
return int((2 * math.Log2(float64(ivt.Len()+1))) + 0.5)
|
||||
}
|
||||
|
||||
// InternalVisitor is used on tree searchs; return false to stop searching.
|
||||
type IntervalVisitor func(n *IntervalValue) bool
|
||||
|
||||
// Visit calls a visitor function on every tree node intersecting the given interval.
|
||||
func (ivt *IntervalTree) Visit(ivl Interval, ivv IntervalVisitor) {
|
||||
ivt.root.visit(&ivl, func(n *intervalNode) bool { return ivv(&n.iv) })
|
||||
}
|
||||
|
||||
// find the exact node for a given interval
|
||||
func (ivt *IntervalTree) find(ivl Interval) (ret *intervalNode) {
|
||||
f := func(n *intervalNode) bool {
|
||||
if n.iv.Ivl != ivl {
|
||||
return true
|
||||
}
|
||||
ret = n
|
||||
return false
|
||||
}
|
||||
ivt.root.visit(&ivl, f)
|
||||
return ret
|
||||
}
|
||||
|
||||
// Find gets the IntervalValue for the node matching the given interval
|
||||
func (ivt *IntervalTree) Find(ivl Interval) (ret *IntervalValue) {
|
||||
n := ivt.find(ivl)
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
return &n.iv
|
||||
}
|
||||
|
||||
// Contains returns true if there is some tree node intersecting the given interval.
|
||||
func (ivt *IntervalTree) Contains(iv Interval) bool {
|
||||
x := ivt.root
|
||||
for x != nil && iv.Compare(&x.iv.Ivl) != 0 {
|
||||
if x.left != nil && x.left.max.Compare(iv.Begin) > 0 {
|
||||
x = x.left
|
||||
} else {
|
||||
x = x.right
|
||||
}
|
||||
}
|
||||
return x != nil
|
||||
}
|
||||
|
||||
// Stab returns a slice with all elements in the tree intersecting the interval.
|
||||
func (ivt *IntervalTree) Stab(iv Interval) (ivs []*IntervalValue) {
|
||||
f := func(n *IntervalValue) bool { ivs = append(ivs, n); return true }
|
||||
ivt.Visit(iv, f)
|
||||
return ivs
|
||||
}
|
||||
|
||||
type StringComparable string
|
||||
|
||||
func (s StringComparable) Compare(c Comparable) int {
|
||||
sc := c.(StringComparable)
|
||||
if s < sc {
|
||||
return -1
|
||||
}
|
||||
if s > sc {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewStringInterval(begin, end string) Interval {
|
||||
return Interval{StringComparable(begin), StringComparable(end)}
|
||||
}
|
||||
|
||||
func NewStringPoint(s string) Interval {
|
||||
return Interval{StringComparable(s), StringComparable(s + "\x00")}
|
||||
}
|
||||
|
||||
// StringAffineComparable treats "" as > all other strings
|
||||
type StringAffineComparable string
|
||||
|
||||
func (s StringAffineComparable) Compare(c Comparable) int {
|
||||
sc := c.(StringAffineComparable)
|
||||
|
||||
if len(s) == 0 {
|
||||
if len(sc) == 0 {
|
||||
return 0
|
||||
}
|
||||
return 1
|
||||
}
|
||||
if len(sc) == 0 {
|
||||
return -1
|
||||
}
|
||||
|
||||
if s < sc {
|
||||
return -1
|
||||
}
|
||||
if s > sc {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func NewStringAffineInterval(begin, end string) Interval {
|
||||
return Interval{StringAffineComparable(begin), StringAffineComparable(end)}
|
||||
}
|
||||
func NewStringAffinePoint(s string) Interval {
|
||||
return NewStringAffineInterval(s, s+"\x00")
|
||||
}
|
||||
|
||||
func NewInt64Interval(a int64, b int64) Interval {
|
||||
return Interval{Int64Comparable(a), Int64Comparable(b)}
|
||||
}
|
||||
|
||||
func NewInt64Point(a int64) Interval {
|
||||
return Interval{Int64Comparable(a), Int64Comparable(a + 1)}
|
||||
}
|
||||
|
||||
type Int64Comparable int64
|
||||
|
||||
func (v Int64Comparable) Compare(c Comparable) int {
|
||||
vc := c.(Int64Comparable)
|
||||
cmp := v - vc
|
||||
if cmp < 0 {
|
||||
return -1
|
||||
}
|
||||
if cmp > 0 {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
69
Godeps/_workspace/src/github.com/coreos/etcd/pkg/contention/contention.go
generated
vendored
Normal file
69
Godeps/_workspace/src/github.com/coreos/etcd/pkg/contention/contention.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package contention
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TimeoutDetector detects routine starvations by
|
||||
// observing the actual time duration to finish an action
|
||||
// or between two events that should happen in a fixed
|
||||
// interval. If the observed duration is longer than
|
||||
// the expectation, the detector will report the result.
|
||||
type TimeoutDetector struct {
|
||||
mu sync.Mutex // protects all
|
||||
maxDuration time.Duration
|
||||
// map from event to time
|
||||
// time is the last seen time of the event.
|
||||
records map[uint64]time.Time
|
||||
}
|
||||
|
||||
// NewTimeoutDetector creates the TimeoutDetector.
|
||||
func NewTimeoutDetector(maxDuration time.Duration) *TimeoutDetector {
|
||||
return &TimeoutDetector{
|
||||
maxDuration: maxDuration,
|
||||
records: make(map[uint64]time.Time),
|
||||
}
|
||||
}
|
||||
|
||||
// Reset resets the NewTimeoutDetector.
|
||||
func (td *TimeoutDetector) Reset() {
|
||||
td.mu.Lock()
|
||||
defer td.mu.Unlock()
|
||||
|
||||
td.records = make(map[uint64]time.Time)
|
||||
}
|
||||
|
||||
// Observe observes an event for given id. It returns false and exceeded duration
|
||||
// if the interval is longer than the expectation.
|
||||
func (td *TimeoutDetector) Observe(which uint64) (bool, time.Duration) {
|
||||
td.mu.Lock()
|
||||
defer td.mu.Unlock()
|
||||
|
||||
ok := true
|
||||
now := time.Now()
|
||||
exceed := time.Duration(0)
|
||||
|
||||
if pt, found := td.records[which]; found {
|
||||
exceed = now.Sub(pt) - td.maxDuration
|
||||
if exceed > 0 {
|
||||
ok = false
|
||||
}
|
||||
}
|
||||
td.records[which] = now
|
||||
return ok, exceed
|
||||
}
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/crc/crc.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/crc/crc.go
generated
vendored
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package crc provides utility function for cyclic redundancy check
|
||||
// algorithms.
|
||||
package crc
|
||||
|
||||
import (
|
||||
|
||||
8
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/fileutil.go
generated
vendored
8
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/fileutil.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package fileutil implements utility functions related to files and paths.
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
@@ -58,7 +59,7 @@ func ReadDir(dirpath string) ([]string, error) {
|
||||
return names, nil
|
||||
}
|
||||
|
||||
// TouchDirAll is simliar to os.MkdirAll. It creates directories with 0700 permission if any directory
|
||||
// TouchDirAll is similar to os.MkdirAll. It creates directories with 0700 permission if any directory
|
||||
// does not exists. TouchDirAll also ensures the given directory is writable.
|
||||
func TouchDirAll(dir string) error {
|
||||
err := os.MkdirAll(dir, privateDirMode)
|
||||
@@ -67,3 +68,8 @@ func TouchDirAll(dir string) error {
|
||||
}
|
||||
return IsDirWriteable(dir)
|
||||
}
|
||||
|
||||
func Exist(name string) bool {
|
||||
_, err := os.Stat(name)
|
||||
return err == nil
|
||||
}
|
||||
|
||||
29
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fileutil
|
||||
|
||||
type Lock interface {
|
||||
// Name returns the name of the file.
|
||||
Name() string
|
||||
// TryLock acquires exclusivity on the lock without blocking.
|
||||
TryLock() error
|
||||
// Lock acquires exclusivity on the lock.
|
||||
Lock() error
|
||||
// Unlock unlocks the lock.
|
||||
Unlock() error
|
||||
// Destroy should be called after Unlock to clean up
|
||||
// the resources.
|
||||
Destroy() error
|
||||
}
|
||||
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_plan9.go
generated
vendored
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_plan9.go
generated
vendored
@@ -25,14 +25,6 @@ var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type Lock interface {
|
||||
Name() string
|
||||
TryLock() error
|
||||
Lock() error
|
||||
Unlock() error
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
type lock struct {
|
||||
fname string
|
||||
file *os.File
|
||||
@@ -42,7 +34,6 @@ func (l *lock) Name() string {
|
||||
return l.fname
|
||||
}
|
||||
|
||||
// TryLock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) TryLock() error {
|
||||
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
|
||||
if err != nil {
|
||||
@@ -58,7 +49,6 @@ func (l *lock) TryLock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock acquires exclusivity on the lock with blocking
|
||||
func (l *lock) Lock() error {
|
||||
err := os.Chmod(l.fname, syscall.DMEXCL|0600)
|
||||
if err != nil {
|
||||
@@ -75,7 +65,6 @@ func (l *lock) Lock() error {
|
||||
}
|
||||
}
|
||||
|
||||
// Unlock unlocks the lock
|
||||
func (l *lock) Unlock() error {
|
||||
return l.file.Close()
|
||||
}
|
||||
|
||||
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_solaris.go
generated
vendored
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_solaris.go
generated
vendored
@@ -26,14 +26,6 @@ var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type Lock interface {
|
||||
Name() string
|
||||
TryLock() error
|
||||
Lock() error
|
||||
Unlock() error
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
@@ -43,7 +35,6 @@ func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
// TryLock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) TryLock() error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
@@ -59,7 +50,6 @@ func (l *lock) TryLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) Lock() error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
@@ -70,7 +60,6 @@ func (l *lock) Lock() error {
|
||||
return syscall.FcntlFlock(uintptr(l.fd), syscall.F_SETLK, &lock)
|
||||
}
|
||||
|
||||
// Unlock unlocks the lock
|
||||
func (l *lock) Unlock() error {
|
||||
var lock syscall.Flock_t
|
||||
lock.Start = 0
|
||||
|
||||
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_unix.go
generated
vendored
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_unix.go
generated
vendored
@@ -26,14 +26,6 @@ var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type Lock interface {
|
||||
Name() string
|
||||
TryLock() error
|
||||
Lock() error
|
||||
Unlock() error
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
@@ -43,7 +35,6 @@ func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
// TryLock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) TryLock() error {
|
||||
err := syscall.Flock(l.fd, syscall.LOCK_EX|syscall.LOCK_NB)
|
||||
if err != nil && err == syscall.EWOULDBLOCK {
|
||||
@@ -52,12 +43,10 @@ func (l *lock) TryLock() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// Lock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) Lock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_EX)
|
||||
}
|
||||
|
||||
// Unlock unlocks the lock
|
||||
func (l *lock) Unlock() error {
|
||||
return syscall.Flock(l.fd, syscall.LOCK_UN)
|
||||
}
|
||||
|
||||
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_windows.go
generated
vendored
11
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/lock_windows.go
generated
vendored
@@ -25,14 +25,6 @@ var (
|
||||
ErrLocked = errors.New("file already locked")
|
||||
)
|
||||
|
||||
type Lock interface {
|
||||
Name() string
|
||||
TryLock() error
|
||||
Lock() error
|
||||
Unlock() error
|
||||
Destroy() error
|
||||
}
|
||||
|
||||
type lock struct {
|
||||
fd int
|
||||
file *os.File
|
||||
@@ -42,17 +34,14 @@ func (l *lock) Name() string {
|
||||
return l.file.Name()
|
||||
}
|
||||
|
||||
// TryLock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) TryLock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Lock acquires exclusivity on the lock without blocking
|
||||
func (l *lock) Lock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Unlock unlocks the lock
|
||||
func (l *lock) Unlock() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
26
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/sync.go
generated
vendored
Normal file
26
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/sync.go
generated
vendored
Normal file
@@ -0,0 +1,26 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build !linux
|
||||
|
||||
package fileutil
|
||||
|
||||
import "os"
|
||||
|
||||
// Fdatasync is similar to fsync(), but does not flush modified metadata
|
||||
// unless that metadata is needed in order to allow a subsequent data retrieval
|
||||
// to be correctly handled.
|
||||
func Fdatasync(f *os.File) error {
|
||||
return f.Sync()
|
||||
}
|
||||
29
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/sync_linux.go
generated
vendored
Normal file
29
Godeps/_workspace/src/github.com/coreos/etcd/pkg/fileutil/sync_linux.go
generated
vendored
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// +build linux
|
||||
|
||||
package fileutil
|
||||
|
||||
import (
|
||||
"os"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
// Fdatasync is similar to fsync(), but does not flush modified metadata
|
||||
// unless that metadata is needed in order to allow a subsequent data retrieval
|
||||
// to be correctly handled.
|
||||
func Fdatasync(f *os.File) error {
|
||||
return syscall.Fdatasync(int(f.Fd()))
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/httputil/cancelreq.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/httputil/cancelreq.go
generated
vendored
@@ -6,6 +6,7 @@
|
||||
|
||||
// +build go1.5
|
||||
|
||||
// Package httputil provides HTTP utility functions.
|
||||
package httputil
|
||||
|
||||
import "net/http"
|
||||
|
||||
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/idutil/id.go
generated
vendored
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/idutil/id.go
generated
vendored
@@ -12,6 +12,8 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package idutil implements utility functions for generating unique,
|
||||
// randomized ids.
|
||||
package idutil
|
||||
|
||||
import (
|
||||
@@ -22,15 +24,18 @@ import (
|
||||
|
||||
const (
|
||||
tsLen = 5 * 8
|
||||
cntLen = 2 * 8
|
||||
cntLen = 8
|
||||
suffixLen = tsLen + cntLen
|
||||
)
|
||||
|
||||
// Generator generates unique identifiers based on counters, timestamps, and
|
||||
// a node member ID.
|
||||
//
|
||||
// The initial id is in this format:
|
||||
// High order byte is memberID, next 5 bytes are from timestamp,
|
||||
// and low order 2 bytes are 0s.
|
||||
// | prefix | suffix |
|
||||
// | 1 byte | 5 bytes | 2 bytes |
|
||||
// | 2 bytes | 5 bytes | 1 byte |
|
||||
// | memberID | timestamp | cnt |
|
||||
//
|
||||
// The timestamp 5 bytes is different when the machine is restart
|
||||
@@ -40,16 +45,16 @@ const (
|
||||
// The count field may overflow to timestamp field, which is intentional.
|
||||
// It helps to extend the event window to 2^56. This doesn't break that
|
||||
// id generated after restart is unique because etcd throughput is <<
|
||||
// 65536req/ms.
|
||||
// 256req/ms(250k reqs/second).
|
||||
type Generator struct {
|
||||
mu sync.Mutex
|
||||
// high order byte
|
||||
// high order 2 bytes
|
||||
prefix uint64
|
||||
// low order 7 bytes
|
||||
// low order 6 bytes
|
||||
suffix uint64
|
||||
}
|
||||
|
||||
func NewGenerator(memberID uint8, now time.Time) *Generator {
|
||||
func NewGenerator(memberID uint16, now time.Time) *Generator {
|
||||
prefix := uint64(memberID) << suffixLen
|
||||
unixMilli := uint64(now.UnixNano()) / uint64(time.Millisecond/time.Nanosecond)
|
||||
suffix := lowbit(unixMilli, tsLen) << cntLen
|
||||
|
||||
@@ -12,16 +12,13 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package timeutil
|
||||
package ioutil
|
||||
|
||||
import "time"
|
||||
import "io"
|
||||
|
||||
// UnixNanoToTime returns the local time corresponding to the given Unix time in nanoseconds.
|
||||
// If the given Unix time is zero, an uninitialized zero time is returned.
|
||||
func UnixNanoToTime(ns int64) time.Time {
|
||||
var t time.Time
|
||||
if ns != 0 {
|
||||
t = time.Unix(0, ns)
|
||||
}
|
||||
return t
|
||||
// ReaderAndCloser implements io.ReadCloser interface by combining
|
||||
// reader and closer together.
|
||||
type ReaderAndCloser struct {
|
||||
io.Reader
|
||||
io.Closer
|
||||
}
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/ioutil/reader.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/ioutil/reader.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package ioutil implements I/O utility functions.
|
||||
package ioutil
|
||||
|
||||
import "io"
|
||||
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/ioutil/util.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/ioutil/util.go
generated
vendored
@@ -19,7 +19,7 @@ import (
|
||||
"os"
|
||||
)
|
||||
|
||||
// WriteAndSyncFile behaviors just like ioutil.WriteFile in standard library
|
||||
// WriteAndSyncFile behaves just like ioutil.WriteFile in the standard library,
|
||||
// but calls Sync before closing the file. WriteAndSyncFile guarantees the data
|
||||
// is synced if there is no error returned.
|
||||
func WriteAndSyncFile(filename string, data []byte, perm os.FileMode) error {
|
||||
|
||||
195
Godeps/_workspace/src/github.com/coreos/etcd/pkg/logutil/merge_logger.go
generated
vendored
Normal file
195
Godeps/_workspace/src/github.com/coreos/etcd/pkg/logutil/merge_logger.go
generated
vendored
Normal file
@@ -0,0 +1,195 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package logutil includes utilities to facilitate logging.
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
24
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/isolate_linux.go
generated
vendored
24
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/isolate_linux.go
generated
vendored
@@ -40,3 +40,27 @@ func RecoverPort(port int) error {
|
||||
_, err := exec.Command("/bin/sh", "-c", cmdStr).Output()
|
||||
return err
|
||||
}
|
||||
|
||||
// SetLatency adds latency in millisecond scale with random variations.
|
||||
func SetLatency(ms, rv int) error {
|
||||
if rv > ms {
|
||||
rv = 1
|
||||
}
|
||||
cmdStr := fmt.Sprintf("sudo tc qdisc add dev eth0 root netem delay %dms %dms distribution normal", ms, rv)
|
||||
_, err := exec.Command("/bin/sh", "-c", cmdStr).Output()
|
||||
if err != nil {
|
||||
// the rule has already been added. Overwrite it.
|
||||
cmdStr = fmt.Sprintf("sudo tc qdisc change dev eth0 root netem delay %dms %dms distribution normal", ms, rv)
|
||||
_, err = exec.Command("/bin/sh", "-c", cmdStr).Output()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RemoveLatency resets latency configurations.
|
||||
func RemoveLatency() error {
|
||||
_, err := exec.Command("/bin/sh", "-c", "sudo tc qdisc del dev eth0 root netem").Output()
|
||||
return err
|
||||
}
|
||||
|
||||
4
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/isolate_stub.go
generated
vendored
4
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/isolate_stub.go
generated
vendored
@@ -19,3 +19,7 @@ package netutil
|
||||
func DropPort(port int) error { return nil }
|
||||
|
||||
func RecoverPort(port int) error { return nil }
|
||||
|
||||
func SetLatency(ms, rv int) error { return nil }
|
||||
|
||||
func RemoveLatency() error { return nil }
|
||||
|
||||
37
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/netutil.go
generated
vendored
37
Godeps/_workspace/src/github.com/coreos/etcd/pkg/netutil/netutil.go
generated
vendored
@@ -12,16 +12,14 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package netutil implements network-related utility functions.
|
||||
package netutil
|
||||
|
||||
import (
|
||||
"encoding/base64"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
@@ -118,36 +116,3 @@ func URLStringsEqual(a []string, b []string) bool {
|
||||
|
||||
return urlsEqual(urlsA, urlsB)
|
||||
}
|
||||
|
||||
// BasicAuth returns the username and password provided in the request's
|
||||
// Authorization header, if the request uses HTTP Basic Authentication.
|
||||
// See RFC 2617, Section 2.
|
||||
// Based on the BasicAuth method from the Golang standard lib.
|
||||
// TODO: use the standard lib BasicAuth method when we move to Go 1.4.
|
||||
func BasicAuth(r *http.Request) (username, password string, ok bool) {
|
||||
auth := r.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
return
|
||||
}
|
||||
return parseBasicAuth(auth)
|
||||
}
|
||||
|
||||
// parseBasicAuth parses an HTTP Basic Authentication string.
|
||||
// "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==" returns ("Aladdin", "open sesame", true).
|
||||
// Taken from the Golang standard lib.
|
||||
// TODO: use the standard lib BasicAuth method when we move to Go 1.4.
|
||||
func parseBasicAuth(auth string) (username, password string, ok bool) {
|
||||
if !strings.HasPrefix(auth, "Basic ") {
|
||||
return
|
||||
}
|
||||
c, err := base64.StdEncoding.DecodeString(strings.TrimPrefix(auth, "Basic "))
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
cs := string(c)
|
||||
s := strings.IndexByte(cs, ':')
|
||||
if s < 0 {
|
||||
return
|
||||
}
|
||||
return cs[:s], cs[s+1:], true
|
||||
}
|
||||
|
||||
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
2
Godeps/_workspace/src/github.com/coreos/etcd/pkg/pathutil/path.go
generated
vendored
@@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
// Package pathutil implements utility functions for handling slash-separated
|
||||
// paths.
|
||||
package pathutil
|
||||
|
||||
import "path"
|
||||
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/pbutil/pbutil.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/pbutil/pbutil.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package pbutil defines interfaces for handling Protocol Buffer objects.
|
||||
package pbutil
|
||||
|
||||
import "github.com/coreos/pkg/capnslog"
|
||||
|
||||
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/runtime/fds_linux.go
generated
vendored
1
Godeps/_workspace/src/github.com/coreos/etcd/pkg/runtime/fds_linux.go
generated
vendored
@@ -12,6 +12,7 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package runtime implements utility functions for runtime systems.
|
||||
package runtime
|
||||
|
||||
import (
|
||||
|
||||
168
Godeps/_workspace/src/github.com/coreos/etcd/pkg/schedule/schedule.go
generated
vendored
Normal file
168
Godeps/_workspace/src/github.com/coreos/etcd/pkg/schedule/schedule.go
generated
vendored
Normal file
@@ -0,0 +1,168 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package schedule
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Job func(context.Context)
|
||||
|
||||
// Scheduler can schedule jobs.
|
||||
type Scheduler interface {
|
||||
// Schedule asks the scheduler to schedule a job defined by the given func.
|
||||
// Schedule to a stopped scheduler might panic.
|
||||
Schedule(j Job)
|
||||
|
||||
// Pending returns number of pending jobs
|
||||
Pending() int
|
||||
|
||||
// Scheduled returns the number of scheduled jobs (excluding pending jobs)
|
||||
Scheduled() int
|
||||
|
||||
// Finished returns the number of finished jobs
|
||||
Finished() int
|
||||
|
||||
// WaitFinish waits until at least n job are finished and all pending jobs are finished.
|
||||
WaitFinish(n int)
|
||||
|
||||
// Stop stops the scheduler.
|
||||
Stop()
|
||||
}
|
||||
|
||||
type fifo struct {
|
||||
mu sync.Mutex
|
||||
|
||||
resume chan struct{}
|
||||
scheduled int
|
||||
finished int
|
||||
pendings []Job
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
finishCond *sync.Cond
|
||||
donec chan struct{}
|
||||
}
|
||||
|
||||
// NewFIFOScheduler returns a Scheduler that schedules jobs in FIFO
|
||||
// order sequentially
|
||||
func NewFIFOScheduler() Scheduler {
|
||||
f := &fifo{
|
||||
resume: make(chan struct{}, 1),
|
||||
donec: make(chan struct{}, 1),
|
||||
}
|
||||
f.finishCond = sync.NewCond(&f.mu)
|
||||
f.ctx, f.cancel = context.WithCancel(context.Background())
|
||||
go f.run()
|
||||
return f
|
||||
}
|
||||
|
||||
// Schedule schedules a job that will be ran in FIFO order sequentially.
|
||||
func (f *fifo) Schedule(j Job) {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
|
||||
if f.cancel == nil {
|
||||
panic("schedule: schedule to stopped scheduler")
|
||||
}
|
||||
|
||||
if len(f.pendings) == 0 {
|
||||
select {
|
||||
case f.resume <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
}
|
||||
f.pendings = append(f.pendings, j)
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func (f *fifo) Pending() int {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return len(f.pendings)
|
||||
}
|
||||
|
||||
func (f *fifo) Scheduled() int {
|
||||
f.mu.Lock()
|
||||
defer f.mu.Unlock()
|
||||
return f.scheduled
|
||||
}
|
||||
|
||||
func (f *fifo) Finished() int {
|
||||
f.finishCond.L.Lock()
|
||||
defer f.finishCond.L.Unlock()
|
||||
return f.finished
|
||||
}
|
||||
|
||||
func (f *fifo) WaitFinish(n int) {
|
||||
f.finishCond.L.Lock()
|
||||
for f.finished < n || len(f.pendings) != 0 {
|
||||
f.finishCond.Wait()
|
||||
}
|
||||
f.finishCond.L.Unlock()
|
||||
}
|
||||
|
||||
// Stop stops the scheduler and cancels all pending jobs.
|
||||
func (f *fifo) Stop() {
|
||||
f.mu.Lock()
|
||||
f.cancel()
|
||||
f.cancel = nil
|
||||
f.mu.Unlock()
|
||||
<-f.donec
|
||||
}
|
||||
|
||||
func (f *fifo) run() {
|
||||
// TODO: recover from job panic?
|
||||
defer func() {
|
||||
close(f.donec)
|
||||
close(f.resume)
|
||||
}()
|
||||
|
||||
for {
|
||||
var todo Job
|
||||
f.mu.Lock()
|
||||
if len(f.pendings) != 0 {
|
||||
f.scheduled++
|
||||
todo = f.pendings[0]
|
||||
}
|
||||
f.mu.Unlock()
|
||||
if todo == nil {
|
||||
select {
|
||||
case <-f.resume:
|
||||
case <-f.ctx.Done():
|
||||
f.mu.Lock()
|
||||
pendings := f.pendings
|
||||
f.pendings = nil
|
||||
f.mu.Unlock()
|
||||
// clean up pending jobs
|
||||
for _, todo := range pendings {
|
||||
todo(f.ctx)
|
||||
}
|
||||
return
|
||||
}
|
||||
} else {
|
||||
todo(f.ctx)
|
||||
f.finishCond.L.Lock()
|
||||
f.finished++
|
||||
f.pendings = f.pendings[1:]
|
||||
f.finishCond.Broadcast()
|
||||
f.finishCond.L.Unlock()
|
||||
}
|
||||
}
|
||||
}
|
||||
136
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/leak.go
generated
vendored
Normal file
136
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/leak.go
generated
vendored
Normal file
@@ -0,0 +1,136 @@
|
||||
// Copyright 2013 The Go Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
/*
|
||||
CheckLeakedGoroutine verifies tests do not leave any leaky
|
||||
goroutines. It returns true when there are goroutines still
|
||||
running(leaking) after all tests.
|
||||
|
||||
import "github.com/coreos/etcd/pkg/testutil"
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
v := m.Run()
|
||||
if v == 0 && testutil.CheckLeakedGoroutine() {
|
||||
os.Exit(1)
|
||||
}
|
||||
os.Exit(v)
|
||||
}
|
||||
|
||||
func TestSample(t *testing.T) {
|
||||
defer testutil.AfterTest(t)
|
||||
...
|
||||
}
|
||||
|
||||
*/
|
||||
func CheckLeakedGoroutine() bool {
|
||||
if testing.Short() {
|
||||
// not counting goroutines for leakage in -short mode
|
||||
return false
|
||||
}
|
||||
gs := interestingGoroutines()
|
||||
if len(gs) == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
stackCount := make(map[string]int)
|
||||
re := regexp.MustCompile("\\(0[0-9a-fx, ]*\\)")
|
||||
for _, g := range gs {
|
||||
// strip out pointer arguments in first function of stack dump
|
||||
normalized := string(re.ReplaceAll([]byte(g), []byte("(...)")))
|
||||
stackCount[normalized]++
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "Too many goroutines running after all test(s).\n")
|
||||
for stack, count := range stackCount {
|
||||
fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func AfterTest(t *testing.T) {
|
||||
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
|
||||
if testing.Short() {
|
||||
return
|
||||
}
|
||||
var bad string
|
||||
badSubstring := map[string]string{
|
||||
").writeLoop(": "a Transport",
|
||||
"created by net/http/httptest.(*Server).Start": "an httptest.Server",
|
||||
"timeoutHandler": "a TimeoutHandler",
|
||||
"net.(*netFD).connect(": "a timing out dial",
|
||||
").noteClientGone(": "a closenotifier sender",
|
||||
}
|
||||
|
||||
// readLoop was buggy before go1.5:
|
||||
// https://github.com/golang/go/issues/10457
|
||||
if getAtLeastGo15() {
|
||||
badSubstring[").readLoop("] = "a Transport"
|
||||
}
|
||||
|
||||
var stacks string
|
||||
for i := 0; i < 6; i++ {
|
||||
bad = ""
|
||||
stacks = strings.Join(interestingGoroutines(), "\n\n")
|
||||
for substr, what := range badSubstring {
|
||||
if strings.Contains(stacks, substr) {
|
||||
bad = what
|
||||
}
|
||||
}
|
||||
if bad == "" {
|
||||
return
|
||||
}
|
||||
// Bad stuff found, but goroutines might just still be
|
||||
// shutting down, so give it some time.
|
||||
time.Sleep(50 * time.Millisecond)
|
||||
}
|
||||
t.Errorf("Test appears to have leaked %s:\n%s", bad, stacks)
|
||||
}
|
||||
|
||||
func interestingGoroutines() (gs []string) {
|
||||
buf := make([]byte, 2<<20)
|
||||
buf = buf[:runtime.Stack(buf, true)]
|
||||
for _, g := range strings.Split(string(buf), "\n\n") {
|
||||
sl := strings.SplitN(g, "\n", 2)
|
||||
if len(sl) != 2 {
|
||||
continue
|
||||
}
|
||||
stack := strings.TrimSpace(sl[1])
|
||||
if stack == "" ||
|
||||
strings.Contains(stack, "created by testing.RunTests") ||
|
||||
strings.Contains(stack, "testing.Main(") ||
|
||||
strings.Contains(stack, "runtime.goexit") ||
|
||||
strings.Contains(stack, "github.com/coreos/etcd/pkg/testutil.interestingGoroutines") ||
|
||||
strings.Contains(stack, "github.com/coreos/etcd/pkg/logutil.(*MergeLogger).outputLoop") ||
|
||||
strings.Contains(stack, "github.com/golang/glog.(*loggingT).flushDaemon") ||
|
||||
strings.Contains(stack, "created by runtime.gc") ||
|
||||
strings.Contains(stack, "runtime.MHeap_Scavenger") {
|
||||
continue
|
||||
}
|
||||
gs = append(gs, stack)
|
||||
}
|
||||
sort.Strings(gs)
|
||||
return
|
||||
}
|
||||
|
||||
// getAtLeastGo15 returns true if the runtime has go1.5+.
|
||||
func getAtLeastGo15() bool {
|
||||
var major, minor int
|
||||
var discard string
|
||||
i, err := fmt.Sscanf(runtime.Version(), "go%d.%d%s", &major, &minor, &discard)
|
||||
return (err == nil && i == 3 && (major > 1 || major == 1 && minor >= 5))
|
||||
}
|
||||
57
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/pauseable_handler.go
generated
vendored
Normal file
57
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/pauseable_handler.go
generated
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"sync"
|
||||
)
|
||||
|
||||
type PauseableHandler struct {
|
||||
Next http.Handler
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
}
|
||||
|
||||
func (ph *PauseableHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
ph.mu.Lock()
|
||||
paused := ph.paused
|
||||
ph.mu.Unlock()
|
||||
if !paused {
|
||||
ph.Next.ServeHTTP(w, r)
|
||||
} else {
|
||||
hj, ok := w.(http.Hijacker)
|
||||
if !ok {
|
||||
panic("webserver doesn't support hijacking")
|
||||
}
|
||||
conn, _, err := hj.Hijack()
|
||||
if err != nil {
|
||||
panic(err.Error())
|
||||
}
|
||||
conn.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func (ph *PauseableHandler) Pause() {
|
||||
ph.mu.Lock()
|
||||
defer ph.mu.Unlock()
|
||||
ph.paused = true
|
||||
}
|
||||
|
||||
func (ph *PauseableHandler) Resume() {
|
||||
ph.mu.Lock()
|
||||
defer ph.mu.Unlock()
|
||||
ph.paused = false
|
||||
}
|
||||
132
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/recorder.go
generated
vendored
Normal file
132
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/recorder.go
generated
vendored
Normal file
@@ -0,0 +1,132 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Action struct {
|
||||
Name string
|
||||
Params []interface{}
|
||||
}
|
||||
|
||||
type Recorder interface {
|
||||
// Record publishes an Action (e.g., function call) which will
|
||||
// be reflected by Wait() or Chan()
|
||||
Record(a Action)
|
||||
// Wait waits until at least n Actions are available or returns with error
|
||||
Wait(n int) ([]Action, error)
|
||||
// Action returns immediately available Actions
|
||||
Action() []Action
|
||||
// Chan returns the channel for actions published by Record
|
||||
Chan() <-chan Action
|
||||
}
|
||||
|
||||
// RecorderBuffered appends all Actions to a slice
|
||||
type RecorderBuffered struct {
|
||||
sync.Mutex
|
||||
actions []Action
|
||||
}
|
||||
|
||||
func (r *RecorderBuffered) Record(a Action) {
|
||||
r.Lock()
|
||||
r.actions = append(r.actions, a)
|
||||
r.Unlock()
|
||||
}
|
||||
func (r *RecorderBuffered) Action() []Action {
|
||||
r.Lock()
|
||||
cpy := make([]Action, len(r.actions))
|
||||
copy(cpy, r.actions)
|
||||
r.Unlock()
|
||||
return cpy
|
||||
}
|
||||
func (r *RecorderBuffered) Wait(n int) (acts []Action, err error) {
|
||||
// legacy racey behavior
|
||||
WaitSchedule()
|
||||
acts = r.Action()
|
||||
if len(acts) < n {
|
||||
err = newLenErr(n, len(acts))
|
||||
}
|
||||
return acts, err
|
||||
}
|
||||
|
||||
func (r *RecorderBuffered) Chan() <-chan Action {
|
||||
ch := make(chan Action)
|
||||
go func() {
|
||||
acts := r.Action()
|
||||
for i := range acts {
|
||||
ch <- acts[i]
|
||||
}
|
||||
close(ch)
|
||||
}()
|
||||
return ch
|
||||
}
|
||||
|
||||
// RecorderStream writes all Actions to an unbuffered channel
|
||||
type recorderStream struct {
|
||||
ch chan Action
|
||||
}
|
||||
|
||||
func NewRecorderStream() Recorder {
|
||||
return &recorderStream{ch: make(chan Action)}
|
||||
}
|
||||
|
||||
func (r *recorderStream) Record(a Action) {
|
||||
r.ch <- a
|
||||
}
|
||||
|
||||
func (r *recorderStream) Action() (acts []Action) {
|
||||
for {
|
||||
select {
|
||||
case act := <-r.ch:
|
||||
acts = append(acts, act)
|
||||
default:
|
||||
return acts
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (r *recorderStream) Chan() <-chan Action {
|
||||
return r.ch
|
||||
}
|
||||
|
||||
func (r *recorderStream) Wait(n int) ([]Action, error) {
|
||||
acts := make([]Action, n)
|
||||
timeoutC := time.After(5 * time.Second)
|
||||
for i := 0; i < n; i++ {
|
||||
select {
|
||||
case acts[i] = <-r.ch:
|
||||
case <-timeoutC:
|
||||
acts = acts[:i]
|
||||
return acts, newLenErr(n, i)
|
||||
}
|
||||
}
|
||||
// extra wait to catch any Action spew
|
||||
select {
|
||||
case act := <-r.ch:
|
||||
acts = append(acts, act)
|
||||
case <-time.After(10 * time.Millisecond):
|
||||
}
|
||||
return acts, nil
|
||||
}
|
||||
|
||||
func newLenErr(expected int, actual int) error {
|
||||
s := fmt.Sprintf("len(actions) = %d, expected >= %d", actual, expected)
|
||||
return errors.New(s)
|
||||
}
|
||||
56
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/testutil.go
generated
vendored
Normal file
56
Godeps/_workspace/src/github.com/coreos/etcd/pkg/testutil/testutil.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package testutil provides test utility functions.
|
||||
package testutil
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"runtime"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// TODO: improve this when we are able to know the schedule or status of target go-routine.
|
||||
func WaitSchedule() {
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
}
|
||||
|
||||
func MustNewURLs(t *testing.T, urls []string) []url.URL {
|
||||
if urls == nil {
|
||||
return nil
|
||||
}
|
||||
var us []url.URL
|
||||
for _, url := range urls {
|
||||
u := MustNewURL(t, url)
|
||||
us = append(us, *u)
|
||||
}
|
||||
return us
|
||||
}
|
||||
|
||||
func MustNewURL(t *testing.T, s string) *url.URL {
|
||||
u, err := url.Parse(s)
|
||||
if err != nil {
|
||||
t.Fatalf("parse %v error: %v", s, err)
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
// FatalStack helps to fatal the test and print out the stacks of all running goroutines.
|
||||
func FatalStack(t *testing.T, s string) {
|
||||
stackTrace := make([]byte, 8*1024)
|
||||
n := runtime.Stack(stackTrace, true)
|
||||
t.Error(string(stackTrace[:n]))
|
||||
t.Fatalf(s)
|
||||
}
|
||||
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/doc.go
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/doc.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package transport implements various HTTP transport utilities based on Go
|
||||
// net package.
|
||||
package transport
|
||||
15
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/listener.go
generated
vendored
15
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/listener.go
generated
vendored
@@ -26,7 +26,13 @@ import (
|
||||
)
|
||||
|
||||
func NewListener(addr string, scheme string, info TLSInfo) (net.Listener, error) {
|
||||
l, err := net.Listen("tcp", addr)
|
||||
nettype := "tcp"
|
||||
if scheme == "unix" {
|
||||
// unix sockets via unix://laddr
|
||||
nettype = scheme
|
||||
}
|
||||
|
||||
l, err := net.Listen(nettype, addr)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -46,18 +52,19 @@ func NewListener(addr string, scheme string, info TLSInfo) (net.Listener, error)
|
||||
return l, nil
|
||||
}
|
||||
|
||||
func NewTransport(info TLSInfo) (*http.Transport, error) {
|
||||
func NewTransport(info TLSInfo, dialtimeoutd time.Duration) (*http.Transport, error) {
|
||||
cfg, err := info.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
t := &http.Transport{
|
||||
// timeouts taken from http.DefaultTransport
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
Timeout: dialtimeoutd,
|
||||
// value taken from http.DefaultTransport
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
// value taken from http.DefaultTransport
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
TLSClientConfig: cfg,
|
||||
}
|
||||
|
||||
13
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/timeout_transport.go
generated
vendored
13
Godeps/_workspace/src/github.com/coreos/etcd/pkg/transport/timeout_transport.go
generated
vendored
@@ -23,14 +23,19 @@ import (
|
||||
// NewTimeoutTransport returns a transport created using the given TLS info.
|
||||
// If read/write on the created connection blocks longer than its time limit,
|
||||
// it will return timeout error.
|
||||
// If read/write timeout is set, transport will not be able to reuse connection.
|
||||
func NewTimeoutTransport(info TLSInfo, dialtimeoutd, rdtimeoutd, wtimeoutd time.Duration) (*http.Transport, error) {
|
||||
tr, err := NewTransport(info)
|
||||
tr, err := NewTransport(info, dialtimeoutd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// the timeouted connection will tiemout soon after it is idle.
|
||||
// it should not be put back to http transport as an idle connection for future usage.
|
||||
tr.MaxIdleConnsPerHost = -1
|
||||
|
||||
if rdtimeoutd != 0 || wtimeoutd != 0 {
|
||||
// the timed out connection will timeout soon after it is idle.
|
||||
// it should not be put back to http transport as an idle connection for future usage.
|
||||
tr.MaxIdleConnsPerHost = -1
|
||||
}
|
||||
|
||||
tr.Dial = (&rwTimeoutDialer{
|
||||
Dialer: net.Dialer{
|
||||
Timeout: dialtimeoutd,
|
||||
|
||||
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
Normal file
17
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/doc.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package types declares various data types and implements type-checking
|
||||
// functions.
|
||||
package types
|
||||
8
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urlsmap.go
generated
vendored
8
Godeps/_workspace/src/github.com/coreos/etcd/pkg/types/urlsmap.go
generated
vendored
@@ -20,6 +20,7 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// URLsMap is a map from a name to its URLs.
|
||||
type URLsMap map[string]URLs
|
||||
|
||||
// NewURLsMap returns a URLsMap instantiated from the given string,
|
||||
@@ -39,9 +40,9 @@ func NewURLsMap(s string) (URLsMap, error) {
|
||||
return cl, nil
|
||||
}
|
||||
|
||||
// String returns NameURLPairs into discovery-formatted name-to-URLs sorted by name.
|
||||
// String turns URLsMap into discovery-formatted name-to-URLs sorted by name.
|
||||
func (c URLsMap) String() string {
|
||||
pairs := make([]string, 0)
|
||||
var pairs []string
|
||||
for name, urls := range c {
|
||||
for _, url := range urls {
|
||||
pairs = append(pairs, fmt.Sprintf("%s=%s", name, url.String()))
|
||||
@@ -54,7 +55,7 @@ func (c URLsMap) String() string {
|
||||
// URLs returns a list of all URLs.
|
||||
// The returned list is sorted in ascending lexicographical order.
|
||||
func (c URLsMap) URLs() []string {
|
||||
urls := make([]string, 0)
|
||||
var urls []string
|
||||
for _, us := range c {
|
||||
for _, u := range us {
|
||||
urls = append(urls, u.String())
|
||||
@@ -64,6 +65,7 @@ func (c URLsMap) URLs() []string {
|
||||
return urls
|
||||
}
|
||||
|
||||
// Len returns the size of URLsMap.
|
||||
func (c URLsMap) Len() int {
|
||||
return len(c)
|
||||
}
|
||||
|
||||
18
Godeps/_workspace/src/github.com/coreos/etcd/pkg/wait/wait.go
generated
vendored
18
Godeps/_workspace/src/github.com/coreos/etcd/pkg/wait/wait.go
generated
vendored
@@ -12,9 +12,12 @@
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package wait provides utility functions for polling, listening using Go
|
||||
// channel.
|
||||
package wait
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
)
|
||||
|
||||
@@ -39,6 +42,8 @@ func (w *List) Register(id uint64) <-chan interface{} {
|
||||
if ch == nil {
|
||||
ch = make(chan interface{}, 1)
|
||||
w.m[id] = ch
|
||||
} else {
|
||||
log.Panicf("dup id %x", id)
|
||||
}
|
||||
return ch
|
||||
}
|
||||
@@ -53,3 +58,16 @@ func (w *List) Trigger(id uint64, x interface{}) {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
type waitWithResponse struct {
|
||||
ch <-chan interface{}
|
||||
}
|
||||
|
||||
func NewWithResponse(ch <-chan interface{}) Wait {
|
||||
return &waitWithResponse{ch: ch}
|
||||
}
|
||||
|
||||
func (w *waitWithResponse) Register(id uint64) <-chan interface{} {
|
||||
return w.ch
|
||||
}
|
||||
func (w *waitWithResponse) Trigger(id uint64, x interface{}) {}
|
||||
|
||||
30
Godeps/_workspace/src/github.com/coreos/etcd/pkg/wait/wait_time.go
generated
vendored
30
Godeps/_workspace/src/github.com/coreos/etcd/pkg/wait/wait_time.go
generated
vendored
@@ -1,18 +1,16 @@
|
||||
/*
|
||||
Copyright 2015 CoreOS, Inc.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package wait
|
||||
|
||||
@@ -26,7 +24,7 @@ type WaitTime interface {
|
||||
// The chan will be triggered when Trigger is called with a
|
||||
// deadline that is later than the one it is waiting for.
|
||||
// The given deadline MUST be unique. The deadline should be
|
||||
// retrived by calling time.Now() in most cases.
|
||||
// retrieved by calling time.Now() in most cases.
|
||||
Wait(deadline time.Time) <-chan struct{}
|
||||
// Trigger triggers all the waiting chans with an earlier deadline.
|
||||
Trigger(deadline time.Time)
|
||||
|
||||
12
Godeps/_workspace/src/github.com/coreos/etcd/raft/design.md
generated
vendored
12
Godeps/_workspace/src/github.com/coreos/etcd/raft/design.md
generated
vendored
@@ -37,21 +37,21 @@ receives msgAppResp(rej=true)
|
||||
+--------------------+
|
||||
```
|
||||
|
||||
When in `probe` state, leader sends at most one `replication message` per heartbeat interval. The leader sends `replication message` slowly and probing the actual progress of the follower. A `msgHeartbeatResp` or a `msgAppResp` with reject might trigger the sending of the next `replication message`.
|
||||
When the progress of a follower is in `probe` state, leader sends at most one `replication message` per heartbeat interval. The leader sends `replication message` slowly and probing the actual progress of the follower. A `msgHeartbeatResp` or a `msgAppResp` with reject might trigger the sending of the next `replication message`.
|
||||
|
||||
When in `replicate` state, leader sends `replication message`, then optimistically increases `next` to the latest entry sent. This is an optimized state for fast replicating log entries to the follower.
|
||||
When the progress of a follower is in `replicate` state, leader sends `replication message`, then optimistically increases `next` to the latest entry sent. This is an optimized state for fast replicating log entries to the follower.
|
||||
|
||||
When in `snapshot` state, leader stops sending any `replication message`.
|
||||
When the progress of a follower is in `snapshot` state, leader stops sending any `replication message`.
|
||||
|
||||
A newly elected leader sets the progresses of all the followers to `probe` state with `match` = 0 and `next` = last index. The leader slowly (at most once per heartbeat) sends `replication message` to the follower and probes its progress.
|
||||
|
||||
A progress changes to `replicate` when the follower replies with a non-rejection `msgAppResp`, which implies that it has matched the index sent. At this point, leader starts to stream log entries to the follower fast. The progress will fall back to `probe` when the follower replies a rejection `msgAppResp` or the link layer reports the follower is unreachable. We aggressively reset `next` to `match`+1 since if we receive any `msgAppResp` soon, both `match` and `next` will increase directly to the `index` in `msgAppResp`. (We might end up with sending some duplicate entries when agressively reset `next` too low. see open question)
|
||||
A progress changes to `replicate` when the follower replies with a non-rejection `msgAppResp`, which implies that it has matched the index sent. At this point, leader starts to stream log entries to the follower fast. The progress will fall back to `probe` when the follower replies a rejection `msgAppResp` or the link layer reports the follower is unreachable. We aggressively reset `next` to `match`+1 since if we receive any `msgAppResp` soon, both `match` and `next` will increase directly to the `index` in `msgAppResp`. (We might end up with sending some duplicate entries when aggressively reset `next` too low. see open question)
|
||||
|
||||
A progress changes from `probe` to `snapshot` when the follower falls very far behind and requires a snapshot. After sending `msgSnap`, the leader waits until the success, failure or abortion of the previous snapshot sent. The progress will go back to `probe` after the sending result is applied.
|
||||
|
||||
### Flow Control
|
||||
|
||||
1. limit the max size of message sent per message. Max should be configurable.
|
||||
Lower the cost at probing state as we limit the size per message; lower the penalty when aggressively decrease to a too low `next`
|
||||
Lower the cost at probing state as we limit the size per message; lower the penalty when aggressively decreased to a too low `next`
|
||||
|
||||
2. limit the # of in flight messages < N when in `replicate` state. N should be configurable. Most implementation will have a sending buffer on top of its actual network transport layer (not blocking raft node). We want to make sure raft does not overflow that buffer, which can cause message dropping and triggering a bunch of unnecessary resend in repeatedly.
|
||||
2. limit the # of in flight messages < N when in `replicate` state. N should be configurable. Most implementation will have a sending buffer on top of its actual network transport layer (not blocking raft node). We want to make sure raft does not overflow that buffer, which can cause message dropping and triggering a bunch of unnecessary resending repeatedly.
|
||||
|
||||
152
Godeps/_workspace/src/github.com/coreos/etcd/raft/doc.go
generated
vendored
152
Godeps/_workspace/src/github.com/coreos/etcd/raft/doc.go
generated
vendored
@@ -13,12 +13,25 @@
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package raft provides an implementation of the raft consensus algorithm.
|
||||
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,
|
||||
@@ -30,6 +43,29 @@ using raft.StartNode or start a Node from some initial state using raft.RestartN
|
||||
}
|
||||
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
|
||||
@@ -43,8 +79,16 @@ 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 after 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). If any Message has type MsgSnap,
|
||||
call Node.ReportSnapshot() after it has been sent (these messages may be large).
|
||||
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()
|
||||
@@ -86,9 +130,9 @@ The total state machine handling loop will look something like this:
|
||||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
processSnapshot(rd.Snapshot)
|
||||
}
|
||||
for entry := range rd.CommittedEntries {
|
||||
for _, entry := range rd.CommittedEntries {
|
||||
process(entry)
|
||||
if entry.Type == raftpb.EntryConfChange:
|
||||
if entry.Type == raftpb.EntryConfChange {
|
||||
var cc raftpb.ConfChange
|
||||
cc.Unmarshal(entry.Data)
|
||||
s.Node.ApplyConfChange(cc)
|
||||
@@ -146,8 +190,104 @@ 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 recommened to use three or more nodes in
|
||||
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 leaders 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 sends periodic heartbeat
|
||||
messages of the 'MsgBeat' type 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.
|
||||
|
||||
'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
|
||||
|
||||
10
Godeps/_workspace/src/github.com/coreos/etcd/raft/log.go
generated
vendored
10
Godeps/_workspace/src/github.com/coreos/etcd/raft/log.go
generated
vendored
@@ -148,6 +148,16 @@ func (l *raftLog) nextEnts() (ents []pb.Entry) {
|
||||
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())
|
||||
if l.committed+1 > off {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (l *raftLog) snapshot() (pb.Snapshot, error) {
|
||||
if l.unstable.snapshot != nil {
|
||||
return *l.unstable.snapshot, nil
|
||||
|
||||
8
Godeps/_workspace/src/github.com/coreos/etcd/raft/log_unstable.go
generated
vendored
8
Godeps/_workspace/src/github.com/coreos/etcd/raft/log_unstable.go
generated
vendored
@@ -16,7 +16,7 @@ package raft
|
||||
|
||||
import pb "github.com/coreos/etcd/raft/raftpb"
|
||||
|
||||
// unstable.entris[i] has raft log position i+unstable.offset.
|
||||
// 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.
|
||||
@@ -51,7 +51,7 @@ func (u *unstable) maybeLastIndex() (uint64, bool) {
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// myabeTerm returns the term of the entry at index i, if there
|
||||
// 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 {
|
||||
@@ -79,8 +79,8 @@ func (u *unstable) stableTo(i, t uint64) {
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
// if i < offest, term is matched with the snapshot
|
||||
// only update the unstalbe entries if term is matched with
|
||||
// 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:]
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user