github.com/cayleygraph/cayley@v0.7.7/internal/dock/dock.go (about)

     1  // +build docker
     2  
     3  package dock
     4  
     5  import (
     6  	"fmt"
     7  	"math/rand"
     8  	"net"
     9  	"runtime"
    10  	"strconv"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/fsouza/go-dockerclient"
    15  )
    16  
    17  var (
    18  	Address = `unix:///var/run/docker.sock`
    19  )
    20  
    21  type Config struct {
    22  	docker.Config
    23  }
    24  
    25  type fullConfig struct {
    26  	docker.Config
    27  	docker.HostConfig
    28  }
    29  
    30  func run(t testing.TB, conf fullConfig) (addr string, closer func()) {
    31  	if testing.Short() {
    32  		t.SkipNow()
    33  	}
    34  	cli, err := docker.NewClient(Address)
    35  	if err != nil {
    36  		t.Fatal(err)
    37  	}
    38  
    39  	// If there is not relevant image at local, pull image from remote repository.
    40  	if err := cli.PullImage(
    41  		docker.PullImageOptions{
    42  			Repository: conf.Image,
    43  		},
    44  		docker.AuthConfiguration{},
    45  	); err != nil {
    46  		// If pull image fail, skip the test.
    47  		t.Skip(err)
    48  	}
    49  
    50  	cont, err := cli.CreateContainer(docker.CreateContainerOptions{
    51  		Config:     &conf.Config,
    52  		HostConfig: &conf.HostConfig,
    53  	})
    54  	if err != nil {
    55  		t.Skip(err)
    56  	}
    57  
    58  	closer = func() {
    59  		cli.RemoveContainer(docker.RemoveContainerOptions{
    60  			ID:    cont.ID,
    61  			Force: true,
    62  		})
    63  	}
    64  
    65  	if err := cli.StartContainer(cont.ID, &conf.HostConfig); err != nil {
    66  		closer()
    67  		t.Skip(err)
    68  	}
    69  
    70  	info, err := cli.InspectContainer(cont.ID)
    71  	if err != nil {
    72  		closer()
    73  		t.Skip(err)
    74  	}
    75  	addr = info.NetworkSettings.IPAddress
    76  	return
    77  }
    78  
    79  func randPort() int {
    80  	const (
    81  		min = 10000
    82  		max = 30000
    83  	)
    84  	for {
    85  		port := min + rand.Intn(max-min)
    86  		c, err := net.DialTimeout("tcp", fmt.Sprintf("%s:%d", localhost, port), time.Second)
    87  		if c != nil {
    88  			c.Close()
    89  		}
    90  		if err != nil {
    91  			// TODO: check for a specific error
    92  			return port
    93  		}
    94  	}
    95  }
    96  
    97  const localhost = "127.0.0.1"
    98  
    99  func RunAndWait(t testing.TB, conf Config, port string, check func(string) bool) (addr string, closer func()) {
   100  	fconf := fullConfig{Config: conf.Config}
   101  	if runtime.GOOS != "linux" {
   102  		lport := strconv.Itoa(randPort())
   103  		// nothing except Linux runs Docker natively,
   104  		// so we randomize the port and expose it on Docker VM
   105  		fconf.PortBindings = map[docker.Port][]docker.PortBinding{
   106  			docker.Port(port + "/tcp"): {{
   107  				HostIP:   localhost,
   108  				HostPort: lport,
   109  			}},
   110  		}
   111  		port = lport
   112  	}
   113  	addr, closer = run(t, fconf)
   114  	if runtime.GOOS != "linux" {
   115  		// VM ports are automatically exposed on localhost
   116  		addr = localhost
   117  	}
   118  	addr += ":" + port
   119  	if check == nil {
   120  		check = waitPort
   121  	}
   122  	ok := false
   123  	for i := 0; i < 10 && !ok; i++ {
   124  		ok = check(addr)
   125  		if !ok {
   126  			time.Sleep(time.Second * 2)
   127  		}
   128  	}
   129  	if !ok {
   130  		closer()
   131  		t.Fatal("Container check fails.")
   132  	}
   133  	return addr, closer
   134  }
   135  
   136  const wait = time.Second * 5
   137  
   138  func waitPort(addr string) bool {
   139  	start := time.Now()
   140  	c, err := net.DialTimeout("tcp", addr, wait)
   141  	if err == nil {
   142  		c.Close()
   143  	} else if dt := time.Since(start); dt < wait {
   144  		time.Sleep(wait - dt)
   145  	}
   146  	return err == nil
   147  }