Compare commits

...

21 Commits

Author SHA1 Message Date
df60227765 version: bump to v2.3.4 2016-05-12 14:33:37 -07:00
4db35c113d README: add known bugs
cherry-picked from https://github.com/coreos/etcd/pull/5320.
2016-05-12 14:22:45 -07:00
cf68c2285e etcdctl: Add --wal-dir and --backup-wal-dir options to backup command.
If the WAL is stored in a separate directory then the backup command
would need a --wal-dir option to pick the path to the WAL directory.
The user might also want to store the backup of data and wal separately
for which --backup-wal-dir option is provided.
2016-05-12 14:17:31 -07:00
743f9c9bb0 etcdctl/ctlv2: total-timeout for Sync
Fix https://github.com/coreos/etcd/issues/4897.
2016-05-12 14:15:13 -07:00
f9e09e1b1a httpproxy: fix race on getting close notifier channel
Fixes #5267
2016-05-12 14:14:16 -07:00
d6eb1e7a5f *: bump to v2.3.3+git 2016-04-29 14:18:50 -07:00
c41345d393 *: bump to v2.3.3 2016-04-29 12:38:04 -07:00
506ef9fe8d etcdserver/auth: check empty password in merge
Fix https://github.com/coreos/etcd/issues/5182.
2016-04-29 12:37:24 -07:00
49141d5916 *: bump to v2.3.2+git 2016-04-21 12:57:57 -07:00
ce63f10738 *: bump to v2.3.2 2016-04-21 11:26:37 -07:00
31bd750141 etcdserver: close response body when getting cluster information 2016-04-21 10:58:58 -07:00
37510d0306 e2e: test etcdtl user list on root user 2016-04-21 10:58:46 -07:00
d7da3787bc client: accept roles in response for ListUser
Fixes #5046
2016-04-21 10:58:40 -07:00
54d0f1d43b e2e: test etcdctl v2 double user grant
Crashes in 2.3.1
2016-04-21 10:58:30 -07:00
20db10f6f7 etcdctl: print grant/revoke error instead of scanning roles for changes
Fixes #5045
2016-04-21 10:58:17 -07:00
11c09373e1 etcdmain: start on unsupported arch when ETCD_UNSUPPORTED_ARCH is set 2016-04-21 10:42:45 -07:00
96f412e4d7 *: bump to v2.3.1+git 2016-04-01 14:56:48 -07:00
2b67f5256a *: bump to v2.3.1 2016-04-01 14:32:28 -07:00
6aa8b631e6 client: return original ctx error
Fix https://github.com/coreos/etcd/issues/3209.
2016-04-01 14:30:31 -07:00
72dea51e6a rafthttp: do not block on proposal 2016-04-01 14:28:50 -07:00
74fa0270a4 *: bump to v2.3.0+git 2016-03-18 10:23:04 -07:00
17 changed files with 193 additions and 94 deletions

View File

