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  }