Add vendor folder to git

This commit is contained in:
Lucas Käldström 2017-06-26 19:23:05 +03:00
parent 66cf5eaafb
commit 183585f56f
No known key found for this signature in database
GPG key ID: 600FEFBBD0D40D21
6916 changed files with 2629581 additions and 1 deletions

110
vendor/github.com/coreos/etcd/proxy/grpcproxy/auth.go generated vendored Normal file
View file

@ -0,0 +1,110 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"golang.org/x/net/context"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
type AuthProxy struct {
client *clientv3.Client
}
func NewAuthProxy(c *clientv3.Client) pb.AuthServer {
return &AuthProxy{client: c}
}
func (ap *AuthProxy) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).AuthEnable(ctx, r)
}
func (ap *AuthProxy) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).AuthDisable(ctx, r)
}
func (ap *AuthProxy) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).Authenticate(ctx, r)
}
func (ap *AuthProxy) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleAdd(ctx, r)
}
func (ap *AuthProxy) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleDelete(ctx, r)
}
func (ap *AuthProxy) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleGet(ctx, r)
}
func (ap *AuthProxy) RoleList(ctx context.Context, r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleList(ctx, r)
}
func (ap *AuthProxy) RoleRevokePermission(ctx context.Context, r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleRevokePermission(ctx, r)
}
func (ap *AuthProxy) RoleGrantPermission(ctx context.Context, r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleGrantPermission(ctx, r)
}
func (ap *AuthProxy) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserAdd(ctx, r)
}
func (ap *AuthProxy) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserDelete(ctx, r)
}
func (ap *AuthProxy) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserGet(ctx, r)
}
func (ap *AuthProxy) UserList(ctx context.Context, r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserList(ctx, r)
}
func (ap *AuthProxy) UserGrantRole(ctx context.Context, r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserGrantRole(ctx, r)
}
func (ap *AuthProxy) UserRevokeRole(ctx context.Context, r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserRevokeRole(ctx, r)
}
func (ap *AuthProxy) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserChangePassword(ctx, r)
}

View file

@ -0,0 +1,168 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cache
import (
"errors"
"sync"
"time"
"github.com/karlseguin/ccache"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/pkg/adt"
)
var (
DefaultMaxEntries = 2048
ErrCompacted = rpctypes.ErrGRPCCompacted
)
const defaultHistoricTTL = time.Hour
const defaultCurrentTTL = time.Minute
type Cache interface {
Add(req *pb.RangeRequest, resp *pb.RangeResponse)
Get(req *pb.RangeRequest) (*pb.RangeResponse, error)
Compact(revision int64)
Invalidate(key []byte, endkey []byte)
Close()
}
// keyFunc returns the key of an request, which is used to look up in the cache for it's caching response.
func keyFunc(req *pb.RangeRequest) string {
// TODO: use marshalTo to reduce allocation
b, err := req.Marshal()
if err != nil {
panic(err)
}
return string(b)
}
func NewCache(maxCacheEntries int) Cache {
return &cache{
lru: ccache.New(ccache.Configure().MaxSize(int64(maxCacheEntries))),
compactedRev: -1,
}
}
func (c *cache) Close() { c.lru.Stop() }
// cache implements Cache
type cache struct {
mu sync.RWMutex
lru *ccache.Cache
// a reverse index for cache invalidation
cachedRanges adt.IntervalTree
compactedRev int64
}
// Add adds the response of a request to the cache if its revision is larger than the compacted revision of the cache.
func (c *cache) Add(req *pb.RangeRequest, resp *pb.RangeResponse) {
key := keyFunc(req)
c.mu.Lock()
defer c.mu.Unlock()
if req.Revision > c.compactedRev {
if req.Revision == 0 {
c.lru.Set(key, resp, defaultCurrentTTL)
} else {
c.lru.Set(key, resp, defaultHistoricTTL)
}
}
// we do not need to invalidate a request with a revision specified.
// so we do not need to add it into the reverse index.
if req.Revision != 0 {
return
}
var (
iv *adt.IntervalValue
ivl adt.Interval
)
if len(req.RangeEnd) != 0 {
ivl = adt.NewStringAffineInterval(string(req.Key), string(req.RangeEnd))
} else {
ivl = adt.NewStringAffinePoint(string(req.Key))
}
iv = c.cachedRanges.Find(ivl)
if iv == nil {
c.cachedRanges.Insert(ivl, []string{key})
} else {
iv.Val = append(iv.Val.([]string), key)
}
}
// Get looks up the caching response for a given request.
// Get is also responsible for lazy eviction when accessing compacted entries.
func (c *cache) Get(req *pb.RangeRequest) (*pb.RangeResponse, error) {
key := keyFunc(req)
c.mu.RLock()
defer c.mu.RUnlock()
if req.Revision < c.compactedRev {
c.lru.Delete(key)
return nil, ErrCompacted
}
if item := c.lru.Get(key); item != nil {
return item.Value().(*pb.RangeResponse), nil
}
return nil, errors.New("not exist")
}
// Invalidate invalidates the cache entries that intersecting with the given range from key to endkey.
func (c *cache) Invalidate(key, endkey []byte) {
c.mu.Lock()
defer c.mu.Unlock()
var (
ivs []*adt.IntervalValue
ivl adt.Interval
)
if len(endkey) == 0 {
ivl = adt.NewStringAffinePoint(string(key))
} else {
ivl = adt.NewStringAffineInterval(string(key), string(endkey))
}
ivs = c.cachedRanges.Stab(ivl)
for _, iv := range ivs {
keys := iv.Val.([]string)
for _, key := range keys {
c.lru.Delete(key)
}
}
// delete after removing all keys since it is destructive to 'ivs'
c.cachedRanges.Delete(ivl)
}
// Compact invalidate all caching response before the given rev.
// Replace with the invalidation is lazy. The actual removal happens when the entries is accessed.
func (c *cache) Compact(revision int64) {
c.mu.Lock()
defer c.mu.Unlock()
if revision > c.compactedRev {
c.compactedRev = revision
}
}

View file

@ -0,0 +1,52 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
)
type clusterProxy struct {
client *clientv3.Client
}
func NewClusterProxy(c *clientv3.Client) pb.ClusterServer {
return &clusterProxy{
client: c,
}
}
func (cp *clusterProxy) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
conn := cp.client.ActiveConnection()
return pb.NewClusterClient(conn).MemberAdd(ctx, r)
}
func (cp *clusterProxy) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest) (*pb.MemberRemoveResponse, error) {
conn := cp.client.ActiveConnection()
return pb.NewClusterClient(conn).MemberRemove(ctx, r)
}
func (cp *clusterProxy) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest) (*pb.MemberUpdateResponse, error) {
conn := cp.client.ActiveConnection()
return pb.NewClusterClient(conn).MemberUpdate(ctx, r)
}
func (cp *clusterProxy) MemberList(ctx context.Context, r *pb.MemberListRequest) (*pb.MemberListResponse, error) {
conn := cp.client.ActiveConnection()
return pb.NewClusterClient(conn).MemberList(ctx, r)
}

16
vendor/github.com/coreos/etcd/proxy/grpcproxy/doc.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package grpcproxy is an OSI level 7 proxy for etcd v3 API requests.
package grpcproxy

210
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv.go generated vendored Normal file
View file

