-
Notifications
You must be signed in to change notification settings - Fork 4.3k
/
watch_service.go
236 lines (216 loc) · 6.51 KB
/
watch_service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
/*
*
* Copyright 2020 gRPC 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 resolver
import (
"fmt"
"strings"
"sync"
"google.golang.org/grpc/internal/grpclog"
xdsclient "google.golang.org/grpc/xds/internal/client"
)
// serviceUpdate contains information received from the RDS responses which is
// of interested to the xds resolver. The RDS request is built by first making a
// LDS to get the RouteConfig name.
type serviceUpdate struct {
// Routes contain matchers+actions to route RPCs.
Routes []*xdsclient.Route
}
// watchService uses LDS and RDS to discover information about the provided
// serviceName.
//
// Note that during race (e.g. an xDS response is received while the user is
// calling cancel()), there's a small window where the callback can be called
// after the watcher is canceled. The caller needs to handle this case.
func watchService(c xdsClientInterface, serviceName string, cb func(serviceUpdate, error), logger *grpclog.PrefixLogger) (cancel func()) {
w := &serviceUpdateWatcher{
logger: logger,
c: c,
serviceName: serviceName,
serviceCb: cb,
}
w.ldsCancel = c.WatchListener(serviceName, w.handleLDSResp)
return w.close
}
// serviceUpdateWatcher handles LDS and RDS response, and calls the service
// callback at the right time.
type serviceUpdateWatcher struct {
logger *grpclog.PrefixLogger
c xdsClientInterface
serviceName string
ldsCancel func()
serviceCb func(serviceUpdate, error)
mu sync.Mutex
closed bool
rdsName string
rdsCancel func()
}
func (w *serviceUpdateWatcher) handleLDSResp(update xdsclient.ListenerUpdate, err error) {
w.logger.Infof("received LDS update: %+v, err: %v", update, err)
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return
}
if err != nil {
// We check the error type and do different things. For now, the only
// type we check is ResourceNotFound, which indicates the LDS resource
// was removed, and besides sending the error to callback, we also
// cancel the RDS watch.
if xdsclient.ErrType(err) == xdsclient.ErrorTypeResourceNotFound && w.rdsCancel != nil {
w.rdsCancel()
w.rdsName = ""
w.rdsCancel = nil
}
// The other error cases still return early without canceling the
// existing RDS watch.
w.serviceCb(serviceUpdate{}, err)
return
}
if w.rdsName == update.RouteConfigName {
// If the new RouteConfigName is same as the previous, don't cancel and
// restart the RDS watch.
return
}
w.rdsName = update.RouteConfigName
if w.rdsCancel != nil {
w.rdsCancel()
}
w.rdsCancel = w.c.WatchRouteConfig(update.RouteConfigName, w.handleRDSResp)
}
func (w *serviceUpdateWatcher) handleRDSResp(update xdsclient.RouteConfigUpdate, err error) {
w.logger.Infof("received RDS update: %+v, err: %v", update, err)
w.mu.Lock()
defer w.mu.Unlock()
if w.closed {
return
}
if w.rdsCancel == nil {
// This mean only the RDS watch is canceled, can happen if the LDS
// resource is removed.
return
}
if err != nil {
w.serviceCb(serviceUpdate{}, err)
return
}
matchVh := findBestMatchingVirtualHost(w.serviceName, update.VirtualHosts)
if matchVh == nil {
// No matching virtual host found.
w.serviceCb(serviceUpdate{}, fmt.Errorf("no matching virtual host found for %q", w.serviceName))
return
}
w.serviceCb(serviceUpdate{Routes: matchVh.Routes}, nil)
}
func (w *serviceUpdateWatcher) close() {
w.mu.Lock()
defer w.mu.Unlock()
w.closed = true
w.ldsCancel()
if w.rdsCancel != nil {
w.rdsCancel()
w.rdsCancel = nil
}
}
type domainMatchType int
const (
domainMatchTypeInvalid domainMatchType = iota
domainMatchTypeUniversal
domainMatchTypePrefix
domainMatchTypeSuffix
domainMatchTypeExact
)
// Exact > Suffix > Prefix > Universal > Invalid.
func (t domainMatchType) betterThan(b domainMatchType) bool {
return t > b
}
func matchTypeForDomain(d string) domainMatchType {
if d == "" {
return domainMatchTypeInvalid
}
if d == "*" {
return domainMatchTypeUniversal
}
if strings.HasPrefix(d, "*") {
return domainMatchTypeSuffix
}
if strings.HasSuffix(d, "*") {
return domainMatchTypePrefix
}
if strings.Contains(d, "*") {
return domainMatchTypeInvalid
}
return domainMatchTypeExact
}
func match(domain, host string) (domainMatchType, bool) {
switch typ := matchTypeForDomain(domain); typ {
case domainMatchTypeInvalid:
return typ, false
case domainMatchTypeUniversal:
return typ, true
case domainMatchTypePrefix:
// abc.*
return typ, strings.HasPrefix(host, strings.TrimSuffix(domain, "*"))
case domainMatchTypeSuffix:
// *.123
return typ, strings.HasSuffix(host, strings.TrimPrefix(domain, "*"))
case domainMatchTypeExact:
return typ, domain == host
default:
return domainMatchTypeInvalid, false
}
}
// findBestMatchingVirtualHost returns the virtual host whose domains field best
// matches host
//
// The domains field support 4 different matching pattern types:
// - Exact match
// - Suffix match (e.g. “*ABC”)
// - Prefix match (e.g. “ABC*)
// - Universal match (e.g. “*”)
//
// The best match is defined as:
// - A match is better if it’s matching pattern type is better
// - Exact match > suffix match > prefix match > universal match
// - If two matches are of the same pattern type, the longer match is better
// - This is to compare the length of the matching pattern, e.g. “*ABCDE” >
// “*ABC”
func findBestMatchingVirtualHost(host string, vHosts []*xdsclient.VirtualHost) *xdsclient.VirtualHost {
var (
matchVh *xdsclient.VirtualHost
matchType = domainMatchTypeInvalid
matchLen int
)
for _, vh := range vHosts {
for _, domain := range vh.Domains {
typ, matched := match(domain, host)
if typ == domainMatchTypeInvalid {
// The rds response is invalid.
return nil
}
if matchType.betterThan(typ) || matchType == typ && matchLen >= len(domain) || !matched {
// The previous match has better type, or the previous match has
// better length, or this domain isn't a match.
continue
}
matchVh = vh
matchType = typ
matchLen = len(domain)
}
}
return matchVh
}