go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv3/command/migrate_command.go (about) 1 // Copyright 2016 The etcd Authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package command 16 17 import ( 18 "encoding/binary" 19 "encoding/json" 20 "fmt" 21 "io" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "time" 26 27 "github.com/coreos/etcd/client" 28 etcdErr "github.com/coreos/etcd/error" 29 "github.com/coreos/etcd/etcdserver" 30 "github.com/coreos/etcd/etcdserver/api" 31 pb "github.com/coreos/etcd/etcdserver/etcdserverpb" 32 "github.com/coreos/etcd/etcdserver/membership" 33 "github.com/coreos/etcd/mvcc" 34 "github.com/coreos/etcd/mvcc/backend" 35 "github.com/coreos/etcd/mvcc/mvccpb" 36 "github.com/coreos/etcd/pkg/pbutil" 37 "github.com/coreos/etcd/pkg/types" 38 "github.com/coreos/etcd/raft/raftpb" 39 "github.com/coreos/etcd/snap" 40 "github.com/coreos/etcd/store" 41 "github.com/coreos/etcd/wal" 42 "github.com/coreos/etcd/wal/walpb" 43 "github.com/gogo/protobuf/proto" 44 "github.com/spf13/cobra" 45 ) 46 47 var ( 48 migrateExcludeTTLKey bool 49 migrateDatadir string 50 migrateWALdir string 51 migrateTransformer string 52 ) 53 54 // NewMigrateCommand returns the cobra command for "migrate". 55 func NewMigrateCommand() *cobra.Command { 56 mc := &cobra.Command{ 57 Use: "migrate", 58 Short: "Migrates keys in a v2 store to a mvcc store", 59 Run: migrateCommandFunc, 60 } 61 62 mc.Flags().BoolVar(&migrateExcludeTTLKey, "no-ttl", false, "Do not convert TTL keys") 63 mc.Flags().StringVar(&migrateDatadir, "data-dir", "", "Path to the data directory") 64 mc.Flags().StringVar(&migrateWALdir, "wal-dir", "", "Path to the WAL directory") 65 mc.Flags().StringVar(&migrateTransformer, "transformer", "", "Path to the user-provided transformer program") 66 return mc 67 } 68 69 func migrateCommandFunc(cmd *cobra.Command, args []string) { 70 var ( 71 writer io.WriteCloser 72 reader io.ReadCloser 73 errc chan error 74 ) 75 if migrateTransformer != "" { 76 writer, reader, errc = startTransformer() 77 } else { 78 fmt.Println("using default transformer") 79 writer, reader, errc = defaultTransformer() 80 } 81 82 st, index := rebuildStoreV2() 83 be := prepareBackend() 84 defer be.Close() 85 86 go func() { 87 writeStore(writer, st) 88 writer.Close() 89 }() 90 91 readKeys(reader, be) 92 mvcc.UpdateConsistentIndex(be, index) 93 err := <-errc 94 if err != nil { 95 fmt.Println("failed to transform keys") 96 ExitWithError(ExitError, err) 97 } 98 99 fmt.Println("finished transforming keys") 100 } 101 102 func prepareBackend() backend.Backend { 103 var be backend.Backend 104 105 bch := make(chan struct{}) 106 dbpath := filepath.Join(migrateDatadir, "member", "snap", "db") 107 go func() { 108 defer close(bch) 109 be = backend.NewDefaultBackend(dbpath) 110 111 }() 112 select { 113 case <-bch: 114 case <-time.After(time.Second): 115 fmt.Fprintf(os.Stderr, "waiting for etcd to close and release its lock on %q\n", dbpath) 116 <-bch 117 } 118 119 tx := be.BatchTx() 120 tx.Lock() 121 tx.UnsafeCreateBucket([]byte("key")) 122 tx.UnsafeCreateBucket([]byte("meta")) 123 tx.Unlock() 124 return be 125 } 126 127 func rebuildStoreV2() (store.Store, uint64) { 128 var index uint64 129 cl := membership.NewCluster("") 130 131 waldir := migrateWALdir 132 if len(waldir) == 0 { 133 waldir = filepath.Join(migrateDatadir, "member", "wal") 134 } 135 snapdir := filepath.Join(migrateDatadir, "member", "snap") 136 137 ss := snap.New(snapdir) 138 snapshot, err := ss.Load() 139 if err != nil && err != snap.ErrNoSnapshot { 140 ExitWithError(ExitError, err) 141 } 142 143 var walsnap walpb.Snapshot 144 if snapshot != nil { 145 walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term 146 index = snapshot.Metadata.Index 147 } 148 149 w, err := wal.OpenForRead(waldir, walsnap) 150 if err != nil { 151 ExitWithError(ExitError, err) 152 } 153 defer w.Close() 154 155 _, _, ents, err := w.ReadAll() 156 if err != nil { 157 ExitWithError(ExitError, err) 158 } 159 160 st := store.New() 161 if snapshot != nil { 162 err := st.Recovery(snapshot.Data) 163 if err != nil { 164 ExitWithError(ExitError, err) 165 } 166 } 167 168 cl.SetStore(st) 169 cl.Recover(api.UpdateCapability) 170 171 applier := etcdserver.NewApplierV2(st, cl) 172 for _, ent := range ents { 173 if ent.Type == raftpb.EntryConfChange { 174 var cc raftpb.ConfChange 175 pbutil.MustUnmarshal(&cc, ent.Data) 176 applyConf(cc, cl) 177 continue 178 } 179 180 var raftReq pb.InternalRaftRequest 181 if !pbutil.MaybeUnmarshal(&raftReq, ent.Data) { // backward compatible 182 var r pb.Request 183 pbutil.MustUnmarshal(&r, ent.Data) 184 applyRequest(&r, applier) 185 } else { 186 if raftReq.V2 != nil { 187 req := raftReq.V2 188 applyRequest(req, applier) 189 } 190 } 191 if ent.Index > index { 192 index = ent.Index 193 } 194 } 195 196 return st, index 197 } 198 199 func applyConf(cc raftpb.ConfChange, cl *membership.RaftCluster) { 200 if err := cl.ValidateConfigurationChange(cc); err != nil { 201 return 202 } 203 switch cc.Type { 204 case raftpb.ConfChangeAddNode: 205 m := new(membership.Member) 206 if err := json.Unmarshal(cc.Context, m); err != nil { 207 panic(err) 208 } 209 cl.AddMember(m) 210 case raftpb.ConfChangeRemoveNode: 211 cl.RemoveMember(types.ID(cc.NodeID)) 212 case raftpb.ConfChangeUpdateNode: 213 m := new(membership.Member) 214 if err := json.Unmarshal(cc.Context, m); err != nil { 215 panic(err) 216 } 217 cl.UpdateRaftAttributes(m.ID, m.RaftAttributes) 218 } 219 } 220 221 func applyRequest(req *pb.Request, applyV2 etcdserver.ApplierV2) { 222 r := (*etcdserver.RequestV2)(req) 223 r.TTLOptions() 224 switch r.Method { 225 case "POST": 226 applyV2.Post(r) 227 case "PUT": 228 applyV2.Put(r) 229 case "DELETE": 230 applyV2.Delete(r) 231 case "QGET": 232 applyV2.QGet(r) 233 case "SYNC": 234 applyV2.Sync(r) 235 default: 236 panic("unknown command") 237 } 238 } 239 240 func writeStore(w io.Writer, st store.Store) uint64 { 241 all, err := st.Get("/1", true, true) 242 if err != nil { 243 if eerr, ok := err.(*etcdErr.Error); ok && eerr.ErrorCode == etcdErr.EcodeKeyNotFound { 244 fmt.Println("no v2 keys to migrate") 245 os.Exit(0) 246 } 247 ExitWithError(ExitError, err) 248 } 249 return writeKeys(w, all.Node) 250 } 251 252 func writeKeys(w io.Writer, n *store.NodeExtern) uint64 { 253 maxIndex := n.ModifiedIndex 254 255 nodes := n.Nodes 256 // remove store v2 bucket prefix 257 n.Key = n.Key[2:] 258 if n.Key == "" { 259 n.Key = "/" 260 } 261 if n.Dir { 262 n.Nodes = nil 263 } 264 if !migrateExcludeTTLKey || n.TTL == 0 { 265 b, err := json.Marshal(n) 266 if err != nil { 267 ExitWithError(ExitError, err) 268 } 269 fmt.Fprint(w, string(b)) 270 } 271 for _, nn := range nodes { 272 max := writeKeys(w, nn) 273 if max > maxIndex { 274 maxIndex = max 275 } 276 } 277 return maxIndex 278 } 279 280 func readKeys(r io.Reader, be backend.Backend) error { 281 for { 282 length64, err := readInt64(r) 283 if err != nil { 284 if err == io.EOF { 285 return nil 286 } 287 return err 288 } 289 290 buf := make([]byte, int(length64)) 291 if _, err = io.ReadFull(r, buf); err != nil { 292 return err 293 } 294 295 var kv mvccpb.KeyValue 296 err = proto.Unmarshal(buf, &kv) 297 if err != nil { 298 return err 299 } 300 301 mvcc.WriteKV(be, kv) 302 } 303 } 304 305 func readInt64(r io.Reader) (int64, error) { 306 var n int64 307 err := binary.Read(r, binary.LittleEndian, &n) 308 return n, err 309 } 310 311 func startTransformer() (io.WriteCloser, io.ReadCloser, chan error) { 312 cmd := exec.Command(migrateTransformer) 313 cmd.Stderr = os.Stderr 314 315 writer, err := cmd.StdinPipe() 316 if err != nil { 317 ExitWithError(ExitError, err) 318 } 319 320 reader, rerr := cmd.StdoutPipe() 321 if rerr != nil { 322 ExitWithError(ExitError, rerr) 323 } 324 325 if err := cmd.Start(); err != nil { 326 ExitWithError(ExitError, err) 327 } 328 329 errc := make(chan error, 1) 330 331 go func() { 332 errc <- cmd.Wait() 333 }() 334 335 return writer, reader, errc 336 } 337 338 func defaultTransformer() (io.WriteCloser, io.ReadCloser, chan error) { 339 // transformer decodes v2 keys from sr 340 sr, sw := io.Pipe() 341 // transformer encodes v3 keys into dw 342 dr, dw := io.Pipe() 343 344 decoder := json.NewDecoder(sr) 345 346 errc := make(chan error, 1) 347 348 go func() { 349 defer func() { 350 sr.Close() 351 dw.Close() 352 }() 353 354 for decoder.More() { 355 node := &client.Node{} 356 if err := decoder.Decode(node); err != nil { 357 errc <- err 358 return 359 } 360 361 kv := transform(node) 362 if kv == nil { 363 continue 364 } 365 366 data, err := proto.Marshal(kv) 367 if err != nil { 368 errc <- err 369 return 370 } 371 buf := make([]byte, 8) 372 binary.LittleEndian.PutUint64(buf, uint64(len(data))) 373 if _, err := dw.Write(buf); err != nil { 374 errc <- err 375 return 376 } 377 if _, err := dw.Write(data); err != nil { 378 errc <- err 379 return 380 } 381 } 382 383 errc <- nil 384 }() 385 386 return sw, dr, errc 387 } 388 389 func transform(n *client.Node) *mvccpb.KeyValue { 390 const unKnownVersion = 1 391 if n.Dir { 392 return nil 393 } 394 kv := &mvccpb.KeyValue{ 395 Key: []byte(n.Key), 396 Value: []byte(n.Value), 397 CreateRevision: int64(n.CreatedIndex), 398 ModRevision: int64(n.ModifiedIndex), 399 Version: unKnownVersion, 400 } 401 return kv 402 }