@ -217,12 +217,14 @@ To recover from such scenarios, etcd provides functionality to backup and restor
**NB:** Windows users must stop etcd before running the backup command.
The first step of the recovery is to backup the data directory on a functioning etcd node. To do this, use the `etcdctl backup` command, passing in the original data directory used by etcd. For example:
The first step of the recovery is to backup the data directory and wal directory, if stored separately, on a functioning etcd node. To do this, use the `etcdctl backup` command, passing in the original data (and wal) directory used by etcd. For example:
```sh
etcdctl backup \
--data-dir %data_dir% \
[--wal-dir %wal_dir%] \
--backup-dir %backup_data_dir%
[--backup-wal-dir %backup_wal_dir%]
```
This command will rewrite some of the metadata contained in the backup (specifically, the node ID and cluster ID), which means that the node will lose its former identity. In order to recreate a cluster from the backup, you will need to start a new, single-node cluster. The metadata is rewritten to prevent the new node from inadvertently being joined onto an existing cluster.
@ -234,20 +236,24 @@ To restore a backup using the procedure created above, start etcd with the `-for
```sh
etcd \
-data-dir=%backup_data_dir% \
[-wal-dir=%backup_wal_dir%] \
-force-new-cluster \
...
```
Now etcd should be available on this node and serving the original datastore.
Once you have verified that etcd has started successfully, shut it down and move the data back to the previous location (you may wish to make another copy as well to be safe):
Once you have verified that etcd has started successfully, shut it down and move the data and wal, if stored separately, back to the previous location (you may wish to make another copy as well to be safe):
```sh
pkill etcd
rm -fr %data_dir%
rm -fr %wal_dir%
mv %backup_data_dir% %data_dir%
mv %backup_wal_dir% %wal_dir%
etcd \
-data-dir=%data_dir% \
[-wal-dir=%wal_dir%] \
...
```

View File

@ -118,6 +118,17 @@ See [CONTRIBUTING](CONTRIBUTING.md) for details on submitting patches and the co
See [reporting bugs](Documentation/reporting_bugs.md) for details about reporting any issue you may encounter.
## Known bugs
[GH518](https://github.com/coreos/etcd/issues/518) is a known bug. Issue is that:
```
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d value=bar
curl http://127.0.0.1:2379/v2/keys/foo -XPUT -d dir=true -d prevExist=true
```
If the previous node is a key and client tries to overwrite it with `dir=true`, it does not give warnings such as `Not a directory`. Instead, the key is set to empty value.
## Project Details
### Versioning
@ -137,12 +148,15 @@ curl -L http://127.0.0.1:2379/version
The `v2` API responses should not change after the 2.0.0 release but new features will be added over time.
#### 32-bit systems
#### 32-bit and other unsupported systems
etcd has known issues on 32-bit systems due to a bug in the Go runtime. See #[358][358] for more information.
To avoid inadvertantly producing an unstable etcd server, 32-bit builds emit an `etcd` that prints
a warning message and immediately exits.
To avoid inadvertantly running a possibly unstable etcd server, `etcd` on unsupported architectures will print
a warning message and immediately exit if the environment variable `ETCD_UNSUPPORTED_ARCH` is not set to
the target architecture.
Currently only the amd64 architecture is officially supported by `etcd`.
[358]: https://github.com/coreos/etcd/issues/358

View File

@ -36,6 +36,12 @@ type User struct {
Revoke []string `json:"revoke,omitempty"`
}
// userListEntry is the user representation given by the server for ListUsers
type userListEntry struct {
User string `json:"user"`
Roles []Role `json:"roles"`
}
type UserRoles struct {
User string `json:"user"`
Roles []Role `json:"roles"`
@ -198,7 +204,7 @@ func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
}
var userList struct {
Users []User `json:"users"`
Users []userListEntry `json:"users"`
}
if err = json.Unmarshal(body, &userList); err != nil {

View File

@ -342,7 +342,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
resp, body, err = hc.Do(ctx, action)
if err != nil {
cerr.Errors = append(cerr.Errors, err)
// mask previous errors with context error, which is controlled by user
if err == ctx.Err() {
return nil, nil, ctx.Err()
}
if err == context.Canceled || err == context.DeadlineExceeded {
return nil, nil, err
}

View File

@ -295,7 +295,7 @@ func TestSimpleHTTPClientDoHeaderTimeout(t *testing.T) {
t.Fatalf("expected non-nil error, got nil")
}
case <-time.After(time.Second):
t.Fatalf("unexpected timeout when waitting for the test to finish")
t.Fatalf("unexpected timeout when waiting for the test to finish")
}
}
@ -444,7 +444,51 @@ func TestHTTPClusterClientDoDeadlineExceedContext(t *testing.T) {
t.Errorf("err = %+v, want %+v", err, context.DeadlineExceeded)
}
case <-time.After(time.Second):
t.Fatalf("unexpected timeout when waitting for request to deadline exceed")
t.Fatalf("unexpected timeout when waiting for request to deadline exceed")
}
}
type fakeCancelContext struct{}
var fakeCancelContextError = errors.New("fake context canceled")
func (f fakeCancelContext) Deadline() (time.Time, bool) { return time.Time{}, false }
func (f fakeCancelContext) Done() <-chan struct{} {
d := make(chan struct{}, 1)
d <- struct{}{}
return d
}
func (f fakeCancelContext) Err() error { return fakeCancelContextError }
func (f fakeCancelContext) Value(key interface{}) interface{} { return 1 }
func withTimeout(parent context.Context, timeout time.Duration) (context.Context, context.CancelFunc) {
return parent, func() { parent = nil }
}
func TestHTTPClusterClientDoCanceledContext(t *testing.T) {
fakeURL := url.URL{}
tr := newFakeTransport()
tr.finishCancel <- struct{}{}
c := &httpClusterClient{
clientFactory: newHTTPClientFactory(tr, DefaultCheckRedirect, 0),
endpoints: []url.URL{fakeURL},
}
errc := make(chan error)
go func() {
ctx, cancel := withTimeout(fakeCancelContext{}, time.Millisecond)
cancel()
_, _, err := c.Do(ctx, &fakeAction{})
errc <- err
}()
select {
case err := <-errc:
if err != fakeCancelContextError {
t.Errorf("err = %+v, want %+v", err, fakeCancelContextError)
}
case <-time.After(time.Second):
t.Fatalf("unexpected timeout when waiting for request to fake context canceled")
}
}

View File

@ -185,9 +185,18 @@ func testCtlV2GetRoleUser(t *testing.T, cfg *etcdProcessClusterConfig) {
if err := etcdctlUserGet(epc, "username"); err != nil {
t.Fatalf("failed to get user (%v)", err)
}
// ensure double grant gives an error; was crashing in 2.3.1
regrantArgs := etcdctlPrefixArgs(epc)
regrantArgs = append(regrantArgs, "user", "grant", "--roles", "foo", "username")
if err := spawnWithExpect(regrantArgs, "duplicate"); err != nil {
t.Fatalf("missing duplicate error on double grant role (%v)", err)
}
}
func TestCtlV2UserList(t *testing.T) {
func TestCtlV2UserListUsername(t *testing.T) { testCtlV2UserList(t, "username") }
func TestCtlV2UserListRoot(t *testing.T) { testCtlV2UserList(t, "root") }
func testCtlV2UserList(t *testing.T, username string) {
defer testutil.AfterTest(t)
epc := setupEtcdctlTest(t, &configWithProxy, false)
@ -197,10 +206,10 @@ func TestCtlV2UserList(t *testing.T) {
}
}()
if err := etcdctlUserAdd(epc, "username", "password"); err != nil {
if err := etcdctlUserAdd(epc, username, "password"); err != nil {
t.Fatalf("failed to add user (%v)", err)
}
if err := etcdctlUserList(epc, "username"); err != nil {
if err := etcdctlUserList(epc, username); err != nil {
t.Fatalf("failed to list users (%v)", err)
}
}

View File

@ -37,7 +37,9 @@ func NewBackupCommand() cli.Command {
ArgsUsage: " ",
Flags: []cli.Flag{
cli.StringFlag{Name: "data-dir", Value: "", Usage: "Path to the etcd data dir"},
cli.StringFlag{Name: "wal-dir", Value: "", Usage: "Path to the etcd wal dir"},
cli.StringFlag{Name: "backup-dir", Value: "", Usage: "Path to the backup dir"},
cli.StringFlag{Name: "backup-wal-dir", Value: "", Usage: "Path to the backup wal dir"},
},
Action: handleBackup,
}
@ -45,10 +47,23 @@ func NewBackupCommand() cli.Command {
// handleBackup handles a request that intends to do a backup.
func handleBackup(c *cli.Context) {
var srcWAL string
var destWAL string
srcSnap := path.Join(c.String("data-dir"), "member", "snap")
destSnap := path.Join(c.String("backup-dir"), "member", "snap")
srcWAL := path.Join(c.String("data-dir"), "member", "wal")
destWAL := path.Join(c.String("backup-dir"), "member", "wal")
if c.String("wal-dir") != "" {
srcWAL = c.String("wal-dir")
} else {
srcWAL = path.Join(c.String("data-dir"), "member", "wal")
}
if c.String("backup-wal-dir") != "" {
destWAL = c.String("backup-wal-dir")
} else {
destWAL = path.Join(c.String("backup-dir"), "member", "wal")
}
if err := os.MkdirAll(destSnap, 0700); err != nil {
log.Fatalf("failed creating backup snapshot dir %v: %v", destSnap, err)

View File

@ -17,8 +17,6 @@ package command
import (
"fmt"
"os"
"reflect"
"sort"
"strings"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/bgentry/speakeasy"
@ -195,21 +193,12 @@ func userGrantRevoke(c *cli.Context, grant bool) {
os.Exit(1)
}
var newUser *client.User
if grant {
newUser, err = api.GrantUser(ctx, user, roles)
_, err = api.GrantUser(ctx, user, roles)
} else {
newUser, err = api.RevokeUser(ctx, user, roles)
}
sort.Strings(newUser.Roles)
sort.Strings(currentUser.Roles)
if reflect.DeepEqual(newUser.Roles, currentUser.Roles) {
if grant {
fmt.Printf("User unchanged; roles already granted")
} else {
fmt.Printf("User unchanged; roles already revoked")
}
_, err = api.RevokeUser(ctx, user, roles)
}
if err != nil {
fmt.Fprintln(os.Stderr, err.Error())
os.Exit(1)

View File

@ -168,7 +168,13 @@ func getTransport(c *cli.Context) (*http.Transport, error) {
CertFile: certfile,
KeyFile: keyfile,
}
return transport.NewTransport(tls, defaultDialTimeout)
dialTimeout := defaultDialTimeout
totalTimeout := c.GlobalDuration("total-timeout")
if totalTimeout != 0 && totalTimeout < dialTimeout {
dialTimeout = totalTimeout
}
return transport.NewTransport(tls, dialTimeout)
}
func getUsernamePasswordFromFlag(usernameFlag string) (username string, password string, err error) {
@ -215,7 +221,7 @@ func mustNewClient(c *cli.Context) client.Client {
if debug {
fmt.Fprintf(os.Stderr, "start to sync cluster using endpoints(%s)\n", strings.Join(hc.Endpoints(), ","))
}
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
ctx, cancel := contextWithTotalTimeout(c)
err := hc.Sync(ctx)
cancel()
if err != nil {

View File

@ -12,9 +12,6 @@
// See the License for the specific language governing permissions and
// limitations under the License.
// TODO: support arm64
// +build amd64
package etcdmain
import (
@ -79,6 +76,8 @@ var (
)
func Main() {
checkSupportArch()
cfg := NewConfig()
err := cfg.Parse(os.Args[1:])
if err != nil {
@ -554,3 +553,16 @@ func setupLogging(cfg *config) {
repoLog.SetLogLevel(settings)
}
}
func checkSupportArch() {
// TODO qualify arm64
if runtime.GOARCH == "amd64" {
return
}
if env, ok := os.LookupEnv("ETCD_UNSUPPORTED_ARCH"); ok && env == runtime.GOARCH {
plog.Warningf("running etcd on unsupported architecture %q since ETCD_UNSUPPORTED_ARCH is set", env)
return
}
plog.Errorf("etcd on unsupported platform without ETCD_UNSUPPORTED_ARCH=%s set.", runtime.GOARCH)
os.Exit(1)
}

View File

@ -1,31 +0,0 @@
// 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 !amd64
package etcdmain
import (
"fmt"
"os"
"github.com/coreos/etcd/Godeps/_workspace/src/github.com/coreos/pkg/capnslog"
)
var plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdmain")
func Main() {
fmt.Println("unsupported architecture; unreliable, unstable")
os.Exit(-1)
}

View File

@ -281,13 +281,7 @@ 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)
newUser, err := old.merge(user, s.PasswordStore)
if err != nil {
return old, err
}
@ -452,29 +446,33 @@ func (s *store) DisableAuth() error {
// is called and returns a new User with these modifications applied. Think of
// all Users as immutable sets of data. Merge allows you to perform the set
// operations (desired grants and revokes) atomically
func (u User) merge(n User) (User, error) {
func (ou User) merge(nu User, s PasswordStore) (User, error) {
var out User
if u.User != n.User {
return out, authErr(http.StatusConflict, "Merging user data with conflicting usernames: %s %s", u.User, n.User)
if ou.User != nu.User {
return out, authErr(http.StatusConflict, "Merging user data with conflicting usernames: %s %s", ou.User, nu.User)
}
out.User = u.User
if n.Password != "" {
out.Password = n.Password
out.User = ou.User
if nu.Password != "" {
hash, err := s.HashPassword(nu.Password)
if err != nil {
return ou, err
}
out.Password = hash
} else {
out.Password = u.Password
out.Password = ou.Password
}
currentRoles := types.NewUnsafeSet(u.Roles...)
for _, g := range n.Grant {
currentRoles := types.NewUnsafeSet(ou.Roles...)
for _, g := range nu.Grant {
if currentRoles.Contains(g) {
plog.Noticef("granting duplicate role %s for user %s", g, n.User)
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Granting duplicate role %s for user %s", g, n.User))
plog.Noticef("granting duplicate role %s for user %s", g, nu.User)
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Granting duplicate role %s for user %s", g, nu.User))
}
currentRoles.Add(g)
}
for _, r := range n.Revoke {
for _, r := range nu.Revoke {
if !currentRoles.Contains(r) {
plog.Noticef("revoking ungranted role %s for user %s", r, n.User)
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Revoking ungranted role %s for user %s", r, n.User))
plog.Noticef("revoking ungranted role %s for user %s", r, nu.User)
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Revoking ungranted role %s for user %s", r, nu.User))
}
currentRoles.Remove(r)
}

View File

@ -26,6 +26,21 @@ import (
etcdstore "github.com/coreos/etcd/store"
)
type fakeDoer struct{}
func (_ fakeDoer) Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error) {
return etcdserver.Response{}, nil
}
func TestCheckPassword(t *testing.T) {
st := NewStore(fakeDoer{}, 5*time.Second)
u := User{Password: "$2a$10$I3iddh1D..EIOXXQtsra4u8AjOtgEa2ERxVvYGfXFBJDo1omXwP.q"}
matched := st.CheckPassword(u, "foo")
if matched {
t.Fatalf("expected false, got %v", matched)
}
}
const testTimeout = time.Millisecond
func TestMergeUser(t *testing.T) {
@ -71,16 +86,16 @@ func TestMergeUser(t *testing.T) {
User{User: "foo", Roles: []string{"role1", "role2"}},
false,
},
{
User{User: "foo"},
User{User: "foo", Password: "$2a$10$aUPOdbOGNawaVSusg3g2wuC3AH6XxIr9/Ms4VgDvzrAVOJPYzZILa"},
User{User: "foo", Roles: []string{}, Password: "$2a$10$aUPOdbOGNawaVSusg3g2wuC3AH6XxIr9/Ms4VgDvzrAVOJPYzZILa"},
{ // empty password will not overwrite the previous password
User{User: "foo", Password: "foo", Roles: []string{}},
User{User: "foo", Password: ""},
User{User: "foo", Password: "foo", Roles: []string{}},
false,
},
}
for i, tt := range tbl {
out, err := tt.input.merge(tt.merge)
out, err := tt.input.merge(tt.merge, passwordStore{})
if err != nil && !tt.iserr {
t.Fatalf("Got unexpected error on item %d", i)
}

View File

@ -71,6 +71,7 @@ func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool
continue
}
b, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
if logerr {
plog.Warningf("could not read the body of cluster response: %v", err)

View File

@ -111,9 +111,10 @@ func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request
closeNotifier, ok := rw.(http.CloseNotifier)
cancel := httputil.RequestCanceler(p.transport, proxyreq)
if ok {
closeCh := closeNotifier.CloseNotify()
go func() {
select {
case <-closeNotifier.CloseNotify():
case <-closeCh:
atomic.StoreInt32(&requestClosed, 1)
log.Printf("proxy: client %v closed request prematurely", clientreq.RemoteAddr)
cancel()

View File

@ -142,11 +142,23 @@ func startPeer(transport *Transport, urls types.URLs, local, to, cid types.ID, r
go func() {
for {
select {
case mm := <-p.propc:
case mm := <-p.recvc:
if err := r.Process(ctx, mm); err != nil {
plog.Warningf("failed to process raft message (%v)", err)
}
case mm := <-p.recvc:
case <-p.stopc:
return
}
}
}()
// r.Process might block for processing proposal when there is no leader.
// Thus propc must be put into a separate routine with recvc to avoid blocking
// processing other raft messages.
go func() {
for {
select {
case mm := <-p.propc:
if err := r.Process(ctx, mm); err != nil {
plog.Warningf("failed to process raft message (%v)", err)
}

View File

@ -29,7 +29,7 @@ import (
var (
// MinClusterVersion is the min cluster version this etcd binary is compatible with.
MinClusterVersion = "2.2.0"
Version = "2.3.0"
Version = "2.3.4"
// Git SHA Value will be set during build
GitSHA = "Not provided (use ./build instead of go build)"