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  }