vitess.io/vitess@v0.16.2/go/vt/topo/helpers/tee.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 helpers
    18  
    19  import (
    20  	"context"
    21  
    22  	"vitess.io/vitess/go/vt/log"
    23  	"vitess.io/vitess/go/vt/topo"
    24  )
    25  
    26  // TeeFactory is an implementation of topo.Factory that uses a primary
    27  // underlying topo.Server for all changes, but also duplicates the
    28  // changes to a secondary topo.Server. It also locks both topo servers
    29  // when needed.  It is meant to be used during transitions from one
    30  // topo.Server to another.
    31  //
    32  //   - primary: we read everything from it, and write to it. We also create
    33  //     LeaderParticipation from it.
    34  //   - secondary: we write to it as well, but we usually don't fail.
    35  //   - we lock primary/secondary if reverseLockOrder is False,
    36  //
    37  // or secondary/primary if reverseLockOrder is True.
    38  type TeeFactory struct {
    39  	primary          *topo.Server
    40  	secondary        *topo.Server
    41  	reverseLockOrder bool
    42  }
    43  
    44  // HasGlobalReadOnlyCell is part of the topo.Factory interface.
    45  func (f *TeeFactory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
    46  	return false
    47  }
    48  
    49  // Create is part of the topo.Factory interface.
    50  func (f *TeeFactory) Create(cell, serverAddr, root string) (topo.Conn, error) {
    51  	ctx := context.Background()
    52  	primaryConn, err := f.primary.ConnForCell(ctx, cell)
    53  	if err != nil {
    54  		return nil, err
    55  	}
    56  	secondaryConn, err := f.secondary.ConnForCell(ctx, cell)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	lockFirst := primaryConn
    62  	lockSecond := secondaryConn
    63  	if f.reverseLockOrder {
    64  		lockFirst = secondaryConn
    65  		lockSecond = primaryConn
    66  	}
    67  
    68  	return &TeeConn{
    69  		primary:    primaryConn,
    70  		secondary:  secondaryConn,
    71  		lockFirst:  lockFirst,
    72  		lockSecond: lockSecond,
    73  	}, nil
    74  }
    75  
    76  // NewTee returns a new topo.Server object. It uses a TeeFactory.
    77  func NewTee(primary, secondary *topo.Server, reverseLockOrder bool) (*topo.Server, error) {
    78  	f := &TeeFactory{
    79  		primary:          primary,
    80  		secondary:        secondary,
    81  		reverseLockOrder: reverseLockOrder,
    82  	}
    83  	return topo.NewWithFactory(f, "" /*serverAddress*/, "" /*root*/)
    84  }
    85  
    86  // TeeConn implements the topo.Conn interface.
    87  type TeeConn struct {
    88  	primary   topo.Conn
    89  	secondary topo.Conn
    90  
    91  	lockFirst  topo.Conn
    92  	lockSecond topo.Conn
    93  }
    94  
    95  // Close is part of the topo.Conn interface.
    96  func (c *TeeConn) Close() {
    97  	c.primary.Close()
    98  	c.secondary.Close()
    99  }
   100  
   101  // ListDir is part of the topo.Conn interface.
   102  func (c *TeeConn) ListDir(ctx context.Context, dirPath string, full bool) ([]topo.DirEntry, error) {
   103  	return c.primary.ListDir(ctx, dirPath, full)
   104  }
   105  
   106  // Create is part of the topo.Conn interface.
   107  func (c *TeeConn) Create(ctx context.Context, filePath string, contents []byte) (topo.Version, error) {
   108  	primaryVersion, err := c.primary.Create(ctx, filePath, contents)
   109  	if err != nil {
   110  		return nil, err
   111  	}
   112  
   113  	// This is critical enough that we want to fail. However, we support
   114  	// an unconditional update if the file already exists.
   115  	_, err = c.secondary.Create(ctx, filePath, contents)
   116  	if topo.IsErrType(err, topo.NodeExists) {
   117  		_, err = c.secondary.Update(ctx, filePath, contents, nil)
   118  	}
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  
   123  	return primaryVersion, nil
   124  }
   125  
   126  // Update is part of the topo.Conn interface.
   127  func (c *TeeConn) Update(ctx context.Context, filePath string, contents []byte, version topo.Version) (topo.Version, error) {
   128  	primaryVersion, err := c.primary.Update(ctx, filePath, contents, version)
   129  	if err != nil {
   130  		// Failed on primary, not updating secondary.
   131  		return nil, err
   132  	}
   133  
   134  	// Always do an unconditional update on secondary.
   135  	if _, err = c.secondary.Update(ctx, filePath, contents, nil); err != nil {
   136  		log.Warningf("secondary.Update(%v,unconditonal) failed: %v", filePath, err)
   137  	}
   138  	return primaryVersion, nil
   139  }
   140  
   141  // Get is part of the topo.Conn interface.
   142  func (c *TeeConn) Get(ctx context.Context, filePath string) ([]byte, topo.Version, error) {
   143  	return c.primary.Get(ctx, filePath)
   144  }
   145  
   146  // List is part of the topo.Conn interface.
   147  func (c *TeeConn) List(ctx context.Context, filePathPrefix string) ([]topo.KVInfo, error) {
   148  	return c.primary.List(ctx, filePathPrefix)
   149  }
   150  
   151  // Delete is part of the topo.Conn interface.
   152  func (c *TeeConn) Delete(ctx context.Context, filePath string, version topo.Version) error {
   153  	// If primary fails, no need to go further.
   154  	if err := c.primary.Delete(ctx, filePath, version); err != nil {
   155  		return err
   156  	}
   157  
   158  	// Always do an unconditonal delete on secondary.
   159  	if err := c.secondary.Delete(ctx, filePath, nil); err != nil && !topo.IsErrType(err, topo.NoNode) {
   160  		// Secondary didn't work, and the node wasn't gone already.
   161  		log.Warningf("secondary.Delete(%v) failed: %v", filePath, err)
   162  	}
   163  
   164  	return nil
   165  }
   166  
   167  // Watch is part of the topo.Conn interface
   168  func (c *TeeConn) Watch(ctx context.Context, filePath string) (*topo.WatchData, <-chan *topo.WatchData, error) {
   169  	return c.primary.Watch(ctx, filePath)
   170  }
   171  
   172  func (c *TeeConn) WatchRecursive(ctx context.Context, path string) ([]*topo.WatchDataRecursive, <-chan *topo.WatchDataRecursive, error) {
   173  	return c.primary.WatchRecursive(ctx, path)
   174  }
   175  
   176  //
   177  // Lock management.
   178  //
   179  
   180  // teeTopoLockDescriptor implements the topo.LockDescriptor interface.
   181  type teeTopoLockDescriptor struct {
   182  	c                    *TeeConn
   183  	dirPath              string
   184  	firstLockDescriptor  topo.LockDescriptor
   185  	secondLockDescriptor topo.LockDescriptor
   186  }
   187  
   188  // Lock is part of the topo.Conn interface.
   189  func (c *TeeConn) Lock(ctx context.Context, dirPath, contents string) (topo.LockDescriptor, error) {
   190  	return c.lock(ctx, dirPath, contents)
   191  }
   192  
   193  // TryLock is part of the topo.Conn interface. Its implementation is same as Lock
   194  func (c *TeeConn) TryLock(ctx context.Context, dirPath, contents string) (topo.LockDescriptor, error) {
   195  	return c.Lock(ctx, dirPath, contents)
   196  }
   197  
   198  // Lock is part of the topo.Conn interface.
   199  func (c *TeeConn) lock(ctx context.Context, dirPath, contents string) (topo.LockDescriptor, error) {
   200  	// Lock lockFirst.
   201  	fLD, err := c.lockFirst.Lock(ctx, dirPath, contents)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	// Lock lockSecond.
   207  	sLD, err := c.lockSecond.Lock(ctx, dirPath, contents)
   208  	if err != nil {
   209  		if err := fLD.Unlock(ctx); err != nil {
   210  			log.Warningf("Failed to unlock lockFirst after failed lockSecond lock for %v: %v", dirPath, err)
   211  		}
   212  		return nil, err
   213  	}
   214  
   215  	// Remember both locks in teeTopoLockDescriptor.
   216  	return &teeTopoLockDescriptor{
   217  		c:                    c,
   218  		dirPath:              dirPath,
   219  		firstLockDescriptor:  fLD,
   220  		secondLockDescriptor: sLD,
   221  	}, nil
   222  }
   223  
   224  // Check is part of the topo.LockDescriptor interface.
   225  func (ld *teeTopoLockDescriptor) Check(ctx context.Context) error {
   226  	if err := ld.firstLockDescriptor.Check(ctx); err != nil {
   227  		return err
   228  	}
   229  	return ld.secondLockDescriptor.Check(ctx)
   230  }
   231  
   232  // Unlock is part of the topo.LockDescriptor interface.
   233  func (ld *teeTopoLockDescriptor) Unlock(ctx context.Context) error {
   234  	// Unlock lockSecond, then lockFirst.
   235  	serr := ld.secondLockDescriptor.Unlock(ctx)
   236  	ferr := ld.firstLockDescriptor.Unlock(ctx)
   237  
   238  	if serr != nil {
   239  		if ferr != nil {
   240  			log.Warningf("First Unlock(%v) failed: %v", ld.dirPath, ferr)
   241  		}
   242  		return serr
   243  	}
   244  	return ferr
   245  }
   246  
   247  // NewLeaderParticipation is part of the topo.Conn interface.
   248  func (c *TeeConn) NewLeaderParticipation(name, id string) (topo.LeaderParticipation, error) {
   249  	return c.primary.NewLeaderParticipation(name, id)
   250  }