@ -0,0 +1,210 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/proxy/grpcproxy/cache"
"golang.org/x/net/context"
)
type kvProxy struct {
kv clientv3.KV
cache cache.Cache
}
func NewKvProxy(c *clientv3.Client) (pb.KVServer, <-chan struct{}) {
kv := &kvProxy{
kv: c.KV,
cache: cache.NewCache(cache.DefaultMaxEntries),
}
donec := make(chan struct{})
go func() {
defer close(donec)
<-c.Ctx().Done()
kv.cache.Close()
}()
return kv, donec
}
func (p *kvProxy) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
if r.Serializable {
resp, err := p.cache.Get(r)
switch err {
case nil:
cacheHits.Inc()
return resp, nil
case cache.ErrCompacted:
cacheHits.Inc()
return nil, err
}
}
cachedMisses.Inc()
resp, err := p.kv.Do(ctx, RangeRequestToOp(r))
if err != nil {
return nil, err
}
// cache linearizable as serializable
req := *r
req.Serializable = true
gresp := (*pb.RangeResponse)(resp.Get())
p.cache.Add(&req, gresp)
return gresp, nil
}
func (p *kvProxy) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
p.cache.Invalidate(r.Key, nil)
resp, err := p.kv.Do(ctx, PutRequestToOp(r))
return (*pb.PutResponse)(resp.Put()), err
}
func (p *kvProxy) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
p.cache.Invalidate(r.Key, r.RangeEnd)
resp, err := p.kv.Do(ctx, DelRequestToOp(r))
return (*pb.DeleteRangeResponse)(resp.Del()), err
}
func (p *kvProxy) txnToCache(reqs []*pb.RequestOp, resps []*pb.ResponseOp) {
for i := range resps {
switch tv := resps[i].Response.(type) {
case *pb.ResponseOp_ResponsePut:
p.cache.Invalidate(reqs[i].GetRequestPut().Key, nil)
case *pb.ResponseOp_ResponseDeleteRange:
rdr := reqs[i].GetRequestDeleteRange()
p.cache.Invalidate(rdr.Key, rdr.RangeEnd)
case *pb.ResponseOp_ResponseRange:
req := *(reqs[i].GetRequestRange())
req.Serializable = true
p.cache.Add(&req, tv.ResponseRange)
}
}
}
func (p *kvProxy) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
txn := p.kv.Txn(ctx)
cmps := make([]clientv3.Cmp, len(r.Compare))
thenops := make([]clientv3.Op, len(r.Success))
elseops := make([]clientv3.Op, len(r.Failure))
for i := range r.Compare {
cmps[i] = (clientv3.Cmp)(*r.Compare[i])
}
for i := range r.Success {
thenops[i] = requestOpToOp(r.Success[i])
}
for i := range r.Failure {
elseops[i] = requestOpToOp(r.Failure[i])
}
resp, err := txn.If(cmps...).Then(thenops...).Else(elseops...).Commit()
if err != nil {
return nil, err
}
// txn may claim an outdated key is updated; be safe and invalidate
for _, cmp := range r.Compare {
p.cache.Invalidate(cmp.Key, nil)
}
// update any fetched keys
if resp.Succeeded {
p.txnToCache(r.Success, resp.Responses)
} else {
p.txnToCache(r.Failure, resp.Responses)
}
return (*pb.TxnResponse)(resp), nil
}
func (p *kvProxy) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
var opts []clientv3.CompactOption
if r.Physical {
opts = append(opts, clientv3.WithCompactPhysical())
}
resp, err := p.kv.Compact(ctx, r.Revision, opts...)
if err == nil {
p.cache.Compact(r.Revision)
}
return (*pb.CompactionResponse)(resp), err
}
func requestOpToOp(union *pb.RequestOp) clientv3.Op {
switch tv := union.Request.(type) {
case *pb.RequestOp_RequestRange:
if tv.RequestRange != nil {
return RangeRequestToOp(tv.RequestRange)
}
case *pb.RequestOp_RequestPut:
if tv.RequestPut != nil {
return PutRequestToOp(tv.RequestPut)
}
case *pb.RequestOp_RequestDeleteRange:
if tv.RequestDeleteRange != nil {
return DelRequestToOp(tv.RequestDeleteRange)
}
}
panic("unknown request")
}
func RangeRequestToOp(r *pb.RangeRequest) clientv3.Op {
opts := []clientv3.OpOption{}
if len(r.RangeEnd) != 0 {
opts = append(opts, clientv3.WithRange(string(r.RangeEnd)))
}
opts = append(opts, clientv3.WithRev(r.Revision))
opts = append(opts, clientv3.WithLimit(r.Limit))
opts = append(opts, clientv3.WithSort(
clientv3.SortTarget(r.SortTarget),
clientv3.SortOrder(r.SortOrder)),
)
opts = append(opts, clientv3.WithMaxCreateRev(r.MaxCreateRevision))
opts = append(opts, clientv3.WithMinCreateRev(r.MinCreateRevision))
opts = append(opts, clientv3.WithMaxModRev(r.MaxModRevision))
opts = append(opts, clientv3.WithMinModRev(r.MinModRevision))
if r.Serializable {
opts = append(opts, clientv3.WithSerializable())
}
return clientv3.OpGet(string(r.Key), opts...)
}
func PutRequestToOp(r *pb.PutRequest) clientv3.Op {
opts := []clientv3.OpOption{}
opts = append(opts, clientv3.WithLease(clientv3.LeaseID(r.Lease)))
return clientv3.OpPut(string(r.Key), string(r.Value), opts...)
}
func DelRequestToOp(r *pb.DeleteRangeRequest) clientv3.Op {
opts := []clientv3.OpOption{}
if len(r.RangeEnd) != 0 {
opts = append(opts, clientv3.WithRange(string(r.RangeEnd)))
}
if r.PrevKv {
opts = append(opts, clientv3.WithPrevKV())
}
return clientv3.OpDelete(string(r.Key), opts...)
}

View file

@ -0,0 +1,47 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
type kvs2kvc struct{ kvs pb.KVServer }
func KvServerToKvClient(kvs pb.KVServer) pb.KVClient {
return &kvs2kvc{kvs}
}
func (s *kvs2kvc) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (*pb.RangeResponse, error) {
return s.kvs.Range(ctx, in)
}
func (s *kvs2kvc) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (*pb.PutResponse, error) {
return s.kvs.Put(ctx, in)
}
func (s *kvs2kvc) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (*pb.DeleteRangeResponse, error) {
return s.kvs.DeleteRange(ctx, in)
}
func (s *kvs2kvc) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (*pb.TxnResponse, error) {
return s.kvs.Txn(ctx, in)
}
func (s *kvs2kvc) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (*pb.CompactionResponse, error) {
return s.kvs.Compact(ctx, in)
}

View file

@ -0,0 +1,98 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"net"
"testing"
"time"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/integration"
"github.com/coreos/etcd/pkg/testutil"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
func TestKVProxyRange(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
kvts := newKVProxyServer([]string{clus.Members[0].GRPCAddr()}, t)
defer kvts.close()
// create a client and try to get key from proxy.
cfg := clientv3.Config{
Endpoints: []string{kvts.l.Addr().String()},
DialTimeout: 5 * time.Second,
}
client, err := clientv3.New(cfg)
if err != nil {
t.Fatalf("err = %v, want nil", err)
}
_, err = client.Get(context.Background(), "foo")
if err != nil {
t.Fatalf("err = %v, want nil", err)
}
client.Close()
}
type kvproxyTestServer struct {
kp pb.KVServer
c *clientv3.Client
server *grpc.Server
l net.Listener
}
func (kts *kvproxyTestServer) close() {
kts.server.Stop()
kts.l.Close()
kts.c.Close()
}
func newKVProxyServer(endpoints []string, t *testing.T) *kvproxyTestServer {
cfg := clientv3.Config{
Endpoints: endpoints,
DialTimeout: 5 * time.Second,
}
client, err := clientv3.New(cfg)
if err != nil {
t.Fatal(err)
}
kvp, _ := NewKvProxy(client)
kvts := &kvproxyTestServer{
kp: kvp,
c: client,
}
var opts []grpc.ServerOption
kvts.server = grpc.NewServer(opts...)
pb.RegisterKVServer(kvts.server, kvts.kp)
kvts.l, err = net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
go kvts.server.Serve(kvts.l)
return kvts
}

87
vendor/github.com/coreos/etcd/proxy/grpcproxy/lease.go generated vendored Normal file
View file

@ -0,0 +1,87 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"golang.org/x/net/context"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
type leaseProxy struct {
client *clientv3.Client
}
func NewLeaseProxy(c *clientv3.Client) pb.LeaseServer {
return &leaseProxy{
client: c,
}
}
func (lp *leaseProxy) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
conn := lp.client.ActiveConnection()
return pb.NewLeaseClient(conn).LeaseGrant(ctx, cr)
}
func (lp *leaseProxy) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
conn := lp.client.ActiveConnection()
return pb.NewLeaseClient(conn).LeaseRevoke(ctx, rr)
}
func (lp *leaseProxy) LeaseTimeToLive(ctx context.Context, rr *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) {
conn := lp.client.ActiveConnection()
return pb.NewLeaseClient(conn).LeaseTimeToLive(ctx, rr)
}
func (lp *leaseProxy) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
conn := lp.client.ActiveConnection()
ctx, cancel := context.WithCancel(stream.Context())
lc, err := pb.NewLeaseClient(conn).LeaseKeepAlive(ctx)
if err != nil {
cancel()
return err
}
go func() {
// Cancel the context attached to lc to unblock lc.Recv when
// this routine returns on error.
defer cancel()
for {
// stream.Recv will be unblock when the loop in the parent routine
// returns on error.
rr, err := stream.Recv()
if err != nil {
return
}
err = lc.Send(rr)
if err != nil {
return
}
}
}()
for {
rr, err := lc.Recv()
if err != nil {
return err
}
err = stream.Send(rr)
if err != nil {
return err
}
}
}

View file

