mirror of
https://github.com/kubernetes-sigs/prometheus-adapter.git
synced 2026-04-06 17:57:51 +00:00
Add vendor folder to git
This commit is contained in:
parent
66cf5eaafb
commit
183585f56f
6916 changed files with 2629581 additions and 1 deletions
110
vendor/github.com/coreos/etcd/proxy/grpcproxy/auth.go
generated
vendored
Normal file
110
vendor/github.com/coreos/etcd/proxy/grpcproxy/auth.go
generated
vendored
Normal 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)
|
||||
}
|
||||
168
vendor/github.com/coreos/etcd/proxy/grpcproxy/cache/store.go
generated
vendored
Normal file
168
vendor/github.com/coreos/etcd/proxy/grpcproxy/cache/store.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
52
vendor/github.com/coreos/etcd/proxy/grpcproxy/cluster.go
generated
vendored
Normal file
52
vendor/github.com/coreos/etcd/proxy/grpcproxy/cluster.go
generated
vendored
Normal 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
16
vendor/github.com/coreos/etcd/proxy/grpcproxy/doc.go
generated
vendored
Normal 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
210
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv.go
generated
vendored
Normal 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...)
|
||||
}
|
||||
47
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv_client_adapter.go
generated
vendored
Normal file
47
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv_client_adapter.go
generated
vendored
Normal 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)
|
||||
}
|
||||
98
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv_test.go
generated
vendored
Normal file
98
vendor/github.com/coreos/etcd/proxy/grpcproxy/kv_test.go
generated
vendored
Normal 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
87
vendor/github.com/coreos/etcd/proxy/grpcproxy/lease.go
generated
vendored
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
||||
74
vendor/github.com/coreos/etcd/proxy/grpcproxy/maintenance.go
generated
vendored
Normal file
74
vendor/github.com/coreos/etcd/proxy/grpcproxy/maintenance.go
generated
vendored
Normal 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)
|
||||
}
|
||||
51
vendor/github.com/coreos/etcd/proxy/grpcproxy/metrics.go
generated
vendored
Normal file
51
vendor/github.com/coreos/etcd/proxy/grpcproxy/metrics.go
generated
vendored
Normal 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
267
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch.go
generated
vendored
Normal 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
|
||||
}
|
||||
158
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_broadcast.go
generated
vendored
Normal file
158
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_broadcast.go
generated
vendored
Normal 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
|
||||
}
|
||||
135
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_broadcasts.go
generated
vendored
Normal file
135
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_broadcasts.go
generated
vendored
Normal 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:
|
||||
}
|
||||
}
|
||||
196
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_client_adapter.go
generated
vendored
Normal file
196
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_client_adapter.go
generated
vendored
Normal 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()
|
||||
}
|
||||
69
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_ranges.go
generated
vendored
Normal file
69
vendor/github.com/coreos/etcd/proxy/grpcproxy/watch_ranges.go
generated
vendored
Normal 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
|
||||
}
|
||||
127
vendor/github.com/coreos/etcd/proxy/grpcproxy/watcher.go
generated
vendored
Normal file
127
vendor/github.com/coreos/etcd/proxy/grpcproxy/watcher.go
generated
vendored
Normal 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
|
||||
}
|
||||
158
vendor/github.com/coreos/etcd/proxy/httpproxy/director.go
generated
vendored
Normal file
158
vendor/github.com/coreos/etcd/proxy/httpproxy/director.go
generated
vendored
Normal 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())
|
||||
})
|
||||
}
|
||||
}
|
||||
95
vendor/github.com/coreos/etcd/proxy/httpproxy/director_test.go
generated
vendored
Normal file
95
vendor/github.com/coreos/etcd/proxy/httpproxy/director_test.go
generated
vendored
Normal 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
18
vendor/github.com/coreos/etcd/proxy/httpproxy/doc.go
generated
vendored
Normal 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
|
||||
88
vendor/github.com/coreos/etcd/proxy/httpproxy/metrics.go
generated
vendored
Normal file
88
vendor/github.com/coreos/etcd/proxy/httpproxy/metrics.go
generated
vendored
Normal 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
116
vendor/github.com/coreos/etcd/proxy/httpproxy/proxy.go
generated
vendored
Normal 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
|
||||
}
|
||||
98
vendor/github.com/coreos/etcd/proxy/httpproxy/proxy_test.go
generated
vendored
Normal file
98
vendor/github.com/coreos/etcd/proxy/httpproxy/proxy_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
207
vendor/github.com/coreos/etcd/proxy/httpproxy/reverse.go
generated
vendored
Normal file
207
vendor/github.com/coreos/etcd/proxy/httpproxy/reverse.go
generated
vendored
Normal 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)
|
||||
}
|
||||
245
vendor/github.com/coreos/etcd/proxy/httpproxy/reverse_test.go
generated
vendored
Normal file
245
vendor/github.com/coreos/etcd/proxy/httpproxy/reverse_test.go
generated
vendored
Normal 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
16
vendor/github.com/coreos/etcd/proxy/tcpproxy/doc.go
generated
vendored
Normal 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
|
||||
174
vendor/github.com/coreos/etcd/proxy/tcpproxy/userspace.go
generated
vendored
Normal file
174
vendor/github.com/coreos/etcd/proxy/tcpproxy/userspace.go
generated
vendored
Normal 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)
|
||||
}
|
||||
67
vendor/github.com/coreos/etcd/proxy/tcpproxy/userspace_test.go
generated
vendored
Normal file
67
vendor/github.com/coreos/etcd/proxy/tcpproxy/userspace_test.go
generated
vendored
Normal 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)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue