@@ -74,8 +74,9 @@ func (o *migrateOptions) AddFlags(cmd *cobra.Command) {
74
74
75
75
func (o * migrateOptions ) Config () (* migrateConfig , error ) {
76
76
c := & migrateConfig {
77
- force : o .force ,
78
- lg : GetLogger (),
77
+ force : o .force ,
78
+ dataDir : o .dataDir ,
79
+ lg : GetLogger (),
79
80
}
80
81
var err error
81
82
dotCount := strings .Count (o .targetVersion , "." )
@@ -90,47 +91,75 @@ func (o *migrateOptions) Config() (*migrateConfig, error) {
90
91
return nil , fmt .Errorf (`target version %q not supported. Minimal "3.5"` , storageVersionToString (c .targetVersion ))
91
92
}
92
93
93
- dbPath := datadir .ToBackendFileName (o .dataDir )
94
- c .be = backend .NewDefaultBackend (GetLogger (), dbPath )
94
+ return c , nil
95
+ }
96
+
97
+ type migrateConfig struct {
98
+ lg * zap.Logger
99
+ be backend.Backend
100
+ targetVersion * semver.Version
101
+ walVersion schema.WALVersion
102
+ dataDir string
103
+ force bool
104
+ }
95
105
96
- walPath := datadir .ToWALDir (o .dataDir )
97
- walSnap , err := getLatestWALSnap (c .lg , o .dataDir )
106
+ func (c * migrateConfig ) finalize () error {
107
+ walPath := datadir .ToWALDir (c .dataDir )
108
+ walSnap , err := getLatestWALSnap (c .lg , c .dataDir )
98
109
if err != nil {
99
- return nil , fmt .Errorf ("failed to get the lastest snapshot: %w" , err )
110
+ return fmt .Errorf ("failed to get the lastest snapshot: %w" , err )
100
111
}
101
112
w , err := wal .OpenForRead (c .lg , walPath , walSnap )
102
113
if err != nil {
103
- return nil , fmt .Errorf (`failed to open wal: %w` , err )
114
+ return fmt .Errorf (`failed to open wal: %w` , err )
104
115
}
105
116
defer w .Close ()
106
117
c .walVersion , err = wal .ReadWALVersion (w )
107
118
if err != nil {
108
- return nil , fmt .Errorf (`failed to read wal: %w` , err )
119
+ return fmt .Errorf (`failed to read wal: %w` , err )
109
120
}
110
121
111
- return c , nil
112
- }
113
-
114
- type migrateConfig struct {
115
- lg * zap.Logger
116
- be backend.Backend
117
- targetVersion * semver.Version
118
- walVersion schema.WALVersion
119
- force bool
122
+ return nil
120
123
}
121
124
122
125
func migrateCommandFunc (c * migrateConfig ) error {
126
+ dbPath := datadir .ToBackendFileName (c .dataDir )
127
+ c .be = backend .NewDefaultBackend (GetLogger (), dbPath )
123
128
defer c .be .Close ()
129
+
124
130
tx := c .be .BatchTx ()
125
131
current , err := schema .DetectSchemaVersion (c .lg , c .be .ReadTx ())
126
132
if err != nil {
127
- c .lg .Error ("failed to detect storage version. Please make sure you are using data dir from etcd v3.5 and older" )
133
+ c .lg .Error ("failed to detect storage version. Please make sure you are using data dir from etcd v3.5 and older" , zap . Error ( err ) )
128
134
return err
129
135
}
130
136
if current == * c .targetVersion {
131
137
c .lg .Info ("storage version up-to-date" , zap .String ("storage-version" , storageVersionToString (& current )))
132
138
return nil
133
139
}
140
+
141
+ downgrade , err := isDowngrade (c .lg , tx , c .targetVersion )
142
+ if err != nil {
143
+ return err
144
+ }
145
+ if downgrade {
146
+ // Update cluster version
147
+ be := schema .NewMembershipBackend (c .lg , c .be )
148
+ be .MustSaveClusterVersionToBackend (c .targetVersion )
149
+
150
+ // forcibly create a v2 snapshot file
151
+ // TODO: remove in 3.8
152
+ if err = createV2SnapshotFromV3Store (c .dataDir , c .be ); err != nil {
153
+ c .lg .Error ("Failed to create v2 snapshot file" , zap .Error (err ))
154
+ return err
155
+ }
156
+ }
157
+
158
+ if err = c .finalize (); err != nil {
159
+ c .lg .Error ("Failed to finalize config" , zap .Error (err ))
160
+ return err
161
+ }
162
+
134
163
err = schema .Migrate (c .lg , tx , c .walVersion , * c .targetVersion )
135
164
if err != nil {
136
165
if ! c .force {
@@ -139,7 +168,9 @@ func migrateCommandFunc(c *migrateConfig) error {
139
168
c .lg .Info ("normal migrate failed, trying with force" , zap .Error (err ))
140
169
migrateForce (c .lg , tx , c .targetVersion )
141
170
}
171
+
142
172
c .be .ForceCommit ()
173
+
143
174
return nil
144
175
}
145
176
@@ -156,6 +187,17 @@ func migrateForce(lg *zap.Logger, tx backend.BatchTx, target *semver.Version) {
156
187
}
157
188
}
158
189
190
+ func isDowngrade (lg * zap.Logger , tx backend.BatchTx , target * semver.Version ) (bool , error ) {
191
+ tx .LockOutsideApply ()
192
+ defer tx .Unlock ()
193
+ ver , err := schema .UnsafeDetectSchemaVersion (lg , tx )
194
+ if err != nil {
195
+ lg .Error ("Failed to detect current storage version" , zap .Error (err ))
196
+ return false , err
197
+ }
198
+ return target .LessThan (ver ), nil
199
+ }
200
+
159
201
func storageVersionToString (ver * semver.Version ) string {
160
202
return fmt .Sprintf ("%d.%d" , ver .Major , ver .Minor )
161
203
}
0 commit comments