@ -0,0 +1,74 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"golang.org/x/net/context"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
type maintenanceProxy struct {
client *clientv3.Client
}
func NewMaintenanceProxy(c *clientv3.Client) pb.MaintenanceServer {
return &maintenanceProxy{
client: c,
}
}
func (mp *maintenanceProxy) Defragment(ctx context.Context, dr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Defragment(ctx, dr)
}
func (mp *maintenanceProxy) Snapshot(sr *pb.SnapshotRequest, stream pb.Maintenance_SnapshotServer) error {
conn := mp.client.ActiveConnection()
ctx, cancel := context.WithCancel(stream.Context())
defer cancel()
sc, err := pb.NewMaintenanceClient(conn).Snapshot(ctx, sr)
if err != nil {
return err
}
for {
rr, err := sc.Recv()
if err != nil {
return err
}
err = stream.Send(rr)
if err != nil {
return err
}
}
}
func (mp *maintenanceProxy) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Hash(ctx, r)
}
func (mp *maintenanceProxy) Alarm(ctx context.Context, r *pb.AlarmRequest) (*pb.AlarmResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Alarm(ctx, r)
}
func (mp *maintenanceProxy) Status(ctx context.Context, r *pb.StatusRequest) (*pb.StatusResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Status(ctx, r)
}

View file

@ -0,0 +1,51 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import "github.com/prometheus/client_golang/prometheus"
var (
watchersCoalescing = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "watchers_coalescing_total",
Help: "Total number of current watchers coalescing",
})
eventsCoalescing = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "events_coalescing_total",
Help: "Total number of events coalescing",
})
cacheHits = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "cache_hits_total",
Help: "Total number of cache hits",
})
cachedMisses = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "cache_misses_total",
Help: "Total number of cache misses",
})
)
func init() {
prometheus.MustRegister(watchersCoalescing)
prometheus.MustRegister(eventsCoalescing)
prometheus.MustRegister(cacheHits)
prometheus.MustRegister(cachedMisses)
}

267
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch.go generated vendored Normal file
View file

@ -0,0 +1,267 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
"golang.org/x/net/context"
"golang.org/x/time/rate"
"google.golang.org/grpc/metadata"
"github.com/coreos/etcd/clientv3"
"github.com/coreos/etcd/etcdserver/api/v3rpc"
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
type watchProxy struct {
cw clientv3.Watcher
ctx context.Context
ranges *watchRanges
// retryLimiter controls the create watch retry rate on lost leaders.
retryLimiter *rate.Limiter
// mu protects leaderc updates.
mu sync.RWMutex
leaderc chan struct{}
// wg waits until all outstanding watch servers quit.
wg sync.WaitGroup
}
const (
lostLeaderKey = "__lostleader" // watched to detect leader loss
retryPerSecond = 10
)
func NewWatchProxy(c *clientv3.Client) (pb.WatchServer, <-chan struct{}) {
wp := &watchProxy{
cw: c.Watcher,
ctx: clientv3.WithRequireLeader(c.Ctx()),
retryLimiter: rate.NewLimiter(rate.Limit(retryPerSecond), retryPerSecond),
leaderc: make(chan struct{}),
}
wp.ranges = newWatchRanges(wp)
ch := make(chan struct{})
go func() {
defer close(ch)
// a new streams without opening any watchers won't catch
// a lost leader event, so have a special watch to monitor it
rev := int64((uint64(1) << 63) - 2)
for wp.ctx.Err() == nil {
wch := wp.cw.Watch(wp.ctx, lostLeaderKey, clientv3.WithRev(rev))
for range wch {
}
wp.mu.Lock()
close(wp.leaderc)
wp.leaderc = make(chan struct{})
wp.mu.Unlock()
wp.retryLimiter.Wait(wp.ctx)
}
wp.mu.Lock()
<-wp.ctx.Done()
wp.mu.Unlock()
wp.wg.Wait()
wp.ranges.stop()
}()
return wp, ch
}
func (wp *watchProxy) Watch(stream pb.Watch_WatchServer) (err error) {
wp.mu.Lock()
select {
case <-wp.ctx.Done():
wp.mu.Unlock()
return
default:
wp.wg.Add(1)
}
wp.mu.Unlock()
ctx, cancel := context.WithCancel(stream.Context())
wps := &watchProxyStream{
ranges: wp.ranges,
watchers: make(map[int64]*watcher),
stream: stream,
watchCh: make(chan *pb.WatchResponse, 1024),
ctx: ctx,
cancel: cancel,
}
var leaderc <-chan struct{}
if md, ok := metadata.FromContext(stream.Context()); ok {
v := md[rpctypes.MetadataRequireLeaderKey]
if len(v) > 0 && v[0] == rpctypes.MetadataHasLeader {
leaderc = wp.lostLeaderNotify()
}
}
// post to stopc => terminate server stream; can't use a waitgroup
// since all goroutines will only terminate after Watch() exits.
stopc := make(chan struct{}, 3)
go func() {
defer func() { stopc <- struct{}{} }()
wps.recvLoop()
}()
go func() {
defer func() { stopc <- struct{}{} }()
wps.sendLoop()
}()
// tear down watch if leader goes down or entire watch proxy is terminated
go func() {
defer func() { stopc <- struct{}{} }()
select {
case <-leaderc:
case <-ctx.Done():
case <-wp.ctx.Done():
}
}()
<-stopc
cancel()
// recv/send may only shutdown after function exits;
// goroutine notifies proxy that stream is through
go func() {
<-stopc
<-stopc
wps.close()
wp.wg.Done()
}()
select {
case <-leaderc:
return rpctypes.ErrNoLeader
default:
return wps.ctx.Err()
}
}
func (wp *watchProxy) lostLeaderNotify() <-chan struct{} {
wp.mu.RLock()
defer wp.mu.RUnlock()
return wp.leaderc
}
// watchProxyStream forwards etcd watch events to a proxied client stream.
type watchProxyStream struct {
ranges *watchRanges
// mu protects watchers and nextWatcherID
mu sync.Mutex
// watchers receive events from watch broadcast.
watchers map[int64]*watcher
// nextWatcherID is the id to assign the next watcher on this stream.
nextWatcherID int64
stream pb.Watch_WatchServer
// watchCh receives watch responses from the watchers.
watchCh chan *pb.WatchResponse
ctx context.Context
cancel context.CancelFunc
}
func (wps *watchProxyStream) close() {
var wg sync.WaitGroup
wps.cancel()
wps.mu.Lock()
wg.Add(len(wps.watchers))
for _, wpsw := range wps.watchers {
go func(w *watcher) {
wps.ranges.delete(w)
wg.Done()
}(wpsw)
}
wps.watchers = nil
wps.mu.Unlock()
wg.Wait()
close(wps.watchCh)
}
func (wps *watchProxyStream) recvLoop() error {
for {
req, err := wps.stream.Recv()
if err != nil {
return err
}
switch uv := req.RequestUnion.(type) {
case *pb.WatchRequest_CreateRequest:
cr := uv.CreateRequest
w := &watcher{
wr: watchRange{string(cr.Key), string(cr.RangeEnd)},
id: wps.nextWatcherID,
wps: wps,
nextrev: cr.StartRevision,
progress: cr.ProgressNotify,
prevKV: cr.PrevKv,
filters: v3rpc.FiltersFromRequest(cr),
}
if !w.wr.valid() {
w.post(&pb.WatchResponse{WatchId: -1, Created: true, Canceled: true})
continue
}
wps.nextWatcherID++
w.nextrev = cr.StartRevision
wps.watchers[w.id] = w
wps.ranges.add(w)
case *pb.WatchRequest_CancelRequest:
wps.delete(uv.CancelRequest.WatchId)
default:
panic("not implemented")
}
}
}
func (wps *watchProxyStream) sendLoop() {
for {
select {
case wresp, ok := <-wps.watchCh:
if !ok {
return
}
if err := wps.stream.Send(wresp); err != nil {
return
}
case <-wps.ctx.Done():
return
}
}
}
func (wps *watchProxyStream) delete(id int64) {
wps.mu.Lock()
defer wps.mu.Unlock()
w, ok := wps.watchers[id]
if !ok {
return
}
wps.ranges.delete(w)
delete(wps.watchers, id)
resp := &pb.WatchResponse{
Header: &w.lastHeader,
WatchId: id,
Canceled: true,
}
wps.watchCh <- resp
}

View file

