dubbo.apache.org/dubbo-go/v3@v3.1.1/xds/resolver/watch_service.go (about) 1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 /* 19 * 20 * Copyright 2020 gRPC authors. 21 * 22 */ 23 24 package resolver 25 26 import ( 27 "fmt" 28 "sync" 29 "time" 30 ) 31 32 import ( 33 dubbogoLogger "github.com/dubbogo/gost/log/logger" 34 ) 35 36 import ( 37 "dubbo.apache.org/dubbo-go/v3/xds/client" 38 "dubbo.apache.org/dubbo-go/v3/xds/client/resource" 39 "dubbo.apache.org/dubbo-go/v3/xds/clusterspecifier" 40 "dubbo.apache.org/dubbo-go/v3/xds/utils/pretty" 41 ) 42 43 // serviceUpdate contains information received from the LDS/RDS responses which 44 // are of interest to the xds resolver. The RDS request is built by first 45 // making a LDS to get the RouteConfig name. 46 type serviceUpdate struct { 47 // virtualHost contains routes and other configuration to route RPCs. 48 virtualHost *resource.VirtualHost 49 // clusterSpecifierPlugins contains the configurations for any cluster 50 // specifier plugins emitted by the client. 51 clusterSpecifierPlugins map[string]clusterspecifier.BalancerConfig 52 // ldsConfig contains configuration that applies to all routes. 53 ldsConfig ldsConfig 54 } 55 56 // ldsConfig contains information received from the LDS responses which are of 57 // interest to the xds resolver. 58 type ldsConfig struct { 59 // maxStreamDuration is from the HTTP connection manager's 60 // common_http_protocol_options field. 61 maxStreamDuration time.Duration 62 httpFilterConfig []resource.HTTPFilter 63 } 64 65 // watchService uses LDS and RDS to discover information about the provided 66 // serviceName. 67 // 68 // Note that during race (e.g. an xDS response is received while the user is 69 // calling cancel()), there's a small window where the callback can be called 70 // after the watcher is canceled. The caller needs to handle this case. 71 func watchService(c client.XDSClient, serviceName string, cb func(serviceUpdate, error), logger dubbogoLogger.Logger) (cancel func()) { 72 w := &serviceUpdateWatcher{ 73 logger: logger, 74 c: c, 75 serviceName: serviceName, 76 serviceCb: cb, 77 } 78 w.ldsCancel = c.WatchListener(serviceName, w.handleLDSResp) 79 80 return w.close 81 } 82 83 // serviceUpdateWatcher handles LDS and RDS response, and calls the service 84 // callback at the right time. 85 type serviceUpdateWatcher struct { 86 logger dubbogoLogger.Logger 87 c client.XDSClient 88 serviceName string 89 ldsCancel func() 90 serviceCb func(serviceUpdate, error) 91 lastUpdate serviceUpdate 92 93 mu sync.Mutex 94 closed bool 95 rdsName string 96 rdsCancel func() 97 } 98 99 func (w *serviceUpdateWatcher) handleLDSResp(update resource.ListenerUpdate, err error) { 100 w.logger.Infof("received LDS update: %+v, err: %v", pretty.ToJSON(update), err) 101 w.mu.Lock() 102 defer w.mu.Unlock() 103 if w.closed { 104 return 105 } 106 if err != nil { 107 // We check the error type and do different things. For now, the only 108 // type we check is ResourceNotFound, which indicates the LDS resource 109 // was removed, and besides sending the error to callback, we also 110 // cancel the RDS watch. 111 if resource.ErrType(err) == resource.ErrorTypeResourceNotFound && w.rdsCancel != nil { 112 w.rdsCancel() 113 w.rdsName = "" 114 w.rdsCancel = nil 115 w.lastUpdate = serviceUpdate{} 116 } 117 // The other error cases still return early without canceling the 118 // existing RDS watch. 119 w.serviceCb(serviceUpdate{}, err) 120 return 121 } 122 123 w.lastUpdate.ldsConfig = ldsConfig{ 124 maxStreamDuration: update.MaxStreamDuration, 125 httpFilterConfig: update.HTTPFilters, 126 } 127 128 if update.InlineRouteConfig != nil { 129 // If there was an RDS watch, cancel it. 130 w.rdsName = "" 131 if w.rdsCancel != nil { 132 w.rdsCancel() 133 w.rdsCancel = nil 134 } 135 136 // Handle the inline RDS update as if it's from an RDS watch. 137 w.applyRouteConfigUpdate(*update.InlineRouteConfig) 138 return 139 } 140 141 // RDS name from update is not an empty string, need RDS to fetch the 142 // routes. 143 144 if w.rdsName == update.RouteConfigName { 145 // If the new RouteConfigName is same as the previous, don't cancel and 146 // restart the RDS watch. 147 // 148 // If the route name did change, then we must wait until the first RDS 149 // update before reporting this LDS config. 150 if w.lastUpdate.virtualHost != nil { 151 // We want to send an update with the new fields from the new LDS 152 // (e.g. max stream duration), and old fields from the the previous 153 // RDS. 154 // 155 // But note that this should only happen when virtual host is set, 156 // which means an RDS was received. 157 w.serviceCb(w.lastUpdate, nil) 158 } 159 return 160 } 161 w.rdsName = update.RouteConfigName 162 if w.rdsCancel != nil { 163 w.rdsCancel() 164 } 165 w.rdsCancel = w.c.WatchRouteConfig(update.RouteConfigName, w.handleRDSResp) 166 } 167 168 func (w *serviceUpdateWatcher) applyRouteConfigUpdate(update resource.RouteConfigUpdate) { 169 matchVh := resource.FindBestMatchingVirtualHost(w.serviceName, update.VirtualHosts) 170 if matchVh == nil { 171 // No matching virtual host found. 172 w.serviceCb(serviceUpdate{}, fmt.Errorf("no matching virtual host found for %q", w.serviceName)) 173 return 174 } 175 176 w.lastUpdate.virtualHost = matchVh 177 w.lastUpdate.clusterSpecifierPlugins = update.ClusterSpecifierPlugins 178 w.serviceCb(w.lastUpdate, nil) 179 } 180 181 func (w *serviceUpdateWatcher) handleRDSResp(update resource.RouteConfigUpdate, err error) { 182 w.logger.Infof("received RDS update: %+v, err: %v", pretty.ToJSON(update), err) 183 w.mu.Lock() 184 defer w.mu.Unlock() 185 if w.closed { 186 return 187 } 188 if w.rdsCancel == nil { 189 // This mean only the RDS watch is canceled, can happen if the LDS 190 // resource is removed. 191 return 192 } 193 if err != nil { 194 w.serviceCb(serviceUpdate{}, err) 195 return 196 } 197 w.applyRouteConfigUpdate(update) 198 } 199 200 func (w *serviceUpdateWatcher) close() { 201 w.mu.Lock() 202 defer w.mu.Unlock() 203 w.closed = true 204 w.ldsCancel() 205 if w.rdsCancel != nil { 206 w.rdsCancel() 207 w.rdsCancel = nil 208 } 209 }