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 }