@ -0,0 +1,158 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
"golang.org/x/net/context"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
)
// watchBroadcast broadcasts a server watcher to many client watchers.
type watchBroadcast struct {
// cancel stops the underlying etcd server watcher and closes ch.
cancel context.CancelFunc
donec chan struct{}
// mu protects rev and receivers.
mu sync.RWMutex
// nextrev is the minimum expected next revision of the watcher on ch.
nextrev int64
// receivers contains all the client-side watchers to serve.
receivers map[*watcher]struct{}
// responses counts the number of responses
responses int
}
func newWatchBroadcast(wp *watchProxy, w *watcher, update func(*watchBroadcast)) *watchBroadcast {
cctx, cancel := context.WithCancel(wp.ctx)
wb := &watchBroadcast{
cancel: cancel,
nextrev: w.nextrev,
receivers: make(map[*watcher]struct{}),
donec: make(chan struct{}),
}
wb.add(w)
go func() {
defer close(wb.donec)
// loop because leader loss will close channel
for cctx.Err() == nil {
opts := []clientv3.OpOption{
clientv3.WithRange(w.wr.end),
clientv3.WithProgressNotify(),
clientv3.WithRev(wb.nextrev),
clientv3.WithPrevKV(),
}
// The create notification should be the first response;
// if the watch is recreated following leader loss, it
// shouldn't post a second create response to the client.
if wb.responses == 0 {
opts = append(opts, clientv3.WithCreatedNotify())
}
wch := wp.cw.Watch(cctx, w.wr.key, opts...)
for wr := range wch {
wb.bcast(wr)
update(wb)
}
wp.retryLimiter.Wait(cctx)
}
}()
return wb
}
func (wb *watchBroadcast) bcast(wr clientv3.WatchResponse) {
wb.mu.Lock()
defer wb.mu.Unlock()
// watchers start on the given revision, if any; ignore header rev on create
if wb.responses > 0 || wb.nextrev == 0 {
wb.nextrev = wr.Header.Revision + 1
}
wb.responses++
for r := range wb.receivers {
r.send(wr)
}
if len(wb.receivers) > 0 {
eventsCoalescing.Add(float64(len(wb.receivers) - 1))
}
}
// add puts a watcher into receiving a broadcast if its revision at least
// meets the broadcast revision. Returns true if added.
func (wb *watchBroadcast) add(w *watcher) bool {
wb.mu.Lock()
defer wb.mu.Unlock()
if wb.nextrev > w.nextrev || (wb.nextrev == 0 && w.nextrev != 0) {
// wb is too far ahead, w will miss events
// or wb is being established with a current watcher
return false
}
if wb.responses == 0 {
// Newly created; create event will be sent by etcd.
wb.receivers[w] = struct{}{}
return true
}
// already sent by etcd; emulate create event
ok := w.post(&pb.WatchResponse{
Header: &pb.ResponseHeader{
// todo: fill in ClusterId
// todo: fill in MemberId:
Revision: w.nextrev,
// todo: fill in RaftTerm:
},
WatchId: w.id,
Created: true,
})
if !ok {
return false
}
wb.receivers[w] = struct{}{}
watchersCoalescing.Inc()
return true
}
func (wb *watchBroadcast) delete(w *watcher) {
wb.mu.Lock()
defer wb.mu.Unlock()
if _, ok := wb.receivers[w]; !ok {
panic("deleting missing watcher from broadcast")
}
delete(wb.receivers, w)
if len(wb.receivers) > 0 {
// do not dec the only left watcher for coalescing.
watchersCoalescing.Dec()
}
}
func (wb *watchBroadcast) size() int {
wb.mu.RLock()
defer wb.mu.RUnlock()
return len(wb.receivers)
}
func (wb *watchBroadcast) empty() bool { return wb.size() == 0 }
func (wb *watchBroadcast) stop() {
if !wb.empty() {
// do not dec the only left watcher for coalescing.
watchersCoalescing.Sub(float64(wb.size() - 1))
}
wb.cancel()
<-wb.donec
}

View file

@ -0,0 +1,135 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
)
type watchBroadcasts struct {
wp *watchProxy
// mu protects bcasts and watchers from the coalesce loop.
mu sync.Mutex
bcasts map[*watchBroadcast]struct{}
watchers map[*watcher]*watchBroadcast
updatec chan *watchBroadcast
donec chan struct{}
}
// maxCoalesceRecievers prevents a popular watchBroadcast from being coalseced.
const maxCoalesceReceivers = 5
func newWatchBroadcasts(wp *watchProxy) *watchBroadcasts {
wbs := &watchBroadcasts{
wp: wp,
bcasts: make(map[*watchBroadcast]struct{}),
watchers: make(map[*watcher]*watchBroadcast),
updatec: make(chan *watchBroadcast, 1),
donec: make(chan struct{}),
}
go func() {
defer close(wbs.donec)
for wb := range wbs.updatec {
wbs.coalesce(wb)
}
}()
return wbs
}
func (wbs *watchBroadcasts) coalesce(wb *watchBroadcast) {
if wb.size() >= maxCoalesceReceivers {
return
}
wbs.mu.Lock()
for wbswb := range wbs.bcasts {
if wbswb == wb {
continue
}
wb.mu.Lock()
wbswb.mu.Lock()
// 1. check if wbswb is behind wb so it won't skip any events in wb
// 2. ensure wbswb started; nextrev == 0 may mean wbswb is waiting
// for a current watcher and expects a create event from the server.
if wb.nextrev >= wbswb.nextrev && wbswb.responses > 0 {
for w := range wb.receivers {
wbswb.receivers[w] = struct{}{}
wbs.watchers[w] = wbswb
}
wb.receivers = nil
}
wbswb.mu.Unlock()
wb.mu.Unlock()
if wb.empty() {
delete(wbs.bcasts, wb)
wb.stop()
break
}
}
wbs.mu.Unlock()
}
func (wbs *watchBroadcasts) add(w *watcher) {
wbs.mu.Lock()
defer wbs.mu.Unlock()
// find fitting bcast
for wb := range wbs.bcasts {
if wb.add(w) {
wbs.watchers[w] = wb
return
}
}
// no fit; create a bcast
wb := newWatchBroadcast(wbs.wp, w, wbs.update)
wbs.watchers[w] = wb
wbs.bcasts[wb] = struct{}{}
}
// delete removes a watcher and returns the number of remaining watchers.
func (wbs *watchBroadcasts) delete(w *watcher) int {
wbs.mu.Lock()
defer wbs.mu.Unlock()
wb, ok := wbs.watchers[w]
if !ok {
panic("deleting missing watcher from broadcasts")
}
delete(wbs.watchers, w)
wb.delete(w)
if wb.empty() {
delete(wbs.bcasts, wb)
wb.stop()
}
return len(wbs.bcasts)
}
func (wbs *watchBroadcasts) stop() {
wbs.mu.Lock()
for wb := range wbs.bcasts {
wb.stop()
}
wbs.bcasts = nil
close(wbs.updatec)
wbs.mu.Unlock()
<-wbs.donec
}
func (wbs *watchBroadcasts) update(wb *watchBroadcast) {
select {
case wbs.updatec <- wb:
default:
}
}

View file

