github.com/kaituanwang/hyperledger@v2.0.1+incompatible/orderer/consensus/etcdraft/membership.go (about)

     1  /*
     2  Copyright IBM Corp. All Rights Reserved.
     3  
     4  SPDX-License-Identifier: Apache-2.0
     5  */
     6  
     7  package etcdraft
     8  
     9  import (
    10  	"fmt"
    11  
    12  	"github.com/golang/protobuf/proto"
    13  	"github.com/hyperledger/fabric-protos-go/orderer/etcdraft"
    14  	"github.com/pkg/errors"
    15  	"go.etcd.io/etcd/raft"
    16  	"go.etcd.io/etcd/raft/raftpb"
    17  )
    18  
    19  // MembershipByCert convert consenters map into set encapsulated by map
    20  // where key is client TLS certificate
    21  func MembershipByCert(consenters map[uint64]*etcdraft.Consenter) map[string]uint64 {
    22  	set := map[string]uint64{}
    23  	for nodeID, c := range consenters {
    24  		set[string(c.ClientTlsCert)] = nodeID
    25  	}
    26  	return set
    27  }
    28  
    29  // MembershipChanges keeps information about membership
    30  // changes introduced during configuration update
    31  type MembershipChanges struct {
    32  	NewBlockMetadata *etcdraft.BlockMetadata
    33  	NewConsenters    map[uint64]*etcdraft.Consenter
    34  	AddedNodes       []*etcdraft.Consenter
    35  	RemovedNodes     []*etcdraft.Consenter
    36  	ConfChange       *raftpb.ConfChange
    37  	RotatedNode      uint64
    38  }
    39  
    40  // ComputeMembershipChanges computes membership update based on information about new consenters, returns
    41  // two slices: a slice of added consenters and a slice of consenters to be removed
    42  func ComputeMembershipChanges(oldMetadata *etcdraft.BlockMetadata, oldConsenters map[uint64]*etcdraft.Consenter, newConsenters []*etcdraft.Consenter) (mc *MembershipChanges, err error) {
    43  	result := &MembershipChanges{
    44  		NewConsenters:    map[uint64]*etcdraft.Consenter{},
    45  		NewBlockMetadata: proto.Clone(oldMetadata).(*etcdraft.BlockMetadata),
    46  		AddedNodes:       []*etcdraft.Consenter{},
    47  		RemovedNodes:     []*etcdraft.Consenter{},
    48  	}
    49  
    50  	result.NewBlockMetadata.ConsenterIds = make([]uint64, len(newConsenters))
    51  
    52  	var addedNodeIndex int
    53  	currentConsentersSet := MembershipByCert(oldConsenters)
    54  	for i, c := range newConsenters {
    55  		if nodeID, exists := currentConsentersSet[string(c.ClientTlsCert)]; exists {
    56  			result.NewBlockMetadata.ConsenterIds[i] = nodeID
    57  			result.NewConsenters[nodeID] = c
    58  			continue
    59  		}
    60  		addedNodeIndex = i
    61  		result.AddedNodes = append(result.AddedNodes, c)
    62  	}
    63  
    64  	var deletedNodeID uint64
    65  	newConsentersSet := ConsentersToMap(newConsenters)
    66  	for nodeID, c := range oldConsenters {
    67  		if _, exists := newConsentersSet[string(c.ClientTlsCert)]; !exists {
    68  			result.RemovedNodes = append(result.RemovedNodes, c)
    69  			deletedNodeID = nodeID
    70  		}
    71  	}
    72  
    73  	switch {
    74  	case len(result.AddedNodes) == 1 && len(result.RemovedNodes) == 1:
    75  		// A cert is considered being rotated, iff exact one new node is being added
    76  		// AND exact one existing node is being removed
    77  		result.RotatedNode = deletedNodeID
    78  		result.NewBlockMetadata.ConsenterIds[addedNodeIndex] = deletedNodeID
    79  		result.NewConsenters[deletedNodeID] = result.AddedNodes[0]
    80  	case len(result.AddedNodes) == 1 && len(result.RemovedNodes) == 0:
    81  		// new node
    82  		nodeID := result.NewBlockMetadata.NextConsenterId
    83  		result.NewConsenters[nodeID] = result.AddedNodes[0]
    84  		result.NewBlockMetadata.ConsenterIds[addedNodeIndex] = nodeID
    85  		result.NewBlockMetadata.NextConsenterId++
    86  		result.ConfChange = &raftpb.ConfChange{
    87  			NodeID: nodeID,
    88  			Type:   raftpb.ConfChangeAddNode,
    89  		}
    90  	case len(result.AddedNodes) == 0 && len(result.RemovedNodes) == 1:
    91  		// removed node
    92  		nodeID := deletedNodeID
    93  		result.ConfChange = &raftpb.ConfChange{
    94  			NodeID: nodeID,
    95  			Type:   raftpb.ConfChangeRemoveNode,
    96  		}
    97  		delete(result.NewConsenters, nodeID)
    98  	case len(result.AddedNodes) == 0 && len(result.RemovedNodes) == 0:
    99  		// no change
   100  	default:
   101  		// len(result.AddedNodes) > 1 || len(result.RemovedNodes) > 1 {
   102  		return nil, errors.Errorf("update of more than one consenter at a time is not supported, requested changes: %s", result)
   103  	}
   104  
   105  	return result, nil
   106  }
   107  
   108  // Stringer implements fmt.Stringer interface
   109  func (mc *MembershipChanges) String() string {
   110  	return fmt.Sprintf("add %d node(s), remove %d node(s)", len(mc.AddedNodes), len(mc.RemovedNodes))
   111  }
   112  
   113  // Changed indicates whether these changes actually do anything
   114  func (mc *MembershipChanges) Changed() bool {
   115  	return len(mc.AddedNodes) > 0 || len(mc.RemovedNodes) > 0
   116  }
   117  
   118  // Rotated indicates whether the change was a rotation
   119  func (mc *MembershipChanges) Rotated() bool {
   120  	return len(mc.AddedNodes) == 1 && len(mc.RemovedNodes) == 1
   121  }
   122  
   123  // UnacceptableQuorumLoss returns true if membership change will result in avoidable quorum loss,
   124  // given current number of active nodes in cluster. Avoidable means that more nodes can be started
   125  // to prevent quorum loss. Sometimes, quorum loss is inevitable, for example expanding 1-node cluster.
   126  func (mc *MembershipChanges) UnacceptableQuorumLoss(active []uint64) bool {
   127  	activeMap := make(map[uint64]struct{})
   128  	for _, i := range active {
   129  		activeMap[i] = struct{}{}
   130  	}
   131  
   132  	isCFT := len(mc.NewConsenters) > 2 // if resulting cluster cannot tolerate any fault, quorum loss is inevitable
   133  	quorum := len(mc.NewConsenters)/2 + 1
   134  
   135  	switch {
   136  	case mc.ConfChange != nil && mc.ConfChange.Type == raftpb.ConfChangeAddNode: // Add
   137  		return isCFT && len(active) < quorum
   138  
   139  	case mc.RotatedNode != raft.None: // Rotate
   140  		delete(activeMap, mc.RotatedNode)
   141  		return isCFT && len(activeMap) < quorum
   142  
   143  	case mc.ConfChange != nil && mc.ConfChange.Type == raftpb.ConfChangeRemoveNode: // Remove
   144  		delete(activeMap, mc.ConfChange.NodeID)
   145  		return len(activeMap) < quorum
   146  
   147  	default: // No change
   148  		return false
   149  	}
   150  }