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 }