vitess.io/vitess@v0.16.2/go/vt/topo/etcd2topo/watch.go (about)

     1  /*
     2  Copyright 2019 The Vitess Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package etcd2topo
    18  
    19  import (
    20  	"context"
    21  	"path"
    22  	"strings"
    23  	"time"
    24  
    25  	"go.etcd.io/etcd/api/v3/mvccpb"
    26  	clientv3 "go.etcd.io/etcd/client/v3"
    27  
    28  	"vitess.io/vitess/go/vt/proto/vtrpc"
    29  	"vitess.io/vitess/go/vt/vterrors"
    30  
    31  	"vitess.io/vitess/go/vt/log"
    32  	"vitess.io/vitess/go/vt/topo"
    33  )
    34  
    35  // Watch is part of the topo.Conn interface.
    36  func (s *Server) Watch(ctx context.Context, filePath string) (*topo.WatchData, <-chan *topo.WatchData, error) {
    37  	nodePath := path.Join(s.root, filePath)
    38  
    39  	// Get the initial version of the file
    40  	initialCtx, initialCancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout)
    41  	defer initialCancel()
    42  	initial, err := s.cli.Get(initialCtx, nodePath)
    43  	if err != nil {
    44  		// Generic error.
    45  		return nil, nil, convertError(err, nodePath)
    46  	}
    47  
    48  	if len(initial.Kvs) != 1 {
    49  		// Node doesn't exist.
    50  		return nil, nil, topo.NewError(topo.NoNode, nodePath)
    51  	}
    52  	wd := &topo.WatchData{
    53  		Contents: initial.Kvs[0].Value,
    54  		Version:  EtcdVersion(initial.Kvs[0].ModRevision),
    55  	}
    56  
    57  	// Create an outer context that will be canceled on return and will cancel all inner watches.
    58  	outerCtx, outerCancel := context.WithCancel(ctx)
    59  
    60  	// Create a context, will be used to cancel the watch on retry.
    61  	watchCtx, watchCancel := context.WithCancel(outerCtx)
    62  
    63  	// Create the Watcher.  We start watching from the response we
    64  	// got, not from the file original version, as the server may
    65  	// not have that much history.
    66  	watcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(initial.Header.Revision))
    67  	if watcher == nil {
    68  		watchCancel()
    69  		outerCancel()
    70  		return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "Watch failed")
    71  	}
    72  
    73  	// Create the notifications channel, send updates to it.
    74  	notifications := make(chan *topo.WatchData, 10)
    75  	go func() {
    76  		defer close(notifications)
    77  		defer outerCancel()
    78  
    79  		var currVersion = initial.Header.Revision
    80  		var watchRetries int
    81  		for {
    82  			select {
    83  			case <-s.running:
    84  				return
    85  			case <-watchCtx.Done():
    86  				// This includes context cancellation errors.
    87  				notifications <- &topo.WatchData{
    88  					Err: convertError(watchCtx.Err(), nodePath),
    89  				}
    90  				return
    91  			case wresp, ok := <-watcher:
    92  				if !ok {
    93  					if watchRetries > 10 {
    94  						t := time.NewTimer(time.Duration(watchRetries) * time.Second)
    95  						select {
    96  						case <-t.C:
    97  							t.Stop()
    98  						case <-s.running:
    99  							t.Stop()
   100  							continue
   101  						case <-watchCtx.Done():
   102  							t.Stop()
   103  							continue
   104  						}
   105  					}
   106  					watchRetries++
   107  					// Cancel inner context on retry and create new one.
   108  					watchCancel()
   109  					watchCtx, watchCancel = context.WithCancel(ctx)
   110  					newWatcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(currVersion))
   111  					if newWatcher == nil {
   112  						log.Warningf("watch %v failed and get a nil channel returned, currVersion: %v", nodePath, currVersion)
   113  					} else {
   114  						watcher = newWatcher
   115  					}
   116  					continue
   117  				}
   118  
   119  				watchRetries = 0
   120  
   121  				if wresp.Canceled {
   122  					// Final notification.
   123  					notifications <- &topo.WatchData{
   124  						Err: convertError(wresp.Err(), nodePath),
   125  					}
   126  					return
   127  				}
   128  
   129  				currVersion = wresp.Header.GetRevision()
   130  
   131  				for _, ev := range wresp.Events {
   132  					switch ev.Type {
   133  					case mvccpb.PUT:
   134  						notifications <- &topo.WatchData{
   135  							Contents: ev.Kv.Value,
   136  							Version:  EtcdVersion(ev.Kv.Version),
   137  						}
   138  					case mvccpb.DELETE:
   139  						// Node is gone, send a final notice.
   140  						notifications <- &topo.WatchData{
   141  							Err: topo.NewError(topo.NoNode, nodePath),
   142  						}
   143  						return
   144  					default:
   145  						notifications <- &topo.WatchData{
   146  							Err: vterrors.Errorf(vtrpc.Code_INTERNAL, "unexpected event received: %v", ev),
   147  						}
   148  						return
   149  					}
   150  				}
   151  			}
   152  		}
   153  	}()
   154  
   155  	return wd, notifications, nil
   156  }
   157  
   158  // WatchRecursive is part of the topo.Conn interface.
   159  func (s *Server) WatchRecursive(ctx context.Context, dirpath string) ([]*topo.WatchDataRecursive, <-chan *topo.WatchDataRecursive, error) {
   160  	nodePath := path.Join(s.root, dirpath)
   161  	if !strings.HasSuffix(nodePath, "/") {
   162  		nodePath = nodePath + "/"
   163  	}
   164  
   165  	// Get the initial version of the file
   166  	initial, err := s.cli.Get(ctx, nodePath, clientv3.WithPrefix())
   167  	if err != nil {
   168  		return nil, nil, convertError(err, nodePath)
   169  	}
   170  
   171  	var initialwd []*topo.WatchDataRecursive
   172  
   173  	for _, kv := range initial.Kvs {
   174  		var wd topo.WatchDataRecursive
   175  		wd.Path = string(kv.Key)
   176  		wd.Contents = kv.Value
   177  		wd.Version = EtcdVersion(initial.Kvs[0].ModRevision)
   178  		initialwd = append(initialwd, &wd)
   179  	}
   180  
   181  	// Create an outer context that will be canceled on return and will cancel all inner watches.
   182  	outerCtx, outerCancel := context.WithCancel(ctx)
   183  
   184  	// Create a context, will be used to cancel the watch on retry.
   185  	watchCtx, watchCancel := context.WithCancel(outerCtx)
   186  
   187  	// Create the Watcher.  We start watching from the response we
   188  	// got, not from the file original version, as the server may
   189  	// not have that much history.
   190  	watcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(initial.Header.Revision), clientv3.WithPrefix())
   191  	if watcher == nil {
   192  		watchCancel()
   193  		outerCancel()
   194  		return nil, nil, vterrors.Errorf(vtrpc.Code_INVALID_ARGUMENT, "Watch failed")
   195  	}
   196  
   197  	// Create the notifications channel, send updates to it.
   198  	notifications := make(chan *topo.WatchDataRecursive, 10)
   199  	go func() {
   200  		defer close(notifications)
   201  		defer outerCancel()
   202  
   203  		var currVersion = initial.Header.Revision
   204  		var watchRetries int
   205  		for {
   206  			select {
   207  			case <-s.running:
   208  				return
   209  			case <-watchCtx.Done():
   210  				// This includes context cancellation errors.
   211  				notifications <- &topo.WatchDataRecursive{
   212  					WatchData: topo.WatchData{Err: convertError(watchCtx.Err(), nodePath)},
   213  				}
   214  				return
   215  			case wresp, ok := <-watcher:
   216  				if !ok {
   217  					if watchRetries > 10 {
   218  						select {
   219  						case <-time.After(time.Duration(watchRetries) * time.Second):
   220  						case <-s.running:
   221  							continue
   222  						case <-watchCtx.Done():
   223  							continue
   224  						}
   225  					}
   226  					watchRetries++
   227  					// Cancel inner context on retry and create new one.
   228  					watchCancel()
   229  					watchCtx, watchCancel = context.WithCancel(ctx)
   230  
   231  					newWatcher := s.cli.Watch(watchCtx, nodePath, clientv3.WithRev(currVersion), clientv3.WithPrefix())
   232  					if newWatcher == nil {
   233  						log.Warningf("watch %v failed and get a nil channel returned, currVersion: %v", nodePath, currVersion)
   234  					} else {
   235  						watcher = newWatcher
   236  					}
   237  					continue
   238  				}
   239  
   240  				watchRetries = 0
   241  
   242  				if wresp.Canceled {
   243  					// Final notification.
   244  					notifications <- &topo.WatchDataRecursive{
   245  						WatchData: topo.WatchData{Err: convertError(wresp.Err(), nodePath)},
   246  					}
   247  					return
   248  				}
   249  
   250  				currVersion = wresp.Header.GetRevision()
   251  
   252  				for _, ev := range wresp.Events {
   253  					switch ev.Type {
   254  					case mvccpb.PUT:
   255  						notifications <- &topo.WatchDataRecursive{
   256  							Path: string(ev.Kv.Key),
   257  							WatchData: topo.WatchData{
   258  								Contents: ev.Kv.Value,
   259  								Version:  EtcdVersion(ev.Kv.Version),
   260  							},
   261  						}
   262  					case mvccpb.DELETE:
   263  						notifications <- &topo.WatchDataRecursive{
   264  							Path: string(ev.Kv.Key),
   265  							WatchData: topo.WatchData{
   266  								Err: topo.NewError(topo.NoNode, nodePath),
   267  							},
   268  						}
   269  					}
   270  				}
   271  			}
   272  		}
   273  	}()
   274  
   275  	return initialwd, notifications, nil
   276  }