@ -0,0 +1,196 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"errors"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
var errAlreadySentHeader = errors.New("grpcproxy: already send header")
type ws2wc struct{ wserv pb.WatchServer }
func WatchServerToWatchClient(wserv pb.WatchServer) pb.WatchClient {
return &ws2wc{wserv}
}
func (s *ws2wc) Watch(ctx context.Context, opts ...grpc.CallOption) (pb.Watch_WatchClient, error) {
// ch1 is buffered so server can send error on close
ch1, ch2 := make(chan interface{}, 1), make(chan interface{})
headerc, trailerc := make(chan metadata.MD, 1), make(chan metadata.MD, 1)
cctx, ccancel := context.WithCancel(ctx)
cli := &chanStream{recvc: ch1, sendc: ch2, ctx: cctx, cancel: ccancel}
wclient := &ws2wcClientStream{chanClientStream{headerc, trailerc, cli}}
sctx, scancel := context.WithCancel(ctx)
srv := &chanStream{recvc: ch2, sendc: ch1, ctx: sctx, cancel: scancel}
wserver := &ws2wcServerStream{chanServerStream{headerc, trailerc, srv, nil}}
go func() {
if err := s.wserv.Watch(wserver); err != nil {
select {
case srv.sendc <- err:
case <-sctx.Done():
case <-cctx.Done():
}
}
scancel()
ccancel()
}()
return wclient, nil
}
// ws2wcClientStream implements Watch_WatchClient
type ws2wcClientStream struct{ chanClientStream }
// ws2wcServerStream implements Watch_WatchServer
type ws2wcServerStream struct{ chanServerStream }
func (s *ws2wcClientStream) Send(wr *pb.WatchRequest) error {
return s.SendMsg(wr)
}
func (s *ws2wcClientStream) Recv() (*pb.WatchResponse, error) {
var v interface{}
if err := s.RecvMsg(&v); err != nil {
return nil, err
}
return v.(*pb.WatchResponse), nil
}
func (s *ws2wcServerStream) Send(wr *pb.WatchResponse) error {
return s.SendMsg(wr)
}
func (s *ws2wcServerStream) Recv() (*pb.WatchRequest, error) {
var v interface{}
if err := s.RecvMsg(&v); err != nil {
return nil, err
}
return v.(*pb.WatchRequest), nil
}
// chanServerStream implements grpc.ServerStream with a chanStream
type chanServerStream struct {
headerc chan<- metadata.MD
trailerc chan<- metadata.MD
grpc.Stream
headers []metadata.MD
}
func (ss *chanServerStream) SendHeader(md metadata.MD) error {
if ss.headerc == nil {
return errAlreadySentHeader
}
outmd := make(map[string][]string)
for _, h := range append(ss.headers, md) {
for k, v := range h {
outmd[k] = v
}
}
select {
case ss.headerc <- outmd:
ss.headerc = nil
ss.headers = nil
return nil
case <-ss.Context().Done():
}
return ss.Context().Err()
}
func (ss *chanServerStream) SetHeader(md metadata.MD) error {
if ss.headerc == nil {
return errAlreadySentHeader
}
ss.headers = append(ss.headers, md)
return nil
}
func (ss *chanServerStream) SetTrailer(md metadata.MD) {
ss.trailerc <- md
}
// chanClientStream implements grpc.ClientStream with a chanStream
type chanClientStream struct {
headerc <-chan metadata.MD
trailerc <-chan metadata.MD
*chanStream
}
func (cs *chanClientStream) Header() (metadata.MD, error) {
select {
case md := <-cs.headerc:
return md, nil
case <-cs.Context().Done():
}
return nil, cs.Context().Err()
}
func (cs *chanClientStream) Trailer() metadata.MD {
select {
case md := <-cs.trailerc:
return md
case <-cs.Context().Done():
return nil
}
}
func (s *chanClientStream) CloseSend() error {
close(s.chanStream.sendc)
return nil
}
// chanStream implements grpc.Stream using channels
type chanStream struct {
recvc <-chan interface{}
sendc chan<- interface{}
ctx context.Context
cancel context.CancelFunc
}
func (s *chanStream) Context() context.Context { return s.ctx }
func (s *chanStream) SendMsg(m interface{}) error {
select {
case s.sendc <- m:
if err, ok := m.(error); ok {
return err
}
return nil
case <-s.ctx.Done():
}
return s.ctx.Err()
}
func (s *chanStream) RecvMsg(m interface{}) error {
v := m.(*interface{})
select {
case msg, ok := <-s.recvc:
if !ok {
return grpc.ErrClientConnClosing
}
if err, ok := msg.(error); ok {
return err
}
*v = msg
return nil
case <-s.ctx.Done():
}
return s.ctx.Err()
}

View file

@ -0,0 +1,69 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
)
// watchRanges tracks all open watches for the proxy.
type watchRanges struct {
wp *watchProxy
mu sync.Mutex
bcasts map[watchRange]*watchBroadcasts
}
func newWatchRanges(wp *watchProxy) *watchRanges {
return &watchRanges{
wp: wp,
bcasts: make(map[watchRange]*watchBroadcasts),
}
}
func (wrs *watchRanges) add(w *watcher) {
wrs.mu.Lock()
defer wrs.mu.Unlock()
if wbs := wrs.bcasts[w.wr]; wbs != nil {
wbs.add(w)
return
}
wbs := newWatchBroadcasts(wrs.wp)
wrs.bcasts[w.wr] = wbs
wbs.add(w)
}
func (wrs *watchRanges) delete(w *watcher) {
wrs.mu.Lock()
defer wrs.mu.Unlock()
wbs, ok := wrs.bcasts[w.wr]
if !ok {
panic("deleting missing range")
}
if wbs.delete(w) == 0 {
wbs.stop()
delete(wrs.bcasts, w.wr)
}
}
func (wrs *watchRanges) stop() {
wrs.mu.Lock()
defer wrs.mu.Unlock()
for _, wb := range wrs.bcasts {
wb.stop()
}
wrs.bcasts = nil
}

View file

@ -0,0 +1,127 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"time"
"github.com/coreos/etcd/clientv3"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"github.com/coreos/etcd/mvcc"
"github.com/coreos/etcd/mvcc/mvccpb"
)
type watchRange struct {
key, end string
}
func (wr *watchRange) valid() bool {
return len(wr.end) == 0 || wr.end > wr.key || (wr.end[0] == 0 && len(wr.end) == 1)
}
type watcher struct {
// user configuration
wr watchRange
filters []mvcc.FilterFunc
progress bool
prevKV bool
// id is the id returned to the client on its watch stream.
id int64
// nextrev is the minimum expected next event revision.
nextrev int64
// lastHeader has the last header sent over the stream.
lastHeader pb.ResponseHeader
// wps is the parent.
wps *watchProxyStream
}
// send filters out repeated events by discarding revisions older
// than the last one sent over the watch channel.
func (w *watcher) send(wr clientv3.WatchResponse) {
if wr.IsProgressNotify() && !w.progress {
return
}
if w.nextrev > wr.Header.Revision && len(wr.Events) > 0 {
return
}
if w.nextrev == 0 {
// current watch; expect updates following this revision
w.nextrev = wr.Header.Revision + 1
}
events := make([]*mvccpb.Event, 0, len(wr.Events))
var lastRev int64
for i := range wr.Events {
ev := (*mvccpb.Event)(wr.Events[i])
if ev.Kv.ModRevision < w.nextrev {
continue
} else {
// We cannot update w.rev here.
// txn can have multiple events with the same rev.
// If w.nextrev updates here, it would skip events in the same txn.
lastRev = ev.Kv.ModRevision
}
filtered := false
for _, filter := range w.filters {
if filter(*ev) {
filtered = true
break
}
}
if filtered {
continue
}
if !w.prevKV {
evCopy := *ev
evCopy.PrevKv = nil
ev = &evCopy
}
events = append(events, ev)
}
if lastRev >= w.nextrev {
w.nextrev = lastRev + 1
}
// all events are filtered out?
if !wr.IsProgressNotify() && !wr.Created && len(events) == 0 {
return
}
w.lastHeader = wr.Header
w.post(&pb.WatchResponse{
Header: &wr.Header,
Created: wr.Created,
WatchId: w.id,
Events: events,
})
}
// post puts a watch response on the watcher's proxy stream channel
func (w *watcher) post(wr *pb.WatchResponse) bool {
select {
case w.wps.watchCh <- wr:
case <-time.After(50 * time.Millisecond):
w.wps.cancel()
return false
}
return true
}

View file

@ -0,0 +1,158 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"math/rand"
"net/url"
"sync"
"time"
)
// defaultRefreshInterval is the default proxyRefreshIntervalMs value
// as in etcdmain/config.go.
const defaultRefreshInterval = 30000 * time.Millisecond
var once sync.Once
func init() {
rand.Seed(time.Now().UnixNano())
}
func newDirector(urlsFunc GetProxyURLs, failureWait time.Duration, refreshInterval time.Duration) *director {
d := &director{
uf: urlsFunc,
failureWait: failureWait,
}
d.refresh()
go func() {
// In order to prevent missing proxy endpoints in the first try:
// when given refresh interval of defaultRefreshInterval or greater
// and whenever there is no available proxy endpoints,
// give 1-second refreshInterval.
for {
es := d.endpoints()
ri := refreshInterval
if ri >= defaultRefreshInterval {
if len(es) == 0 {
ri = time.Second
}
}
if len(es) > 0 {
once.Do(func() {
var sl []string
for _, e := range es {
sl = append(sl, e.URL.String())
}
plog.Infof("endpoints found %q", sl)
})
}
time.Sleep(ri)
d.refresh()
}
}()
return d
}
type director struct {
sync.Mutex
ep []*endpoint
uf GetProxyURLs
failureWait time.Duration
}
func (d *director) refresh() {
urls := d.uf()
d.Lock()
defer d.Unlock()
var endpoints []*endpoint
for _, u := range urls {
uu, err := url.Parse(u)
if err != nil {
plog.Printf("upstream URL invalid: %v", err)
continue
}
endpoints = append(endpoints, newEndpoint(*uu, d.failureWait))
}
// shuffle array to avoid connections being "stuck" to a single endpoint
for i := range endpoints {
j := rand.Intn(i + 1)
endpoints[i], endpoints[j] = endpoints[j], endpoints[i]
}
d.ep = endpoints
}
func (d *director) endpoints() []*endpoint {
d.Lock()
defer d.Unlock()
filtered := make([]*endpoint, 0)
for _, ep := range d.ep {
if ep.Available {
filtered = append(filtered, ep)
}
}
return filtered
}
func newEndpoint(u url.URL, failureWait time.Duration) *endpoint {
ep := endpoint{
URL: u,
Available: true,
failFunc: timedUnavailabilityFunc(failureWait),
}
return &ep
}
type endpoint struct {
sync.Mutex
URL url.URL
Available bool
failFunc func(ep *endpoint)
}
func (ep *endpoint) Failed() {
ep.Lock()
if !ep.Available {
ep.Unlock()
return
}
ep.Available = false
ep.Unlock()
plog.Printf("marked endpoint %s unavailable", ep.URL.String())
if ep.failFunc == nil {
plog.Printf("no failFunc defined, endpoint %s will be unavailable forever.", ep.URL.String())
return
}
ep.failFunc(ep)
}
func timedUnavailabilityFunc(wait time.Duration) func(*endpoint) {
return func(ep *endpoint) {
time.AfterFunc(wait, func() {
ep.Available = true
plog.Printf("marked endpoint %s available, to retest connectivity", ep.URL.String())
})
}
}

