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

     1  /*
     2  Copyright 2021 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 faketopo
    18  
    19  import (
    20  	"context"
    21  	"strings"
    22  	"sync"
    23  
    24  	"vitess.io/vitess/go/vt/topo/memorytopo"
    25  
    26  	"vitess.io/vitess/go/vt/log"
    27  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    28  
    29  	"vitess.io/vitess/go/vt/topo"
    30  )
    31  
    32  // FakeFactory implements the Factory interface. This is supposed to be used only for testing
    33  type FakeFactory struct {
    34  	// mu protects the following field.
    35  	mu sync.Mutex
    36  	// cells is the toplevel map that has one entry per cell. It has a list of connections that this fake server will return
    37  	cells map[string][]*FakeConn
    38  }
    39  
    40  var _ topo.Factory = (*FakeFactory)(nil)
    41  
    42  // NewFakeTopoFactory creates a new fake topo factory
    43  func NewFakeTopoFactory() *FakeFactory {
    44  	factory := &FakeFactory{
    45  		mu:    sync.Mutex{},
    46  		cells: map[string][]*FakeConn{},
    47  	}
    48  	factory.cells[topo.GlobalCell] = []*FakeConn{newFakeConnection()}
    49  	return factory
    50  }
    51  
    52  // AddCell is used to add a cell to the factory. It returns the fake connection created. This connection can then be used to set get and update errors
    53  func (f *FakeFactory) AddCell(cell string) *FakeConn {
    54  	conn := newFakeConnection()
    55  	f.cells[cell] = []*FakeConn{conn}
    56  	return conn
    57  }
    58  
    59  // HasGlobalReadOnlyCell implements the Factory interface
    60  func (f *FakeFactory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
    61  	return false
    62  }
    63  
    64  // Create implements the Factory interface
    65  // It creates a fake connection which is supposed to be used only for testing
    66  func (f *FakeFactory) Create(cell, serverAddr, root string) (topo.Conn, error) {
    67  	f.mu.Lock()
    68  	defer f.mu.Unlock()
    69  	connections, ok := f.cells[cell]
    70  	if !ok || len(connections) == 0 {
    71  		return nil, topo.NewError(topo.NoNode, cell)
    72  	}
    73  	// pick the first connection and remove it from the list
    74  	conn := connections[0]
    75  	f.cells[cell] = connections[1:]
    76  
    77  	conn.serverAddr = serverAddr
    78  	conn.cell = cell
    79  	return conn, nil
    80  }
    81  
    82  // FakeConn implements the Conn interface. It is used only for testing
    83  type FakeConn struct {
    84  	cell       string
    85  	serverAddr string
    86  
    87  	// mutex to protect all the operations
    88  	mu sync.Mutex
    89  
    90  	// getResultMap is a map storing the results for each filepath
    91  	getResultMap map[string]result
    92  	// updateErrors stores whether update function call should error or not
    93  	updateErrors []updateError
    94  	// getErrors stores whether the get function call should error or not
    95  	getErrors []bool
    96  
    97  	// watches is a map of all watches for this connection to the cell keyed by the filepath.
    98  	watches map[string][]chan *topo.WatchData
    99  }
   100  
   101  // updateError contains the information whether a update call should return an error or not
   102  // it also stores if the current write should persist or not
   103  type updateError struct {
   104  	shouldError   bool
   105  	writePersists bool
   106  }
   107  
   108  // newFakeConnection creates a new fake connection
   109  func newFakeConnection() *FakeConn {
   110  	return &FakeConn{
   111  		getResultMap: map[string]result{},
   112  		watches:      map[string][]chan *topo.WatchData{},
   113  		getErrors:    []bool{},
   114  		updateErrors: []updateError{},
   115  	}
   116  }
   117  
   118  // AddGetError is used to add a get error to the fake connection
   119  func (f *FakeConn) AddGetError(shouldErr bool) {
   120  	f.mu.Lock()
   121  	defer f.mu.Unlock()
   122  	f.getErrors = append(f.getErrors, shouldErr)
   123  }
   124  
   125  // AddUpdateError is used to add an update error to the fake connection
   126  func (f *FakeConn) AddUpdateError(shouldErr bool, writePersists bool) {
   127  	f.mu.Lock()
   128  	defer f.mu.Unlock()
   129  	f.updateErrors = append(f.updateErrors, updateError{
   130  		shouldError:   shouldErr,
   131  		writePersists: writePersists,
   132  	})
   133  }
   134  
   135  // result keeps track of the fields needed to respond to a Get function call
   136  type result struct {
   137  	contents []byte
   138  	version  uint64
   139  }
   140  
   141  var _ topo.Conn = (*FakeConn)(nil)
   142  
   143  // ListDir implements the Conn interface
   144  func (f *FakeConn) ListDir(ctx context.Context, dirPath string, full bool) ([]topo.DirEntry, error) {
   145  	f.mu.Lock()
   146  	defer f.mu.Unlock()
   147  	var res []topo.DirEntry
   148  
   149  	for filePath := range f.getResultMap {
   150  		if strings.HasPrefix(filePath, dirPath) {
   151  			remaining := filePath[len(dirPath)+1:]
   152  			idx := strings.Index(remaining, "/")
   153  			if idx == -1 {
   154  				res = addToListOfDirEntries(res, topo.DirEntry{
   155  					Name: remaining,
   156  					Type: topo.TypeFile,
   157  				})
   158  			} else {
   159  				res = addToListOfDirEntries(res, topo.DirEntry{
   160  					Name: remaining[0:idx],
   161  					Type: topo.TypeDirectory,
   162  				})
   163  			}
   164  		}
   165  	}
   166  
   167  	if len(res) == 0 {
   168  		return nil, topo.NewError(topo.NoNode, dirPath)
   169  	}
   170  	return res, nil
   171  }
   172  
   173  func addToListOfDirEntries(list []topo.DirEntry, elem topo.DirEntry) []topo.DirEntry {
   174  	for _, entry := range list {
   175  		if entry.Name == elem.Name {
   176  			return list
   177  		}
   178  	}
   179  	list = append(list, elem)
   180  	return list
   181  }
   182  
   183  // Create implements the Conn interface
   184  func (f *FakeConn) Create(ctx context.Context, filePath string, contents []byte) (topo.Version, error) {
   185  	f.mu.Lock()
   186  	defer f.mu.Unlock()
   187  	f.getResultMap[filePath] = result{
   188  		contents: contents,
   189  		version:  1,
   190  	}
   191  	return memorytopo.NodeVersion(1), nil
   192  }
   193  
   194  // Update implements the Conn interface
   195  func (f *FakeConn) Update(ctx context.Context, filePath string, contents []byte, version topo.Version) (topo.Version, error) {
   196  	f.mu.Lock()
   197  	defer f.mu.Unlock()
   198  	shouldErr := false
   199  	writeSucceeds := true
   200  	if len(f.updateErrors) > 0 {
   201  		shouldErr = f.updateErrors[0].shouldError
   202  		writeSucceeds = f.updateErrors[0].writePersists
   203  		f.updateErrors = f.updateErrors[1:]
   204  	}
   205  	if version == nil {
   206  		f.getResultMap[filePath] = result{
   207  			contents: contents,
   208  			version:  1,
   209  		}
   210  		return memorytopo.NodeVersion(1), nil
   211  	}
   212  	res, isPresent := f.getResultMap[filePath]
   213  	if !isPresent {
   214  		return nil, topo.NewError(topo.NoNode, filePath)
   215  	}
   216  	if writeSucceeds {
   217  		res.contents = contents
   218  		f.getResultMap[filePath] = res
   219  	}
   220  	if shouldErr {
   221  		return nil, topo.NewError(topo.Timeout, filePath)
   222  	}
   223  
   224  	// Call the watches
   225  	for path, watches := range f.watches {
   226  		if path != filePath {
   227  			continue
   228  		}
   229  		for _, watch := range watches {
   230  			watch <- &topo.WatchData{
   231  				Contents: res.contents,
   232  				Version:  memorytopo.NodeVersion(res.version),
   233  			}
   234  		}
   235  	}
   236  	return memorytopo.NodeVersion(res.version), nil
   237  }
   238  
   239  // Get implements the Conn interface
   240  func (f *FakeConn) Get(ctx context.Context, filePath string) ([]byte, topo.Version, error) {
   241  	f.mu.Lock()
   242  	defer f.mu.Unlock()
   243  	if len(f.getErrors) > 0 {
   244  		shouldErr := f.getErrors[0]
   245  		f.getErrors = f.getErrors[1:]
   246  		if shouldErr {
   247  			return nil, nil, topo.NewError(topo.Timeout, filePath)
   248  		}
   249  	}
   250  	res, isPresent := f.getResultMap[filePath]
   251  	if !isPresent {
   252  		return nil, nil, topo.NewError(topo.NoNode, filePath)
   253  	}
   254  	return res.contents, memorytopo.NodeVersion(res.version), nil
   255  }
   256  
   257  // List is part of the topo.Conn interface.
   258  func (f *FakeConn) List(ctx context.Context, filePathPrefix string) ([]topo.KVInfo, error) {
   259  	return nil, topo.NewError(topo.NoImplementation, "List not supported in fake topo")
   260  }
   261  
   262  // Delete implements the Conn interface
   263  func (f *FakeConn) Delete(ctx context.Context, filePath string, version topo.Version) error {
   264  	panic("implement me")
   265  }
   266  
   267  // fakeLockDescriptor implements the topo.LockDescriptor interface
   268  type fakeLockDescriptor struct {
   269  }
   270  
   271  // Check implements the topo.LockDescriptor interface
   272  func (f fakeLockDescriptor) Check(ctx context.Context) error {
   273  	return nil
   274  }
   275  
   276  // Unlock implements the topo.LockDescriptor interface
   277  func (f fakeLockDescriptor) Unlock(ctx context.Context) error {
   278  	return nil
   279  }
   280  
   281  var _ topo.LockDescriptor = (*fakeLockDescriptor)(nil)
   282  
   283  // Lock implements the Conn interface
   284  func (f *FakeConn) Lock(ctx context.Context, dirPath, contents string) (topo.LockDescriptor, error) {
   285  	f.mu.Lock()
   286  	defer f.mu.Unlock()
   287  	return &fakeLockDescriptor{}, nil
   288  }
   289  
   290  // TryLock is part of the topo.Conn interface. Its implementation is same as Lock
   291  func (f *FakeConn) TryLock(ctx context.Context, dirPath, contents string) (topo.LockDescriptor, error) {
   292  	return f.Lock(ctx, dirPath, contents)
   293  }
   294  
   295  // Watch implements the Conn interface
   296  func (f *FakeConn) Watch(ctx context.Context, filePath string) (*topo.WatchData, <-chan *topo.WatchData, error) {
   297  	f.mu.Lock()
   298  	defer f.mu.Unlock()
   299  	res, isPresent := f.getResultMap[filePath]
   300  	if !isPresent {
   301  		return nil, nil, topo.NewError(topo.NoNode, filePath)
   302  	}
   303  	current := &topo.WatchData{
   304  		Contents: res.contents,
   305  		Version:  memorytopo.NodeVersion(res.version),
   306  	}
   307  
   308  	notifications := make(chan *topo.WatchData, 100)
   309  	f.watches[filePath] = append(f.watches[filePath], notifications)
   310  
   311  	go func() {
   312  		<-ctx.Done()
   313  		watches, isPresent := f.watches[filePath]
   314  		if !isPresent {
   315  			return
   316  		}
   317  		for i, watch := range watches {
   318  			if notifications == watch {
   319  				close(notifications)
   320  				f.watches[filePath] = append(watches[0:i], watches[i+1:]...)
   321  				break
   322  			}
   323  		}
   324  	}()
   325  	return current, notifications, nil
   326  }
   327  
   328  func (f *FakeConn) WatchRecursive(ctx context.Context, path string) ([]*topo.WatchDataRecursive, <-chan *topo.WatchDataRecursive, error) {
   329  	panic("implement me")
   330  }
   331  
   332  // NewLeaderParticipation implements the Conn interface
   333  func (f *FakeConn) NewLeaderParticipation(string, string) (topo.LeaderParticipation, error) {
   334  	panic("implement me")
   335  }
   336  
   337  // Close implements the Conn interface
   338  func (f *FakeConn) Close() {
   339  	panic("implement me")
   340  }
   341  
   342  // NewFakeTopoServer creates a new fake topo server
   343  func NewFakeTopoServer(factory *FakeFactory) *topo.Server {
   344  	ts, err := topo.NewWithFactory(factory, "" /*serverAddress*/, "" /*root*/)
   345  	if err != nil {
   346  		log.Exitf("topo.NewWithFactory() failed: %v", err)
   347  	}
   348  	for cell := range factory.cells {
   349  		if err := ts.CreateCellInfo(context.Background(), cell, &topodatapb.CellInfo{}); err != nil {
   350  			log.Exitf("ts.CreateCellInfo(%v) failed: %v", cell, err)
   351  		}
   352  	}
   353  	return ts
   354  }