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  }