...

Source file src/sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane/etcd.go

Documentation: sigs.k8s.io/controller-runtime/pkg/internal/testing/controlplane

     1  /*
     2  Copyright 2021 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package controlplane
    18  
    19  import (
    20  	"io"
    21  	"net"
    22  	"net/url"
    23  	"strconv"
    24  	"time"
    25  
    26  	"sigs.k8s.io/controller-runtime/pkg/internal/testing/addr"
    27  	"sigs.k8s.io/controller-runtime/pkg/internal/testing/process"
    28  )
    29  
    30  // Etcd knows how to run an etcd server.
    31  type Etcd struct {
    32  	// URL is the address the Etcd should listen on for client connections.
    33  	//
    34  	// If this is not specified, we default to a random free port on localhost.
    35  	URL *url.URL
    36  
    37  	// Path is the path to the etcd binary.
    38  	//
    39  	// If this is left as the empty string, we will attempt to locate a binary,
    40  	// by checking for the TEST_ASSET_ETCD environment variable, and the default
    41  	// test assets directory. See the "Binaries" section above (in doc.go) for
    42  	// details.
    43  	Path string
    44  
    45  	// Args is a list of arguments which will passed to the Etcd binary. Before
    46  	// they are passed on, the`y will be evaluated as go-template strings. This
    47  	// means you can use fields which are defined and exported on this Etcd
    48  	// struct (e.g. "--data-dir={{ .Dir }}").
    49  	// Those templates will be evaluated after the defaulting of the Etcd's
    50  	// fields has already happened and just before the binary actually gets
    51  	// started. Thus you have access to calculated fields like `URL` and others.
    52  	//
    53  	// If not specified, the minimal set of arguments to run the Etcd will be
    54  	// used.
    55  	//
    56  	// They will be loaded into the same argument set as Configure.  Each flag
    57  	// will be Append-ed to the configured arguments just before launch.
    58  	//
    59  	// Deprecated: use Configure instead.
    60  	Args []string
    61  
    62  	// DataDir is a path to a directory in which etcd can store its state.
    63  	//
    64  	// If left unspecified, then the Start() method will create a fresh temporary
    65  	// directory, and the Stop() method will clean it up.
    66  	DataDir string
    67  
    68  	// StartTimeout, StopTimeout specify the time the Etcd is allowed to
    69  	// take when starting and stopping before an error is emitted.
    70  	//
    71  	// If not specified, these default to 20 seconds.
    72  	StartTimeout time.Duration
    73  	StopTimeout  time.Duration
    74  
    75  	// Out, Err specify where Etcd should write its StdOut, StdErr to.
    76  	//
    77  	// If not specified, the output will be discarded.
    78  	Out io.Writer
    79  	Err io.Writer
    80  
    81  	// processState contains the actual details about this running process
    82  	processState *process.State
    83  
    84  	// args contains the structured arguments to use for running etcd.
    85  	// Lazily initialized by .Configure(), Defaulted eventually with .defaultArgs()
    86  	args *process.Arguments
    87  
    88  	// listenPeerURL is the address the Etcd should listen on for peer connections.
    89  	// It's automatically generated and a random port is picked during execution.
    90  	listenPeerURL *url.URL
    91  }
    92  
    93  // Start starts the etcd, waits for it to come up, and returns an error, if one
    94  // occurred.
    95  func (e *Etcd) Start() error {
    96  	if err := e.setProcessState(); err != nil {
    97  		return err
    98  	}
    99  	return e.processState.Start(e.Out, e.Err)
   100  }
   101  
   102  func (e *Etcd) setProcessState() error {
   103  	e.processState = &process.State{
   104  		Dir:          e.DataDir,
   105  		Path:         e.Path,
   106  		StartTimeout: e.StartTimeout,
   107  		StopTimeout:  e.StopTimeout,
   108  	}
   109  
   110  	// unconditionally re-set this so we can successfully restart
   111  	// TODO(directxman12): we supported this in the past, but do we actually
   112  	// want to support re-using an API server object to restart?  The loss
   113  	// of provisioned users is surprising to say the least.
   114  	if err := e.processState.Init("etcd"); err != nil {
   115  		return err
   116  	}
   117  
   118  	// Set the listen url.
   119  	if e.URL == nil {
   120  		port, host, err := addr.Suggest("")
   121  		if err != nil {
   122  			return err
   123  		}
   124  		e.URL = &url.URL{
   125  			Scheme: "http",
   126  			Host:   net.JoinHostPort(host, strconv.Itoa(port)),
   127  		}
   128  	}
   129  
   130  	// Set the listen peer URL.
   131  	{
   132  		port, host, err := addr.Suggest("")
   133  		if err != nil {
   134  			return err
   135  		}
   136  		e.listenPeerURL = &url.URL{
   137  			Scheme: "http",
   138  			Host:   net.JoinHostPort(host, strconv.Itoa(port)),
   139  		}
   140  	}
   141  
   142  	// can use /health as of etcd 3.3.0
   143  	e.processState.HealthCheck.URL = *e.URL
   144  	e.processState.HealthCheck.Path = "/health"
   145  
   146  	e.DataDir = e.processState.Dir
   147  	e.Path = e.processState.Path
   148  	e.StartTimeout = e.processState.StartTimeout
   149  	e.StopTimeout = e.processState.StopTimeout
   150  
   151  	var err error
   152  	e.processState.Args, e.Args, err = process.TemplateAndArguments(e.Args, e.Configure(), process.TemplateDefaults{ //nolint:staticcheck
   153  		Data:     e,
   154  		Defaults: e.defaultArgs(),
   155  	})
   156  	return err
   157  }
   158  
   159  // Stop stops this process gracefully, waits for its termination, and cleans up
   160  // the DataDir if necessary.
   161  func (e *Etcd) Stop() error {
   162  	if e.processState.DirNeedsCleaning {
   163  		e.DataDir = "" // reset the directory if it was randomly allocated, so that we can safely restart
   164  	}
   165  	return e.processState.Stop()
   166  }
   167  
   168  func (e *Etcd) defaultArgs() map[string][]string {
   169  	args := map[string][]string{
   170  		"listen-peer-urls": {e.listenPeerURL.String()},
   171  		"data-dir":         {e.DataDir},
   172  	}
   173  	if e.URL != nil {
   174  		args["advertise-client-urls"] = []string{e.URL.String()}
   175  		args["listen-client-urls"] = []string{e.URL.String()}
   176  	}
   177  
   178  	// Add unsafe no fsync, available from etcd 3.5
   179  	if ok, _ := e.processState.CheckFlag("unsafe-no-fsync"); ok {
   180  		args["unsafe-no-fsync"] = []string{"true"}
   181  	}
   182  	return args
   183  }
   184  
   185  // Configure returns Arguments that may be used to customize the
   186  // flags used to launch etcd.  A set of defaults will
   187  // be applied underneath.
   188  func (e *Etcd) Configure() *process.Arguments {
   189  	if e.args == nil {
   190  		e.args = process.EmptyArguments()
   191  	}
   192  	return e.args
   193  }
   194  
   195  // EtcdDefaultArgs exposes the default args for Etcd so that you
   196  // can use those to append your own additional arguments.
   197  var EtcdDefaultArgs = []string{
   198  	"--listen-peer-urls=http://localhost:0",
   199  	"--advertise-client-urls={{ if .URL }}{{ .URL.String }}{{ end }}",
   200  	"--listen-client-urls={{ if .URL }}{{ .URL.String }}{{ end }}",
   201  	"--data-dir={{ .DataDir }}",
   202  }
   203  

View as plain text