github.com/lfch/etcd-io/tests/v3@v3.0.0-20221004140520-eac99acd3e9d/framework/e2e/etcd_process.go (about) 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 15 package e2e 16 17 import ( 18 "context" 19 "fmt" 20 "net/url" 21 "os" 22 "testing" 23 "time" 24 25 "go.uber.org/zap" 26 27 "github.com/lfch/etcd-io/client/pkg/v3/fileutil" 28 "github.com/lfch/etcd-io/pkg/v3/expect" 29 ) 30 31 var ( 32 EtcdServerReadyLines = []string{"ready to serve client requests"} 33 BinPath string 34 CtlBinPath string 35 UtlBinPath string 36 ) 37 38 // EtcdProcess is a process that serves etcd requests. 39 type EtcdProcess interface { 40 EndpointsV2() []string 41 EndpointsV3() []string 42 EndpointsMetrics() []string 43 44 Start(ctx context.Context) error 45 Restart(ctx context.Context) error 46 Stop() error 47 Close() error 48 Config() *EtcdServerProcessConfig 49 Logs() LogsExpect 50 } 51 52 type LogsExpect interface { 53 ExpectWithContext(context.Context, string) (string, error) 54 Lines() []string 55 LineCount() int 56 } 57 58 type EtcdServerProcess struct { 59 cfg *EtcdServerProcessConfig 60 proc *expect.ExpectProcess 61 donec chan struct{} // closed when Interact() terminates 62 } 63 64 type EtcdServerProcessConfig struct { 65 lg *zap.Logger 66 ExecPath string 67 Args []string 68 TlsArgs []string 69 EnvVars map[string]string 70 71 DataDirPath string 72 KeepDataDir bool 73 74 Name string 75 76 Purl url.URL 77 78 Acurl string 79 Murl string 80 81 InitialToken string 82 InitialCluster string 83 } 84 85 func NewEtcdServerProcess(cfg *EtcdServerProcessConfig) (*EtcdServerProcess, error) { 86 if !fileutil.Exist(cfg.ExecPath) { 87 return nil, fmt.Errorf("could not find etcd binary: %s", cfg.ExecPath) 88 } 89 if !cfg.KeepDataDir { 90 if err := os.RemoveAll(cfg.DataDirPath); err != nil { 91 return nil, err 92 } 93 } 94 return &EtcdServerProcess{cfg: cfg, donec: make(chan struct{})}, nil 95 } 96 97 func (ep *EtcdServerProcess) EndpointsV2() []string { return []string{ep.cfg.Acurl} } 98 func (ep *EtcdServerProcess) EndpointsV3() []string { return ep.EndpointsV2() } 99 func (ep *EtcdServerProcess) EndpointsMetrics() []string { return []string{ep.cfg.Murl} } 100 101 func (ep *EtcdServerProcess) Start(ctx context.Context) error { 102 ep.donec = make(chan struct{}) 103 if ep.proc != nil { 104 panic("already started") 105 } 106 ep.cfg.lg.Info("starting server...", zap.String("name", ep.cfg.Name)) 107 proc, err := SpawnCmdWithLogger(ep.cfg.lg, append([]string{ep.cfg.ExecPath}, ep.cfg.Args...), ep.cfg.EnvVars, ep.cfg.Name) 108 if err != nil { 109 return err 110 } 111 ep.proc = proc 112 err = ep.waitReady(ctx) 113 if err == nil { 114 ep.cfg.lg.Info("started server.", zap.String("name", ep.cfg.Name), zap.Int("pid", ep.proc.Pid())) 115 } 116 return err 117 } 118 119 func (ep *EtcdServerProcess) Restart(ctx context.Context) error { 120 ep.cfg.lg.Info("restarting server...", zap.String("name", ep.cfg.Name)) 121 if err := ep.Stop(); err != nil { 122 return err 123 } 124 err := ep.Start(ctx) 125 if err == nil { 126 ep.cfg.lg.Info("restarted server", zap.String("name", ep.cfg.Name)) 127 } 128 return err 129 } 130 131 func (ep *EtcdServerProcess) Stop() (err error) { 132 ep.cfg.lg.Info("stopping server...", zap.String("name", ep.cfg.Name)) 133 if ep == nil || ep.proc == nil { 134 return nil 135 } 136 err = ep.proc.Stop() 137 ep.proc = nil 138 if err != nil { 139 return err 140 } 141 <-ep.donec 142 ep.donec = make(chan struct{}) 143 if ep.cfg.Purl.Scheme == "unix" || ep.cfg.Purl.Scheme == "unixs" { 144 err = os.Remove(ep.cfg.Purl.Host + ep.cfg.Purl.Path) 145 if err != nil && !os.IsNotExist(err) { 146 return err 147 } 148 } 149 ep.cfg.lg.Info("stopped server.", zap.String("name", ep.cfg.Name)) 150 return nil 151 } 152 153 func (ep *EtcdServerProcess) Close() error { 154 ep.cfg.lg.Info("closing server...", zap.String("name", ep.cfg.Name)) 155 if err := ep.Stop(); err != nil { 156 return err 157 } 158 if !ep.cfg.KeepDataDir { 159 ep.cfg.lg.Info("removing directory", zap.String("data-dir", ep.cfg.DataDirPath)) 160 return os.RemoveAll(ep.cfg.DataDirPath) 161 } 162 return nil 163 } 164 165 func (ep *EtcdServerProcess) waitReady(ctx context.Context) error { 166 defer close(ep.donec) 167 return WaitReadyExpectProc(ctx, ep.proc, EtcdServerReadyLines) 168 } 169 170 func (ep *EtcdServerProcess) Config() *EtcdServerProcessConfig { return ep.cfg } 171 172 func (ep *EtcdServerProcess) Logs() LogsExpect { 173 if ep.proc == nil { 174 ep.cfg.lg.Panic("Please grab logs before process is stopped") 175 } 176 return ep.proc 177 } 178 179 func AssertProcessLogs(t *testing.T, ep EtcdProcess, expectLog string) { 180 t.Helper() 181 var err error 182 ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) 183 defer cancel() 184 _, err = ep.Logs().ExpectWithContext(ctx, expectLog) 185 if err != nil { 186 t.Fatal(err) 187 } 188 }