View file

@ -0,0 +1,95 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"net/url"
"reflect"
"sort"
"testing"
"time"
)
func TestNewDirectorScheme(t *testing.T) {
tests := []struct {
urls []string
want []string
}{
{
urls: []string{"http://192.0.2.8:4002", "http://example.com:8080"},
want: []string{"http://192.0.2.8:4002", "http://example.com:8080"},
},
{
urls: []string{"https://192.0.2.8:4002", "https://example.com:8080"},
want: []string{"https://192.0.2.8:4002", "https://example.com:8080"},
},
// accept urls without a port
{
urls: []string{"http://192.0.2.8"},
want: []string{"http://192.0.2.8"},
},
// accept urls even if they are garbage
{
urls: []string{"http://."},
want: []string{"http://."},
},
}
for i, tt := range tests {
uf := func() []string {
return tt.urls
}
got := newDirector(uf, time.Minute, time.Minute)
var gep []string
for _, ep := range got.ep {
gep = append(gep, ep.URL.String())
}
sort.Strings(tt.want)
sort.Strings(gep)
if !reflect.DeepEqual(tt.want, gep) {
t.Errorf("#%d: want endpoints = %#v, got = %#v", i, tt.want, gep)
}
}
}
func TestDirectorEndpointsFiltering(t *testing.T) {
d := director{
ep: []*endpoint{
{
URL: url.URL{Scheme: "http", Host: "192.0.2.5:5050"},
Available: false,
},
{
URL: url.URL{Scheme: "http", Host: "192.0.2.4:4000"},
Available: true,
},
},
}
got := d.endpoints()
want := []*endpoint{
{
URL: url.URL{Scheme: "http", Host: "192.0.2.4:4000"},
Available: true,
},
}
if !reflect.DeepEqual(want, got) {
t.Fatalf("directed to incorrect endpoint: want = %#v, got = %#v", want, got)
}
}

18
vendor/github.com/coreos/etcd/proxy/httpproxy/doc.go generated vendored Normal file
View file

@ -0,0 +1,18 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package httpproxy implements etcd httpproxy. The etcd proxy acts as a reverse
// http proxy forwarding client requests to active etcd cluster members, and does
// not participate in consensus.
package httpproxy

View file

@ -0,0 +1,88 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"net/http"
"strconv"
"time"
"github.com/prometheus/client_golang/prometheus"
)
var (
requestsIncoming = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "proxy",
Name: "requests_total",
Help: "Counter requests incoming by method.",
}, []string{"method"})
requestsHandled = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "proxy",
Name: "handled_total",
Help: "Counter of requests fully handled (by authoratitave servers)",
}, []string{"method", "code"})
requestsDropped = prometheus.NewCounterVec(
prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "proxy",
Name: "dropped_total",
Help: "Counter of requests dropped on the proxy.",
}, []string{"method", "proxying_error"})
requestsHandlingTime = prometheus.NewHistogramVec(
prometheus.HistogramOpts{
Namespace: "etcd",
Subsystem: "proxy",
Name: "handling_duration_seconds",
Help: "Bucketed histogram of handling time of successful events (non-watches), by method " +
"(GET/PUT etc.).",
Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13),
}, []string{"method"})
)
type forwardingError string
const (
zeroEndpoints forwardingError = "zero_endpoints"
failedSendingRequest forwardingError = "failed_sending_request"
failedGettingResponse forwardingError = "failed_getting_response"
)
func init() {
prometheus.MustRegister(requestsIncoming)
prometheus.MustRegister(requestsHandled)
prometheus.MustRegister(requestsDropped)
prometheus.MustRegister(requestsHandlingTime)
}
func reportIncomingRequest(request *http.Request) {
requestsIncoming.WithLabelValues(request.Method).Inc()
}
func reportRequestHandled(request *http.Request, response *http.Response, startTime time.Time) {
method := request.Method
requestsHandled.WithLabelValues(method, strconv.Itoa(response.StatusCode)).Inc()
requestsHandlingTime.WithLabelValues(method).Observe(time.Since(startTime).Seconds())
}
func reportRequestDropped(request *http.Request, err forwardingError) {
requestsDropped.WithLabelValues(request.Method, string(err)).Inc()
}

116
vendor/github.com/coreos/etcd/proxy/httpproxy/proxy.go generated vendored Normal file
View file

@ -0,0 +1,116 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"encoding/json"
"net/http"
"strings"
"time"
"golang.org/x/net/http2"
)
const (
// DefaultMaxIdleConnsPerHost indicates the default maximum idle connection
// count maintained between proxy and each member. We set it to 128 to
// let proxy handle 128 concurrent requests in long term smoothly.
// If the number of concurrent requests is bigger than this value,
// proxy needs to create one new connection when handling each request in
// the delta, which is bad because the creation consumes resource and
// may eat up ephemeral ports.
DefaultMaxIdleConnsPerHost = 128
)
// GetProxyURLs is a function which should return the current set of URLs to
// which client requests should be proxied. This function will be queried
// periodically by the proxy Handler to refresh the set of available
// backends.
type GetProxyURLs func() []string
// NewHandler creates a new HTTP handler, listening on the given transport,
// which will proxy requests to an etcd cluster.
// The handler will periodically update its view of the cluster.
func NewHandler(t *http.Transport, urlsFunc GetProxyURLs, failureWait time.Duration, refreshInterval time.Duration) http.Handler {
if t.TLSClientConfig != nil {
// Enable http2, see Issue 5033.
err := http2.ConfigureTransport(t)
if err != nil {
plog.Infof("Error enabling Transport HTTP/2 support: %v", err)
}
}
p := &reverseProxy{
director: newDirector(urlsFunc, failureWait, refreshInterval),
transport: t,
}
mux := http.NewServeMux()
mux.Handle("/", p)
mux.HandleFunc("/v2/config/local/proxy", p.configHandler)
return mux
}
// NewReadonlyHandler wraps the given HTTP handler to allow only GET requests
func NewReadonlyHandler(hdlr http.Handler) http.Handler {
readonly := readonlyHandlerFunc(hdlr)
return http.HandlerFunc(readonly)
}
func readonlyHandlerFunc(next http.Handler) func(http.ResponseWriter, *http.Request) {
return func(w http.ResponseWriter, req *http.Request) {
if req.Method != "GET" {
w.WriteHeader(http.StatusNotImplemented)
return
}
next.ServeHTTP(w, req)
}
}
func (p *reverseProxy) configHandler(w http.ResponseWriter, r *http.Request) {
if !allowMethod(w, r.Method, "GET") {
return
}
eps := p.director.endpoints()
epstr := make([]string, len(eps))
for i, e := range eps {
epstr[i] = e.URL.String()
}
proxyConfig := struct {
Endpoints []string `json:"endpoints"`
}{
Endpoints: epstr,
}
json.NewEncoder(w).Encode(proxyConfig)
}
// allowMethod verifies that the given method is one of the allowed methods,
// and if not, it writes an error to w. A boolean is returned indicating
// whether or not the method is allowed.
func allowMethod(w http.ResponseWriter, m string, ms ...string) bool {
for _, meth := range ms {
if m == meth {
return true
}
}
w.Header().Set("Allow", strings.Join(ms, ","))
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
return false
}

View file

