go.etcd.io/etcd@v3.3.27+incompatible/etcdctl/ctlv2/command/backup_command.go (about) 1 // Copyright 2015 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 "log" 20 "os" 21 "path" 22 "path/filepath" 23 "regexp" 24 "time" 25 26 "github.com/coreos/etcd/etcdserver/etcdserverpb" 27 "github.com/coreos/etcd/etcdserver/membership" 28 "github.com/coreos/etcd/pkg/fileutil" 29 "github.com/coreos/etcd/pkg/idutil" 30 "github.com/coreos/etcd/pkg/pbutil" 31 "github.com/coreos/etcd/raft/raftpb" 32 "github.com/coreos/etcd/snap" 33 "github.com/coreos/etcd/wal" 34 "github.com/coreos/etcd/wal/walpb" 35 36 bolt "github.com/coreos/bbolt" 37 "github.com/urfave/cli" 38 ) 39 40 func NewBackupCommand() cli.Command { 41 return cli.Command{ 42 Name: "backup", 43 Usage: "backup an etcd directory", 44 ArgsUsage: " ", 45 Flags: []cli.Flag{ 46 cli.StringFlag{Name: "data-dir", Value: "", Usage: "Path to the etcd data dir"}, 47 cli.StringFlag{Name: "wal-dir", Value: "", Usage: "Path to the etcd wal dir"}, 48 cli.StringFlag{Name: "backup-dir", Value: "", Usage: "Path to the backup dir"}, 49 cli.StringFlag{Name: "backup-wal-dir", Value: "", Usage: "Path to the backup wal dir"}, 50 cli.BoolFlag{Name: "with-v3", Usage: "Backup v3 backend data"}, 51 }, 52 Action: handleBackup, 53 } 54 } 55 56 // handleBackup handles a request that intends to do a backup. 57 func handleBackup(c *cli.Context) error { 58 var srcWAL string 59 var destWAL string 60 61 withV3 := c.Bool("with-v3") 62 srcSnap := filepath.Join(c.String("data-dir"), "member", "snap") 63 destSnap := filepath.Join(c.String("backup-dir"), "member", "snap") 64 65 if c.String("wal-dir") != "" { 66 srcWAL = c.String("wal-dir") 67 } else { 68 srcWAL = filepath.Join(c.String("data-dir"), "member", "wal") 69 } 70 71 if c.String("backup-wal-dir") != "" { 72 destWAL = c.String("backup-wal-dir") 73 } else { 74 destWAL = filepath.Join(c.String("backup-dir"), "member", "wal") 75 } 76 77 if err := fileutil.CreateDirAll(destSnap); err != nil { 78 log.Fatalf("failed creating backup snapshot dir %v: %v", destSnap, err) 79 } 80 81 walsnap := saveSnap(destSnap, srcSnap) 82 metadata, state, ents := loadWAL(srcWAL, walsnap, withV3) 83 saveDB(filepath.Join(destSnap, "db"), filepath.Join(srcSnap, "db"), state.Commit, withV3) 84 85 idgen := idutil.NewGenerator(0, time.Now()) 86 metadata.NodeID = idgen.Next() 87 metadata.ClusterID = idgen.Next() 88 89 neww, err := wal.Create(destWAL, pbutil.MustMarshal(&metadata)) 90 if err != nil { 91 log.Fatal(err) 92 } 93 defer neww.Close() 94 if err := neww.Save(state, ents); err != nil { 95 log.Fatal(err) 96 } 97 if err := neww.SaveSnapshot(walsnap); err != nil { 98 log.Fatal(err) 99 } 100 101 return nil 102 } 103 104 func saveSnap(destSnap, srcSnap string) (walsnap walpb.Snapshot) { 105 ss := snap.New(srcSnap) 106 snapshot, err := ss.Load() 107 if err != nil && err != snap.ErrNoSnapshot { 108 log.Fatal(err) 109 } 110 if snapshot != nil { 111 walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term 112 newss := snap.New(destSnap) 113 if err = newss.SaveSnap(*snapshot); err != nil { 114 log.Fatal(err) 115 } 116 } 117 return walsnap 118 } 119 120 func loadWAL(srcWAL string, walsnap walpb.Snapshot, v3 bool) (etcdserverpb.Metadata, raftpb.HardState, []raftpb.Entry) { 121 w, err := wal.OpenForRead(srcWAL, walsnap) 122 if err != nil { 123 log.Fatal(err) 124 } 125 defer w.Close() 126 wmetadata, state, ents, err := w.ReadAll() 127 switch err { 128 case nil: 129 case wal.ErrSnapshotNotFound: 130 log.Printf("Failed to find the match snapshot record %+v in wal %v.", walsnap, srcWAL) 131 log.Printf("etcdctl will add it back. Start auto fixing...") 132 default: 133 log.Fatal(err) 134 } 135 136 re := path.Join(membership.StoreMembersPrefix, "[[:xdigit:]]{1,16}", "attributes") 137 memberAttrRE := regexp.MustCompile(re) 138 139 removed := uint64(0) 140 i := 0 141 remove := func() { 142 ents = append(ents[:i], ents[i+1:]...) 143 removed++ 144 i-- 145 } 146 for i = 0; i < len(ents); i++ { 147 ents[i].Index -= removed 148 if ents[i].Type == raftpb.EntryConfChange { 149 log.Println("ignoring EntryConfChange raft entry") 150 remove() 151 continue 152 } 153 154 var raftReq etcdserverpb.InternalRaftRequest 155 var v2Req *etcdserverpb.Request 156 if pbutil.MaybeUnmarshal(&raftReq, ents[i].Data) { 157 v2Req = raftReq.V2 158 } else { 159 v2Req = &etcdserverpb.Request{} 160 pbutil.MustUnmarshal(v2Req, ents[i].Data) 161 } 162 163 if v2Req != nil && v2Req.Method == "PUT" && memberAttrRE.MatchString(v2Req.Path) { 164 log.Println("ignoring member attribute update on", v2Req.Path) 165 remove() 166 continue 167 } 168 169 if v2Req != nil { 170 continue 171 } 172 173 if v3 || raftReq.Header == nil { 174 continue 175 } 176 log.Println("ignoring v3 raft entry") 177 remove() 178 } 179 state.Commit -= removed 180 var metadata etcdserverpb.Metadata 181 pbutil.MustUnmarshal(&metadata, wmetadata) 182 return metadata, state, ents 183 } 184 185 // saveDB copies the v3 backend and strips cluster information. 186 func saveDB(destDB, srcDB string, idx uint64, v3 bool) { 187 // open src db to safely copy db state 188 if v3 { 189 var src *bolt.DB 190 ch := make(chan *bolt.DB, 1) 191 go func() { 192 src, err := bolt.Open(srcDB, 0444, &bolt.Options{ReadOnly: true}) 193 if err != nil { 194 log.Fatal(err) 195 } 196 ch <- src 197 }() 198 select { 199 case src = <-ch: 200 case <-time.After(time.Second): 201 log.Println("waiting to acquire lock on", srcDB) 202 src = <-ch 203 } 204 defer src.Close() 205 206 tx, err := src.Begin(false) 207 if err != nil { 208 log.Fatal(err) 209 } 210 211 // copy srcDB to destDB 212 dest, err := os.Create(destDB) 213 if err != nil { 214 log.Fatal(err) 215 } 216 if _, err := tx.WriteTo(dest); err != nil { 217 log.Fatal(err) 218 } 219 dest.Close() 220 if err := tx.Rollback(); err != nil { 221 log.Fatal(err) 222 } 223 } 224 225 db, err := bolt.Open(destDB, 0644, &bolt.Options{}) 226 if err != nil { 227 log.Fatal(err) 228 } 229 tx, err := db.Begin(true) 230 if err != nil { 231 log.Fatal(err) 232 } 233 234 // remove membership information; should be clobbered by --force-new-cluster 235 for _, bucket := range []string{"members", "members_removed", "cluster"} { 236 tx.DeleteBucket([]byte(bucket)) 237 } 238 239 // update consistent index to match hard state 240 if !v3 { 241 idxBytes := make([]byte, 8) 242 binary.BigEndian.PutUint64(idxBytes, idx) 243 b, err := tx.CreateBucketIfNotExists([]byte("meta")) 244 if err != nil { 245 log.Fatal(err) 246 } 247 b.Put([]byte("consistent_index"), idxBytes) 248 } 249 250 if err := tx.Commit(); err != nil { 251 log.Fatal(err) 252 } 253 if err := db.Close(); err != nil { 254 log.Fatal(err) 255 } 256 }