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

     1  // Copyright 2017 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
    12  
    13  import (
    14  	"context"
    15  
    16  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    17  	"github.com/cockroachdb/cockroach/pkg/settings"
    18  	"github.com/cockroachdb/cockroach/pkg/util/log"
    19  	"github.com/cockroachdb/cockroach/pkg/util/protoutil"
    20  	"github.com/cockroachdb/errors"
    21  )
    22  
    23  // KeyVersionSetting is the "version" settings key.
    24  const KeyVersionSetting = "version"
    25  
    26  // version represents the cluster's "active version". This is a cluster setting,
    27  // but a special one. It can only advance to higher and higher versions. The
    28  // setting can be used to see if migrations are to be considered enabled or
    29  // disabled through the isActive() method. All external usage of the cluster
    30  // settings takes place through a Handle and `Initialize()`/`SetBeforeChange()`.
    31  //
    32  // During the node startup sequence, an initial version (persisted to the
    33  // engines) is read and passed to `version.initialize`. It is only after that
    34  // that `version.{activeVersion,isActive} can be called. In turn, the node
    35  // usually registers itself as a callback to be notified of any further updates
    36  // to the setting, which are also persisted.
    37  //
    38  // This dance is necessary because we cannot determine a safe default value for
    39  // the version setting without looking at what's been persisted: The setting
    40  // specifies the minimum binary version we have to expect to be in a mixed
    41  // cluster with. We can't assume it is this binary's
    42  // binaryMinSupportedVersion as the cluster could've started up earlier and
    43  // enabled features that are no longer compatible it; we can't assume it's our
    44  // binaryVersion as that would enable features that may trip up older versions
    45  // running in the same cluster. Hence, only once we get word of the "safe"
    46  // version to use can we allow moving parts that actually need to know what's
    47  // going on.
    48  //
    49  // Additionally, whenever the version changes, we want to persist that update to
    50  // wherever the caller to initialize() got the initial version from
    51  // (typically a collection of `engine.Engine`s), which the caller will do by
    52  // registering itself via setBeforeChange()`, which is invoked *before* exposing
    53  // the new version to callers of `activeVersion()` and `isActive()`.
    54  var version = registerClusterVersionSetting()
    55  
    56  // clusterVersionSetting is the implementation of the 'version' setting. Like all
    57  // setting structs, it is immutable, as Version is a global; all the state is
    58  // maintained in a Handle instance.
    59  type clusterVersionSetting struct {
    60  	settings.StateMachineSetting
    61  }
    62  
    63  var _ settings.StateMachineSettingImpl = &clusterVersionSetting{}
    64  
    65  // registerClusterVersionSetting creates a clusterVersionSetting and registers
    66  // it with the cluster settings registry.
    67  func registerClusterVersionSetting() *clusterVersionSetting {
    68  	s := makeClusterVersionSetting()
    69  	s.StateMachineSetting.SetReportable(true)
    70  	settings.RegisterStateMachineSetting(
    71  		KeyVersionSetting,
    72  		"set the active cluster version in the format '<major>.<minor>'", // hide optional `-<unstable>,
    73  		&s.StateMachineSetting)
    74  	s.SetVisibility(settings.Public)
    75  	return s
    76  }
    77  
    78  func makeClusterVersionSetting() *clusterVersionSetting {
    79  	s := &clusterVersionSetting{}
    80  	s.StateMachineSetting = settings.MakeStateMachineSetting(s)
    81  	return s
    82  }
    83  
    84  // initialize initializes cluster version. Before this method has been called,
    85  // usage of the version is illegal and leads to a fatal error.
    86  func (cv *clusterVersionSetting) initialize(
    87  	ctx context.Context, version roachpb.Version, sv *settings.Values,
    88  ) error {
    89  	if ver := cv.activeVersionOrEmpty(ctx, sv); ver != (ClusterVersion{}) {
    90  		// Allow initializing a second time as long as it's not regressing.
    91  		//
    92  		// This is useful in tests that use MakeTestingClusterSettings() which
    93  		// initializes the version, and the start a server which again
    94  		// initializes it once more.
    95  		//
    96  		// It's also used in production code during bootstrap, where the version
    97  		// is first initialized to BinaryMinSupportedVersion and then
    98  		// re-initialized to BootstrapVersion (=BinaryVersion).
    99  		if version.Less(ver.Version) {
   100  			return errors.AssertionFailedf("cannot initialize version to %s because already set to: %s",
   101  				version, ver)
   102  		}
   103  		if version == ver.Version {
   104  			// Don't trigger callbacks, etc, a second time.
   105  			return nil
   106  		}
   107  		// Now version > ver.Version.
   108  	}
   109  	if err := cv.validateSupportedVersionInner(ctx, version, sv); err != nil {
   110  		return err
   111  	}
   112  
   113  	// Return the serialized form of the new version.
   114  	newV := ClusterVersion{Version: version}
   115  	encoded, err := protoutil.Marshal(&newV)
   116  	if err != nil {
   117  		return err
   118  	}
   119  	cv.SetInternal(sv, encoded)
   120  	return nil
   121  }
   122  
   123  // activeVersion returns the cluster's current active version: the minimum
   124  // cluster version the caller may assume is in effect.
   125  //
   126  // activeVersion fatals if the version has not been initialized.
   127  func (cv *clusterVersionSetting) activeVersion(
   128  	ctx context.Context, sv *settings.Values,
   129  ) ClusterVersion {
   130  	ver := cv.activeVersionOrEmpty(ctx, sv)
   131  	if ver == (ClusterVersion{}) {
   132  		log.Fatalf(ctx, "version not initialized")
   133  	}
   134  	return ver
   135  }
   136  
   137  // activeVersionOrEmpty is like activeVersion, but returns an empty version if
   138  // the active version was not initialized.
   139  func (cv *clusterVersionSetting) activeVersionOrEmpty(
   140  	ctx context.Context, sv *settings.Values,
   141  ) ClusterVersion {
   142  	encoded := cv.GetInternal(sv)
   143  	if encoded == nil {
   144  		return ClusterVersion{}
   145  	}
   146  	var curVer ClusterVersion
   147  	if err := protoutil.Unmarshal(encoded.([]byte), &curVer); err != nil {
   148  		log.Fatalf(ctx, "%v", err)
   149  	}
   150  	return curVer
   151  }
   152  
   153  // isActive returns true if the features of the supplied version key are active
   154  // at the running version. See comment on Handle.IsActive for intended usage.
   155  func (cv *clusterVersionSetting) isActive(
   156  	ctx context.Context, sv *settings.Values, versionKey VersionKey,
   157  ) bool {
   158  	return cv.activeVersion(ctx, sv).IsActive(versionKey)
   159  }
   160  
   161  // setBeforeChange registers a callback to be called before the cluster version
   162  // is updated. The new cluster version will only become "visible" after the
   163  // callback has returned.
   164  //
   165  // The callback can be set at most once.
   166  func (cv *clusterVersionSetting) setBeforeChange(
   167  	ctx context.Context, cb func(ctx context.Context, newVersion ClusterVersion), sv *settings.Values,
   168  ) {
   169  	vh := sv.Opaque().(Handle)
   170  	h := vh.(*handleImpl)
   171  	h.beforeClusterVersionChangeMu.Lock()
   172  	defer h.beforeClusterVersionChangeMu.Unlock()
   173  	if h.beforeClusterVersionChangeMu.cb != nil {
   174  		log.Fatalf(ctx, "beforeClusterVersionChange already set")
   175  	}
   176  	h.beforeClusterVersionChangeMu.cb = cb
   177  }
   178  
   179  // Decode is part of the StateMachineSettingImpl interface.
   180  func (cv *clusterVersionSetting) Decode(val []byte) (interface{}, error) {
   181  	var clusterVersion ClusterVersion
   182  	if err := protoutil.Unmarshal(val, &clusterVersion); err != nil {
   183  		return "", err
   184  	}
   185  	return clusterVersion, nil
   186  }
   187  
   188  // DecodeToString is part of the StateMachineSettingImpl interface.
   189  func (cv *clusterVersionSetting) DecodeToString(val []byte) (string, error) {
   190  	clusterVersion, err := cv.Decode(val)
   191  	if err != nil {
   192  		return "", err
   193  	}
   194  	return clusterVersion.(ClusterVersion).Version.String(), nil
   195  }
   196  
   197  // ValidateLogical is part of the StateMachineSettingImpl interface.
   198  func (cv *clusterVersionSetting) ValidateLogical(
   199  	ctx context.Context, sv *settings.Values, curRawProto []byte, newVal string,
   200  ) ([]byte, error) {
   201  	newVersion, err := roachpb.ParseVersion(newVal)
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  	if err := cv.validateSupportedVersionInner(ctx, newVersion, sv); err != nil {
   206  		return nil, err
   207  	}
   208  
   209  	var oldV ClusterVersion
   210  	if err := protoutil.Unmarshal(curRawProto, &oldV); err != nil {
   211  		return nil, err
   212  	}
   213  
   214  	// Versions cannot be downgraded.
   215  	if newVersion.Less(oldV.Version) {
   216  		return nil, errors.Errorf(
   217  			"versions cannot be downgraded (attempting to downgrade from %s to %s)",
   218  			oldV.Version, newVersion)
   219  	}
   220  
   221  	// Prevent cluster version upgrade until cluster.preserve_downgrade_option is reset.
   222  	if downgrade := preserveDowngradeVersion.Get(sv); downgrade != "" {
   223  		return nil, errors.Errorf(
   224  			"cannot upgrade to %s: cluster.preserve_downgrade_option is set to %s",
   225  			newVersion, downgrade)
   226  	}
   227  
   228  	// Return the serialized form of the new version.
   229  	newV := ClusterVersion{Version: newVersion}
   230  	return protoutil.Marshal(&newV)
   231  }
   232  
   233  // ValidateGossipVersion is part of the StateMachineSettingImpl interface.
   234  func (cv *clusterVersionSetting) ValidateGossipUpdate(
   235  	ctx context.Context, sv *settings.Values, rawProto []byte,
   236  ) (retErr error) {
   237  
   238  	defer func() {
   239  		// This implementation of ValidateGossipUpdate never returns errors. Instead,
   240  		// we crash. Not being able to update our version to what the rest of the cluster is running
   241  		// is a serious issue.
   242  		if retErr != nil {
   243  			log.Fatalf(ctx, "failed to validate version upgrade: %s", retErr)
   244  		}
   245  	}()
   246  
   247  	var ver ClusterVersion
   248  	if err := protoutil.Unmarshal(rawProto, &ver); err != nil {
   249  		return err
   250  	}
   251  	return cv.validateSupportedVersionInner(ctx, ver.Version, sv)
   252  }
   253  
   254  // SettingsListDefault is part of the StateMachineSettingImpl interface.
   255  func (cv *clusterVersionSetting) SettingsListDefault() string {
   256  	return binaryVersion.String()
   257  }
   258  
   259  // BeforeChange is part of the StateMachineSettingImpl interface
   260  func (cv *clusterVersionSetting) BeforeChange(
   261  	ctx context.Context, encodedVal []byte, sv *settings.Values,
   262  ) {
   263  	var clusterVersion ClusterVersion
   264  	if err := protoutil.Unmarshal(encodedVal, &clusterVersion); err != nil {
   265  		log.Fatalf(ctx, "failed to unmarshall version: %s", err)
   266  	}
   267  
   268  	vh := sv.Opaque().(Handle)
   269  	h := vh.(*handleImpl)
   270  	h.beforeClusterVersionChangeMu.Lock()
   271  	if cb := h.beforeClusterVersionChangeMu.cb; cb != nil {
   272  		cb(ctx, clusterVersion)
   273  	}
   274  	h.beforeClusterVersionChangeMu.Unlock()
   275  }
   276  
   277  func (cv *clusterVersionSetting) validateSupportedVersionInner(
   278  	ctx context.Context, ver roachpb.Version, sv *settings.Values,
   279  ) error {
   280  	vh := sv.Opaque().(Handle)
   281  	if vh.BinaryMinSupportedVersion() == (roachpb.Version{}) {
   282  		panic("BinaryMinSupportedVersion not set")
   283  	}
   284  	if vh.BinaryVersion().Less(ver) {
   285  		// TODO(tschottdorf): also ask gossip about other nodes.
   286  		return errors.Errorf("cannot upgrade to %s: node running %s",
   287  			ver, vh.BinaryVersion())
   288  	}
   289  	if ver.Less(vh.BinaryMinSupportedVersion()) {
   290  		return errors.Errorf("node at %s cannot run %s (minimum version is %s)",
   291  			vh.BinaryVersion(), ver, vh.BinaryMinSupportedVersion())
   292  	}
   293  	return nil
   294  }
   295  
   296  var preserveDowngradeVersion = registerPreserveDowngradeVersionSetting()
   297  
   298  func registerPreserveDowngradeVersionSetting() *settings.StringSetting {
   299  	s := settings.RegisterValidatedStringSetting(
   300  		"cluster.preserve_downgrade_option",
   301  		"disable (automatic or manual) cluster version upgrade from the specified version until reset",
   302  		"",
   303  		func(sv *settings.Values, s string) error {
   304  			if sv == nil || s == "" {
   305  				return nil
   306  			}
   307  			clusterVersion := version.activeVersion(context.TODO(), sv).Version
   308  			downgradeVersion, err := roachpb.ParseVersion(s)
   309  			if err != nil {
   310  				return err
   311  			}
   312  
   313  			// cluster.preserve_downgrade_option can only be set to the current cluster version.
   314  			if downgradeVersion != clusterVersion {
   315  				return errors.Errorf(
   316  					"cannot set cluster.preserve_downgrade_option to %s (cluster version is %s)",
   317  					s, clusterVersion)
   318  			}
   319  			return nil
   320  		},
   321  	)
   322  	s.SetReportable(true)
   323  	s.SetVisibility(settings.Public)
   324  	return s
   325  }