github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/clusterversion/clusterversion.go (about)

     1  // Copyright 2020 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  // Package clusterversion defines the interfaces to interact with cluster/binary
    12  // versions in order accommodate backward incompatible behaviors. It handles the
    13  // feature gates and so must maintain a fairly lightweight set of dependencies.
    14  // The migration sub-package will handle advancing a cluster from one version to
    15  // a later one.
    16  //
    17  // Ideally, every code change in a database would be backward compatible, but
    18  // this is not always possible. Some features, fixes, or cleanups need to
    19  // introduce a backward incompatibility and others are dramatically simplified by
    20  // it. This package provides a way to do this safely with (hopefully) minimal
    21  // disruption. It works as follows:
    22  //
    23  // - Each node in the cluster is running a binary that was released at some
    24  //   version ("binary version"). We allow for rolling upgrades, so two nodes in
    25  //   the cluster may be running different binary versions. All nodes in a given
    26  //   cluster must be within 1 major release of each other (i.e. to upgrade two
    27  //   major releases, the cluster must first be rolled onto X+1 and then to X+2).
    28  // - Separate from the build versions of the binaries, the cluster itself has a
    29  //   logical "active cluster version", the version all the binaries are
    30  //   currently operating at. This is used for two related things: first as a
    31  //   promise from the user that they'll never downgrade any nodes in the cluster
    32  //   to a binary below some "minimum supported version", and second, to unlock
    33  //   features that are not backwards compatible (which is now safe given that
    34  //   the old binary will never be used).
    35  // - Each binary can operate within a "range of supported versions". When a
    36  // 	 cluster is initialized, the binary doing the initialization uses the upper
    37  //	 end of its supported range as the initial "active cluster version". Each
    38  //	 node that joins this cluster then must be compatible with this cluster
    39  //	 version.
    40  package clusterversion
    41  
    42  import (
    43  	"context"
    44  
    45  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    46  	"github.com/cockroachdb/cockroach/pkg/settings"
    47  	"github.com/cockroachdb/cockroach/pkg/util/syncutil"
    48  )
    49  
    50  // TODO(irfansharif): Should Initialize and SetBeforeChange be a part of the
    51  // Handle interface? For SetBeforeChange at least, the callback is captured in
    52  // the Handle implementation. Given that Initialize uses recorded state from a
    53  // settings.Values (which is also a part of the Handle implementation), it seems
    54  // appropriate. On the other hand, Handle.Initialize does not make it
    55  // sufficiently clear that what's being initialized is the global cluster
    56  // version setting, despite being done through a stand alone Handle.
    57  
    58  // Initialize initializes the global cluster version. Before this method has
    59  // been called, usage of the cluster version (through Handle) is illegal and
    60  // leads to a fatal error.
    61  func Initialize(ctx context.Context, ver roachpb.Version, sv *settings.Values) error {
    62  	return version.initialize(ctx, ver, sv)
    63  }
    64  
    65  // SetBeforeChange registers a callback to be called before the global cluster
    66  // version is updated. The new cluster version will only become "visible" after
    67  // the callback has returned.
    68  //
    69  // The callback can be set at most once.
    70  func SetBeforeChange(
    71  	ctx context.Context, sv *settings.Values, cb func(context.Context, ClusterVersion),
    72  ) {
    73  	version.setBeforeChange(ctx, cb, sv)
    74  }
    75  
    76  // Handle is a read-only view to the active cluster version and this binary's
    77  // version details.
    78  type Handle interface {
    79  	// ActiveVersion returns the cluster's current active version: the minimum
    80  	// cluster version the caller may assume is in effect.
    81  	//
    82  	// ActiveVersion fatals if the cluster version setting has not been
    83  	// initialized (through `Initialize()`).
    84  	ActiveVersion(context.Context) ClusterVersion
    85  
    86  	// ActiveVersionOrEmpty is like ActiveVersion, but returns an empty version
    87  	// if the active version was not initialized.
    88  	ActiveVersionOrEmpty(context.Context) ClusterVersion
    89  
    90  	// IsActive returns true if the features of the supplied version key are
    91  	// active at the running version. In other words, if a particular version
    92  	// `v` returns true from this method, it means that you're guaranteed that
    93  	// all of the nodes in the cluster have running binaries that are at least
    94  	// as new as `v`, and that those nodes will never be downgraded to a binary
    95  	// with a version less than `v`.
    96  	//
    97  	// If this returns true then all nodes in the cluster will eventually see
    98  	// this version. However, this is not atomic because versions are gossiped.
    99  	// Because of this, nodes should not be gating proper handling of remotely
   100  	// initiated requests that their binary knows how to handle on this state.
   101  	// The following example shows why this is important:
   102  	//
   103  	//  The cluster restarts into the new version and the operator issues a SET
   104  	//  VERSION, but node1 learns of the bump 10 seconds before node2, so during
   105  	//  that window node1 might be receiving "old" requests that it itself
   106  	//  wouldn't issue any more. Similarly, node2 might be receiving "new"
   107  	//  requests that its binary must necessarily be able to handle (because the
   108  	//  SET VERSION was successful) but that it itself wouldn't issue yet.
   109  	//
   110  	// This is still a useful method to have as node1, in the example above, can
   111  	// use this information to know when it's safe to start issuing "new"
   112  	// outbound requests. When receiving these "new" inbound requests, despite
   113  	// not seeing the latest active version, node2 is aware that the sending
   114  	// node has, and it will too, eventually.
   115  	IsActive(context.Context, VersionKey) bool
   116  
   117  	// BinaryVersion returns the build version of this binary.
   118  	BinaryVersion() roachpb.Version
   119  
   120  	// BinaryMinSupportedVersion returns the earliest binary version that can
   121  	// interoperate with this binary.
   122  	BinaryMinSupportedVersion() roachpb.Version
   123  }
   124  
   125  // handleImpl is a concrete implementation of Handle. It mostly relegates to the
   126  // underlying cluster version setting, though provides a way for callers to
   127  // override the binary and minimum supported versions. It also stores the
   128  // callback that can be attached on cluster version change.
   129  type handleImpl struct {
   130  	// sv captures the mutable state associated with usage of the otherwise
   131  	// immutable cluster version setting.
   132  	sv *settings.Values
   133  
   134  	// Each handler stores its own view of the binary and minimum supported
   135  	// version. Tests can use `MakeVersionHandleWithOverride` to specify
   136  	// versions other than the baked in ones, but by default
   137  	// (`MakeVersionHandle`) they are initialized with this binary's build
   138  	// and minimum supported versions.
   139  	binaryVersion             roachpb.Version
   140  	binaryMinSupportedVersion roachpb.Version
   141  
   142  	// beforeClusterVersionChangeMu captures the callback that can be attached
   143  	// to the cluster version setting via SetBeforeChange.
   144  	beforeClusterVersionChangeMu struct {
   145  		syncutil.Mutex
   146  		// Callback to be called when the cluster version is about to be updated.
   147  		cb func(ctx context.Context, newVersion ClusterVersion)
   148  	}
   149  }
   150  
   151  var _ Handle = (*handleImpl)(nil)
   152  
   153  // MakeVersionHandle returns a Handle that has its binary and minimum
   154  // supported versions initialized to this binary's build and it's minimum
   155  // supported versions respectively.
   156  func MakeVersionHandle(sv *settings.Values) Handle {
   157  	return MakeVersionHandleWithOverride(sv, binaryVersion, binaryMinSupportedVersion)
   158  }
   159  
   160  // MakeVersionHandleWithOverride returns a Handle that has its
   161  // binary and minimum supported versions initialized to the provided versions.
   162  //
   163  // It's typically used in tests that want to override the default binary and
   164  // minimum supported versions.
   165  func MakeVersionHandleWithOverride(
   166  	sv *settings.Values, binaryVersion, binaryMinSupportedVersion roachpb.Version,
   167  ) Handle {
   168  	return &handleImpl{
   169  		sv: sv,
   170  
   171  		binaryVersion:             binaryVersion,
   172  		binaryMinSupportedVersion: binaryMinSupportedVersion,
   173  	}
   174  }
   175  func (v *handleImpl) ActiveVersion(ctx context.Context) ClusterVersion {
   176  	return version.activeVersion(ctx, v.sv)
   177  }
   178  
   179  func (v *handleImpl) ActiveVersionOrEmpty(ctx context.Context) ClusterVersion {
   180  	return version.activeVersionOrEmpty(ctx, v.sv)
   181  }
   182  
   183  func (v *handleImpl) IsActive(ctx context.Context, key VersionKey) bool {
   184  	return version.isActive(ctx, v.sv, key)
   185  }
   186  
   187  func (v *handleImpl) BinaryVersion() roachpb.Version {
   188  	return v.binaryVersion
   189  }
   190  
   191  func (v *handleImpl) BinaryMinSupportedVersion() roachpb.Version {
   192  	return v.binaryMinSupportedVersion
   193  }
   194  
   195  // IsActiveVersion returns true if the features of the supplied version are
   196  // active at the running version.
   197  func (cv ClusterVersion) IsActiveVersion(v roachpb.Version) bool {
   198  	return !cv.Less(v)
   199  }
   200  
   201  // IsActive returns true if the features of the supplied version are active at
   202  // the running version.
   203  func (cv ClusterVersion) IsActive(versionKey VersionKey) bool {
   204  	v := VersionByKey(versionKey)
   205  	return cv.IsActiveVersion(v)
   206  }
   207  
   208  func (cv ClusterVersion) String() string {
   209  	return cv.Version.String()
   210  }