Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions libbeat/tests/compose/compose.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,74 +19,97 @@ package compose

import (
"errors"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
)

// EnsureUp starts all the requested services (must be defined in docker-compose.yml)
// with a default timeout of 300 seconds
func EnsureUp(t *testing.T, services ...string) {
EnsureUpWithTimeout(t, 60, services...)
func EnsureUp(t *testing.T, service string) R {
return EnsureUpWithTimeout(t, 60, service)
}

// EnsureUpWithTimeout starts all the requested services (must be defined in docker-compose.yml)
// Wait for `timeout` seconds for health
func EnsureUpWithTimeout(t *testing.T, timeout int, services ...string) {
func EnsureUpWithTimeout(t *testing.T, timeout int, service string) R {
// The NO_COMPOSE env variables makes it possible to skip the starting of the environment.
// This is useful if the service is already running locally.
if noCompose, err := strconv.ParseBool(os.Getenv("NO_COMPOSE")); err == nil && noCompose {
return
envVar := fmt.Sprintf("%s_HOST", strings.ToUpper(service))
host := os.Getenv(envVar)
if host == "" {
t.Fatalf("%s environment variable must be set as the host:port where %s is running", envVar, service)
}
return &runnerControl{host: host}
}

compose, err := getComposeProject()
compose, err := getComposeProject(os.Getenv("DOCKER_COMPOSE_PROJECT_NAME"))
if err != nil {
t.Fatal(err)
}

// Kill no longer used containers
err = compose.KillOld(services)
err = compose.KillOld([]string{service})
if err != nil {
t.Fatal(err)
}

for _, service := range services {
err = compose.Start(service)
if err != nil {
t.Fatal("failed to start service", service, err)
}
// Start container
err = compose.Start(service, RecreateOnUp(false))
if err != nil {
t.Fatal("failed to start service", service, err)
}

// Wait for health
err = compose.Wait(timeout, services...)
err = compose.Wait(timeout, service)
if err != nil {
t.Fatal(err)
}

// Get host information
host, err := compose.Host(service)
if err != nil {
t.Fatalf("getting host for %s", service)
}

return &runnerControl{host: host}
}

func getComposeProject() (*Project, error) {
func findComposePath() (string, error) {
// find docker-compose
path, err := os.Getwd()
if err != nil {
return nil, err
return "", err
}
for {
if path == "/" {
return nil, errors.New("docker-compose.yml not found")
break
}

if _, err = os.Stat(filepath.Join(path, "docker-compose.yml")); err != nil {
path = filepath.Dir(path)
} else {
break
composePath := filepath.Join(path, "docker-compose.yml")
if _, err = os.Stat(composePath); err == nil {
return composePath, nil
}
path = filepath.Dir(path)
}

return "", errors.New("docker-compose.yml not found")
}

func getComposeProject(name string) (*Project, error) {
path, err := findComposePath()
if err != nil {
return nil, err
}

return NewProject(
os.Getenv("DOCKER_COMPOSE_PROJECT_NAME"),
name,
[]string{
filepath.Join(path, "docker-compose.yml"),
path,
},
)
}
69 changes: 53 additions & 16 deletions libbeat/tests/compose/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,16 @@ type UpOptions struct {
Create CreateOptions
}

// UpOption customizes UpOptions
type UpOption func(*UpOptions)

// RecreateOnUp enables or disables recreation of container on startup
func RecreateOnUp(recreate bool) UpOption {
return func(opts *UpOptions) {
opts.Create.ForceRecreate = recreate
}
}

// Filter options for services
type Filter struct {
State State
Expand All @@ -56,9 +66,11 @@ const (
// Driver is the interface of docker compose implementations
type Driver interface {
Up(ctx context.Context, opts UpOptions, service string) error
Down(ctx context.Context) error
Kill(ctx context.Context, signal string, service string) error
Ps(ctx context.Context, filter ...string) ([]ContainerStatus, error)
// Containers(ctx context.Context, projectFilter Filter, filter ...string) ([]string, error)

SetParameters(map[string]string)

LockFile() string
}
Expand All @@ -69,6 +81,7 @@ type ContainerStatus interface {
Healthy() bool
Running() bool
Old() bool
Host() string
}

// Project is a docker-compose project
Expand All @@ -93,28 +106,22 @@ func NewProject(name string, files []string) (*Project, error) {
}

// Start the container, unless it's running already
func (c *Project) Start(service string) error {
servicesStatus, err := c.getServices(service)
if err != nil {
return err
}

if servicesStatus[service] != nil {
if servicesStatus[service].Running {
// Someone is running it
return nil
}
}

func (c *Project) Start(service string, upOptions ...UpOption) error {
c.Lock()
defer c.Unlock()

return c.Driver.Up(context.Background(), UpOptions{
options := UpOptions{
Create: CreateOptions{
Build: true,
ForceRecreate: true,
},
}, service)
}

for _, option := range upOptions {
option(&options)
}

return c.Driver.Up(context.Background(), options, service)
}

// Wait ensures all wanted services are healthy. Wait loop (60s timeout)
Expand Down Expand Up @@ -149,6 +156,25 @@ func (c *Project) Wait(seconds int, services ...string) error {
return nil
}

// Host gets the host and port of a service
func (c *Project) Host(service string) (string, error) {
servicesStatus, err := c.getServices(service)
if err != nil {
return "", err
}

if len(servicesStatus) == 0 {
return "", errors.New("no container running for service")
}

status, ok := servicesStatus[service]
if !ok || status.Host == "" {
return "", errors.New("unknown host:port for service")
}

return status.Host, nil
}

// Kill a container
func (c *Project) Kill(service string) error {
c.Lock()
Expand Down Expand Up @@ -190,6 +216,14 @@ func (c *Project) KillOld(except []string) error {
return nil
}

// Down removes all resources of a project
func (c *Project) Down() error {
c.Lock()
defer c.Unlock()

return c.Driver.Down(context.Background())
}

// Lock acquires the lock (300s) timeout
// Normally it should only be seconds that the lock is used, but in some cases it can take longer.
func (c *Project) Lock() {
Expand Down Expand Up @@ -226,6 +260,8 @@ type serviceInfo struct {
Running bool
Healthy bool

Host string

// Has been up for too long?:
Old bool
}
Expand Down Expand Up @@ -259,6 +295,7 @@ func (c *Project) getServices(filter ...string) (map[string]*serviceInfo, error)
if service.Healthy {
service.Old = c.Old()
}
service.Host = c.Host()
result[name] = service
}

Expand Down
Loading