1// Copyright 2017 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 e2e
16
17import (
18	"fmt"
19	"net/url"
20	"os"
21
22	"github.com/coreos/etcd/pkg/expect"
23	"github.com/coreos/etcd/pkg/fileutil"
24)
25
26var (
27	etcdServerReadyLines = []string{"enabled capabilities for version", "published"}
28	binPath              string
29	ctlBinPath           string
30)
31
32// etcdProcess is a process that serves etcd requests.
33type etcdProcess interface {
34	EndpointsV2() []string
35	EndpointsV3() []string
36	EndpointsMetrics() []string
37
38	Start() error
39	Restart() error
40	Stop() error
41	Close() error
42	WithStopSignal(sig os.Signal) os.Signal
43	Config() *etcdServerProcessConfig
44}
45
46type etcdServerProcess struct {
47	cfg   *etcdServerProcessConfig
48	proc  *expect.ExpectProcess
49	donec chan struct{} // closed when Interact() terminates
50}
51
52type etcdServerProcessConfig struct {
53	execPath string
54	args     []string
55	tlsArgs  []string
56
57	dataDirPath string
58	keepDataDir bool
59
60	name string
61
62	purl url.URL
63
64	acurl string
65	murl  string
66
67	initialToken   string
68	initialCluster string
69}
70
71func newEtcdServerProcess(cfg *etcdServerProcessConfig) (*etcdServerProcess, error) {
72	if !fileutil.Exist(cfg.execPath) {
73		return nil, fmt.Errorf("could not find etcd binary")
74	}
75	if !cfg.keepDataDir {
76		if err := os.RemoveAll(cfg.dataDirPath); err != nil {
77			return nil, err
78		}
79	}
80	return &etcdServerProcess{cfg: cfg, donec: make(chan struct{})}, nil
81}
82
83func (ep *etcdServerProcess) EndpointsV2() []string      { return []string{ep.cfg.acurl} }
84func (ep *etcdServerProcess) EndpointsV3() []string      { return ep.EndpointsV2() }
85func (ep *etcdServerProcess) EndpointsMetrics() []string { return []string{ep.cfg.murl} }
86
87func (ep *etcdServerProcess) Start() error {
88	if ep.proc != nil {
89		panic("already started")
90	}
91	proc, err := spawnCmd(append([]string{ep.cfg.execPath}, ep.cfg.args...))
92	if err != nil {
93		return err
94	}
95	ep.proc = proc
96	return ep.waitReady()
97}
98
99func (ep *etcdServerProcess) Restart() error {
100	if err := ep.Stop(); err != nil {
101		return err
102	}
103	ep.donec = make(chan struct{})
104	return ep.Start()
105}
106
107func (ep *etcdServerProcess) Stop() error {
108	if ep == nil || ep.proc == nil {
109		return nil
110	}
111	if err := ep.proc.Stop(); err != nil {
112		return err
113	}
114	ep.proc = nil
115	<-ep.donec
116	ep.donec = make(chan struct{})
117	if ep.cfg.purl.Scheme == "unix" || ep.cfg.purl.Scheme == "unixs" {
118		os.Remove(ep.cfg.purl.Host + ep.cfg.purl.Path)
119	}
120	return nil
121}
122
123func (ep *etcdServerProcess) Close() error {
124	if err := ep.Stop(); err != nil {
125		return err
126	}
127	return os.RemoveAll(ep.cfg.dataDirPath)
128}
129
130func (ep *etcdServerProcess) WithStopSignal(sig os.Signal) os.Signal {
131	ret := ep.proc.StopSignal
132	ep.proc.StopSignal = sig
133	return ret
134}
135
136func (ep *etcdServerProcess) waitReady() error {
137	defer close(ep.donec)
138	return waitReadyExpectProc(ep.proc, etcdServerReadyLines)
139}
140
141func (ep *etcdServerProcess) Config() *etcdServerProcessConfig { return ep.cfg }
142