Skip to content

Commit b0ac601

Browse files
authored
rls: LB policy with only control channel handling (#3496)
1 parent b2df44e commit b0ac601

File tree

10 files changed

+654
-107
lines changed

10 files changed

+654
-107
lines changed

balancer/rls/internal/balancer.go

Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
// +build go1.10
2+
3+
/*
4+
*
5+
* Copyright 2020 gRPC authors.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
*
11+
* http://www.apache.org/licenses/LICENSE-2.0
12+
*
13+
* Unless required by applicable law or agreed to in writing, software
14+
* distributed under the License is distributed on an "AS IS" BASIS,
15+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
* See the License for the specific language governing permissions and
17+
* limitations under the License.
18+
*
19+
*/
20+
21+
package rls
22+
23+
import (
24+
"sync"
25+
26+
"google.golang.org/grpc"
27+
"google.golang.org/grpc/balancer"
28+
"google.golang.org/grpc/connectivity"
29+
"google.golang.org/grpc/grpclog"
30+
"google.golang.org/grpc/internal/grpcsync"
31+
"google.golang.org/grpc/resolver"
32+
)
33+
34+
var (
35+
_ balancer.Balancer = (*rlsBalancer)(nil)
36+
_ balancer.V2Balancer = (*rlsBalancer)(nil)
37+
38+
// For overriding in tests.
39+
newRLSClientFunc = newRLSClient
40+
)
41+
42+
// rlsBalancer implements the RLS LB policy.
43+
type rlsBalancer struct {
44+
done *grpcsync.Event
45+
cc balancer.ClientConn
46+
opts balancer.BuildOptions
47+
48+
// Mutex protects all the state maintained by the LB policy.
49+
// TODO(easwars): Once we add the cache, we will also have another lock for
50+
// the cache alone.
51+
mu sync.Mutex
52+
lbCfg *lbConfig // Most recently received service config.
53+
rlsCC *grpc.ClientConn // ClientConn to the RLS server.
54+
rlsC *rlsClient // RLS client wrapper.
55+
56+
ccUpdateCh chan *balancer.ClientConnState
57+
}
58+
59+
// run is a long running goroutine which handles all the updates that the
60+
// balancer wishes to handle. The appropriate updateHandler will push the update
61+
// on to a channel that this goroutine will select on, thereby the handling of
62+
// the update will happen asynchronously.
63+
func (lb *rlsBalancer) run() {
64+
for {
65+
// TODO(easwars): Handle other updates like subConn state changes, RLS
66+
// responses from the server etc.
67+
select {
68+
case u := <-lb.ccUpdateCh:
69+
lb.handleClientConnUpdate(u)
70+
case <-lb.done.Done():
71+
return
72+
}
73+
}
74+
}
75+
76+
// handleClientConnUpdate handles updates to the service config.
77+
// If the RLS server name or the RLS RPC timeout changes, it updates the control
78+
// channel accordingly.
79+
// TODO(easwars): Handle updates to other fields in the service config.
80+
func (lb *rlsBalancer) handleClientConnUpdate(ccs *balancer.ClientConnState) {
81+
grpclog.Infof("rls: service config: %+v", ccs.BalancerConfig)
82+
lb.mu.Lock()
83+
defer lb.mu.Unlock()
84+
85+
if lb.done.HasFired() {
86+
grpclog.Warning("rls: received service config after balancer close")
87+
return
88+
}
89+
90+
newCfg := ccs.BalancerConfig.(*lbConfig)
91+
if lb.lbCfg.Equal(newCfg) {
92+
grpclog.Info("rls: new service config matches existing config")
93+
return
94+
}
95+
96+
lb.updateControlChannel(newCfg)
97+
lb.lbCfg = newCfg
98+
}
99+
100+
// UpdateClientConnState pushes the received ClientConnState update on the
101+
// update channel which will be processed asynchronously by the run goroutine.
102+
// Implements balancer.V2Balancer interface.
103+
func (lb *rlsBalancer) UpdateClientConnState(ccs balancer.ClientConnState) error {
104+
select {
105+
case lb.ccUpdateCh <- &ccs:
106+
case <-lb.done.Done():
107+
}
108+
return nil
109+
}
110+
111+
// ResolverErr implements balancer.V2Balancer interface.
112+
func (lb *rlsBalancer) ResolverError(error) {
113+
// ResolverError is called by gRPC when the name resolver reports an error.
114+
// TODO(easwars): How do we handle this?
115+
grpclog.Fatal("rls: ResolverError is not yet unimplemented")
116+
}
117+
118+
// UpdateSubConnState implements balancer.V2Balancer interface.
119+
func (lb *rlsBalancer) UpdateSubConnState(_ balancer.SubConn, _ balancer.SubConnState) {
120+
grpclog.Fatal("rls: UpdateSubConnState is not yet implemented")
121+
}
122+
123+
// Cleans up the resources allocated by the LB policy including the clientConn
124+
// to the RLS server.
125+
// Implements balancer.Balancer and balancer.V2Balancer interfaces.
126+
func (lb *rlsBalancer) Close() {
127+
lb.mu.Lock()
128+
defer lb.mu.Unlock()
129+
130+
lb.done.Fire()
131+
if lb.rlsCC != nil {
132+
lb.rlsCC.Close()
133+
}
134+
}
135+
136+
// HandleSubConnStateChange implements balancer.Balancer interface.
137+
func (lb *rlsBalancer) HandleSubConnStateChange(_ balancer.SubConn, _ connectivity.State) {
138+
grpclog.Fatal("UpdateSubConnState should be called instead of HandleSubConnStateChange")
139+
}
140+
141+
// HandleResolvedAddrs implements balancer.Balancer interface.
142+
func (lb *rlsBalancer) HandleResolvedAddrs(_ []resolver.Address, _ error) {
143+
grpclog.Fatal("UpdateClientConnState should be called instead of HandleResolvedAddrs")
144+
}
145+
146+
// updateControlChannel updates the RLS client if required.
147+
// Caller must hold lb.mu.
148+
func (lb *rlsBalancer) updateControlChannel(newCfg *lbConfig) {
149+
oldCfg := lb.lbCfg
150+
if newCfg.lookupService == oldCfg.lookupService && newCfg.lookupServiceTimeout == oldCfg.lookupServiceTimeout {
151+
return
152+
}
153+
154+
// Use RPC timeout from new config, if different from existing one.
155+
timeout := oldCfg.lookupServiceTimeout
156+
if timeout != newCfg.lookupServiceTimeout {
157+
timeout = newCfg.lookupServiceTimeout
158+
}
159+
160+
if newCfg.lookupService == oldCfg.lookupService {
161+
// This is the case where only the timeout has changed. We will continue
162+
// to use the existing clientConn. but will create a new rlsClient with
163+
// the new timeout.
164+
lb.rlsC = newRLSClientFunc(lb.rlsCC, lb.opts.Target.Endpoint, timeout)
165+
return
166+
}
167+
168+
// This is the case where the RLS server name has changed. We need to create
169+
// a new clientConn and close the old one.
170+
var dopts []grpc.DialOption
171+
if dialer := lb.opts.Dialer; dialer != nil {
172+
dopts = append(dopts, grpc.WithContextDialer(dialer))
173+
}
174+
dopts = append(dopts, dialCreds(lb.opts))
175+
176+
cc, err := grpc.Dial(newCfg.lookupService, dopts...)
177+
if err != nil {
178+
grpclog.Errorf("rls: dialRLS(%s, %v): %v", newCfg.lookupService, lb.opts, err)
179+
// An error from a non-blocking dial indicates something serious. We
180+
// should continue to use the old control channel if one exists, and
181+
// return so that the rest of the config updates can be processes.
182+
return
183+
}
184+
if lb.rlsCC != nil {
185+
lb.rlsCC.Close()
186+
}
187+
lb.rlsCC = cc
188+
lb.rlsC = newRLSClientFunc(cc, lb.opts.Target.Endpoint, timeout)
189+
}
190+
191+
func dialCreds(opts balancer.BuildOptions) grpc.DialOption {
192+
// The control channel should use the same authority as that of the parent
193+
// channel. This ensures that the identify of the RLS server and that of the
194+
// backend is the same, so if the RLS config is injected by an attacker, it
195+
// cannot cause leakage of private information contained in headers set by
196+
// the application.
197+
server := opts.Target.Authority
198+
switch {
199+
case opts.DialCreds != nil:
200+
if err := opts.DialCreds.OverrideServerName(server); err != nil {
201+
grpclog.Warningf("rls: OverrideServerName(%s) = (%v), using Insecure", server, err)
202+
return grpc.WithInsecure()
203+
}
204+
return grpc.WithTransportCredentials(opts.DialCreds)
205+
case opts.CredsBundle != nil:
206+
return grpc.WithTransportCredentials(opts.CredsBundle.TransportCredentials())
207+
default:
208+
grpclog.Warning("rls: no credentials available, using Insecure")
209+
return grpc.WithInsecure()
210+
}
211+
}

0 commit comments

Comments
 (0)