2016-05-13 06:49:40 +03:00
|
|
|
// Copyright 2015 The etcd Authors
|
2015-01-25 06:19:16 +03:00
|
|
|
//
|
|
|
|
// 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.
|
2014-10-18 02:41:22 +04:00
|
|
|
|
2016-04-05 02:31:35 +03:00
|
|
|
package v2http
|
2014-08-28 00:37:22 +04:00
|
|
|
|
2014-08-29 03:41:42 +04:00
|
|
|
import (
|
2017-09-07 00:57:25 +03:00
|
|
|
"context"
|
2014-09-10 23:00:20 +04:00
|
|
|
"errors"
|
2014-08-29 03:41:42 +04:00
|
|
|
"net/http"
|
|
|
|
"net/http/httptest"
|
2014-10-24 02:57:27 +04:00
|
|
|
"sort"
|
2014-08-29 03:41:42 +04:00
|
|
|
"testing"
|
2014-09-03 08:36:14 +04:00
|
|
|
|
2019-05-29 00:59:02 +03:00
|
|
|
"go.etcd.io/etcd/etcdserver"
|
|
|
|
"go.etcd.io/etcd/etcdserver/api/membership"
|
|
|
|
"go.etcd.io/etcd/etcdserver/api/v2error"
|
|
|
|
"go.etcd.io/etcd/etcdserver/etcdserverpb"
|
|
|
|
"go.etcd.io/etcd/pkg/types"
|
|
|
|
"go.etcd.io/etcd/raft/raftpb"
|
2017-09-07 00:57:25 +03:00
|
|
|
|
2016-03-23 03:10:28 +03:00
|
|
|
"github.com/coreos/go-semver/semver"
|
2018-05-02 21:34:21 +03:00
|
|
|
"go.uber.org/zap"
|
2014-08-29 03:41:42 +04:00
|
|
|
)
|
|
|
|
|
2014-10-25 21:54:41 +04:00
|
|
|
type fakeCluster struct {
|
|
|
|
id uint64
|
|
|
|
clientURLs []string
|
2016-04-07 22:02:37 +03:00
|
|
|
members map[uint64]*membership.Member
|
2014-09-24 01:08:18 +04:00
|
|
|
}
|
|
|
|
|
2014-10-31 03:31:14 +03:00
|
|
|
func (c *fakeCluster) ID() types.ID { return types.ID(c.id) }
|
2014-10-25 21:54:41 +04:00
|
|
|
func (c *fakeCluster) ClientURLs() []string { return c.clientURLs }
|
2016-04-07 22:02:37 +03:00
|
|
|
func (c *fakeCluster) Members() []*membership.Member {
|
|
|
|
var ms membership.MembersByID
|
2014-10-25 21:54:41 +04:00
|
|
|
for _, m := range c.members {
|
2015-05-21 19:02:33 +03:00
|
|
|
ms = append(ms, m)
|
2014-09-10 23:00:20 +04:00
|
|
|
}
|
2015-05-21 19:02:33 +03:00
|
|
|
sort.Sort(ms)
|
2016-04-07 22:02:37 +03:00
|
|
|
return []*membership.Member(ms)
|
2014-09-10 23:00:20 +04:00
|
|
|
}
|
2016-04-07 22:02:37 +03:00
|
|
|
func (c *fakeCluster) Member(id types.ID) *membership.Member { return c.members[uint64(id)] }
|
2015-05-13 19:33:38 +03:00
|
|
|
func (c *fakeCluster) Version() *semver.Version { return nil }
|
2014-09-10 23:00:20 +04:00
|
|
|
|
2014-10-25 21:54:41 +04:00
|
|
|
// errServer implements the etcd.Server interface for testing.
|
|
|
|
// It returns the given error from any Do/Process/AddMember/RemoveMember calls.
|
|
|
|
type errServer struct {
|
|
|
|
err error
|
2017-08-07 10:48:05 +03:00
|
|
|
fakeServer
|
2014-09-12 02:12:31 +04:00
|
|
|
}
|
|
|
|
|
2014-10-25 21:54:41 +04:00
|
|
|
func (fs *errServer) Do(ctx context.Context, r etcdserverpb.Request) (etcdserver.Response, error) {
|
|
|
|
return etcdserver.Response{}, fs.err
|
2014-09-09 21:00:12 +04:00
|
|
|
}
|
2014-10-25 21:54:41 +04:00
|
|
|
func (fs *errServer) Process(ctx context.Context, m raftpb.Message) error {
|
|
|
|
return fs.err
|
2014-09-09 12:02:51 +04:00
|
|
|
}
|
2017-04-13 01:27:28 +03:00
|
|
|
func (fs *errServer) AddMember(ctx context.Context, m membership.Member) ([]*membership.Member, error) {
|
|
|
|
return nil, fs.err
|
2014-09-09 11:23:58 +04:00
|
|
|
}
|
2017-04-13 01:27:28 +03:00
|
|
|
func (fs *errServer) RemoveMember(ctx context.Context, id uint64) ([]*membership.Member, error) {
|
|
|
|
return nil, fs.err
|
2014-09-09 11:23:58 +04:00
|
|
|
}
|
2017-04-13 01:27:28 +03:00
|
|
|
func (fs *errServer) UpdateMember(ctx context.Context, m membership.Member) ([]*membership.Member, error) {
|
|
|
|
return nil, fs.err
|
2014-11-11 22:46:49 +03:00
|
|
|
}
|
2019-03-22 12:53:00 +03:00
|
|
|
func (fs *errServer) PromoteMember(ctx context.Context, id uint64) ([]*membership.Member, error) {
|
|
|
|
return nil, fs.err
|
|
|
|
}
|
2014-09-09 11:23:58 +04:00
|
|
|
|
2014-09-11 02:46:13 +04:00
|
|
|
func TestWriteError(t *testing.T) {
|
2014-09-10 23:00:20 +04:00
|
|
|
// nil error should not panic
|
2015-02-18 00:15:48 +03:00
|
|
|
rec := httptest.NewRecorder()
|
2015-09-25 18:06:26 +03:00
|
|
|
r := new(http.Request)
|
2018-05-02 21:34:21 +03:00
|
|
|
writeError(zap.NewExample(), rec, r, nil)
|
2015-02-18 00:15:48 +03:00
|
|
|
h := rec.Header()
|
2014-09-10 23:00:20 +04:00
|
|
|
if len(h) > 0 {
|
|
|
|
t.Fatalf("unexpected non-empty headers: %#v", h)
|
|
|
|
}
|
2015-02-18 00:15:48 +03:00
|
|
|
b := rec.Body.String()
|
2014-09-10 23:00:20 +04:00
|
|
|
if len(b) > 0 {
|
|
|
|
t.Fatalf("unexpected non-empty body: %q", b)
|
|
|
|
}
|
|
|
|
|
2014-09-09 21:00:12 +04:00
|
|
|
tests := []struct {
|
2014-09-11 01:14:14 +04:00
|
|
|
err error
|
|
|
|
wcode int
|
|
|
|
wi string
|
2014-09-10 23:00:20 +04:00
|
|
|
}{
|
|
|
|
{
|
2018-02-26 22:36:56 +03:00
|
|
|
v2error.NewError(v2error.EcodeKeyNotFound, "/foo/bar", 123),
|
2014-09-10 23:00:20 +04:00
|
|
|
http.StatusNotFound,
|
|
|
|
"123",
|
|
|
|
},
|
|
|
|
{
|
2018-02-26 22:36:56 +03:00
|
|
|
v2error.NewError(v2error.EcodeTestFailed, "/foo/bar", 456),
|
2014-09-10 23:00:20 +04:00
|
|
|
http.StatusPreconditionFailed,
|
|
|
|
"456",
|
|
|
|
},
|
|
|
|
{
|
2014-09-11 01:14:14 +04:00
|
|
|
err: errors.New("something went wrong"),
|
|
|
|
wcode: http.StatusInternalServerError,
|
2014-09-10 23:00:20 +04:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range tests {
|
|
|
|
rw := httptest.NewRecorder()
|
2018-05-02 21:34:21 +03:00
|
|
|
writeError(zap.NewExample(), rw, r, tt.err)
|
2014-09-11 01:14:14 +04:00
|
|
|
if code := rw.Code; code != tt.wcode {
|
|
|
|
t.Errorf("#%d: code=%d, want %d", i, code, tt.wcode)
|
2014-09-10 23:00:20 +04:00
|
|
|
}
|
2014-09-11 01:14:14 +04:00
|
|
|
if idx := rw.Header().Get("X-Etcd-Index"); idx != tt.wi {
|
|
|
|
t.Errorf("#%d: X-Etcd-Index=%q, want %q", i, idx, tt.wi)
|
2014-09-10 23:00:20 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2014-09-13 02:16:29 +04:00
|
|
|
func TestAllowMethod(t *testing.T) {
|
|
|
|
tests := []struct {
|
|
|
|
m string
|
|
|
|
ms []string
|
|
|
|
w bool
|
|
|
|
wh string
|
|
|
|
}{
|
|
|
|
// Accepted methods
|
|
|
|
{
|
|
|
|
m: "GET",
|
|
|
|
ms: []string{"GET", "POST", "PUT"},
|
|
|
|
w: true,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
m: "POST",
|
|
|
|
ms: []string{"POST"},
|
|
|
|
w: true,
|
|
|
|
},
|
|
|
|
// Made-up methods no good
|
|
|
|
{
|
|
|
|
m: "FAKE",
|
|
|
|
ms: []string{"GET", "POST", "PUT"},
|
|
|
|
w: false,
|
|
|
|
wh: "GET,POST,PUT",
|
|
|
|
},
|
|
|
|
// Empty methods no good
|
|
|
|
{
|
|
|
|
m: "",
|
|
|
|
ms: []string{"GET", "POST"},
|
|
|
|
w: false,
|
|
|
|
wh: "GET,POST",
|
|
|
|
},
|
|
|
|
// Empty accepted methods no good
|
|
|
|
{
|
|
|
|
m: "GET",
|
|
|
|
ms: []string{""},
|
|
|
|
w: false,
|
|
|
|
wh: "",
|
|
|
|
},
|
|
|
|
// No methods accepted
|
|
|
|
{
|
|
|
|
m: "GET",
|
|
|
|
ms: []string{},
|
|
|
|
w: false,
|
|
|
|
wh: "",
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
for i, tt := range tests {
|
|
|
|
rw := httptest.NewRecorder()
|
|
|
|
g := allowMethod(rw, tt.m, tt.ms...)
|
|
|
|
if g != tt.w {
|
|
|
|
t.Errorf("#%d: got allowMethod()=%t, want %t", i, g, tt.w)
|
|
|
|
}
|
|
|
|
if !tt.w {
|
|
|
|
if rw.Code != http.StatusMethodNotAllowed {
|
|
|
|
t.Errorf("#%d: code=%d, want %d", i, rw.Code, http.StatusMethodNotAllowed)
|
|
|
|
}
|
|
|
|
gh := rw.Header().Get("Allow")
|
|
|
|
if gh != tt.wh {
|
|
|
|
t.Errorf("#%d: Allow header=%q, want %q", i, gh, tt.wh)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|