etcdmain, tcpproxy: srv-priority policy

Adds DNS SRV weighting and priorities to gateway.

Partially addresses #4378
This commit is contained in:
Anthony Romano
2017-05-04 21:57:54 -07:00
parent 07ad18178d
commit c232814003
5 changed files with 116 additions and 41 deletions

View File

@ -91,17 +91,28 @@ func stripSchema(eps []string) []string {
return endpoints return endpoints
} }
func startGateway(cmd *cobra.Command, args []string) {
endpoints := gatewayEndpoints
if eps := discoverEndpoints(gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery); len(eps) != 0 { func startGateway(cmd *cobra.Command, args []string) {
endpoints = eps srvs := discoverEndpoints(gatewayDNSCluster, gatewayCA, gatewayInsecureDiscovery)
if len(srvs.Endpoints) == 0 {
// no endpoints discovered, fall back to provided endpoints
srvs.Endpoints = gatewayEndpoints
}
// Strip the schema from the endpoints because we start just a TCP proxy
srvs.Endpoints = stripSchema(srvs.Endpoints)
if len(srvs.SRVs) == 0 {
for _, ep := range srvs.Endpoints {
h, p, err := net.SplitHostPort(ep)
if err != nil {
plog.Fatalf("error parsing endpoint %q", ep)
}
var port uint16
fmt.Sscanf(p, "%d", &port)
srvs.SRVs = append(srvs.SRVs, &net.SRV{Target: h, Port: port})
}
} }
// Strip the schema from the endpoints because we start just a TCP proxy if len(srvs.Endpoints) == 0 {
endpoints = stripSchema(endpoints)
if len(endpoints) == 0 {
plog.Fatalf("no endpoints found") plog.Fatalf("no endpoints found")
} }
@ -113,7 +124,7 @@ func startGateway(cmd *cobra.Command, args []string) {
tp := tcpproxy.TCPProxy{ tp := tcpproxy.TCPProxy{
Listener: l, Listener: l,
Endpoints: endpoints, Endpoints: srvs.SRVs,
MonitorInterval: getewayRetryDelay, MonitorInterval: getewayRetryDelay,
} }

View File

@ -106,8 +106,9 @@ func startGRPCProxy(cmd *cobra.Command, args []string) {
os.Exit(1) os.Exit(1)
} }
if eps := discoverEndpoints(grpcProxyDNSCluster, grpcProxyCA, grpcProxyInsecureDiscovery); len(eps) != 0 { srvs := discoverEndpoints(grpcProxyDNSCluster, grpcProxyCA, grpcProxyInsecureDiscovery)
grpcProxyEndpoints = eps if len(srvs.Endpoints) != 0 {
grpcProxyEndpoints = srvs.Endpoints
} }
l, err := net.Listen("tcp", grpcProxyListenAddr) l, err := net.Listen("tcp", grpcProxyListenAddr)

View File

@ -22,19 +22,19 @@ import (
"github.com/coreos/etcd/pkg/transport" "github.com/coreos/etcd/pkg/transport"
) )
func discoverEndpoints(dns string, ca string, insecure bool) (endpoints []string) { func discoverEndpoints(dns string, ca string, insecure bool) (s srv.SRVClients) {
if dns == "" { if dns == "" {
return nil return s
} }
srvs, err := srv.GetClient("etcd-client", dns) srvs, err := srv.GetClient("etcd-client", dns)
if err != nil { if err != nil {
fmt.Fprintln(os.Stderr, err) fmt.Fprintln(os.Stderr, err)
os.Exit(1) os.Exit(1)
} }
endpoints = srvs.Endpoints endpoints := srvs.Endpoints
plog.Infof("discovered the cluster %s from %s", endpoints, dns) plog.Infof("discovered the cluster %s from %s", endpoints, dns)
if insecure { if insecure {
return endpoints return *srvs
} }
// confirm TLS connections are good // confirm TLS connections are good
tlsInfo := transport.TLSInfo{ tlsInfo := transport.TLSInfo{
@ -47,5 +47,19 @@ func discoverEndpoints(dns string, ca string, insecure bool) (endpoints []string
plog.Warningf("%v", err) plog.Warningf("%v", err)
} }
plog.Infof("using discovered endpoints %v", endpoints) plog.Infof("using discovered endpoints %v", endpoints)
return endpoints
// map endpoints back to SRVClients struct with SRV data
eps := make(map[string]struct{})
for _, ep := range endpoints {
eps[ep] = struct{}{}
}
for i := range srvs.Endpoints {
if _, ok := eps[srvs.Endpoints[i]]; !ok {
continue
}
s.Endpoints = append(s.Endpoints, srvs.Endpoints[i])
s.SRVs = append(s.SRVs, srvs.SRVs[i])
}
return s
} }

View File

@ -15,7 +15,9 @@
package tcpproxy package tcpproxy
import ( import (
"fmt"
"io" "io"
"math/rand"
"net" "net"
"sync" "sync"
"time" "time"
@ -29,6 +31,7 @@ var (
type remote struct { type remote struct {
mu sync.Mutex mu sync.Mutex
srv *net.SRV
addr string addr string
inactive bool inactive bool
} }
@ -59,14 +62,14 @@ func (r *remote) isActive() bool {
type TCPProxy struct { type TCPProxy struct {
Listener net.Listener Listener net.Listener
Endpoints []string Endpoints []*net.SRV
MonitorInterval time.Duration MonitorInterval time.Duration
donec chan struct{} donec chan struct{}
mu sync.Mutex // guards the following fields mu sync.Mutex // guards the following fields
remotes []*remote remotes []*remote
nextRemote int pickCount int // for round robin
} }
func (tp *TCPProxy) Run() error { func (tp *TCPProxy) Run() error {
@ -74,11 +77,12 @@ func (tp *TCPProxy) Run() error {
if tp.MonitorInterval == 0 { if tp.MonitorInterval == 0 {
tp.MonitorInterval = 5 * time.Minute tp.MonitorInterval = 5 * time.Minute
} }
for _, ep := range tp.Endpoints { for _, srv := range tp.Endpoints {
tp.remotes = append(tp.remotes, &remote{addr: ep}) addr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
tp.remotes = append(tp.remotes, &remote{srv: srv, addr: addr})
} }
plog.Printf("ready to proxy client requests to %v", tp.Endpoints) plog.Printf("ready to proxy client requests to %+v", tp.Endpoints)
go tp.runMonitor() go tp.runMonitor()
for { for {
in, err := tp.Listener.Accept() in, err := tp.Listener.Accept()
@ -90,10 +94,61 @@ func (tp *TCPProxy) Run() error {
} }
} }
func (tp *TCPProxy) numRemotes() int { func (tp *TCPProxy) pick() *remote {
tp.mu.Lock() var weighted []*remote
defer tp.mu.Unlock() var unweighted []*remote
return len(tp.remotes)
bestPr := uint16(65535)
w := 0
// find best priority class
for _, r := range tp.remotes {
switch {
case !r.isActive():
case r.srv.Priority < bestPr:
bestPr = r.srv.Priority
w = 0
weighted, unweighted = nil, nil
unweighted = []*remote{r}
fallthrough
case r.srv.Priority == bestPr:
if r.srv.Weight > 0 {
weighted = append(weighted, r)
w += int(r.srv.Weight)
} else {
unweighted = append(unweighted, r)
}
}
}
if weighted != nil {
if len(unweighted) > 0 && rand.Intn(100) == 1 {
// In the presence of records containing weights greater
// than 0, records with weight 0 should have a very small
// chance of being selected.
r := unweighted[tp.pickCount%len(unweighted)]
tp.pickCount++
return r
}
// choose a uniform random number between 0 and the sum computed
// (inclusive), and select the RR whose running sum value is the
// first in the selected order
choose := rand.Intn(w)
for i := 0; i < len(weighted); i++ {
choose -= int(weighted[i].srv.Weight)
if choose <= 0 {
return weighted[i]
}
}
}
if unweighted != nil {
for i := 0; i < len(tp.remotes); i++ {
picked := tp.remotes[tp.pickCount%len(tp.remotes)]
tp.pickCount++
if picked.isActive() {
return picked
}
}
}
return nil
} }
func (tp *TCPProxy) serve(in net.Conn) { func (tp *TCPProxy) serve(in net.Conn) {
@ -102,10 +157,12 @@ func (tp *TCPProxy) serve(in net.Conn) {
out net.Conn out net.Conn
) )
for i := 0; i < tp.numRemotes(); i++ { for {
tp.mu.Lock()
remote := tp.pick() remote := tp.pick()
if !remote.isActive() { tp.mu.Unlock()
continue if remote == nil {
break
} }
// TODO: add timeout // TODO: add timeout
out, err = net.Dial("tcp", remote.addr) out, err = net.Dial("tcp", remote.addr)
@ -132,16 +189,6 @@ func (tp *TCPProxy) serve(in net.Conn) {
in.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() { func (tp *TCPProxy) runMonitor() {
for { for {
select { select {

View File

@ -42,9 +42,11 @@ func TestUserspaceProxy(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
var port uint16
fmt.Sscanf(u.Port(), "%d", &port)
p := TCPProxy{ p := TCPProxy{
Listener: l, Listener: l,
Endpoints: []string{u.Host}, Endpoints: []*net.SRV{{Target: u.Hostname(), Port: port}},
} }
go p.Run() go p.Run()
defer p.Stop() defer p.Stop()