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 }