...

Source file src/go.etcd.io/etcd/raft/v3/raftpb/confchange.go

Documentation: go.etcd.io/etcd/raft/v3/raftpb

     1  // Copyright 2019 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 raftpb
    16  
    17  import (
    18  	"fmt"
    19  	"strconv"
    20  	"strings"
    21  
    22  	"github.com/gogo/protobuf/proto"
    23  )
    24  
    25  // ConfChangeI abstracts over ConfChangeV2 and (legacy) ConfChange to allow
    26  // treating them in a unified manner.
    27  type ConfChangeI interface {
    28  	AsV2() ConfChangeV2
    29  	AsV1() (ConfChange, bool)
    30  }
    31  
    32  // MarshalConfChange calls Marshal on the underlying ConfChange or ConfChangeV2
    33  // and returns the result along with the corresponding EntryType.
    34  func MarshalConfChange(c ConfChangeI) (EntryType, []byte, error) {
    35  	var typ EntryType
    36  	var ccdata []byte
    37  	var err error
    38  	if ccv1, ok := c.AsV1(); ok {
    39  		typ = EntryConfChange
    40  		ccdata, err = ccv1.Marshal()
    41  	} else {
    42  		ccv2 := c.AsV2()
    43  		typ = EntryConfChangeV2
    44  		ccdata, err = ccv2.Marshal()
    45  	}
    46  	return typ, ccdata, err
    47  }
    48  
    49  // AsV2 returns a V2 configuration change carrying out the same operation.
    50  func (c ConfChange) AsV2() ConfChangeV2 {
    51  	return ConfChangeV2{
    52  		Changes: []ConfChangeSingle{{
    53  			Type:   c.Type,
    54  			NodeID: c.NodeID,
    55  		}},
    56  		Context: c.Context,
    57  	}
    58  }
    59  
    60  // AsV1 returns the ConfChange and true.
    61  func (c ConfChange) AsV1() (ConfChange, bool) {
    62  	return c, true
    63  }
    64  
    65  // AsV2 is the identity.
    66  func (c ConfChangeV2) AsV2() ConfChangeV2 { return c }
    67  
    68  // AsV1 returns ConfChange{} and false.
    69  func (c ConfChangeV2) AsV1() (ConfChange, bool) { return ConfChange{}, false }
    70  
    71  // EnterJoint returns two bools. The second bool is true if and only if this
    72  // config change will use Joint Consensus, which is the case if it contains more
    73  // than one change or if the use of Joint Consensus was requested explicitly.
    74  // The first bool can only be true if second one is, and indicates whether the
    75  // Joint State will be left automatically.
    76  func (c ConfChangeV2) EnterJoint() (autoLeave bool, ok bool) {
    77  	// NB: in theory, more config changes could qualify for the "simple"
    78  	// protocol but it depends on the config on top of which the changes apply.
    79  	// For example, adding two learners is not OK if both nodes are part of the
    80  	// base config (i.e. two voters are turned into learners in the process of
    81  	// applying the conf change). In practice, these distinctions should not
    82  	// matter, so we keep it simple and use Joint Consensus liberally.
    83  	if c.Transition != ConfChangeTransitionAuto || len(c.Changes) > 1 {
    84  		// Use Joint Consensus.
    85  		var autoLeave bool
    86  		switch c.Transition {
    87  		case ConfChangeTransitionAuto:
    88  			autoLeave = true
    89  		case ConfChangeTransitionJointImplicit:
    90  			autoLeave = true
    91  		case ConfChangeTransitionJointExplicit:
    92  		default:
    93  			panic(fmt.Sprintf("unknown transition: %+v", c))
    94  		}
    95  		return autoLeave, true
    96  	}
    97  	return false, false
    98  }
    99  
   100  // LeaveJoint is true if the configuration change leaves a joint configuration.
   101  // This is the case if the ConfChangeV2 is zero, with the possible exception of
   102  // the Context field.
   103  func (c ConfChangeV2) LeaveJoint() bool {
   104  	// NB: c is already a copy.
   105  	c.Context = nil
   106  	return proto.Equal(&c, &ConfChangeV2{})
   107  }
   108  
   109  // ConfChangesFromString parses a Space-delimited sequence of operations into a
   110  // slice of ConfChangeSingle. The supported operations are:
   111  // - vn: make n a voter,
   112  // - ln: make n a learner,
   113  // - rn: remove n, and
   114  // - un: update n.
   115  func ConfChangesFromString(s string) ([]ConfChangeSingle, error) {
   116  	var ccs []ConfChangeSingle
   117  	toks := strings.Split(strings.TrimSpace(s), " ")
   118  	if toks[0] == "" {
   119  		toks = nil
   120  	}
   121  	for _, tok := range toks {
   122  		if len(tok) < 2 {
   123  			return nil, fmt.Errorf("unknown token %s", tok)
   124  		}
   125  		var cc ConfChangeSingle
   126  		switch tok[0] {
   127  		case 'v':
   128  			cc.Type = ConfChangeAddNode
   129  		case 'l':
   130  			cc.Type = ConfChangeAddLearnerNode
   131  		case 'r':
   132  			cc.Type = ConfChangeRemoveNode
   133  		case 'u':
   134  			cc.Type = ConfChangeUpdateNode
   135  		default:
   136  			return nil, fmt.Errorf("unknown input: %s", tok)
   137  		}
   138  		id, err := strconv.ParseUint(tok[1:], 10, 64)
   139  		if err != nil {
   140  			return nil, err
   141  		}
   142  		cc.NodeID = id
   143  		ccs = append(ccs, cc)
   144  	}
   145  	return ccs, nil
   146  }
   147  
   148  // ConfChangesToString is the inverse to ConfChangesFromString.
   149  func ConfChangesToString(ccs []ConfChangeSingle) string {
   150  	var buf strings.Builder
   151  	for i, cc := range ccs {
   152  		if i > 0 {
   153  			buf.WriteByte(' ')
   154  		}
   155  		switch cc.Type {
   156  		case ConfChangeAddNode:
   157  			buf.WriteByte('v')
   158  		case ConfChangeAddLearnerNode:
   159  			buf.WriteByte('l')
   160  		case ConfChangeRemoveNode:
   161  			buf.WriteByte('r')
   162  		case ConfChangeUpdateNode:
   163  			buf.WriteByte('u')
   164  		default:
   165  			buf.WriteString("unknown")
   166  		}
   167  		fmt.Fprintf(&buf, "%d", cc.NodeID)
   168  	}
   169  	return buf.String()
   170  }
   171  

View as plain text