@ -0,0 +1,98 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"testing"
"time"
)
func TestReadonlyHandler(t *testing.T) {
fixture := func(w http.ResponseWriter, req *http.Request) {
w.WriteHeader(http.StatusOK)
}
hdlrFunc := readonlyHandlerFunc(http.HandlerFunc(fixture))
tests := []struct {
method string
want int
}{
// GET is only passing method
{"GET", http.StatusOK},
// everything but GET is StatusNotImplemented
{"POST", http.StatusNotImplemented},
{"PUT", http.StatusNotImplemented},
{"PATCH", http.StatusNotImplemented},
{"DELETE", http.StatusNotImplemented},
{"FOO", http.StatusNotImplemented},
}
for i, tt := range tests {
req, _ := http.NewRequest(tt.method, "http://example.com", nil)
rr := httptest.NewRecorder()
hdlrFunc(rr, req)
if tt.want != rr.Code {
t.Errorf("#%d: incorrect HTTP status code: method=%s want=%d got=%d", i, tt.method, tt.want, rr.Code)
}
}
}
func TestConfigHandlerGET(t *testing.T) {
var err error
us := make([]*url.URL, 3)
us[0], err = url.Parse("http://example1.com")
if err != nil {
t.Fatal(err)
}
us[1], err = url.Parse("http://example2.com")
if err != nil {
t.Fatal(err)
}
us[2], err = url.Parse("http://example3.com")
if err != nil {
t.Fatal(err)
}
rp := reverseProxy{
director: &director{
ep: []*endpoint{
newEndpoint(*us[0], 1*time.Second),
newEndpoint(*us[1], 1*time.Second),
newEndpoint(*us[2], 1*time.Second),
},
},
}
req, _ := http.NewRequest("GET", "http://example.com//v2/config/local/proxy", nil)
rr := httptest.NewRecorder()
rp.configHandler(rr, req)
wbody := "{\"endpoints\":[\"http://example1.com\",\"http://example2.com\",\"http://example3.com\"]}\n"
body, err := ioutil.ReadAll(rr.Body)
if err != nil {
t.Fatal(err)
}
if string(body) != wbody {
t.Errorf("body = %s, want %s", string(body), wbody)
}
}

View file

@ -0,0 +1,207 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"bytes"
"fmt"
"io"
"io/ioutil"
"net"
"net/http"
"net/url"
"strings"
"sync/atomic"
"time"
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
"github.com/coreos/etcd/pkg/httputil"
"github.com/coreos/pkg/capnslog"
)
var (
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "proxy/httpproxy")
// Hop-by-hop headers. These are removed when sent to the backend.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html
// This list of headers borrowed from stdlib httputil.ReverseProxy
singleHopHeaders = []string{
"Connection",
"Keep-Alive",
"Proxy-Authenticate",
"Proxy-Authorization",
"Te", // canonicalized version of "TE"
"Trailers",
"Transfer-Encoding",
"Upgrade",
}
)
func removeSingleHopHeaders(hdrs *http.Header) {
for _, h := range singleHopHeaders {
hdrs.Del(h)
}
}
type reverseProxy struct {
director *director
transport http.RoundTripper
}
func (p *reverseProxy) ServeHTTP(rw http.ResponseWriter, clientreq *http.Request) {
reportIncomingRequest(clientreq)
proxyreq := new(http.Request)
*proxyreq = *clientreq
startTime := time.Now()
var (
proxybody []byte
err error
)
if clientreq.Body != nil {
proxybody, err = ioutil.ReadAll(clientreq.Body)
if err != nil {
msg := fmt.Sprintf("failed to read request body: %v", err)
plog.Println(msg)
e := httptypes.NewHTTPError(http.StatusInternalServerError, "httpproxy: "+msg)
if we := e.WriteTo(rw); we != nil {
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
}
return
}
}
// deep-copy the headers, as these will be modified below
proxyreq.Header = make(http.Header)
copyHeader(proxyreq.Header, clientreq.Header)
normalizeRequest(proxyreq)
removeSingleHopHeaders(&proxyreq.Header)
maybeSetForwardedFor(proxyreq)
endpoints := p.director.endpoints()
if len(endpoints) == 0 {
msg := "zero endpoints currently available"
reportRequestDropped(clientreq, zeroEndpoints)
// TODO: limit the rate of the error logging.
plog.Println(msg)
e := httptypes.NewHTTPError(http.StatusServiceUnavailable, "httpproxy: "+msg)
if we := e.WriteTo(rw); we != nil {
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
}
return
}
var requestClosed int32
completeCh := make(chan bool, 1)
closeNotifier, ok := rw.(http.CloseNotifier)
cancel := httputil.RequestCanceler(proxyreq)
if ok {
closeCh := closeNotifier.CloseNotify()
go func() {
select {
case <-closeCh:
atomic.StoreInt32(&requestClosed, 1)
plog.Printf("client %v closed request prematurely", clientreq.RemoteAddr)
cancel()
case <-completeCh:
}
}()
defer func() {
completeCh <- true
}()
}
var res *http.Response
for _, ep := range endpoints {
if proxybody != nil {
proxyreq.Body = ioutil.NopCloser(bytes.NewBuffer(proxybody))
}
redirectRequest(proxyreq, ep.URL)
res, err = p.transport.RoundTrip(proxyreq)
if atomic.LoadInt32(&requestClosed) == 1 {
return
}
if err != nil {
reportRequestDropped(clientreq, failedSendingRequest)
plog.Printf("failed to direct request to %s: %v", ep.URL.String(), err)
ep.Failed()
continue
}
break
}
if res == nil {
// TODO: limit the rate of the error logging.
msg := fmt.Sprintf("unable to get response from %d endpoint(s)", len(endpoints))
reportRequestDropped(clientreq, failedGettingResponse)
plog.Println(msg)
e := httptypes.NewHTTPError(http.StatusBadGateway, "httpproxy: "+msg)
if we := e.WriteTo(rw); we != nil {
plog.Debugf("error writing HTTPError (%v) to %s", we, clientreq.RemoteAddr)
}
return
}
defer res.Body.Close()
reportRequestHandled(clientreq, res, startTime)
removeSingleHopHeaders(&res.Header)
copyHeader(rw.Header(), res.Header)
rw.WriteHeader(res.StatusCode)
io.Copy(rw, res.Body)
}
func copyHeader(dst, src http.Header) {
for k, vv := range src {
for _, v := range vv {
dst.Add(k, v)
}
}
}
func redirectRequest(req *http.Request, loc url.URL) {
req.URL.Scheme = loc.Scheme
req.URL.Host = loc.Host
}
func normalizeRequest(req *http.Request) {
req.Proto = "HTTP/1.1"
req.ProtoMajor = 1
req.ProtoMinor = 1
req.Close = false
}
func maybeSetForwardedFor(req *http.Request) {
clientIP, _, err := net.SplitHostPort(req.RemoteAddr)
if err != nil {
return
}
// If we aren't the first proxy retain prior
// X-Forwarded-For information as a comma+space
// separated list and fold multiple headers into one.
if prior, ok := req.Header["X-Forwarded-For"]; ok {
clientIP = strings.Join(prior, ", ") + ", " + clientIP
}
req.Header.Set("X-Forwarded-For", clientIP)
}

View file

