-
Notifications
You must be signed in to change notification settings - Fork 12
/
version.go
182 lines (161 loc) · 5.46 KB
/
version.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright 2014-Present Couchbase, Inc.
//
// Use of this software is governed by the Business Source License included
// in the file licenses/BSL-Couchbase.txt. As of the Change Date specified
// in that file, in accordance with the Business Source License, use of this
// software will be governed by the Apache License, Version 2.0, included in
// the file licenses/APL2.txt.
package cbgt
import (
"fmt"
log "github.com/couchbase/clog"
)
// The cbgt.VERSION tracks persistence versioning (schema/format of
// persisted data and configuration). The main.VERSION from "git
// describe" that's part of an executable command, in contrast, is an
// overall "product" version. For example, we might introduce new
// UI-only features or fix a UI typo, in which case we'd bump the
// main.VERSION number; but, if the persisted data/config format was
// unchanged, then the cbgt.VERSION number should remain unchanged.
//
// NOTE: You *must* update cbgt.VERSION if you change what's stored in
// the Cfg (such as the JSON/struct definitions).
const VERSION = "5.8.0"
const VERSION_KEY = "version"
// Returns true if a given version is modern enough to modify the Cfg.
// Older versions (which are running with older JSON/struct definitions
// or planning algorithms) will see false from their CheckVersion()'s.
func CheckVersion(cfg Cfg, myVersion string) (bool, error) {
tries := 0
for cfg != nil {
tries += 1
if tries > 100 {
return false,
fmt.Errorf("version: CheckVersion too many tries")
}
clusterVersion, cas, err := cfg.Get(VERSION_KEY, 0)
if err != nil {
return false, err
}
if clusterVersion == nil {
// First time initialization, so save myVersion to cfg and
// retry in case there was a race.
_, err = cfg.Set(VERSION_KEY, []byte(myVersion), cas)
if err != nil {
if _, ok := err.(*CfgCASError); ok {
// Retry if it was a CAS mismatch due to
// multi-node startup races.
continue
}
return false, fmt.Errorf("version:"+
" could not save VERSION to cfg, err: %v", err)
}
log.Printf("version: CheckVersion, Cfg version updated %s",
myVersion)
continue
}
// this check is retained to keep the same behaviour of
// preventing the older versions to override the newer
// version Cfgs. Now a Cfg version bump happens only when
// all nodes in cluster are on a given homogeneous version.
if VersionGTE(myVersion, string(clusterVersion)) == false {
return false, nil
}
if myVersion != string(clusterVersion) {
bumpVersion, err := VerifyEffectiveClusterVersion(cfg, myVersion)
if err != nil {
return false, err
}
// CheckVersion passes even if no bump version is required
if !bumpVersion {
log.Printf("version: CheckVersion, no bump for current Cfg"+
" verion: %s", clusterVersion)
return true, nil
}
// Found myVersion is higher than the clusterVersion and
// all cluster nodes are on the same myVersion, so save
// myVersion to cfg and retry in case there was a race.
_, err = cfg.Set(VERSION_KEY, []byte(myVersion), cas)
if err != nil {
if _, ok := err.(*CfgCASError); ok {
// Retry if it was a CAS mismatch due to
// multi-node startup races.
continue
}
return false, fmt.Errorf("version:"+
" could not update VERSION in cfg, err: %v", err)
}
log.Printf("version: CheckVersion, Cfg version updated %s",
myVersion)
continue
}
return true, nil
}
return false, nil
}
// VerifyEffectiveClusterVersion checks the cluster version values, and
// if the cluster contains any node which is lower than the given
// myVersion, then return false
func VerifyEffectiveClusterVersion(cfg interface{}, myVersion string) (bool, error) {
// first check with the ns_server for clusterCompatibility value
// On any errors in retrieving the values there, fallback to
// nodeDefinitions level version checks
if rsc, ok := cfg.(VersionReader); ok {
ccVersion, err := retry(3, rsc.ClusterVersion)
if err != nil {
log.Printf("version: RetrieveNsServerCompatibility, err: %v", err)
goto NODEDEFS_CHECKS
}
appVersion, err := CompatibilityVersion(CfgAppVersion)
if appVersion != ccVersion {
log.Printf("version: non matching application compatibility "+
"version: %d and clusterCompatibility version: %d",
appVersion, ccVersion)
return false, nil
}
if err != nil {
log.Printf("version: CompatibilityVersion, err: %v", err)
goto NODEDEFS_CHECKS
}
log.Printf("version: clusterCompatibility: %d matches with"+
" application version: %d", ccVersion, appVersion)
return true, err
}
NODEDEFS_CHECKS:
// fallback in case ns_server checks errors out for unknown reasons
if cfg, ok := cfg.(Cfg); ok {
for _, k := range []string{NODE_DEFS_KNOWN, NODE_DEFS_WANTED} {
key := CfgNodeDefsKey(k)
v, _, err := cfg.Get(key, 0)
if err != nil {
return false, err
}
if v == nil {
// no existing nodes in cluster
continue
}
nodeDefs := &NodeDefs{}
err = UnmarshalJSON(v, nodeDefs)
if err != nil {
return false, err
}
for _, node := range nodeDefs.NodeDefs {
if myVersion != node.ImplVersion &&
VersionGTE(myVersion, node.ImplVersion) {
log.Printf("version: version: %s lower than myVersion: %s"+
" found", node.ImplVersion, myVersion)
return false, nil
}
}
}
}
return true, nil
}
func retry(attempts int, f func() (uint64, error)) (val uint64, err error) {
if val, err = f(); err != nil {
if attempts > 0 {
retry(attempts-1, f)
}
}
return val, err
}