|
19 | 19 | package resolver |
20 | 20 |
|
21 | 21 | import ( |
22 | | - "fmt" |
23 | | - "sync" |
24 | | - "time" |
| 22 | + "context" |
25 | 23 |
|
26 | | - "google.golang.org/grpc/internal/grpclog" |
27 | | - "google.golang.org/grpc/internal/pretty" |
28 | | - "google.golang.org/grpc/xds/internal/clusterspecifier" |
29 | | - "google.golang.org/grpc/xds/internal/xdsclient" |
30 | 24 | "google.golang.org/grpc/xds/internal/xdsclient/xdsresource" |
31 | 25 | ) |
32 | 26 |
|
33 | | -// serviceUpdate contains information received from the LDS/RDS responses which |
34 | | -// are of interest to the xds resolver. The RDS request is built by first |
35 | | -// making a LDS to get the RouteConfig name. |
36 | | -type serviceUpdate struct { |
37 | | - // virtualHost contains routes and other configuration to route RPCs. |
38 | | - virtualHost *xdsresource.VirtualHost |
39 | | - // clusterSpecifierPlugins contains the configurations for any cluster |
40 | | - // specifier plugins emitted by the xdsclient. |
41 | | - clusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig |
42 | | - // ldsConfig contains configuration that applies to all routes. |
43 | | - ldsConfig ldsConfig |
| 27 | +type listenerWatcher struct { |
| 28 | + resourceName string |
| 29 | + cancel func() |
| 30 | + parent *xdsResolver |
44 | 31 | } |
45 | 32 |
|
46 | | -// ldsConfig contains information received from the LDS responses which are of |
47 | | -// interest to the xds resolver. |
48 | | -type ldsConfig struct { |
49 | | - // maxStreamDuration is from the HTTP connection manager's |
50 | | - // common_http_protocol_options field. |
51 | | - maxStreamDuration time.Duration |
52 | | - httpFilterConfig []xdsresource.HTTPFilter |
| 33 | +func newListenerWatcher(resourceName string, parent *xdsResolver) *listenerWatcher { |
| 34 | + lw := &listenerWatcher{resourceName: resourceName, parent: parent} |
| 35 | + lw.cancel = xdsresource.WatchListener(parent.xdsClient, resourceName, lw) |
| 36 | + return lw |
53 | 37 | } |
54 | 38 |
|
55 | | -// watchService uses LDS and RDS to discover information about the provided |
56 | | -// serviceName. |
57 | | -// |
58 | | -// Note that during race (e.g. an xDS response is received while the user is |
59 | | -// calling cancel()), there's a small window where the callback can be called |
60 | | -// after the watcher is canceled. The caller needs to handle this case. |
61 | | -// |
62 | | -// TODO(easwars): Make this function a method on the xdsResolver type. |
63 | | -// Currently, there is a single call site for this function, and all arguments |
64 | | -// passed to it are fields of the xdsResolver type. |
65 | | -func watchService(c xdsclient.XDSClient, serviceName string, cb func(serviceUpdate, error), logger *grpclog.PrefixLogger) (cancel func()) { |
66 | | - w := &serviceUpdateWatcher{ |
67 | | - logger: logger, |
68 | | - c: c, |
69 | | - serviceName: serviceName, |
70 | | - serviceCb: cb, |
71 | | - } |
72 | | - w.ldsCancel = c.WatchListener(serviceName, w.handleLDSResp) |
73 | | - |
74 | | - return w.close |
| 39 | +func (l *listenerWatcher) OnUpdate(update *xdsresource.ListenerResourceData) { |
| 40 | + l.parent.serializer.Schedule(func(context.Context) { |
| 41 | + l.parent.onListenerResourceUpdate(update.Resource) |
| 42 | + }) |
75 | 43 | } |
76 | 44 |
|
77 | | -// serviceUpdateWatcher handles LDS and RDS response, and calls the service |
78 | | -// callback at the right time. |
79 | | -type serviceUpdateWatcher struct { |
80 | | - logger *grpclog.PrefixLogger |
81 | | - c xdsclient.XDSClient |
82 | | - serviceName string |
83 | | - ldsCancel func() |
84 | | - serviceCb func(serviceUpdate, error) |
85 | | - lastUpdate serviceUpdate |
86 | | - |
87 | | - mu sync.Mutex |
88 | | - closed bool |
89 | | - rdsName string |
90 | | - rdsCancel func() |
| 45 | +func (l *listenerWatcher) OnError(err error) { |
| 46 | + l.parent.serializer.Schedule(func(context.Context) { |
| 47 | + l.parent.onListenerResourceError(err) |
| 48 | + }) |
91 | 49 | } |
92 | 50 |
|
93 | | -func (w *serviceUpdateWatcher) handleLDSResp(update xdsresource.ListenerUpdate, err error) { |
94 | | - w.logger.Infof("received LDS update: %+v, err: %v", pretty.ToJSON(update), err) |
95 | | - w.mu.Lock() |
96 | | - defer w.mu.Unlock() |
97 | | - if w.closed { |
98 | | - return |
99 | | - } |
100 | | - if err != nil { |
101 | | - // We check the error type and do different things. For now, the only |
102 | | - // type we check is ResourceNotFound, which indicates the LDS resource |
103 | | - // was removed, and besides sending the error to callback, we also |
104 | | - // cancel the RDS watch. |
105 | | - if xdsresource.ErrType(err) == xdsresource.ErrorTypeResourceNotFound && w.rdsCancel != nil { |
106 | | - w.rdsCancel() |
107 | | - w.rdsName = "" |
108 | | - w.rdsCancel = nil |
109 | | - w.lastUpdate = serviceUpdate{} |
110 | | - } |
111 | | - // The other error cases still return early without canceling the |
112 | | - // existing RDS watch. |
113 | | - w.serviceCb(serviceUpdate{}, err) |
114 | | - return |
115 | | - } |
116 | | - |
117 | | - w.lastUpdate.ldsConfig = ldsConfig{ |
118 | | - maxStreamDuration: update.MaxStreamDuration, |
119 | | - httpFilterConfig: update.HTTPFilters, |
120 | | - } |
121 | | - |
122 | | - if update.InlineRouteConfig != nil { |
123 | | - // If there was an RDS watch, cancel it. |
124 | | - w.rdsName = "" |
125 | | - if w.rdsCancel != nil { |
126 | | - w.rdsCancel() |
127 | | - w.rdsCancel = nil |
128 | | - } |
| 51 | +func (l *listenerWatcher) OnResourceDoesNotExist() { |
| 52 | + l.parent.serializer.Schedule(func(context.Context) { |
| 53 | + l.parent.onListenerResourceNotFound() |
| 54 | + }) |
| 55 | +} |
129 | 56 |
|
130 | | - // Handle the inline RDS update as if it's from an RDS watch. |
131 | | - w.applyRouteConfigUpdate(*update.InlineRouteConfig) |
132 | | - return |
133 | | - } |
| 57 | +func (l *listenerWatcher) stop() { |
| 58 | + l.cancel() |
| 59 | + l.parent.logger.Infof("Canceling watch on Listener resource %q", l.resourceName) |
| 60 | +} |
134 | 61 |
|
135 | | - // RDS name from update is not an empty string, need RDS to fetch the |
136 | | - // routes. |
| 62 | +type routeConfigWatcher struct { |
| 63 | + resourceName string |
| 64 | + cancel func() |
| 65 | + parent *xdsResolver |
| 66 | +} |
137 | 67 |
|
138 | | - if w.rdsName == update.RouteConfigName { |
139 | | - // If the new RouteConfigName is same as the previous, don't cancel and |
140 | | - // restart the RDS watch. |
141 | | - // |
142 | | - // If the route name did change, then we must wait until the first RDS |
143 | | - // update before reporting this LDS config. |
144 | | - if w.lastUpdate.virtualHost != nil { |
145 | | - // We want to send an update with the new fields from the new LDS |
146 | | - // (e.g. max stream duration), and old fields from the previous |
147 | | - // RDS. |
148 | | - // |
149 | | - // But note that this should only happen when virtual host is set, |
150 | | - // which means an RDS was received. |
151 | | - w.serviceCb(w.lastUpdate, nil) |
152 | | - } |
153 | | - return |
154 | | - } |
155 | | - w.rdsName = update.RouteConfigName |
156 | | - if w.rdsCancel != nil { |
157 | | - w.rdsCancel() |
158 | | - } |
159 | | - w.rdsCancel = w.c.WatchRouteConfig(update.RouteConfigName, w.handleRDSResp) |
| 68 | +func newRouteConfigWatcher(resourceName string, parent *xdsResolver) *routeConfigWatcher { |
| 69 | + rw := &routeConfigWatcher{resourceName: resourceName, parent: parent} |
| 70 | + rw.cancel = xdsresource.WatchRouteConfig(parent.xdsClient, resourceName, rw) |
| 71 | + return rw |
160 | 72 | } |
161 | 73 |
|
162 | | -func (w *serviceUpdateWatcher) applyRouteConfigUpdate(update xdsresource.RouteConfigUpdate) { |
163 | | - matchVh := xdsresource.FindBestMatchingVirtualHost(w.serviceName, update.VirtualHosts) |
164 | | - if matchVh == nil { |
165 | | - // No matching virtual host found. |
166 | | - w.serviceCb(serviceUpdate{}, fmt.Errorf("no matching virtual host found for %q", w.serviceName)) |
167 | | - return |
168 | | - } |
| 74 | +func (r *routeConfigWatcher) OnUpdate(update *xdsresource.RouteConfigResourceData) { |
| 75 | + r.parent.serializer.Schedule(func(context.Context) { |
| 76 | + r.parent.onRouteConfigResourceUpdate(r.resourceName, update.Resource) |
| 77 | + }) |
| 78 | +} |
169 | 79 |
|
170 | | - w.lastUpdate.virtualHost = matchVh |
171 | | - w.lastUpdate.clusterSpecifierPlugins = update.ClusterSpecifierPlugins |
172 | | - w.serviceCb(w.lastUpdate, nil) |
| 80 | +func (r *routeConfigWatcher) OnError(err error) { |
| 81 | + r.parent.serializer.Schedule(func(context.Context) { |
| 82 | + r.parent.onRouteConfigResourceError(r.resourceName, err) |
| 83 | + }) |
173 | 84 | } |
174 | 85 |
|
175 | | -func (w *serviceUpdateWatcher) handleRDSResp(update xdsresource.RouteConfigUpdate, err error) { |
176 | | - w.logger.Infof("received RDS update: %+v, err: %v", pretty.ToJSON(update), err) |
177 | | - w.mu.Lock() |
178 | | - defer w.mu.Unlock() |
179 | | - if w.closed { |
180 | | - return |
181 | | - } |
182 | | - if w.rdsCancel == nil { |
183 | | - // This mean only the RDS watch is canceled, can happen if the LDS |
184 | | - // resource is removed. |
185 | | - return |
186 | | - } |
187 | | - if err != nil { |
188 | | - w.serviceCb(serviceUpdate{}, err) |
189 | | - return |
190 | | - } |
191 | | - w.applyRouteConfigUpdate(update) |
| 86 | +func (r *routeConfigWatcher) OnResourceDoesNotExist() { |
| 87 | + r.parent.serializer.Schedule(func(context.Context) { |
| 88 | + r.parent.onRouteConfigResourceNotFound(r.resourceName) |
| 89 | + }) |
192 | 90 | } |
193 | 91 |
|
194 | | -func (w *serviceUpdateWatcher) close() { |
195 | | - w.mu.Lock() |
196 | | - defer w.mu.Unlock() |
197 | | - w.closed = true |
198 | | - w.ldsCancel() |
199 | | - if w.rdsCancel != nil { |
200 | | - w.rdsCancel() |
201 | | - w.rdsCancel = nil |
202 | | - } |
| 92 | +func (r *routeConfigWatcher) stop() { |
| 93 | + r.cancel() |
| 94 | + r.parent.logger.Infof("Canceling watch on RouteConfiguration resource %q", r.resourceName) |
203 | 95 | } |
0 commit comments