e2e

package module
v0.11.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 25, 2021 License: Apache-2.0 Imports: 24 Imported by: 12

README

e2e

golang docs

Go Module providing robust framework for running complex workload scenarios in isolation, using Go and Docker. For integration, e2e tests, benchmarks and more! 💪

What are the goals?

  • Ability to schedule isolated processes programmatically from single process on single machine.
  • Focus on cluster workloads, cloud native services and microservices.
  • Developer scenarios in mind e.g preserving scenario readability, Go unit test integration.
  • Metric monitoring as the first citizen. Assert on Prometheus metric values during test scenarios or check overall performance characteristics.

Usage Models

There are three main use cases envisioned for this Go module:

  • Unit test use (see example). Use e2e in unit tests to quickly run complex test scenarios involving many container services. This was the main reason we created this module. You can check usage of it in Cortex and Thanos projects.
  • Standalone use (see example). Use e2e to run setups in interactive mode where you spin up workloads as you want programmatically and poke with it on your own using your browser or other tools. No longer need to deploy full Kubernetes or external machines.
  • Benchmark use (see example). Use e2e in local Go benchmarks when your code depends on external services with ease.
Getting Started

Let's go through an example leveraging go test flow:

  1. Implement the workload by embeddinge2e.Runnable or *e2e.InstrumentedRunnable. Or you can use existing ones in e2edb package. For example implementing function that schedules Jaeger with our desired configuration could look like this:

    
    	// Setup Jaeger for example purposes, on how easy is to setup tracing pipeline in e2e framework.
    	j := e.Runnable("tracing").
         WithPorts(
             map[string]int{
                 "http.front":    16686,
                 "jaeger.thrift": 14268,
    
  2. Implement test. Start by creating environment. Currently e2e supports Docker environment only. Use unique name for all your tests. It's recommended to keep it stable so resources are consistently cleaned.

    	// Start isolated environment with given ref.
    	e, err := e2e.NewDockerEnvironment("e2e_example")
    	testutil.Ok(t, err)
    	// Make sure resources (e.g docker containers, network, dir) are cleaned.
    	t.Cleanup(e.Close)
    
  3. Program your scenario as you want. You can start, wait for their readiness, stop, check their metrics and use their network endpoints from both unit test (Endpoint) as well as within each workload (InternalEndpoint). You can also access workload directory. There is a shared directory across all workloads. Check Dir and InternalDir runnable methods.

    	// Create structs for Prometheus containers scraping itself.
    	p1 := e2edb.NewPrometheus(e, "prometheus-1")
    	s1 := e2edb.NewThanosSidecar(e, "sidecar-1", p1)
    
    	p2 := e2edb.NewPrometheus(e, "prometheus-2")
    	s2 := e2edb.NewThanosSidecar(e, "sidecar-2", p2)
    
    	// Create Thanos Query container. We can point the peer network addresses of both Prometheus instance
    	// using InternalEndpoint methods, even before they started.
    	t1 := e2edb.NewThanosQuerier(e, "query-1", []string{s1.InternalEndpoint("grpc"), s2.InternalEndpoint("grpc")})
    
    	// Start them.
    	testutil.Ok(t, e2e.StartAndWaitReady(p1, s1, p2, s2, t1))
    
    	// To ensure query should have access we can check its Prometheus metric using WaitSumMetrics method. Since the metric we are looking for
    	// only appears after init, we add option to wait for it.
    	testutil.Ok(t, t1.WaitSumMetricsWithOptions(e2e.Equals(2), []string{"thanos_store_nodes_grpc_connections"}, e2e.WaitMissingMetrics()))
    
    	// To ensure Prometheus scraped already something ensure number of scrapes.
    	testutil.Ok(t, p1.WaitSumMetrics(e2e.Greater(50), "prometheus_tsdb_head_samples_appended_total"))
    	testutil.Ok(t, p2.WaitSumMetrics(e2e.Greater(50), "prometheus_tsdb_head_samples_appended_total"))
    
    	// We can now query Thanos Querier directly from here, using it's host address thanks to Endpoint method.
    	a, err := api.NewClient(api.Config{Address: "http://" + t1.Endpoint("http")})
    	testutil.Ok(t, err)
    
    	{
         now := model.Now()
         v, w, err := v1.NewAPI(a).Query(context.Background(), "up{}", now.Time())
         testutil.Ok(t, err)
         testutil.Equals(t, 0, len(w))
         testutil.Equals(
             t,
             fmt.Sprintf(`up{instance="%v", job="myself", prometheus="prometheus-1"} => 1 @[%v]
    up{instance="%v", job="myself", prometheus="prometheus-2"} => 1 @[%v]`, p1.InternalEndpoint(e2edb.AccessPortName), now, p2.InternalEndpoint(e2edb.AccessPortName), now),
             v.String(),
         )
    	}
    
    	// Stop first Prometheus and sidecar.
    	testutil.Ok(t, s1.Stop())
    	testutil.Ok(t, p1.Stop())
    
    	// Wait a bit until Thanos drops connection to stopped Prometheus.
    	testutil.Ok(t, t1.WaitSumMetricsWithOptions(e2e.Equals(1), []string{"thanos_store_nodes_grpc_connections"}, e2e.WaitMissingMetrics()))
    
    	{
         now := model.Now()
         v, w, err := v1.NewAPI(a).Query(context.Background(), "up{}", now.Time())
         testutil.Ok(t, err)
         testutil.Equals(t, 0, len(w))
         testutil.Equals(
             t,
             fmt.Sprintf(`up{instance="%v", job="myself", prometheus="prometheus-2"} => 1 @[%v]`, p2.InternalEndpoint(e2edb.AccessPortName), now),
             v.String(),
         )
    	}
    }
    
Monitoring

Each instrumented workload have programmatic access to latest metrics with WaitSumMetricsWithOptions methods family. Yet, especially for standalone mode it's often useful to query and visualisate all metrics provided by your services/runnables using PromQL. In order to do so just start monitoring from e2emontioring package:

mon, err := e2emonitoring.Start(e)
if err != nil {
	return err
}

This will start Prometheus with automatic discovery for every new and old instrumented runnables being scraped. It also runs cadvisor that monitors docker itself if env.DockerEnvironment is started and show generic performance metrics per container (e.g container_memory_rss). Run OpenUserInterfaceInBrowser() to open Prometheus UI in browser.

	// Open monitoring page with all metrics.
	if err := mon.OpenUserInterfaceInBrowser(); err != nil {
		return errors.Wrap(err, "open monitoring UI in browser")
	}

To see how it works in practice run our example code in standalone.go by running make run-example. At the end, three UIs should show in your browser. Thanos one, monitoring (Prometheus) one and tracing (Jaeger) one. In monitoring UI you can then e.g query docker container metrics using container_memory_working_set_bytes{id!="/"} metric e.g:

mem metric

NOTE: Due to cgroup modifications and using advanced docker features, this might behave different on non Linux platforms. Let us know in the issue if you encounter any issue on Mac or Windows and help us to add support for those operating systems!

Bonus: Monitoring performance of e2e process itself.

It's common pattern that you want to schedule some containers but also, you might want to run some expensive code inside e2e once integration components are running. In order to learn more about performance of existing process run e2emonitoring.Start with extra parameter:

	// NOTE: This will error out on first run, demanding to setup permissions for cgroups.
	// Remove `WithCurrentProcessAsContainer` to avoid that. This will also descope monitoring current process itself
	// and focus on scheduled containers only.
	mon, err := e2emonitoring.Start(e, e2emonitoring.WithCurrentProcessAsContainer())
	if err != nil {
		return err
	}

This will put current process in cgroup which allows cadvisor to watch it as it was container.

NOTE: This step requires manual step. The step is a command that is printed on first invocation of e2e with above command and should tell you what command should be invoked.

Credits

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func BuildArgs

func BuildArgs(flags map[string]string) []string

func BuildKingpinArgs added in v0.10.0

func BuildKingpinArgs(flags map[string]string) []string

BuildKingpinArgs is like BuildArgs but with special handling of slice args. NOTE(bwplotka): flags with values as comma but not indented to be slice will cause issues.

func EmptyFlags

func EmptyFlags() map[string]string

func EqualsAmongTwo

func EqualsAmongTwo(sums ...float64) bool

EqualsAmongTwo is an isExpected function for WaitSumMetrics that returns true if first sum is equal to the second. NOTE: Be careful on scrapes in between of process that changes two metrics. Those are usually not atomic.

func EqualsSingle

func EqualsSingle(expected float64) func(float64) bool

func GreaterAmongTwo

func GreaterAmongTwo(sums ...float64) bool

GreaterAmongTwo is an isExpected function for WaitSumMetrics that returns true if first sum is greater than second. NOTE: Be careful on scrapes in between of process that changes two metrics. Those are usually not atomic.

func LessAmongTwo

func LessAmongTwo(sums ...float64) bool

LessAmongTwo is an isExpected function for WaitSumMetrics that returns true if first sum is smaller than second. NOTE: Be careful on scrapes in between of process that changes two metrics. Those are usually not atomic.

func MergeFlags

func MergeFlags(inputs ...map[string]string) map[string]string

func MergeFlagsWithoutRemovingEmpty

func MergeFlagsWithoutRemovingEmpty(inputs ...map[string]string) map[string]string

func StartAndWaitReady

func StartAndWaitReady(runnables ...Runnable) error

func SumValues

func SumValues(values []float64) float64

Types

type CmdReadinessProbe

type CmdReadinessProbe struct {
	// contains filtered or unexported fields
}

CmdReadinessProbe checks readiness by `Exec`ing a command (within container) which returns 0 to consider status being ready.

func NewCmdReadinessProbe

func NewCmdReadinessProbe(cmd Command) *CmdReadinessProbe

func (*CmdReadinessProbe) Ready

func (p *CmdReadinessProbe) Ready(runnable Runnable) error

type Command

type Command struct {
	Cmd                string
	Args               []string
	EntrypointDisabled bool
}

func NewCommand

func NewCommand(cmd string, args ...string) Command

func NewCommandWithoutEntrypoint

func NewCommandWithoutEntrypoint(cmd string, args ...string) Command

type CompositeInstrumentedRunnable

type CompositeInstrumentedRunnable struct {
	// contains filtered or unexported fields
}

CompositeInstrumentedRunnable abstract an higher-level service composed by more than one InstrumentedRunnable.

func NewCompositeInstrumentedRunnable

func NewCompositeInstrumentedRunnable(runnables ...*InstrumentedRunnable) *CompositeInstrumentedRunnable

func (*CompositeInstrumentedRunnable) Instances

func (*CompositeInstrumentedRunnable) MetricTargets added in v0.10.0

func (r *CompositeInstrumentedRunnable) MetricTargets() (ret []MetricTarget)

func (*CompositeInstrumentedRunnable) SumMetrics

func (s *CompositeInstrumentedRunnable) SumMetrics(metricNames []string, opts ...MetricsOption) ([]float64, error)

SumMetrics returns the sum of the values of each given metric names.

func (*CompositeInstrumentedRunnable) WaitSumMetrics

func (r *CompositeInstrumentedRunnable) WaitSumMetrics(expected MetricValueExpectation, metricNames ...string) error

WaitSumMetrics waits for at least one instance of each given metric names to be present and their sums, returning true when passed to given expected(...).

func (*CompositeInstrumentedRunnable) WaitSumMetricsWithOptions

func (r *CompositeInstrumentedRunnable) WaitSumMetricsWithOptions(expected MetricValueExpectation, metricNames []string, opts ...MetricsOption) error

type DockerEnvironment

type DockerEnvironment struct {
	// contains filtered or unexported fields
}

DockerEnvironment defines single node docker engine that allows to run Services.

func NewDockerEnvironment

func NewDockerEnvironment(name string, opts ...EnvironmentOption) (*DockerEnvironment, error)

NewDockerEnvironment creates new, isolated docker environment.

func (*DockerEnvironment) AddCloser added in v0.11.0

func (e *DockerEnvironment) AddCloser(f func())

func (*DockerEnvironment) AddListener added in v0.10.0

func (e *DockerEnvironment) AddListener(listener EnvironmentListener)

AddListener registers given listener to be notified on environment runnable changes.

func (*DockerEnvironment) Close

func (e *DockerEnvironment) Close()

func (*DockerEnvironment) HostAddr added in v0.11.0

func (e *DockerEnvironment) HostAddr() string

func (*DockerEnvironment) Name added in v0.11.0

func (e *DockerEnvironment) Name() string

func (*DockerEnvironment) Runnable

func (e *DockerEnvironment) Runnable(name string) RunnableBuilder

func (*DockerEnvironment) SharedDir

func (e *DockerEnvironment) SharedDir() string

type Environment

type Environment interface {
	// Name returns environment name.
	Name() string
	// SharedDir returns host directory that will be shared with all runnables.
	SharedDir() string
	// HostAddr returns host address that is available from runnables.
	HostAddr() string
	// Runnable returns runnable builder which can build runnables that can be started and stopped within this environment.
	Runnable(name string) RunnableBuilder
	// AddListener registers given listener to be notified on environment runnable changes.
	AddListener(listener EnvironmentListener)
	// AddCloser registers function to be invoked on close.
	AddCloser(func())
	// Close shutdowns isolated environment and cleans its resources.
	Close()
}

Environment defines how to run Runnable in isolated area e.g via docker in isolated docker network.

type EnvironmentListener added in v0.10.0

type EnvironmentListener interface {
	OnRunnableChange(started []Runnable) error
}

type EnvironmentOption

type EnvironmentOption func(*environmentOptions)

EnvironmentOption defined the signature of a function used to manipulate options.

func WithLogger

func WithLogger(logger Logger) EnvironmentOption

WithLogger tells environment to use custom logger to default one (stdout).

func WithVerbose

func WithVerbose() EnvironmentOption

WithVerbose tells environment to be verbose i.e print all commands it executes.

type Errorer added in v0.10.0

type Errorer struct {
	// contains filtered or unexported fields
}

func NewErrorer added in v0.10.0

func NewErrorer(name string, err error) Errorer

func (Errorer) Dir added in v0.10.0

func (Errorer) Dir() string

func (Errorer) Endpoint added in v0.10.0

func (Errorer) Endpoint(string) string

func (Errorer) Exec added in v0.10.0

func (e Errorer) Exec(Command) (string, string, error)

func (Errorer) Future added in v0.10.0

func (e Errorer) Future() FutureRunnable

func (Errorer) Init added in v0.10.0

func (e Errorer) Init(StartOptions) Runnable

func (Errorer) InternalDir added in v0.10.0

func (Errorer) InternalDir() string

func (Errorer) InternalEndpoint added in v0.10.0

func (Errorer) InternalEndpoint(string) string

func (Errorer) IsRunning added in v0.10.0

func (Errorer) IsRunning() bool

func (Errorer) Kill added in v0.10.0

func (e Errorer) Kill() error

func (Errorer) Name added in v0.10.0

func (e Errorer) Name() string

func (Errorer) Start added in v0.10.0

func (e Errorer) Start() error

func (Errorer) Stop added in v0.10.0

func (e Errorer) Stop() error

func (Errorer) WaitReady added in v0.10.0

func (e Errorer) WaitReady() error

func (Errorer) WithConcreteType added in v0.10.0

func (e Errorer) WithConcreteType(Runnable) RunnableBuilder

func (Errorer) WithPorts added in v0.10.0

func (e Errorer) WithPorts(map[string]int) RunnableBuilder

type FutureInstrumentedRunnable

type FutureInstrumentedRunnable struct {
	FutureRunnable
	// contains filtered or unexported fields
}

func NewInstrumentedRunnable

func NewInstrumentedRunnable(
	env Environment,
	name string,
	ports map[string]int,
	metricPortName string,
) *FutureInstrumentedRunnable

func (*FutureInstrumentedRunnable) Init

type FutureRunnable

type FutureRunnable interface {
	Linkable

	// Init transforms future into runnable.
	Init(opts StartOptions) Runnable
}

type HTTPReadinessProbe

type HTTPReadinessProbe struct {
	// contains filtered or unexported fields
}

HTTPReadinessProbe checks readiness by making HTTP call and checking for expected HTTP status code.

func NewHTTPReadinessProbe

func NewHTTPReadinessProbe(portName string, path string, expectedStatusRangeStart, expectedStatusRangeEnd int, expectedContent ...string) *HTTPReadinessProbe

func (*HTTPReadinessProbe) Ready

func (p *HTTPReadinessProbe) Ready(runnable Runnable) (err error)

type Instrumented added in v0.10.0

type Instrumented interface {
	MetricTargets() []MetricTarget
}

Instrumented is implemented by all instrumented runnables.

type InstrumentedRunnable

type InstrumentedRunnable struct {
	Runnable
	// contains filtered or unexported fields
}

InstrumentedRunnable represents opinionated microservice with one port marked as HTTP port with metric endpoint.

func NewErrInstrumentedRunnable added in v0.10.0

func NewErrInstrumentedRunnable(name string, err error) *InstrumentedRunnable

func (*InstrumentedRunnable) MetricTargets added in v0.10.0

func (r *InstrumentedRunnable) MetricTargets() []MetricTarget

func (*InstrumentedRunnable) Metrics

func (r *InstrumentedRunnable) Metrics() (_ string, err error)

func (*InstrumentedRunnable) SumMetrics

func (r *InstrumentedRunnable) SumMetrics(metricNames []string, opts ...MetricsOption) ([]float64, error)

SumMetrics returns the sum of the values of each given metric names.

func (*InstrumentedRunnable) WaitRemovedMetric

func (r *InstrumentedRunnable) WaitRemovedMetric(metricName string, opts ...MetricsOption) error

WaitRemovedMetric waits until a metric disappear from the list of metrics exported by the service.

func (*InstrumentedRunnable) WaitSumMetrics

func (r *InstrumentedRunnable) WaitSumMetrics(expected MetricValueExpectation, metricNames ...string) error

WaitSumMetrics waits for at least one instance of each given metric names to be present and their sums, returning true when passed to given expected(...).

func (*InstrumentedRunnable) WaitSumMetricsWithOptions

func (r *InstrumentedRunnable) WaitSumMetricsWithOptions(expected MetricValueExpectation, metricNames []string, opts ...MetricsOption) error

type LinePrefixLogger

type LinePrefixLogger struct {
	// contains filtered or unexported fields
}

func (*LinePrefixLogger) Write

func (w *LinePrefixLogger) Write(p []byte) (n int, err error)

type Linkable

type Linkable interface {
	// Name returns unique name for the Runnable instance.
	Name() string

	// Dir returns host working directory path for this runnable.
	Dir() string

	// InternalDir returns local, environment working directory path for this runnable.
	InternalDir() string

	// InternalEndpoint returns internal runnable endpoint (host:port) for given internal port.
	// Internal means that it will be accessible only from runnable context.
	InternalEndpoint(portName string) string
}

Linkable is the entity that one can use to link runnable to other runnables before started.

type Logger

type Logger interface {
	Log(keyvals ...interface{}) error
}

Logger is the fundamental interface for all log operations. Log creates a log event from keyvals, a variadic sequence of alternating keys and values. Implementations must be safe for concurrent use by multiple goroutines. In particular, any implementation of Logger that appends to keyvals or modifies or retains any of its elements must make a copy first. This is 1:1 copy of "github.com/go-kit/kit/log" interface.

type MetricTarget added in v0.10.0

type MetricTarget struct {
	InternalEndpoint string
	MetricPath       string
}

type MetricValueExpectation

type MetricValueExpectation func(sums ...float64) bool

func Equals

func Equals(value float64) MetricValueExpectation

Equals is an MetricValueExpectation function for WaitSumMetrics that returns true if given single sum is equals to given value.

func Greater

func Greater(value float64) MetricValueExpectation

Greater is an isExpected function for WaitSumMetrics that returns true if given single sum is greater than given value.

func GreaterOrEqual

func GreaterOrEqual(value float64) MetricValueExpectation

GreaterOrEqual is an isExpected function for WaitSumMetrics that returns true if given single sum is greater or equal than given value.

func Less

func Less(value float64) MetricValueExpectation

Less is an isExpected function for WaitSumMetrics that returns true if given single sum is less than given value.

type MetricsOption

type MetricsOption func(*metricsOptions)

MetricsOption defined the signature of a function used to manipulate options.

func SkipMissingMetrics

func SkipMissingMetrics() MetricsOption

SkipMissingMetrics is an option to skip/ignore whenever an expected metric is missing.

func WaitMissingMetrics

func WaitMissingMetrics() MetricsOption

WaitMissingMetrics is an option to wait whenever an expected metric is missing. If this option is not enabled, will return error on missing metrics.

func WithLabelMatchers

func WithLabelMatchers(matchers ...*matchers.Matcher) MetricsOption

WithLabelMatchers is an option to filter only matching series.

func WithMetricCount

func WithMetricCount() MetricsOption

WithMetricCount is an option to get the histogram/summary count as metric value.

type ReadinessProbe

type ReadinessProbe interface {
	Ready(runnable Runnable) (err error)
}

type Runnable

type Runnable interface {
	Linkable

	// IsRunning returns if runnable was started.
	IsRunning() bool

	// Start tells Runnable to start.
	Start() error

	// WaitReady waits until the Runnable is ready. It should return error if runnable is stopped in mean time or
	// it was stopped before.
	WaitReady() error

	// Kill tells Runnable to get killed immediately.
	// It should be ok to Stop and Kill more than once, with next invokes being noop.
	Kill() error

	// Stop tells Runnable to get gracefully stopped.
	// It should be ok to Stop and Kill more than once, with next invokes being noop.
	Stop() error

	// Exec runs the provided command inside the same process context (e.g in the docker container).
	// It returns the stdout, stderr, and error response from attempting to run the command.
	Exec(command Command) (string, string, error)

	// Endpoint returns external runnable endpoint (host:port) for given port name.
	// External means that it will be accessible only from host, but not from docker containers.
	//
	// If your service is not running, this method returns incorrect `stopped` endpoint.
	Endpoint(portName string) string
	// contains filtered or unexported methods
}

Runnable is the entity that environment returns to manage single instance.

type RunnableBuilder added in v0.10.0

type RunnableBuilder interface {
	WithPorts(map[string]int) RunnableBuilder
	WithConcreteType(r Runnable) RunnableBuilder

	// Future returns future runnable
	Future() FutureRunnable
	// Init returns runnable.
	Init(opts StartOptions) Runnable
}

type SimpleLogger

type SimpleLogger struct {
	// contains filtered or unexported fields
}

func NewLogger

func NewLogger(w io.Writer) *SimpleLogger

func (*SimpleLogger) Log

func (l *SimpleLogger) Log(keyvals ...interface{}) error

type StartOptions

type StartOptions struct {
	Image     string
	EnvVars   map[string]string
	User      string
	Command   Command
	Readiness ReadinessProbe
	// WaitReadyBackofff represents backoff used for WaitReady.
	WaitReadyBackoff *backoff.Config
	Volumes          []string
	UserNs           string
	Privileged       bool
}

StartOptions represents starting option of runnable in the environment.

type TCPReadinessProbe

type TCPReadinessProbe struct {
	// contains filtered or unexported fields
}

TCPReadinessProbe checks readiness by ensure a TCP connection can be established.

func NewTCPReadinessProbe

func NewTCPReadinessProbe(portName string) *TCPReadinessProbe

func (*TCPReadinessProbe) Ready

func (p *TCPReadinessProbe) Ready(runnable Runnable) (err error)

Directories

Path Synopsis
Package e2edb is a reference, instrumented runnables that are running various popular databases one could run in their tests or benchmarks.
Package e2edb is a reference, instrumented runnables that are running various popular databases one could run in their tests or benchmarks.
examples
thanos module

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL