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