github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/grpc/xds/internal/xdsclient/pubsub/update.go (about)

     1  /*
     2   *
     3   * Copyright 2021 gRPC authors.
     4   *
     5   * Licensed under the Apache License, Version 2.0 (the "License");
     6   * you may not use this file except in compliance with the License.
     7   * 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  package pubsub
    19  
    20  import (
    21  	"github.com/golang/protobuf/proto"
    22  	"github.com/hxx258456/ccgo/grpc/internal/pretty"
    23  	"github.com/hxx258456/ccgo/grpc/xds/internal/xdsclient/xdsresource"
    24  )
    25  
    26  type watcherInfoWithUpdate struct {
    27  	wi     *watchInfo
    28  	update interface{}
    29  	err    error
    30  }
    31  
    32  // scheduleCallback should only be called by methods of watchInfo, which checks
    33  // for watcher states and maintain consistency.
    34  func (pb *Pubsub) scheduleCallback(wi *watchInfo, update interface{}, err error) {
    35  	pb.updateCh.Put(&watcherInfoWithUpdate{
    36  		wi:     wi,
    37  		update: update,
    38  		err:    err,
    39  	})
    40  }
    41  
    42  func (pb *Pubsub) callCallback(wiu *watcherInfoWithUpdate) {
    43  	pb.mu.Lock()
    44  	// Use a closure to capture the callback and type assertion, to save one
    45  	// more switch case.
    46  	//
    47  	// The callback must be called without pb.mu. Otherwise if the callback calls
    48  	// another watch() inline, it will cause a deadlock. This leaves a small
    49  	// window that a watcher's callback could be called after the watcher is
    50  	// canceled, and the user needs to take care of it.
    51  	var ccb func()
    52  	switch wiu.wi.rType {
    53  	case xdsresource.ListenerResource:
    54  		if s, ok := pb.ldsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
    55  			ccb = func() { wiu.wi.ldsCallback(wiu.update.(xdsresource.ListenerUpdate), wiu.err) }
    56  		}
    57  	case xdsresource.RouteConfigResource:
    58  		if s, ok := pb.rdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
    59  			ccb = func() { wiu.wi.rdsCallback(wiu.update.(xdsresource.RouteConfigUpdate), wiu.err) }
    60  		}
    61  	case xdsresource.ClusterResource:
    62  		if s, ok := pb.cdsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
    63  			ccb = func() { wiu.wi.cdsCallback(wiu.update.(xdsresource.ClusterUpdate), wiu.err) }
    64  		}
    65  	case xdsresource.EndpointsResource:
    66  		if s, ok := pb.edsWatchers[wiu.wi.target]; ok && s[wiu.wi] {
    67  			ccb = func() { wiu.wi.edsCallback(wiu.update.(xdsresource.EndpointsUpdate), wiu.err) }
    68  		}
    69  	}
    70  	pb.mu.Unlock()
    71  
    72  	if ccb != nil {
    73  		ccb()
    74  	}
    75  }
    76  
    77  // NewListeners is called when there's a new LDS update.
    78  func (pb *Pubsub) NewListeners(updates map[string]xdsresource.ListenerUpdateErrTuple, metadata xdsresource.UpdateMetadata) {
    79  	pb.mu.Lock()
    80  	defer pb.mu.Unlock()
    81  
    82  	for name, uErr := range updates {
    83  		if s, ok := pb.ldsWatchers[name]; ok {
    84  			if uErr.Err != nil {
    85  				// On error, keep previous version for each resource. But update
    86  				// status and error.
    87  				mdCopy := pb.ldsMD[name]
    88  				mdCopy.ErrState = metadata.ErrState
    89  				mdCopy.Status = metadata.Status
    90  				pb.ldsMD[name] = mdCopy
    91  				for wi := range s {
    92  					wi.newError(uErr.Err)
    93  				}
    94  				continue
    95  			}
    96  			// If we get here, it means that the update is a valid one. Notify
    97  			// watchers only if this is a first time update or it is different
    98  			// from the one currently cached.
    99  			if cur, ok := pb.ldsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) {
   100  				for wi := range s {
   101  					wi.newUpdate(uErr.Update)
   102  				}
   103  			}
   104  			// Sync cache.
   105  			pb.logger.Debugf("LDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr))
   106  			pb.ldsCache[name] = uErr.Update
   107  			// Set status to ACK, and clear error state. The metadata might be a
   108  			// NACK metadata because some other resources in the same response
   109  			// are invalid.
   110  			mdCopy := metadata
   111  			mdCopy.Status = xdsresource.ServiceStatusACKed
   112  			mdCopy.ErrState = nil
   113  			if metadata.ErrState != nil {
   114  				mdCopy.Version = metadata.ErrState.Version
   115  			}
   116  			pb.ldsMD[name] = mdCopy
   117  		}
   118  	}
   119  	// Resources not in the new update were removed by the server, so delete
   120  	// them.
   121  	for name := range pb.ldsCache {
   122  		if _, ok := updates[name]; !ok {
   123  			// If resource exists in cache, but not in the new update, delete
   124  			// the resource from cache, and also send an resource not found
   125  			// error to indicate resource removed.
   126  			delete(pb.ldsCache, name)
   127  			pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist}
   128  			for wi := range pb.ldsWatchers[name] {
   129  				wi.resourceNotFound()
   130  			}
   131  		}
   132  	}
   133  	// When LDS resource is removed, we don't delete corresponding RDS cached
   134  	// data. The RDS watch will be canceled, and cache entry is removed when the
   135  	// last watch is canceled.
   136  }
   137  
   138  // NewRouteConfigs is called when there's a new RDS update.
   139  func (pb *Pubsub) NewRouteConfigs(updates map[string]xdsresource.RouteConfigUpdateErrTuple, metadata xdsresource.UpdateMetadata) {
   140  	pb.mu.Lock()
   141  	defer pb.mu.Unlock()
   142  
   143  	// If no error received, the status is ACK.
   144  	for name, uErr := range updates {
   145  		if s, ok := pb.rdsWatchers[name]; ok {
   146  			if uErr.Err != nil {
   147  				// On error, keep previous version for each resource. But update
   148  				// status and error.
   149  				mdCopy := pb.rdsMD[name]
   150  				mdCopy.ErrState = metadata.ErrState
   151  				mdCopy.Status = metadata.Status
   152  				pb.rdsMD[name] = mdCopy
   153  				for wi := range s {
   154  					wi.newError(uErr.Err)
   155  				}
   156  				continue
   157  			}
   158  			// If we get here, it means that the update is a valid one. Notify
   159  			// watchers only if this is a first time update or it is different
   160  			// from the one currently cached.
   161  			if cur, ok := pb.rdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) {
   162  				for wi := range s {
   163  					wi.newUpdate(uErr.Update)
   164  				}
   165  			}
   166  			// Sync cache.
   167  			pb.logger.Debugf("RDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr))
   168  			pb.rdsCache[name] = uErr.Update
   169  			// Set status to ACK, and clear error state. The metadata might be a
   170  			// NACK metadata because some other resources in the same response
   171  			// are invalid.
   172  			mdCopy := metadata
   173  			mdCopy.Status = xdsresource.ServiceStatusACKed
   174  			mdCopy.ErrState = nil
   175  			if metadata.ErrState != nil {
   176  				mdCopy.Version = metadata.ErrState.Version
   177  			}
   178  			pb.rdsMD[name] = mdCopy
   179  		}
   180  	}
   181  }
   182  
   183  // NewClusters is called when there's a new CDS update.
   184  func (pb *Pubsub) NewClusters(updates map[string]xdsresource.ClusterUpdateErrTuple, metadata xdsresource.UpdateMetadata) {
   185  	pb.mu.Lock()
   186  	defer pb.mu.Unlock()
   187  
   188  	for name, uErr := range updates {
   189  		if s, ok := pb.cdsWatchers[name]; ok {
   190  			if uErr.Err != nil {
   191  				// On error, keep previous version for each resource. But update
   192  				// status and error.
   193  				mdCopy := pb.cdsMD[name]
   194  				mdCopy.ErrState = metadata.ErrState
   195  				mdCopy.Status = metadata.Status
   196  				pb.cdsMD[name] = mdCopy
   197  				for wi := range s {
   198  					// Send the watcher the individual error, instead of the
   199  					// overall combined error from the metadata.ErrState.
   200  					wi.newError(uErr.Err)
   201  				}
   202  				continue
   203  			}
   204  			// If we get here, it means that the update is a valid one. Notify
   205  			// watchers only if this is a first time update or it is different
   206  			// from the one currently cached.
   207  			if cur, ok := pb.cdsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) {
   208  				for wi := range s {
   209  					wi.newUpdate(uErr.Update)
   210  				}
   211  			}
   212  			// Sync cache.
   213  			pb.logger.Debugf("CDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr))
   214  			pb.cdsCache[name] = uErr.Update
   215  			// Set status to ACK, and clear error state. The metadata might be a
   216  			// NACK metadata because some other resources in the same response
   217  			// are invalid.
   218  			mdCopy := metadata
   219  			mdCopy.Status = xdsresource.ServiceStatusACKed
   220  			mdCopy.ErrState = nil
   221  			if metadata.ErrState != nil {
   222  				mdCopy.Version = metadata.ErrState.Version
   223  			}
   224  			pb.cdsMD[name] = mdCopy
   225  		}
   226  	}
   227  	// Resources not in the new update were removed by the server, so delete
   228  	// them.
   229  	for name := range pb.cdsCache {
   230  		if _, ok := updates[name]; !ok {
   231  			// If resource exists in cache, but not in the new update, delete it
   232  			// from cache, and also send an resource not found error to indicate
   233  			// resource removed.
   234  			delete(pb.cdsCache, name)
   235  			pb.ldsMD[name] = xdsresource.UpdateMetadata{Status: xdsresource.ServiceStatusNotExist}
   236  			for wi := range pb.cdsWatchers[name] {
   237  				wi.resourceNotFound()
   238  			}
   239  		}
   240  	}
   241  	// When CDS resource is removed, we don't delete corresponding EDS cached
   242  	// data. The EDS watch will be canceled, and cache entry is removed when the
   243  	// last watch is canceled.
   244  }
   245  
   246  // NewEndpoints is called when there's anew EDS update.
   247  func (pb *Pubsub) NewEndpoints(updates map[string]xdsresource.EndpointsUpdateErrTuple, metadata xdsresource.UpdateMetadata) {
   248  	pb.mu.Lock()
   249  	defer pb.mu.Unlock()
   250  
   251  	for name, uErr := range updates {
   252  		if s, ok := pb.edsWatchers[name]; ok {
   253  			if uErr.Err != nil {
   254  				// On error, keep previous version for each resource. But update
   255  				// status and error.
   256  				mdCopy := pb.edsMD[name]
   257  				mdCopy.ErrState = metadata.ErrState
   258  				mdCopy.Status = metadata.Status
   259  				pb.edsMD[name] = mdCopy
   260  				for wi := range s {
   261  					// Send the watcher the individual error, instead of the
   262  					// overall combined error from the metadata.ErrState.
   263  					wi.newError(uErr.Err)
   264  				}
   265  				continue
   266  			}
   267  			// If we get here, it means that the update is a valid one. Notify
   268  			// watchers only if this is a first time update or it is different
   269  			// from the one currently cached.
   270  			if cur, ok := pb.edsCache[name]; !ok || !proto.Equal(cur.Raw, uErr.Update.Raw) {
   271  				for wi := range s {
   272  					wi.newUpdate(uErr.Update)
   273  				}
   274  			}
   275  			// Sync cache.
   276  			pb.logger.Debugf("EDS resource with name %v, value %+v added to cache", name, pretty.ToJSON(uErr))
   277  			pb.edsCache[name] = uErr.Update
   278  			// Set status to ACK, and clear error state. The metadata might be a
   279  			// NACK metadata because some other resources in the same response
   280  			// are invalid.
   281  			mdCopy := metadata
   282  			mdCopy.Status = xdsresource.ServiceStatusACKed
   283  			mdCopy.ErrState = nil
   284  			if metadata.ErrState != nil {
   285  				mdCopy.Version = metadata.ErrState.Version
   286  			}
   287  			pb.edsMD[name] = mdCopy
   288  		}
   289  	}
   290  }
   291  
   292  // NewConnectionError is called by the underlying xdsAPIClient when it receives
   293  // a connection error. The error will be forwarded to all the resource watchers.
   294  func (pb *Pubsub) NewConnectionError(err error) {
   295  	pb.mu.Lock()
   296  	defer pb.mu.Unlock()
   297  
   298  	for _, s := range pb.ldsWatchers {
   299  		for wi := range s {
   300  			wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err))
   301  		}
   302  	}
   303  	for _, s := range pb.rdsWatchers {
   304  		for wi := range s {
   305  			wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err))
   306  		}
   307  	}
   308  	for _, s := range pb.cdsWatchers {
   309  		for wi := range s {
   310  			wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err))
   311  		}
   312  	}
   313  	for _, s := range pb.edsWatchers {
   314  		for wi := range s {
   315  			wi.newError(xdsresource.NewErrorf(xdsresource.ErrorTypeConnection, "xds: error received from xDS stream: %v", err))
   316  		}
   317  	}
   318  }