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  }