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	"go.etcd.io/etcd/pkg/expect"
23	"go.etcd.io/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() (err error) {
108	if ep == nil || ep.proc == nil {
109		return nil
110	}
111	err = ep.proc.Stop()
112	if err != nil {
113		return err
114	}
115	ep.proc = nil
116	<-ep.donec
117	ep.donec = make(chan struct{})
118	if ep.cfg.purl.Scheme == "unix" || ep.cfg.purl.Scheme == "unixs" {
119		err = os.Remove(ep.cfg.purl.Host + ep.cfg.purl.Path)
120		if err != nil {
121			return err
122		}
123	}
124	return nil
125}
126
127func (ep *etcdServerProcess) Close() error {
128	if err := ep.Stop(); err != nil {
129		return err
130	}
131	return os.RemoveAll(ep.cfg.dataDirPath)
132}
133
134func (ep *etcdServerProcess) WithStopSignal(sig os.Signal) os.Signal {
135	ret := ep.proc.StopSignal
136	ep.proc.StopSignal = sig
137	return ret
138}
139
140func (ep *etcdServerProcess) waitReady() error {
141	defer close(ep.donec)
142	return waitReadyExpectProc(ep.proc, etcdServerReadyLines)
143}
144
145func (ep *etcdServerProcess) Config() *etcdServerProcessConfig { return ep.cfg }
146