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 etcdserver
16
17import (
18	"fmt"
19	"path/filepath"
20	"sort"
21	"strings"
22	"time"
23
24	"golang.org/x/net/context"
25
26	"github.com/coreos/etcd/pkg/netutil"
27	"github.com/coreos/etcd/pkg/transport"
28	"github.com/coreos/etcd/pkg/types"
29)
30
31// ServerConfig holds the configuration of etcd as taken from the command line or discovery.
32type ServerConfig struct {
33	Name           string
34	DiscoveryURL   string
35	DiscoveryProxy string
36	ClientURLs     types.URLs
37	PeerURLs       types.URLs
38	DataDir        string
39	// DedicatedWALDir config will make the etcd to write the WAL to the WALDir
40	// rather than the dataDir/member/wal.
41	DedicatedWALDir     string
42	SnapCount           uint64
43	MaxSnapFiles        uint
44	MaxWALFiles         uint
45	InitialPeerURLsMap  types.URLsMap
46	InitialClusterToken string
47	NewCluster          bool
48	ForceNewCluster     bool
49	PeerTLSInfo         transport.TLSInfo
50
51	TickMs        uint
52	ElectionTicks int
53
54	// InitialElectionTickAdvance is true, then local member fast-forwards
55	// election ticks to speed up "initial" leader election trigger. This
56	// benefits the case of larger election ticks. For instance, cross
57	// datacenter deployment may require longer election timeout of 10-second.
58	// If true, local node does not need wait up to 10-second. Instead,
59	// forwards its election ticks to 8-second, and have only 2-second left
60	// before leader election.
61	//
62	// Major assumptions are that:
63	//  - cluster has no active leader thus advancing ticks enables faster
64	//    leader election, or
65	//  - cluster already has an established leader, and rejoining follower
66	//    is likely to receive heartbeats from the leader after tick advance
67	//    and before election timeout.
68	//
69	// However, when network from leader to rejoining follower is congested,
70	// and the follower does not receive leader heartbeat within left election
71	// ticks, disruptive election has to happen thus affecting cluster
72	// availabilities.
73	//
74	// Disabling this would slow down initial bootstrap process for cross
75	// datacenter deployments. Make your own tradeoffs by configuring
76	// --initial-election-tick-advance at the cost of slow initial bootstrap.
77	//
78	// If single-node, it advances ticks regardless.
79	//
80	// See https://github.com/coreos/etcd/issues/9333 for more detail.
81	InitialElectionTickAdvance bool
82
83	BootstrapTimeout time.Duration
84
85	AutoCompactionRetention int
86	QuotaBackendBytes       int64
87
88	StrictReconfigCheck bool
89
90	// ClientCertAuthEnabled is true when cert has been signed by the client CA.
91	ClientCertAuthEnabled bool
92}
93
94// VerifyBootstrap sanity-checks the initial config for bootstrap case
95// and returns an error for things that should never happen.
96func (c *ServerConfig) VerifyBootstrap() error {
97	if err := c.hasLocalMember(); err != nil {
98		return err
99	}
100	if err := c.advertiseMatchesCluster(); err != nil {
101		return err
102	}
103	if checkDuplicateURL(c.InitialPeerURLsMap) {
104		return fmt.Errorf("initial cluster %s has duplicate url", c.InitialPeerURLsMap)
105	}
106	if c.InitialPeerURLsMap.String() == "" && c.DiscoveryURL == "" {
107		return fmt.Errorf("initial cluster unset and no discovery URL found")
108	}
109	return nil
110}
111
112// VerifyJoinExisting sanity-checks the initial config for join existing cluster
113// case and returns an error for things that should never happen.
114func (c *ServerConfig) VerifyJoinExisting() error {
115	// The member has announced its peer urls to the cluster before starting; no need to
116	// set the configuration again.
117	if err := c.hasLocalMember(); err != nil {
118		return err
119	}
120	if checkDuplicateURL(c.InitialPeerURLsMap) {
121		return fmt.Errorf("initial cluster %s has duplicate url", c.InitialPeerURLsMap)
122	}
123	if c.DiscoveryURL != "" {
124		return fmt.Errorf("discovery URL should not be set when joining existing initial cluster")
125	}
126	return nil
127}
128
129// hasLocalMember checks that the cluster at least contains the local server.
130func (c *ServerConfig) hasLocalMember() error {
131	if urls := c.InitialPeerURLsMap[c.Name]; urls == nil {
132		return fmt.Errorf("couldn't find local name %q in the initial cluster configuration", c.Name)
133	}
134	return nil
135}
136
137// advertiseMatchesCluster confirms peer URLs match those in the cluster peer list.
138func (c *ServerConfig) advertiseMatchesCluster() error {
139	urls, apurls := c.InitialPeerURLsMap[c.Name], c.PeerURLs.StringSlice()
140	urls.Sort()
141	sort.Strings(apurls)
142	ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
143	defer cancel()
144	if !netutil.URLStringsEqual(ctx, apurls, urls.StringSlice()) {
145		umap := map[string]types.URLs{c.Name: c.PeerURLs}
146		return fmt.Errorf("--initial-cluster must include %s given --initial-advertise-peer-urls=%s", types.URLsMap(umap).String(), strings.Join(apurls, ","))
147	}
148	return nil
149}
150
151func (c *ServerConfig) MemberDir() string { return filepath.Join(c.DataDir, "member") }
152
153func (c *ServerConfig) WALDir() string {
154	if c.DedicatedWALDir != "" {
155		return c.DedicatedWALDir
156	}
157	return filepath.Join(c.MemberDir(), "wal")
158}
159
160func (c *ServerConfig) SnapDir() string { return filepath.Join(c.MemberDir(), "snap") }
161
162func (c *ServerConfig) ShouldDiscover() bool { return c.DiscoveryURL != "" }
163
164// ReqTimeout returns timeout for request to finish.
165func (c *ServerConfig) ReqTimeout() time.Duration {
166	// 5s for queue waiting, computation and disk IO delay
167	// + 2 * election timeout for possible leader election
168	return 5*time.Second + 2*time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond
169}
170
171func (c *ServerConfig) electionTimeout() time.Duration {
172	return time.Duration(c.ElectionTicks) * time.Duration(c.TickMs) * time.Millisecond
173}
174
175func (c *ServerConfig) peerDialTimeout() time.Duration {
176	// 1s for queue wait and system delay
177	// + one RTT, which is smaller than 1/5 election timeout
178	return time.Second + time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond/5
179}
180
181func (c *ServerConfig) PrintWithInitial() { c.print(true) }
182
183func (c *ServerConfig) Print() { c.print(false) }
184
185func (c *ServerConfig) print(initial bool) {
186	plog.Infof("name = %s", c.Name)
187	if c.ForceNewCluster {
188		plog.Infof("force new cluster")
189	}
190	plog.Infof("data dir = %s", c.DataDir)
191	plog.Infof("member dir = %s", c.MemberDir())
192	if c.DedicatedWALDir != "" {
193		plog.Infof("dedicated WAL dir = %s", c.DedicatedWALDir)
194	}
195	plog.Infof("heartbeat = %dms", c.TickMs)
196	plog.Infof("election = %dms", c.ElectionTicks*int(c.TickMs))
197	plog.Infof("snapshot count = %d", c.SnapCount)
198	if len(c.DiscoveryURL) != 0 {
199		plog.Infof("discovery URL= %s", c.DiscoveryURL)
200		if len(c.DiscoveryProxy) != 0 {
201			plog.Infof("discovery proxy = %s", c.DiscoveryProxy)
202		}
203	}
204	plog.Infof("advertise client URLs = %s", c.ClientURLs)
205	if initial {
206		plog.Infof("initial advertise peer URLs = %s", c.PeerURLs)
207		plog.Infof("initial cluster = %s", c.InitialPeerURLsMap)
208	}
209}
210
211func checkDuplicateURL(urlsmap types.URLsMap) bool {
212	um := make(map[string]bool)
213	for _, urls := range urlsmap {
214		for _, url := range urls {
215			u := url.String()
216			if um[u] {
217				return true
218			}
219			um[u] = true
220		}
221	}
222	return false
223}
224
225func (c *ServerConfig) bootstrapTimeout() time.Duration {
226	if c.BootstrapTimeout != 0 {
227		return c.BootstrapTimeout
228	}
229	return time.Second
230}
231