| // Copyright 2021 The etcd Authors |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package storage |
| |
| import ( |
| "encoding/json" |
| "fmt" |
| "sort" |
| |
| "go.uber.org/zap" |
| |
| "go.etcd.io/etcd/client/pkg/v3/types" |
| "go.etcd.io/etcd/pkg/v3/pbutil" |
| "go.etcd.io/etcd/raft/v3/raftpb" |
| "go.etcd.io/etcd/server/v3/config" |
| "go.etcd.io/etcd/server/v3/etcdserver/api/membership" |
| "go.etcd.io/etcd/server/v3/etcdserver/api/v2store" |
| ) |
| |
| // AssertNoV2StoreContent -> depending on the deprecation stage, warns or report an error |
| // if the v2store contains custom content. |
| func AssertNoV2StoreContent(lg *zap.Logger, st v2store.Store, deprecationStage config.V2DeprecationEnum) error { |
| metaOnly, err := membership.IsMetaStoreOnly(st) |
| if err != nil { |
| return err |
| } |
| if metaOnly { |
| return nil |
| } |
| if deprecationStage.IsAtLeast(config.V2_DEPR_1_WRITE_ONLY) { |
| return fmt.Errorf("detected disallowed custom content in v2store for stage --v2-deprecation=%s", deprecationStage) |
| } |
| lg.Warn("detected custom v2store content. Etcd v3.5 is the last version allowing to access it using API v2. Please remove the content.") |
| return nil |
| } |
| |
| // CreateConfigChangeEnts creates a series of Raft entries (i.e. |
| // EntryConfChange) to remove the set of given IDs from the cluster. The ID |
| // `self` is _not_ removed, even if present in the set. |
| // If `self` is not inside the given ids, it creates a Raft entry to add a |
| // default member with the given `self`. |
| func CreateConfigChangeEnts(lg *zap.Logger, ids []uint64, self uint64, term, index uint64) []raftpb.Entry { |
| found := false |
| for _, id := range ids { |
| if id == self { |
| found = true |
| } |
| } |
| |
| var ents []raftpb.Entry |
| next := index + 1 |
| |
| // NB: always add self first, then remove other nodes. Raft will panic if the |
| // set of voters ever becomes empty. |
| if !found { |
| m := membership.Member{ |
| ID: types.ID(self), |
| RaftAttributes: membership.RaftAttributes{PeerURLs: []string{"http://localhost:2380"}}, |
| } |
| ctx, err := json.Marshal(m) |
| if err != nil { |
| lg.Panic("failed to marshal member", zap.Error(err)) |
| } |
| cc := &raftpb.ConfChange{ |
| Type: raftpb.ConfChangeAddNode, |
| NodeID: self, |
| Context: ctx, |
| } |
| e := raftpb.Entry{ |
| Type: raftpb.EntryConfChange, |
| Data: pbutil.MustMarshal(cc), |
| Term: term, |
| Index: next, |
| } |
| ents = append(ents, e) |
| next++ |
| } |
| |
| for _, id := range ids { |
| if id == self { |
| continue |
| } |
| cc := &raftpb.ConfChange{ |
| Type: raftpb.ConfChangeRemoveNode, |
| NodeID: id, |
| } |
| e := raftpb.Entry{ |
| Type: raftpb.EntryConfChange, |
| Data: pbutil.MustMarshal(cc), |
| Term: term, |
| Index: next, |
| } |
| ents = append(ents, e) |
| next++ |
| } |
| |
| return ents |
| } |
| |
| // GetIDs returns an ordered set of IDs included in the given snapshot and |
| // the entries. The given snapshot/entries can contain three kinds of |
| // ID-related entry: |
| // - ConfChangeAddNode, in which case the contained ID will Be added into the set. |
| // - ConfChangeRemoveNode, in which case the contained ID will Be removed from the set. |
| // - ConfChangeAddLearnerNode, in which the contained ID will Be added into the set. |
| func GetIDs(lg *zap.Logger, snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 { |
| ids := make(map[uint64]bool) |
| if snap != nil { |
| for _, id := range snap.Metadata.ConfState.Voters { |
| ids[id] = true |
| } |
| } |
| for _, e := range ents { |
| if e.Type != raftpb.EntryConfChange { |
| continue |
| } |
| var cc raftpb.ConfChange |
| pbutil.MustUnmarshal(&cc, e.Data) |
| switch cc.Type { |
| case raftpb.ConfChangeAddLearnerNode: |
| ids[cc.NodeID] = true |
| case raftpb.ConfChangeAddNode: |
| ids[cc.NodeID] = true |
| case raftpb.ConfChangeRemoveNode: |
| delete(ids, cc.NodeID) |
| case raftpb.ConfChangeUpdateNode: |
| // do nothing |
| default: |
| lg.Panic("unknown ConfChange Type", zap.String("type", cc.Type.String())) |
| } |
| } |
| sids := make(types.Uint64Slice, 0, len(ids)) |
| for id := range ids { |
| sids = append(sids, id) |
| } |
| sort.Sort(sids) |
| return []uint64(sids) |
| } |