vitess.io/vitess@v0.16.2/go/vt/topo/cells_aliases.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  	"fmt"
    21  	"path"
    22  
    23  	"google.golang.org/protobuf/proto"
    24  
    25  	"context"
    26  
    27  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    28  )
    29  
    30  // This file provides the utility methods to save / retrieve CellsAliases
    31  // in the topology server.
    32  //
    33  // CellsAliases records are not meant to be changed while the system is
    34  // running.  In a running system, a CellsAlias can be added, and
    35  // topology server implementations should be able to read them to
    36  // access the cells upon demand.
    37  
    38  func pathForCellsAlias(alias string) string {
    39  	return path.Join(CellsAliasesPath, alias, CellsAliasFile)
    40  }
    41  
    42  // GetCellsAliases returns the names of the existing cells. They are
    43  // sorted by name.
    44  func (ts *Server) GetCellsAliases(ctx context.Context, strongRead bool) (ret map[string]*topodatapb.CellsAlias, err error) {
    45  	conn := ts.globalCell
    46  	if !strongRead {
    47  		conn = ts.globalReadOnlyCell
    48  	}
    49  
    50  	entries, err := ts.globalCell.ListDir(ctx, CellsAliasesPath, false /*full*/)
    51  	switch {
    52  	case IsErrType(err, NoNode):
    53  		return nil, nil
    54  	case err == nil:
    55  		aliases := DirEntriesToStringArray(entries)
    56  		ret = make(map[string]*topodatapb.CellsAlias, len(aliases))
    57  		for _, alias := range aliases {
    58  			aliasPath := pathForCellsAlias(alias)
    59  			contents, _, err := conn.Get(ctx, aliasPath)
    60  			if err != nil {
    61  				return nil, err
    62  			}
    63  
    64  			// Unpack the contents.
    65  			cellsAlias := &topodatapb.CellsAlias{}
    66  			if err := proto.Unmarshal(contents, cellsAlias); err != nil {
    67  				return nil, err
    68  			}
    69  
    70  			ret[alias] = cellsAlias
    71  		}
    72  		return ret, nil
    73  	default:
    74  		return nil, err
    75  	}
    76  }
    77  
    78  // GetCellsAlias returns the CellsAlias that matches the given name.
    79  func (ts *Server) GetCellsAlias(ctx context.Context, name string, strongRead bool) (*topodatapb.CellsAlias, error) {
    80  	conn := ts.globalCell
    81  	if !strongRead {
    82  		conn = ts.globalReadOnlyCell
    83  	}
    84  
    85  	aliasPath := pathForCellsAlias(name)
    86  	contents, _, err := conn.Get(ctx, aliasPath)
    87  	if err != nil {
    88  		return nil, err
    89  	}
    90  
    91  	// Unpack the contents.
    92  	cellsAlias := &topodatapb.CellsAlias{}
    93  	if err := proto.Unmarshal(contents, cellsAlias); err != nil {
    94  		return nil, err
    95  	}
    96  
    97  	return cellsAlias, nil
    98  }
    99  
   100  // DeleteCellsAlias deletes the specified CellsAlias
   101  func (ts *Server) DeleteCellsAlias(ctx context.Context, alias string) error {
   102  	ts.clearCellAliasesCache()
   103  
   104  	filePath := pathForCellsAlias(alias)
   105  	return ts.globalCell.Delete(ctx, filePath, nil)
   106  }
   107  
   108  // CreateCellsAlias creates a new CellInfo with the provided content.
   109  func (ts *Server) CreateCellsAlias(ctx context.Context, alias string, cellsAlias *topodatapb.CellsAlias) error {
   110  	currentAliases, err := ts.GetCellsAliases(ctx, true)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	if err := validateAlias(currentAliases, alias, cellsAlias); err != nil {
   116  		return fmt.Errorf("cells alias %v is not valid: %v", alias, err)
   117  	}
   118  
   119  	ts.clearCellAliasesCache()
   120  
   121  	// Pack the content.
   122  	contents, err := proto.Marshal(cellsAlias)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	// Save it.
   128  	filePath := pathForCellsAlias(alias)
   129  	_, err = ts.globalCell.Create(ctx, filePath, contents)
   130  	return err
   131  }
   132  
   133  // UpdateCellsAlias updates cells for a given alias
   134  func (ts *Server) UpdateCellsAlias(ctx context.Context, alias string, update func(*topodatapb.CellsAlias) error) error {
   135  	ts.clearCellAliasesCache()
   136  
   137  	filePath := pathForCellsAlias(alias)
   138  	for {
   139  		cellsAlias := &topodatapb.CellsAlias{}
   140  
   141  		// Read the file, unpack the contents.
   142  		contents, version, err := ts.globalCell.Get(ctx, filePath)
   143  		switch {
   144  		case err == nil:
   145  			if err := proto.Unmarshal(contents, cellsAlias); err != nil {
   146  				return err
   147  			}
   148  		case IsErrType(err, NoNode):
   149  			// Nothing to do.
   150  		default:
   151  			return err
   152  		}
   153  
   154  		// Call update method.
   155  		if err = update(cellsAlias); err != nil {
   156  			if IsErrType(err, NoUpdateNeeded) {
   157  				return nil
   158  			}
   159  			return err
   160  		}
   161  
   162  		currentAliases, err := ts.GetCellsAliases(ctx, true)
   163  		if err != nil {
   164  			return err
   165  		}
   166  
   167  		if err := validateAlias(currentAliases, alias, cellsAlias); err != nil {
   168  			return fmt.Errorf("cells alias %v is not valid: %v", alias, err)
   169  		}
   170  
   171  		// Pack and save.
   172  		contents, err = proto.Marshal(cellsAlias)
   173  		if err != nil {
   174  			return err
   175  		}
   176  		if _, err = ts.globalCell.Update(ctx, filePath, contents, version); !IsErrType(err, BadVersion) {
   177  			// This includes the 'err=nil' case.
   178  			return err
   179  		}
   180  	}
   181  }
   182  
   183  // validateAlias checks whether the given alias is allowed.
   184  // If the alias overlaps with any existing alias other than itself, this returns
   185  // a non-nil error.
   186  func validateAlias(currentAliases map[string]*topodatapb.CellsAlias, newAliasName string, newAlias *topodatapb.CellsAlias) error {
   187  	for name, alias := range currentAliases {
   188  		// Skip the alias we're checking against. It's allowed to overlap with itself.
   189  		if name == newAliasName {
   190  			continue
   191  		}
   192  
   193  		for _, cell := range alias.Cells {
   194  			if InCellList(cell, newAlias.Cells) {
   195  				return fmt.Errorf("cell set overlaps with existing alias %v", name)
   196  			}
   197  		}
   198  	}
   199  	return nil
   200  }