vitess.io/vitess@v0.16.2/go/vt/topo/keyspace.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 topo
    18  
    19  import (
    20  	"path"
    21  	"strings"
    22  
    23  	"google.golang.org/protobuf/proto"
    24  
    25  	"context"
    26  
    27  	"vitess.io/vitess/go/vt/vterrors"
    28  
    29  	"vitess.io/vitess/go/event"
    30  	"vitess.io/vitess/go/vt/log"
    31  	"vitess.io/vitess/go/vt/topo/events"
    32  
    33  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    34  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    35  )
    36  
    37  // This file contains keyspace utility functions
    38  
    39  // KeyspaceInfo is a meta struct that contains metadata to give the
    40  // data more context and convenience. This is the main way we interact
    41  // with a keyspace.
    42  type KeyspaceInfo struct {
    43  	keyspace string
    44  	version  Version
    45  	*topodatapb.Keyspace
    46  }
    47  
    48  // KeyspaceName returns the keyspace name
    49  func (ki *KeyspaceInfo) KeyspaceName() string {
    50  	return ki.keyspace
    51  }
    52  
    53  // SetKeyspaceName sets the keyspace name
    54  func (ki *KeyspaceInfo) SetKeyspaceName(name string) {
    55  	ki.keyspace = name
    56  }
    57  
    58  var invalidKeyspaceNameChars = "/"
    59  
    60  // ValidateKeyspaceName checks if the provided name is a valid name for a
    61  // keyspace.
    62  //
    63  // As of v16.0.1, "all invalid characters" is just the forward slash ("/").
    64  func ValidateKeyspaceName(name string) error {
    65  	if strings.ContainsAny(name, invalidKeyspaceNameChars) {
    66  		return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "keyspace name %s contains invalid characters; may not contain any of the following: %+v", name, strings.Split(invalidKeyspaceNameChars, ""))
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  // GetServedFrom returns a Keyspace_ServedFrom record if it exists.
    73  func (ki *KeyspaceInfo) GetServedFrom(tabletType topodatapb.TabletType) *topodatapb.Keyspace_ServedFrom {
    74  	for _, ksf := range ki.ServedFroms {
    75  		if ksf.TabletType == tabletType {
    76  			return ksf
    77  		}
    78  	}
    79  	return nil
    80  }
    81  
    82  // CheckServedFromMigration makes sure a requested migration is safe
    83  func (ki *KeyspaceInfo) CheckServedFromMigration(tabletType topodatapb.TabletType, cells []string, keyspace string, remove bool) error {
    84  	// primary is a special case with a few extra checks
    85  	if tabletType == topodatapb.TabletType_PRIMARY {
    86  		// TODO(deepthi): these master references will go away when we delete legacy resharding
    87  		if !remove {
    88  			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot add master back to %v", ki.keyspace)
    89  		}
    90  		if len(cells) > 0 {
    91  			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot migrate only some cells for master removal in keyspace %v", ki.keyspace)
    92  		}
    93  		if len(ki.ServedFroms) > 1 {
    94  			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot migrate master into %v until everything else is migrated", ki.keyspace)
    95  		}
    96  	}
    97  
    98  	// we can't remove a type we don't have
    99  	if ki.GetServedFrom(tabletType) == nil && remove {
   100  		return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "supplied type cannot be migrated")
   101  	}
   102  
   103  	// check the keyspace is consistent in any case
   104  	for _, ksf := range ki.ServedFroms {
   105  		if ksf.Keyspace != keyspace {
   106  			return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "inconsistent keyspace specified in migration: %v != %v for type %v", keyspace, ksf.Keyspace, ksf.TabletType)
   107  		}
   108  	}
   109  
   110  	return nil
   111  }
   112  
   113  // UpdateServedFromMap handles ServedFromMap. It can add or remove
   114  // records, cells, ...
   115  func (ki *KeyspaceInfo) UpdateServedFromMap(tabletType topodatapb.TabletType, cells []string, keyspace string, remove bool, allCells []string) error {
   116  	// check parameters to be sure
   117  	if err := ki.CheckServedFromMigration(tabletType, cells, keyspace, remove); err != nil {
   118  		return err
   119  	}
   120  
   121  	ksf := ki.GetServedFrom(tabletType)
   122  	if ksf == nil {
   123  		// the record doesn't exist
   124  		if remove {
   125  			if len(ki.ServedFroms) == 0 {
   126  				ki.ServedFroms = nil
   127  			}
   128  			log.Warningf("Trying to remove KeyspaceServedFrom for missing type %v in keyspace %v", tabletType, ki.keyspace)
   129  		} else {
   130  			ki.ServedFroms = append(ki.ServedFroms, &topodatapb.Keyspace_ServedFrom{
   131  				TabletType: tabletType,
   132  				Cells:      cells,
   133  				Keyspace:   keyspace,
   134  			})
   135  		}
   136  		return nil
   137  	}
   138  
   139  	if remove {
   140  		result, emptyList := removeCells(ksf.Cells, cells, allCells)
   141  		if emptyList {
   142  			// we don't have any cell left, we need to clear this record
   143  			var newServedFroms []*topodatapb.Keyspace_ServedFrom
   144  			for _, k := range ki.ServedFroms {
   145  				if k != ksf {
   146  					newServedFroms = append(newServedFroms, k)
   147  				}
   148  			}
   149  			ki.ServedFroms = newServedFroms
   150  		} else {
   151  			ksf.Cells = result
   152  		}
   153  	} else {
   154  		if ksf.Keyspace != keyspace {
   155  			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cannot UpdateServedFromMap on existing record for keyspace %v, different keyspace: %v != %v", ki.keyspace, ksf.Keyspace, keyspace)
   156  		}
   157  		ksf.Cells = addCells(ksf.Cells, cells)
   158  	}
   159  	return nil
   160  }
   161  
   162  // ComputeCellServedFrom returns the ServedFrom list for a cell
   163  func (ki *KeyspaceInfo) ComputeCellServedFrom(cell string) []*topodatapb.SrvKeyspace_ServedFrom {
   164  	var result []*topodatapb.SrvKeyspace_ServedFrom
   165  	for _, ksf := range ki.ServedFroms {
   166  		if InCellList(cell, ksf.Cells) {
   167  			result = append(result, &topodatapb.SrvKeyspace_ServedFrom{
   168  				TabletType: ksf.TabletType,
   169  				Keyspace:   ksf.Keyspace,
   170  			})
   171  		}
   172  	}
   173  	return result
   174  }
   175  
   176  // CreateKeyspace wraps the underlying Conn.Create
   177  // and dispatches the event.
   178  func (ts *Server) CreateKeyspace(ctx context.Context, keyspace string, value *topodatapb.Keyspace) error {
   179  	if err := ValidateKeyspaceName(keyspace); err != nil {
   180  		return vterrors.Wrapf(err, "CreateKeyspace: %s", err)
   181  	}
   182  
   183  	data, err := proto.Marshal(value)
   184  	if err != nil {
   185  		return err
   186  	}
   187  
   188  	keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile)
   189  	if _, err := ts.globalCell.Create(ctx, keyspacePath, data); err != nil {
   190  		return err
   191  	}
   192  
   193  	event.Dispatch(&events.KeyspaceChange{
   194  		KeyspaceName: keyspace,
   195  		Keyspace:     value,
   196  		Status:       "created",
   197  	})
   198  	return nil
   199  }
   200  
   201  // GetKeyspace reads the given keyspace and returns it
   202  func (ts *Server) GetKeyspace(ctx context.Context, keyspace string) (*KeyspaceInfo, error) {
   203  	if err := ValidateKeyspaceName(keyspace); err != nil {
   204  		return nil, vterrors.Wrapf(err, "GetKeyspace: %s", err)
   205  	}
   206  
   207  	keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile)
   208  	data, version, err := ts.globalCell.Get(ctx, keyspacePath)
   209  	if err != nil {
   210  		return nil, err
   211  	}
   212  
   213  	k := &topodatapb.Keyspace{}
   214  	if err = proto.Unmarshal(data, k); err != nil {
   215  		return nil, vterrors.Wrap(err, "bad keyspace data")
   216  	}
   217  
   218  	return &KeyspaceInfo{
   219  		keyspace: keyspace,
   220  		version:  version,
   221  		Keyspace: k,
   222  	}, nil
   223  }
   224  
   225  // GetKeyspaceDurability reads the given keyspace and returns its durabilty policy
   226  func (ts *Server) GetKeyspaceDurability(ctx context.Context, keyspace string) (string, error) {
   227  	keyspaceInfo, err := ts.GetKeyspace(ctx, keyspace)
   228  	if err != nil {
   229  		return "", err
   230  	}
   231  	// Get the durability policy from the keyspace information
   232  	// If it is unspecified, use the default durability which is "none" for backward compatibility
   233  	if keyspaceInfo.GetDurabilityPolicy() != "" {
   234  		return keyspaceInfo.GetDurabilityPolicy(), nil
   235  	}
   236  	return "none", nil
   237  }
   238  
   239  func (ts *Server) GetThrottlerConfig(ctx context.Context, keyspace string) (*topodatapb.ThrottlerConfig, error) {
   240  	keyspaceInfo, err := ts.GetKeyspace(ctx, keyspace)
   241  	if err != nil {
   242  		return nil, err
   243  	}
   244  	return keyspaceInfo.ThrottlerConfig, nil
   245  }
   246  
   247  // UpdateKeyspace updates the keyspace data. It checks the keyspace is locked.
   248  func (ts *Server) UpdateKeyspace(ctx context.Context, ki *KeyspaceInfo) error {
   249  	// make sure it is locked first
   250  	if err := CheckKeyspaceLocked(ctx, ki.keyspace); err != nil {
   251  		return err
   252  	}
   253  
   254  	data, err := proto.Marshal(ki.Keyspace)
   255  	if err != nil {
   256  		return err
   257  	}
   258  	keyspacePath := path.Join(KeyspacesPath, ki.keyspace, KeyspaceFile)
   259  	version, err := ts.globalCell.Update(ctx, keyspacePath, data, ki.version)
   260  	if err != nil {
   261  		return err
   262  	}
   263  	ki.version = version
   264  
   265  	event.Dispatch(&events.KeyspaceChange{
   266  		KeyspaceName: ki.keyspace,
   267  		Keyspace:     ki.Keyspace,
   268  		Status:       "updated",
   269  	})
   270  	return nil
   271  }
   272  
   273  // FindAllShardsInKeyspace reads and returns all the existing shards in
   274  // a keyspace. It doesn't take any lock.
   275  func (ts *Server) FindAllShardsInKeyspace(ctx context.Context, keyspace string) (map[string]*ShardInfo, error) {
   276  	shards, err := ts.GetShardNames(ctx, keyspace)
   277  	if err != nil {
   278  		return nil, vterrors.Wrapf(err, "failed to get list of shards for keyspace '%v'", keyspace)
   279  	}
   280  
   281  	result := make(map[string]*ShardInfo, len(shards))
   282  	for _, shard := range shards {
   283  		si, err := ts.GetShard(ctx, keyspace, shard)
   284  		if err != nil {
   285  			if IsErrType(err, NoNode) {
   286  				log.Warningf("GetShard(%v, %v) returned ErrNoNode, consider checking the topology.", keyspace, shard)
   287  			} else {
   288  				return nil, vterrors.Wrapf(err, "GetShard(%v, %v) failed", keyspace, shard)
   289  			}
   290  		}
   291  		result[shard] = si
   292  	}
   293  	return result, nil
   294  }
   295  
   296  // GetServingShards returns all shards where the primary is serving.
   297  func (ts *Server) GetServingShards(ctx context.Context, keyspace string) ([]*ShardInfo, error) {
   298  	shards, err := ts.GetShardNames(ctx, keyspace)
   299  	if err != nil {
   300  		return nil, vterrors.Wrapf(err, "failed to get list of shards for keyspace '%v'", keyspace)
   301  	}
   302  
   303  	result := make([]*ShardInfo, 0, len(shards))
   304  	for _, shard := range shards {
   305  		si, err := ts.GetShard(ctx, keyspace, shard)
   306  		if err != nil {
   307  			return nil, vterrors.Wrapf(err, "GetShard(%v, %v) failed", keyspace, shard)
   308  		}
   309  		if !si.IsPrimaryServing {
   310  			continue
   311  		}
   312  		result = append(result, si)
   313  	}
   314  	if len(result) == 0 {
   315  		return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "%v has no serving shards", keyspace)
   316  	}
   317  	return result, nil
   318  }
   319  
   320  // GetOnlyShard returns the single ShardInfo of an unsharded keyspace.
   321  func (ts *Server) GetOnlyShard(ctx context.Context, keyspace string) (*ShardInfo, error) {
   322  	allShards, err := ts.FindAllShardsInKeyspace(ctx, keyspace)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	if len(allShards) == 1 {
   327  		for _, s := range allShards {
   328  			return s, nil
   329  		}
   330  	}
   331  	return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "keyspace %s must have one and only one shard: %v", keyspace, allShards)
   332  }
   333  
   334  // DeleteKeyspace wraps the underlying Conn.Delete
   335  // and dispatches the event.
   336  func (ts *Server) DeleteKeyspace(ctx context.Context, keyspace string) error {
   337  	keyspacePath := path.Join(KeyspacesPath, keyspace, KeyspaceFile)
   338  	if err := ts.globalCell.Delete(ctx, keyspacePath, nil); err != nil {
   339  		return err
   340  	}
   341  
   342  	// Delete the cell-global VSchema path
   343  	// If not remove this, vtctld web page Dashboard will Display Error
   344  	if err := ts.DeleteVSchema(ctx, keyspace); err != nil && !IsErrType(err, NoNode) {
   345  		return err
   346  	}
   347  
   348  	event.Dispatch(&events.KeyspaceChange{
   349  		KeyspaceName: keyspace,
   350  		Keyspace:     nil,
   351  		Status:       "deleted",
   352  	})
   353  	return nil
   354  }
   355  
   356  // GetKeyspaces returns the list of keyspaces in the topology.
   357  func (ts *Server) GetKeyspaces(ctx context.Context) ([]string, error) {
   358  	children, err := ts.globalCell.ListDir(ctx, KeyspacesPath, false /*full*/)
   359  	switch {
   360  	case err == nil:
   361  		return DirEntriesToStringArray(children), nil
   362  	case IsErrType(err, NoNode):
   363  		return nil, nil
   364  	default:
   365  		return nil, err
   366  	}
   367  }
   368  
   369  // GetShardNames returns the list of shards in a keyspace.
   370  func (ts *Server) GetShardNames(ctx context.Context, keyspace string) ([]string, error) {
   371  	shardsPath := path.Join(KeyspacesPath, keyspace, ShardsPath)
   372  	children, err := ts.globalCell.ListDir(ctx, shardsPath, false /*full*/)
   373  	if IsErrType(err, NoNode) {
   374  		// The directory doesn't exist, let's see if the keyspace
   375  		// is here or not.
   376  		_, kerr := ts.GetKeyspace(ctx, keyspace)
   377  		if kerr == nil {
   378  			// Keyspace is here, means no shards.
   379  			return nil, nil
   380  		}
   381  		return nil, err
   382  	}
   383  	return DirEntriesToStringArray(children), err
   384  }