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 }