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

     1  /*
     2  Copyright 2020 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 agreedto 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 k8stopo
    18  
    19  import (
    20  	"context"
    21  
    22  	"k8s.io/apimachinery/pkg/fields"
    23  	"k8s.io/client-go/tools/cache"
    24  
    25  	"vitess.io/vitess/go/vt/log"
    26  	"vitess.io/vitess/go/vt/topo"
    27  	vtv1beta1 "vitess.io/vitess/go/vt/topo/k8stopo/apis/topo/v1beta1"
    28  )
    29  
    30  // Watch is part of the topo.Conn interface.
    31  func (s *Server) Watch(ctx context.Context, filePath string) (*topo.WatchData, <-chan *topo.WatchData, error) {
    32  	log.Info("Starting Kubernetes topo Watch on ", filePath)
    33  
    34  	current := &topo.WatchData{}
    35  
    36  	// get current
    37  	initialCtx, initialCancel := context.WithTimeout(ctx, topo.RemoteOperationTimeout)
    38  	defer initialCancel()
    39  
    40  	contents, ver, err := s.Get(initialCtx, filePath)
    41  	if err != nil {
    42  		return nil, nil, err
    43  	}
    44  	current.Contents = contents
    45  	current.Version = ver
    46  
    47  	// Create the changes channel
    48  	changes := make(chan *topo.WatchData, 10)
    49  
    50  	// Create a signal channel for non-interrupt shutdowns
    51  	gracefulShutdown := make(chan struct{})
    52  
    53  	resource, err := s.buildFileResource(filePath, []byte{})
    54  	if err != nil {
    55  		return nil, nil, err
    56  	}
    57  
    58  	// Create the informer / indexer to watch the single resource
    59  	restClient := s.vtKubeClient.TopoV1beta1().RESTClient()
    60  	listwatch := cache.NewListWatchFromClient(restClient, "vitesstoponodes", s.namespace, fields.OneTermEqualSelector("metadata.name", resource.Name))
    61  
    62  	// set up index funcs
    63  	indexers := cache.Indexers{}
    64  	indexers["by_parent"] = indexByParent
    65  
    66  	_, memberInformer := cache.NewIndexerInformer(listwatch, &vtv1beta1.VitessTopoNode{}, 0,
    67  		cache.ResourceEventHandlerFuncs{
    68  			AddFunc: func(obj any) {
    69  				vtn := obj.(*vtv1beta1.VitessTopoNode)
    70  				out, err := unpackValue([]byte(vtn.Data.Value))
    71  				if err != nil {
    72  					changes <- &topo.WatchData{Err: err}
    73  					close(gracefulShutdown)
    74  				} else {
    75  					changes <- &topo.WatchData{
    76  						Contents: out,
    77  						Version:  KubernetesVersion(vtn.GetResourceVersion()),
    78  					}
    79  				}
    80  			},
    81  			UpdateFunc: func(oldObj, newObj any) {
    82  				vtn := newObj.(*vtv1beta1.VitessTopoNode)
    83  				out, err := unpackValue([]byte(vtn.Data.Value))
    84  				if err != nil {
    85  					changes <- &topo.WatchData{Err: err}
    86  					close(gracefulShutdown)
    87  				} else {
    88  					changes <- &topo.WatchData{
    89  						Contents: out,
    90  						Version:  KubernetesVersion(vtn.GetResourceVersion()),
    91  					}
    92  				}
    93  			},
    94  			DeleteFunc: func(obj any) {
    95  				vtn := obj.(*vtv1beta1.VitessTopoNode)
    96  				changes <- &topo.WatchData{Err: topo.NewError(topo.NoNode, vtn.Name)}
    97  				close(gracefulShutdown)
    98  			},
    99  		}, indexers)
   100  
   101  	// create control chan for informer and start it
   102  	informerChan := make(chan struct{})
   103  	go memberInformer.Run(informerChan)
   104  
   105  	// Handle interrupts
   106  	go closeOnDone(ctx, filePath, informerChan, gracefulShutdown, changes)
   107  
   108  	return current, changes, nil
   109  }
   110  
   111  func closeOnDone(ctx context.Context, filePath string, informerChan chan struct{}, gracefulShutdown chan struct{}, changes chan *topo.WatchData) {
   112  	select {
   113  	case <-ctx.Done():
   114  		if err := ctx.Err(); err != nil && err == context.Canceled {
   115  			changes <- &topo.WatchData{Err: topo.NewError(topo.Interrupted, filePath)}
   116  		}
   117  	case <-gracefulShutdown:
   118  	}
   119  	close(informerChan)
   120  	close(changes)
   121  }
   122  
   123  // WatchRecursive is part of the topo.Conn interface.
   124  func (s *Server) WatchRecursive(_ context.Context, path string) ([]*topo.WatchDataRecursive, <-chan *topo.WatchDataRecursive, error) {
   125  	// Kubernetes doesn't seem to provide a primitive that watches a prefix
   126  	// or directory, so this likely can never be implemented.
   127  	return nil, nil, topo.NewError(topo.NoImplementation, path)
   128  }