1// Copyright 2015 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
15package membership
16
17import (
18	"crypto/sha1"
19	"encoding/binary"
20	"fmt"
21	"math/rand"
22	"sort"
23	"time"
24
25	"github.com/coreos/pkg/capnslog"
26	"go.etcd.io/etcd/pkg/types"
27)
28
29var (
30	plog = capnslog.NewPackageLogger("go.etcd.io/etcd/v3", "etcdserver/membership")
31)
32
33// RaftAttributes represents the raft related attributes of an etcd member.
34type RaftAttributes struct {
35	// PeerURLs is the list of peers in the raft cluster.
36	// TODO(philips): ensure these are URLs
37	PeerURLs []string `json:"peerURLs"`
38	// IsLearner indicates if the member is raft learner.
39	IsLearner bool `json:"isLearner,omitempty"`
40}
41
42// Attributes represents all the non-raft related attributes of an etcd member.
43type Attributes struct {
44	Name       string   `json:"name,omitempty"`
45	ClientURLs []string `json:"clientURLs,omitempty"`
46}
47
48type Member struct {
49	ID types.ID `json:"id"`
50	RaftAttributes
51	Attributes
52}
53
54// NewMember creates a Member without an ID and generates one based on the
55// cluster name, peer URLs, and time. This is used for bootstrapping/adding new member.
56func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member {
57	return newMember(name, peerURLs, clusterName, now, false)
58}
59
60// NewMemberAsLearner creates a learner Member without an ID and generates one based on the
61// cluster name, peer URLs, and time. This is used for adding new learner member.
62func NewMemberAsLearner(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member {
63	return newMember(name, peerURLs, clusterName, now, true)
64}
65
66func newMember(name string, peerURLs types.URLs, clusterName string, now *time.Time, isLearner bool) *Member {
67	m := &Member{
68		RaftAttributes: RaftAttributes{
69			PeerURLs:  peerURLs.StringSlice(),
70			IsLearner: isLearner,
71		},
72		Attributes: Attributes{Name: name},
73	}
74
75	var b []byte
76	sort.Strings(m.PeerURLs)
77	for _, p := range m.PeerURLs {
78		b = append(b, []byte(p)...)
79	}
80
81	b = append(b, []byte(clusterName)...)
82	if now != nil {
83		b = append(b, []byte(fmt.Sprintf("%d", now.Unix()))...)
84	}
85
86	hash := sha1.Sum(b)
87	m.ID = types.ID(binary.BigEndian.Uint64(hash[:8]))
88	return m
89}
90
91// PickPeerURL chooses a random address from a given Member's PeerURLs.
92// It will panic if there is no PeerURLs available in Member.
93func (m *Member) PickPeerURL() string {
94	if len(m.PeerURLs) == 0 {
95		panic("member should always have some peer url")
96	}
97	return m.PeerURLs[rand.Intn(len(m.PeerURLs))]
98}
99
100func (m *Member) Clone() *Member {
101	if m == nil {
102		return nil
103	}
104	mm := &Member{
105		ID: m.ID,
106		RaftAttributes: RaftAttributes{
107			IsLearner: m.IsLearner,
108		},
109		Attributes: Attributes{
110			Name: m.Name,
111		},
112	}
113	if m.PeerURLs != nil {
114		mm.PeerURLs = make([]string, len(m.PeerURLs))
115		copy(mm.PeerURLs, m.PeerURLs)
116	}
117	if m.ClientURLs != nil {
118		mm.ClientURLs = make([]string, len(m.ClientURLs))
119		copy(mm.ClientURLs, m.ClientURLs)
120	}
121	return mm
122}
123
124func (m *Member) IsStarted() bool {
125	return len(m.Name) != 0
126}
127
128// MembersByID implements sort by ID interface
129type MembersByID []*Member
130
131func (ms MembersByID) Len() int           { return len(ms) }
132func (ms MembersByID) Less(i, j int) bool { return ms[i].ID < ms[j].ID }
133func (ms MembersByID) Swap(i, j int)      { ms[i], ms[j] = ms[j], ms[i] }
134
135// MembersByPeerURLs implements sort by peer urls interface
136type MembersByPeerURLs []*Member
137
138func (ms MembersByPeerURLs) Len() int { return len(ms) }
139func (ms MembersByPeerURLs) Less(i, j int) bool {
140	return ms[i].PeerURLs[0] < ms[j].PeerURLs[0]
141}
142func (ms MembersByPeerURLs) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
143