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 }