@ -0,0 +1,245 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package httpproxy
import (
"bytes"
"errors"
"io/ioutil"
"net/http"
"net/http/httptest"
"net/url"
"reflect"
"testing"
)
type staticRoundTripper struct {
res *http.Response
err error
}
func (srt *staticRoundTripper) RoundTrip(*http.Request) (*http.Response, error) {
return srt.res, srt.err
}
func TestReverseProxyServe(t *testing.T) {
u := url.URL{Scheme: "http", Host: "192.0.2.3:4040"}
tests := []struct {
eps []*endpoint
rt http.RoundTripper
want int
}{
// no endpoints available so no requests are even made
{
eps: []*endpoint{},
rt: &staticRoundTripper{
res: &http.Response{
StatusCode: http.StatusCreated,
Body: ioutil.NopCloser(&bytes.Reader{}),
},
},
want: http.StatusServiceUnavailable,
},
// error is returned from one endpoint that should be available
{
eps: []*endpoint{{URL: u, Available: true}},
rt: &staticRoundTripper{err: errors.New("what a bad trip")},
want: http.StatusBadGateway,
},
// endpoint is available and returns success
{
eps: []*endpoint{{URL: u, Available: true}},
rt: &staticRoundTripper{
res: &http.Response{
StatusCode: http.StatusCreated,
Body: ioutil.NopCloser(&bytes.Reader{}),
Header: map[string][]string{"Content-Type": {"application/json"}},
},
},
want: http.StatusCreated,
},
}
for i, tt := range tests {
rp := reverseProxy{
director: &director{ep: tt.eps},
transport: tt.rt,
}
req, _ := http.NewRequest("GET", "http://192.0.2.2:2379", nil)
rr := httptest.NewRecorder()
rp.ServeHTTP(rr, req)
if rr.Code != tt.want {
t.Errorf("#%d: unexpected HTTP status code: want = %d, got = %d", i, tt.want, rr.Code)
}
if gct := rr.Header().Get("Content-Type"); gct != "application/json" {
t.Errorf("#%d: Content-Type = %s, want %s", i, gct, "application/json")
}
}
}
func TestRedirectRequest(t *testing.T) {
loc := url.URL{
Scheme: "http",
Host: "bar.example.com",
}
req := &http.Request{
Method: "GET",
Host: "foo.example.com",
URL: &url.URL{
Host: "foo.example.com",
Path: "/v2/keys/baz",
},
}
redirectRequest(req, loc)
want := &http.Request{
Method: "GET",
// this field must not change
Host: "foo.example.com",
URL: &url.URL{
// the Scheme field is updated to that of the provided URL
Scheme: "http",
// the Host field is updated to that of the provided URL
Host: "bar.example.com",
Path: "/v2/keys/baz",
},
}
if !reflect.DeepEqual(want, req) {
t.Fatalf("HTTP request does not match expected criteria: want=%#v got=%#v", want, req)
}
}
func TestMaybeSetForwardedFor(t *testing.T) {
tests := []struct {
raddr string
fwdFor string
want string
}{
{"192.0.2.3:8002", "", "192.0.2.3"},
{"192.0.2.3:8002", "192.0.2.2", "192.0.2.2, 192.0.2.3"},
{"192.0.2.3:8002", "192.0.2.1, 192.0.2.2", "192.0.2.1, 192.0.2.2, 192.0.2.3"},
{"example.com:8002", "", "example.com"},
// While these cases look valid, golang net/http will not let it happen
// The RemoteAddr field will always be a valid host:port
{":8002", "", ""},
{"192.0.2.3", "", ""},
// blatantly invalid host w/o a port
{"12", "", ""},
{"12", "192.0.2.3", "192.0.2.3"},
}
for i, tt := range tests {
req := &http.Request{
RemoteAddr: tt.raddr,
Header: make(http.Header),
}
if tt.fwdFor != "" {
req.Header.Set("X-Forwarded-For", tt.fwdFor)
}
maybeSetForwardedFor(req)
got := req.Header.Get("X-Forwarded-For")
if tt.want != got {
t.Errorf("#%d: incorrect header: want = %q, got = %q", i, tt.want, got)
}
}
}
func TestRemoveSingleHopHeaders(t *testing.T) {
hdr := http.Header(map[string][]string{
// single-hop headers that should be removed
"Connection": {"close"},
"Keep-Alive": {"foo"},
"Proxy-Authenticate": {"Basic realm=example.com"},
"Proxy-Authorization": {"foo"},
"Te": {"deflate,gzip"},
"Trailers": {"ETag"},
"Transfer-Encoding": {"chunked"},
"Upgrade": {"WebSocket"},
// headers that should persist
"Accept": {"application/json"},
"X-Foo": {"Bar"},
})
removeSingleHopHeaders(&hdr)
want := http.Header(map[string][]string{
"Accept": {"application/json"},
"X-Foo": {"Bar"},
})
if !reflect.DeepEqual(want, hdr) {
t.Fatalf("unexpected result: want = %#v, got = %#v", want, hdr)
}
}
func TestCopyHeader(t *testing.T) {
tests := []struct {
src http.Header
dst http.Header
want http.Header
}{
{
src: http.Header(map[string][]string{
"Foo": {"bar", "baz"},
}),
dst: http.Header(map[string][]string{}),
want: http.Header(map[string][]string{
"Foo": {"bar", "baz"},
}),
},
{
src: http.Header(map[string][]string{
"Foo": {"bar"},
"Ping": {"pong"},
}),
dst: http.Header(map[string][]string{}),
want: http.Header(map[string][]string{
"Foo": {"bar"},
"Ping": {"pong"},
}),
},
{
src: http.Header(map[string][]string{
"Foo": {"bar", "baz"},
}),
dst: http.Header(map[string][]string{
"Foo": {"qux"},
}),
want: http.Header(map[string][]string{
"Foo": {"qux", "bar", "baz"},
}),
},
}
for i, tt := range tests {
copyHeader(tt.dst, tt.src)
if !reflect.DeepEqual(tt.dst, tt.want) {
t.Errorf("#%d: unexpected headers: want = %v, got = %v", i, tt.want, tt.dst)
}
}
}

16
vendor/github.com/coreos/etcd/proxy/tcpproxy/doc.go generated vendored Normal file
View file

@ -0,0 +1,16 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package tcpproxy is an OSI level 4 proxy for routing etcd clients to etcd servers.
package tcpproxy

View file

@ -0,0 +1,174 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcpproxy
import (
"io"
"net"
"sync"
"time"
"github.com/coreos/pkg/capnslog"
)
var (
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "proxy/tcpproxy")
)
type remote struct {
mu sync.Mutex
addr string
inactive bool
}
func (r *remote) inactivate() {
r.mu.Lock()
defer r.mu.Unlock()
r.inactive = true
}
func (r *remote) tryReactivate() error {
conn, err := net.Dial("tcp", r.addr)
if err != nil {
return err
}
conn.Close()
r.mu.Lock()
defer r.mu.Unlock()
r.inactive = false
return nil
}
func (r *remote) isActive() bool {
r.mu.Lock()
defer r.mu.Unlock()
return !r.inactive
}
type TCPProxy struct {
Listener net.Listener
Endpoints []string
MonitorInterval time.Duration
donec chan struct{}
mu sync.Mutex // guards the following fields
remotes []*remote
nextRemote int
}
func (tp *TCPProxy) Run() error {
tp.donec = make(chan struct{})
if tp.MonitorInterval == 0 {
tp.MonitorInterval = 5 * time.Minute
}
for _, ep := range tp.Endpoints {
tp.remotes = append(tp.remotes, &remote{addr: ep})
}
plog.Printf("ready to proxy client requests to %v", tp.Endpoints)
go tp.runMonitor()
for {
in, err := tp.Listener.Accept()
if err != nil {
return err
}
go tp.serve(in)
}
}
func (tp *TCPProxy) numRemotes() int {
tp.mu.Lock()
defer tp.mu.Unlock()
return len(tp.remotes)
}
func (tp *TCPProxy) serve(in net.Conn) {
var (
err error
out net.Conn
)
for i := 0; i < tp.numRemotes(); i++ {
remote := tp.pick()
if !remote.isActive() {
continue
}
// TODO: add timeout
out, err = net.Dial("tcp", remote.addr)
if err == nil {
break
}
remote.inactivate()
plog.Warningf("deactivated endpoint [%s] due to %v for %v", remote.addr, err, tp.MonitorInterval)
}
if out == nil {
in.Close()
return
}
go func() {
io.Copy(in, out)
in.Close()
out.Close()
}()
io.Copy(out, in)
out.Close()
in.Close()
}
// pick picks a remote in round-robin fashion
func (tp *TCPProxy) pick() *remote {
tp.mu.Lock()
defer tp.mu.Unlock()
picked := tp.remotes[tp.nextRemote]
tp.nextRemote = (tp.nextRemote + 1) % len(tp.remotes)
return picked
}
func (tp *TCPProxy) runMonitor() {
for {
select {
case <-time.After(tp.MonitorInterval):
tp.mu.Lock()
for _, rem := range tp.remotes {
if rem.isActive() {
continue
}
go func(r *remote) {
if err := r.tryReactivate(); err != nil {
plog.Warningf("failed to activate endpoint [%s] due to %v (stay inactive for another %v)", r.addr, err, tp.MonitorInterval)
} else {
plog.Printf("activated %s", r.addr)
}
}(rem)
}
tp.mu.Unlock()
case <-tp.donec:
return
}
}
}
func (tp *TCPProxy) Stop() {
// graceful shutdown?
// shutdown current connections?
tp.Listener.Close()
close(tp.donec)
}

View file

@ -0,0 +1,67 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package tcpproxy
import (
"fmt"
"io/ioutil"
"net"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
func TestUserspaceProxy(t *testing.T) {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
t.Fatal(err)
}
defer l.Close()
want := "hello proxy"
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprint(w, want)
}))
defer ts.Close()
u, err := url.Parse(ts.URL)
if err != nil {
t.Fatal(err)
}
p := TCPProxy{
Listener: l,
Endpoints: []string{u.Host},
}
go p.Run()
defer p.Stop()
u.Host = l.Addr().String()
res, err := http.Get(u.String())
if err != nil {
t.Fatal(err)
}
got, gerr := ioutil.ReadAll(res.Body)
res.Body.Close()
if gerr != nil {
t.Fatal(gerr)
}
if string(got) != want {
t.Errorf("got = %s, want %s", got, want)
}
}