vitess.io/vitess@v0.16.2/go/vt/topo/cell_info.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  	"context"
    21  	"path"
    22  	"strings"
    23  
    24  	"google.golang.org/protobuf/proto"
    25  	"k8s.io/apimachinery/pkg/util/sets"
    26  
    27  	"vitess.io/vitess/go/vt/vterrors"
    28  
    29  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    30  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    31  )
    32  
    33  // This file provides the utility methods to save / retrieve CellInfo
    34  // in the topology server.
    35  //
    36  // CellInfo records are not meant to be changed while the system is
    37  // running.  In a running system, a CellInfo can be added, and
    38  // topology server implementations should be able to read them to
    39  // access the cells upon demand. Topology server implementations can
    40  // also read the available CellInfo at startup to build a list of
    41  // available cells, if necessary. A CellInfo can only be removed if no
    42  // Shard record references the corresponding cell in its Cells list.
    43  
    44  func pathForCellInfo(cell string) string {
    45  	return path.Join(CellsPath, cell, CellInfoFile)
    46  }
    47  
    48  // GetCellInfoNames returns the names of the existing cells. They are
    49  // sorted by name.
    50  func (ts *Server) GetCellInfoNames(ctx context.Context) ([]string, error) {
    51  	entries, err := ts.globalCell.ListDir(ctx, CellsPath, false /*full*/)
    52  	switch {
    53  	case IsErrType(err, NoNode):
    54  		return nil, nil
    55  	case err == nil:
    56  		return DirEntriesToStringArray(entries), nil
    57  	default:
    58  		return nil, err
    59  	}
    60  }
    61  
    62  // GetCellInfo reads a CellInfo from the global Conn.
    63  func (ts *Server) GetCellInfo(ctx context.Context, cell string, strongRead bool) (*topodatapb.CellInfo, error) {
    64  	conn := ts.globalCell
    65  	if !strongRead {
    66  		conn = ts.globalReadOnlyCell
    67  	}
    68  	// Read the file.
    69  	filePath := pathForCellInfo(cell)
    70  	contents, _, err := conn.Get(ctx, filePath)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  
    75  	// Unpack the contents.
    76  	ci := &topodatapb.CellInfo{}
    77  	if err := proto.Unmarshal(contents, ci); err != nil {
    78  		return nil, err
    79  	}
    80  	return ci, nil
    81  }
    82  
    83  // CreateCellInfo creates a new CellInfo with the provided content.
    84  func (ts *Server) CreateCellInfo(ctx context.Context, cell string, ci *topodatapb.CellInfo) error {
    85  	// Pack the content.
    86  	contents, err := proto.Marshal(ci)
    87  	if err != nil {
    88  		return err
    89  	}
    90  
    91  	// Save it.
    92  	filePath := pathForCellInfo(cell)
    93  	_, err = ts.globalCell.Create(ctx, filePath, contents)
    94  	return err
    95  }
    96  
    97  // UpdateCellInfoFields is a high level helper method to read a CellInfo
    98  // object, update its fields, and then write it back. If the write fails due to
    99  // a version mismatch, it will re-read the record and retry the update.
   100  // If the update method returns ErrNoUpdateNeeded, nothing is written,
   101  // and nil is returned.
   102  func (ts *Server) UpdateCellInfoFields(ctx context.Context, cell string, update func(*topodatapb.CellInfo) error) error {
   103  	filePath := pathForCellInfo(cell)
   104  	for {
   105  		ci := &topodatapb.CellInfo{}
   106  
   107  		// Read the file, unpack the contents.
   108  		contents, version, err := ts.globalCell.Get(ctx, filePath)
   109  		switch {
   110  		case err == nil:
   111  			if err := proto.Unmarshal(contents, ci); err != nil {
   112  				return err
   113  			}
   114  		case IsErrType(err, NoNode):
   115  			// Nothing to do.
   116  		default:
   117  			return err
   118  		}
   119  
   120  		// Call update method.
   121  		if err = update(ci); err != nil {
   122  			if IsErrType(err, NoUpdateNeeded) {
   123  				return nil
   124  			}
   125  			return err
   126  		}
   127  
   128  		// Pack and save.
   129  		contents, err = proto.Marshal(ci)
   130  		if err != nil {
   131  			return err
   132  		}
   133  		if _, err = ts.globalCell.Update(ctx, filePath, contents, version); !IsErrType(err, BadVersion) {
   134  			// This includes the 'err=nil' case.
   135  			return err
   136  		}
   137  	}
   138  }
   139  
   140  // DeleteCellInfo deletes the specified CellInfo.
   141  // We first try to make sure no Shard record points to the cell,
   142  // but we'll continue regardless if 'force' is true.
   143  func (ts *Server) DeleteCellInfo(ctx context.Context, cell string, force bool) error {
   144  	srvKeyspaces, err := ts.GetSrvKeyspaceNames(ctx, cell)
   145  	switch {
   146  	case err == nil:
   147  		if len(srvKeyspaces) != 0 && !force {
   148  			return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "cell %v has serving keyspaces. Before deleting, delete keyspace with DeleteKeyspace, or use -force to continue anyway.", cell)
   149  		}
   150  	case IsErrType(err, NoNode):
   151  		// Nothing to do.
   152  	default:
   153  		if !force {
   154  			return vterrors.Wrap(err, "can't list SrvKeyspace entries in the cell; use -force flag to continue anyway (e.g. if cell-local topo was already permanently shut down)")
   155  		}
   156  
   157  		select {
   158  		case <-ctx.Done():
   159  			// If our context has expired and we got an error back from
   160  			// GetSrvKeyspaceNames, we assume that call failed because the
   161  			// local cell topo was down. If force=true, then we make a new
   162  			// background context to cleanup from the global topo. Otherwise a
   163  			// local-down-topo scenario would mean we never can delete it.
   164  			// (see https://github.com/vitessio/vitess/issues/8220).
   165  			var cancel context.CancelFunc
   166  			ctx, cancel = context.WithTimeout(context.Background(), RemoteOperationTimeout)
   167  			defer cancel()
   168  		default:
   169  			// Context still has some time left, no need to make a new one.
   170  		}
   171  	}
   172  
   173  	filePath := pathForCellInfo(cell)
   174  	return ts.globalCell.Delete(ctx, filePath, nil)
   175  }
   176  
   177  // GetKnownCells returns the list of known cells.
   178  // For now, it just lists the 'cells' directory in the global topology server.
   179  // TODO(alainjobart) once the cell map is migrated to this generic
   180  // package, we can do better than this.
   181  func (ts *Server) GetKnownCells(ctx context.Context) ([]string, error) {
   182  	// Note we use the global read-only cell here, as the result
   183  	// is not time sensitive.
   184  	entries, err := ts.globalReadOnlyCell.ListDir(ctx, CellsPath, false /*full*/)
   185  	if err != nil {
   186  		return nil, err
   187  	}
   188  	return DirEntriesToStringArray(entries), nil
   189  }
   190  
   191  // ExpandCells takes a comma-separated list of cells and returns an array of cell names
   192  // Aliases are expanded and an empty string returns all cells
   193  func (ts *Server) ExpandCells(ctx context.Context, cells string) ([]string, error) {
   194  	var (
   195  		err         error
   196  		inputCells  []string
   197  		outputCells = sets.New[string]() // Use a set to dedupe if the input cells list includes an alias and a cell in that alias.
   198  	)
   199  
   200  	if cells == "" {
   201  		inputCells, err = ts.GetCellInfoNames(ctx)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  	} else {
   206  		inputCells = strings.Split(cells, ",")
   207  	}
   208  
   209  	expandCell := func(ctx context.Context, cell string) error {
   210  		shortCtx, cancel := context.WithTimeout(ctx, RemoteOperationTimeout)
   211  		defer cancel()
   212  
   213  		_, err := ts.GetCellInfo(shortCtx, cell, false /* strongRead */)
   214  		if err != nil {
   215  			// Not a valid cell name. Check whether it is an alias.
   216  			shortCtx, cancel := context.WithTimeout(ctx, RemoteOperationTimeout)
   217  			defer cancel()
   218  
   219  			alias, err2 := ts.GetCellsAlias(shortCtx, cell, false /* strongRead */)
   220  			if err2 != nil {
   221  				return err // return the original err to indicate the cell does not exist
   222  			}
   223  
   224  			// Expand the alias cells list into the final set.
   225  			outputCells.Insert(alias.Cells...)
   226  			return nil
   227  		}
   228  
   229  		// Valid cell.
   230  		outputCells.Insert(cell)
   231  		return nil
   232  	}
   233  
   234  	for _, cell := range inputCells {
   235  		cell2 := strings.TrimSpace(cell)
   236  		if err := expandCell(ctx, cell2); err != nil {
   237  			return nil, err
   238  		}
   239  	}
   240  
   241  	return sets.List(outputCells), nil
   242  }