vitess.io/vitess@v0.16.2/go/vt/discovery/fake_healthcheck.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 discovery 18 19 import ( 20 "context" 21 "fmt" 22 "sort" 23 "sync" 24 25 "google.golang.org/protobuf/proto" 26 27 "vitess.io/vitess/go/vt/topo" 28 "vitess.io/vitess/go/vt/topo/topoproto" 29 "vitess.io/vitess/go/vt/vterrors" 30 "vitess.io/vitess/go/vt/vttablet/queryservice" 31 "vitess.io/vitess/go/vt/vttablet/sandboxconn" 32 33 querypb "vitess.io/vitess/go/vt/proto/query" 34 topodatapb "vitess.io/vitess/go/vt/proto/topodata" 35 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 36 ) 37 38 // This file contains the definitions for a FakeHealthCheck class to 39 // simulate a HealthCheck module. Note it is not in a sub-package because 40 // otherwise it couldn't be used in this package's tests because of 41 // circular dependencies. 42 43 // NewFakeHealthCheck returns the fake healthcheck object. 44 func NewFakeHealthCheck(ch chan *TabletHealth) *FakeHealthCheck { 45 return &FakeHealthCheck{ 46 items: make(map[string]*fhcItem), 47 itemsAlias: make(map[string]*fhcItem), 48 ch: ch, 49 } 50 } 51 52 // FakeHealthCheck implements discovery.HealthCheck. 53 type FakeHealthCheck struct { 54 // mu protects the items map 55 mu sync.RWMutex 56 items map[string]*fhcItem 57 itemsAlias map[string]*fhcItem 58 currentTabletUID int 59 // channel to return on subscribe. Pass nil if no subscribe should not return a channel 60 ch chan *TabletHealth 61 } 62 63 type fhcItem struct { 64 ts *TabletHealth 65 } 66 67 // 68 // discovery.HealthCheck interface methods 69 // 70 71 // RegisterStats is not implemented. 72 func (fhc *FakeHealthCheck) RegisterStats() { 73 } 74 75 // WaitForAllServingTablets is not implemented. 76 func (fhc *FakeHealthCheck) WaitForAllServingTablets(ctx context.Context, targets []*querypb.Target) error { 77 return nil 78 } 79 80 // GetHealthyTabletStats returns only the healthy tablets - Serving true and LastError is not nil 81 func (fhc *FakeHealthCheck) GetHealthyTabletStats(target *querypb.Target) []*TabletHealth { 82 result := make([]*TabletHealth, 0) 83 fhc.mu.Lock() 84 defer fhc.mu.Unlock() 85 for _, item := range fhc.items { 86 if proto.Equal(item.ts.Target, target) && item.ts.Serving && item.ts.LastError == nil { 87 result = append(result, item.ts) 88 } 89 } 90 return result 91 } 92 93 // GetTabletHealthByAlias results the TabletHealth of the tablet that matches the given alias 94 func (fhc *FakeHealthCheck) GetTabletHealthByAlias(alias *topodatapb.TabletAlias) (*TabletHealth, error) { 95 return fhc.GetTabletHealth("", alias) 96 } 97 98 // GetTabletHealth results the TabletHealth of the tablet that matches the given alias 99 func (fhc *FakeHealthCheck) GetTabletHealth(kst KeyspaceShardTabletType, alias *topodatapb.TabletAlias) (*TabletHealth, error) { 100 fhc.mu.Lock() 101 defer fhc.mu.Unlock() 102 103 if hd, ok := fhc.itemsAlias[alias.String()]; ok { 104 if hd.ts.Tablet.Alias.String() == alias.String() { 105 return hd.ts, nil 106 } 107 } 108 return nil, fmt.Errorf("could not find tablet: %s", alias.String()) 109 } 110 111 // Subscribe returns the channel in the struct. Subscribe should only be called in one place for this fake health check 112 func (fhc *FakeHealthCheck) Subscribe() chan *TabletHealth { 113 return fhc.ch 114 } 115 116 // GetPrimaryTablet gets the primary tablet from the tablets that healthcheck has seen so far 117 func (fhc *FakeHealthCheck) GetPrimaryTablet() *topodatapb.Tablet { 118 fhc.mu.Lock() 119 defer fhc.mu.Unlock() 120 for _, item := range fhc.items { 121 if item.ts.Tablet.GetType() == topodatapb.TabletType_PRIMARY { 122 return item.ts.Tablet 123 } 124 } 125 return nil 126 } 127 128 // Broadcast broadcasts the healthcheck for the given tablet 129 func (fhc *FakeHealthCheck) Broadcast(tablet *topodatapb.Tablet) { 130 if fhc.ch == nil { 131 return 132 } 133 fhc.mu.Lock() 134 defer fhc.mu.Unlock() 135 key := TabletToMapKey(tablet) 136 item, isPresent := fhc.items[key] 137 if !isPresent { 138 return 139 } 140 fhc.ch <- simpleCopy(item.ts) 141 } 142 143 // SetServing sets the serving variable for the given tablet 144 func (fhc *FakeHealthCheck) SetServing(tablet *topodatapb.Tablet, serving bool) { 145 if fhc.ch == nil { 146 return 147 } 148 fhc.mu.Lock() 149 defer fhc.mu.Unlock() 150 key := TabletToMapKey(tablet) 151 item, isPresent := fhc.items[key] 152 if !isPresent { 153 return 154 } 155 item.ts.Serving = serving 156 } 157 158 // SetTabletType sets the tablet type for the given tablet 159 func (fhc *FakeHealthCheck) SetTabletType(tablet *topodatapb.Tablet, tabletType topodatapb.TabletType) { 160 if fhc.ch == nil { 161 return 162 } 163 fhc.mu.Lock() 164 defer fhc.mu.Unlock() 165 key := TabletToMapKey(tablet) 166 item, isPresent := fhc.items[key] 167 if !isPresent { 168 return 169 } 170 item.ts.Tablet.Type = tabletType 171 tablet.Type = tabletType 172 item.ts.Target.TabletType = tabletType 173 } 174 175 // Unsubscribe is not implemented. 176 func (fhc *FakeHealthCheck) Unsubscribe(c chan *TabletHealth) { 177 } 178 179 // AddTablet adds the tablet. 180 func (fhc *FakeHealthCheck) AddTablet(tablet *topodatapb.Tablet) { 181 key := TabletToMapKey(tablet) 182 item := &fhcItem{ 183 ts: &TabletHealth{ 184 Tablet: tablet, 185 Target: &querypb.Target{ 186 Keyspace: tablet.Keyspace, 187 Shard: tablet.Shard, 188 TabletType: tablet.Type, 189 }, 190 Serving: true, 191 Stats: &querypb.RealtimeStats{}, 192 }, 193 } 194 195 fhc.mu.Lock() 196 defer fhc.mu.Unlock() 197 fhc.items[key] = item 198 fhc.itemsAlias[tablet.Alias.String()] = item 199 } 200 201 // RemoveTablet removes the tablet. 202 func (fhc *FakeHealthCheck) RemoveTablet(tablet *topodatapb.Tablet) { 203 fhc.mu.Lock() 204 defer fhc.mu.Unlock() 205 key := TabletToMapKey(tablet) 206 item, ok := fhc.items[key] 207 if !ok { 208 return 209 } 210 // Make sure the key still corresponds to the tablet we want to delete. 211 // If it doesn't match, we should do nothing. The tablet we were asked to 212 // delete is already gone, and some other tablet is using the key 213 // (host:port) that the original tablet used to use, which is fine. 214 if !topoproto.TabletAliasEqual(tablet.Alias, item.ts.Tablet.Alias) { 215 return 216 } 217 delete(fhc.items, key) 218 } 219 220 // ReplaceTablet removes the old tablet and adds the new. 221 func (fhc *FakeHealthCheck) ReplaceTablet(old, new *topodatapb.Tablet) { 222 fhc.RemoveTablet(old) 223 fhc.AddTablet(new) 224 } 225 226 // TabletConnection returns the TabletConn of the given tablet. 227 func (fhc *FakeHealthCheck) TabletConnection(alias *topodatapb.TabletAlias, target *querypb.Target) (queryservice.QueryService, error) { 228 aliasStr := topoproto.TabletAliasString(alias) 229 fhc.mu.RLock() 230 defer fhc.mu.RUnlock() 231 for _, item := range fhc.items { 232 if proto.Equal(alias, item.ts.Tablet.Alias) { 233 return item.ts.Conn, nil 234 } 235 } 236 return nil, vterrors.Errorf(vtrpcpb.Code_NOT_FOUND, "tablet %v not found", aliasStr) 237 } 238 239 // CacheStatus returns the status for each tablet 240 func (fhc *FakeHealthCheck) CacheStatus() TabletsCacheStatusList { 241 tcsMap := fhc.CacheStatusMap() 242 tcsl := make(TabletsCacheStatusList, 0, len(tcsMap)) 243 for _, tcs := range tcsMap { 244 tcsl = append(tcsl, tcs) 245 } 246 sort.Sort(tcsl) 247 return tcsl 248 } 249 250 // CacheStatusMap returns a map of the health check cache. 251 func (fhc *FakeHealthCheck) CacheStatusMap() map[string]*TabletsCacheStatus { 252 tcsMap := make(map[string]*TabletsCacheStatus) 253 fhc.mu.Lock() 254 defer fhc.mu.Unlock() 255 for _, ths := range fhc.items { 256 key := fmt.Sprintf("%v.%v.%v.%v", ths.ts.Tablet.Alias.Cell, ths.ts.Target.Keyspace, ths.ts.Target.Shard, ths.ts.Target.TabletType.String()) 257 var tcs *TabletsCacheStatus 258 var ok bool 259 if tcs, ok = tcsMap[key]; !ok { 260 tcs = &TabletsCacheStatus{ 261 Cell: ths.ts.Tablet.Alias.Cell, 262 Target: ths.ts.Target, 263 } 264 tcsMap[key] = tcs 265 } 266 tcs.TabletsStats = append(tcs.TabletsStats, ths.ts) 267 } 268 return tcsMap 269 } 270 271 // UpdateHealth adds a TabletHealth for the tablet defined in th. 272 func (fhc *FakeHealthCheck) UpdateHealth(th *TabletHealth) { 273 fhc.mu.Lock() 274 defer fhc.mu.Unlock() 275 276 key := TabletToMapKey(th.Tablet) 277 if t, ok := fhc.items[key]; ok { 278 t.ts = th 279 fhc.itemsAlias[th.Tablet.Alias.String()].ts = th 280 } 281 } 282 283 // Close is not implemented. 284 func (fhc *FakeHealthCheck) Close() error { 285 return nil 286 } 287 288 // 289 // Management methods 290 // 291 292 // Reset cleans up the internal state. 293 func (fhc *FakeHealthCheck) Reset() { 294 fhc.mu.Lock() 295 defer fhc.mu.Unlock() 296 297 fhc.items = make(map[string]*fhcItem) 298 fhc.currentTabletUID = 0 299 } 300 301 // AddFakeTablet inserts a fake entry into FakeHealthCheck. 302 // The Tablet can be talked to using the provided connection. 303 // The Listener is called, as if AddTablet had been called. 304 // For flexibility the connection is created via a connFactory callback 305 func (fhc *FakeHealthCheck) AddFakeTablet(cell, host string, port int32, keyspace, shard string, tabletType topodatapb.TabletType, serving bool, reparentTS int64, err error, connFactory func(*topodatapb.Tablet) queryservice.QueryService) queryservice.QueryService { 306 fhc.mu.Lock() 307 defer fhc.mu.Unlock() 308 309 // tabletUID must be unique 310 fhc.currentTabletUID++ 311 uid := fhc.currentTabletUID 312 t := topo.NewTablet(uint32(uid), cell, host) 313 t.Keyspace = keyspace 314 t.Shard = shard 315 t.Type = tabletType 316 t.PortMap["vt"] = port 317 key := TabletToMapKey(t) 318 319 item := fhc.items[key] 320 if item == nil { 321 item = &fhcItem{ 322 ts: &TabletHealth{ 323 Tablet: t, 324 }, 325 } 326 fhc.items[key] = item 327 fhc.itemsAlias[t.Alias.String()] = item 328 } 329 item.ts.Target = &querypb.Target{ 330 Keyspace: keyspace, 331 Shard: shard, 332 TabletType: tabletType, 333 } 334 item.ts.Serving = serving 335 item.ts.PrimaryTermStartTime = reparentTS 336 item.ts.Stats = &querypb.RealtimeStats{} 337 item.ts.LastError = err 338 conn := connFactory(t) 339 item.ts.Conn = conn 340 341 return conn 342 } 343 344 // AddTestTablet adds a fake tablet for tests using the SandboxConn and returns 345 // the fake connection 346 func (fhc *FakeHealthCheck) AddTestTablet(cell, host string, port int32, keyspace, shard string, tabletType topodatapb.TabletType, serving bool, reparentTS int64, err error) *sandboxconn.SandboxConn { 347 conn := fhc.AddFakeTablet(cell, host, port, keyspace, shard, tabletType, serving, reparentTS, err, func(tablet *topodatapb.Tablet) queryservice.QueryService { 348 return sandboxconn.NewSandboxConn(tablet) 349 }) 350 return conn.(*sandboxconn.SandboxConn) 351 } 352 353 // GetAllTablets returns all the tablets we have. 354 func (fhc *FakeHealthCheck) GetAllTablets() map[string]*topodatapb.Tablet { 355 res := make(map[string]*topodatapb.Tablet) 356 fhc.mu.RLock() 357 defer fhc.mu.RUnlock() 358 for key, t := range fhc.items { 359 res[key] = t.ts.Tablet 360 } 361 return res 362 } 363 364 func simpleCopy(th *TabletHealth) *TabletHealth { 365 return &TabletHealth{ 366 Conn: th.Conn, 367 Tablet: proto.Clone(th.Tablet).(*topodatapb.Tablet), 368 Target: proto.Clone(th.Target).(*querypb.Target), 369 Stats: proto.Clone(th.Stats).(*querypb.RealtimeStats), 370 LastError: th.LastError, 371 PrimaryTermStartTime: th.PrimaryTermStartTime, 372 Serving: th.Serving, 373 } 374 }