github.com/greenboxal/deis@v1.12.1/tests/dockercli/dockercli.go (about)

     1  // Package dockercli provides helper functions for testing with Docker.
     2  package dockercli
     3  
     4  import (
     5  	"bufio"
     6  	"crypto/tls"
     7  	"fmt"
     8  	"io"
     9  	"log"
    10  	"net"
    11  	"net/http"
    12  	"net/url"
    13  	"os"
    14  	"path/filepath"
    15  	"strings"
    16  	"testing"
    17  	"time"
    18  
    19  	"github.com/deis/deis/tests/utils"
    20  	"github.com/docker/docker/api/client"
    21  )
    22  
    23  const (
    24  	defaultKeyFile  = "key.pem"
    25  	defaultCertFile = "cert.pem"
    26  )
    27  
    28  // CloseWrap ensures that an io.Writer is closed.
    29  func CloseWrap(args ...io.Closer) error {
    30  	e := false
    31  	ret := fmt.Errorf("Error closing elements")
    32  	for _, c := range args {
    33  		if err := c.Close(); err != nil {
    34  			e = true
    35  			ret = fmt.Errorf("%s\n%s", ret, err)
    36  		}
    37  	}
    38  	if e {
    39  		return ret
    40  	}
    41  	return nil
    42  }
    43  
    44  // DeisServiceTest tries to connect to a container and port using the
    45  // specified protocol.
    46  func DeisServiceTest(
    47  	t *testing.T, container string, port string, protocol string) {
    48  	ipaddr := utils.HostAddress()
    49  	if ipaddr == "" {
    50  		ipaddr = GetInspectData(
    51  			t, "{{ .NetworkSettings.ipaddr }}", container)
    52  	}
    53  	fmt.Println("Running service test for " + container)
    54  	if strings.Contains(ipaddr, "Error") {
    55  		t.Fatalf("wrong IP %s", ipaddr)
    56  	}
    57  	if protocol == "http" {
    58  		url := "http://" + ipaddr + ":" + port
    59  		response, err := http.Get(url)
    60  		if err != nil {
    61  			t.Fatalf("Not reachable %s", err)
    62  		}
    63  		fmt.Println(response)
    64  	}
    65  	if protocol == "tcp" || protocol == "udp" {
    66  		conn, err := net.Dial(protocol, ipaddr+":"+port)
    67  		if err != nil {
    68  			t.Fatalf("Not reachable %s", err)
    69  		}
    70  		_, err = conn.Write([]byte("HEAD"))
    71  		if err != nil {
    72  			t.Fatalf("Not reachable %s", err)
    73  		}
    74  	}
    75  }
    76  
    77  // DockerHost returns the protocol and address of the docker server.
    78  func DockerHost() (string, string, error) {
    79  	dockerHost := os.Getenv("DOCKER_HOST")
    80  	if dockerHost == "" {
    81  		dockerHost = "unix:///var/run/docker.sock"
    82  	}
    83  	u, err := url.Parse(dockerHost)
    84  	if err != nil {
    85  		return "", "", err
    86  	}
    87  	if u.Scheme == "unix" {
    88  		return u.Scheme, u.Path, nil
    89  	}
    90  	return u.Scheme, u.Host, nil
    91  }
    92  
    93  // NewClient returns a new docker test client.
    94  func NewClient() (
    95  	cli *client.DockerCli, stdout *io.PipeReader, stdoutPipe *io.PipeWriter) {
    96  	proto, addr, _ := DockerHost()
    97  	stdout, stdoutPipe = io.Pipe()
    98  
    99  	dockerCertPath := os.Getenv("DOCKER_CERT_PATH")
   100  	// Boot2docker use TLS per default, Jenkins not
   101  	if dockerCertPath != "" {
   102  		var (
   103  			tlsConfig tls.Config
   104  		)
   105  		tlsConfig.InsecureSkipVerify = true
   106  
   107  		flCert := filepath.Join(dockerCertPath, defaultCertFile)
   108  		flKey := filepath.Join(dockerCertPath, defaultKeyFile)
   109  
   110  		_, errCert := os.Stat(flCert)
   111  		_, errKey := os.Stat(flKey)
   112  		if errCert == nil && errKey == nil {
   113  			cert, err := tls.LoadX509KeyPair(flCert, flKey)
   114  			if err != nil {
   115  				log.Fatalf("Couldn't load X509 key pair: %s. Key encrypted?", err)
   116  			}
   117  			tlsConfig.Certificates = []tls.Certificate{cert}
   118  		}
   119  		// Avoid fallback to SSL protocols < TLS1.0
   120  		tlsConfig.MinVersion = tls.VersionTLS10
   121  		cli = client.NewDockerCli(nil, stdoutPipe, nil, "", proto, addr, &tlsConfig)
   122  	} else {
   123  		cli = client.NewDockerCli(nil, stdoutPipe, nil, "", proto, addr, nil)
   124  	}
   125  	return
   126  }
   127  
   128  func CreateVolume(t *testing.T, cli *client.DockerCli, name string, path string) {
   129  	err := RunContainer(cli,
   130  		"--name", name,
   131  		"-v", path,
   132  		"ubuntu-debootstrap:14.04", "/bin/true")
   133  
   134  	if err != nil {
   135  		t.Fatal(err)
   136  	}
   137  }
   138  
   139  // PrintToStdout prints a string to stdout.
   140  func PrintToStdout(t *testing.T, stdout *io.PipeReader,
   141  	stdoutPipe *io.PipeWriter, stoptag string) string {
   142  	var result string
   143  	r := bufio.NewReader(stdout)
   144  	for {
   145  		cmdBytes, err := r.ReadString('\n')
   146  		if err != nil {
   147  			break
   148  		}
   149  		result = cmdBytes
   150  		fmt.Print(cmdBytes)
   151  		if strings.Contains(cmdBytes, stoptag) == true {
   152  			if err := CloseWrap(stdout, stdoutPipe); err != nil {
   153  				t.Fatal(err)
   154  			}
   155  		}
   156  	}
   157  	return result
   158  }
   159  
   160  func WaitForLine(t *testing.T, stdout *io.PipeReader, stoptag string, trace bool) string {
   161  	var result string
   162  	r := bufio.NewReader(stdout)
   163  	for {
   164  		cmdBytes, err := r.ReadString('\n')
   165  		if err != nil {
   166  			break
   167  		}
   168  		result = cmdBytes
   169  		if trace {
   170  			fmt.Print(cmdBytes)
   171  		}
   172  		if strings.Contains(cmdBytes, stoptag) == true {
   173  			break
   174  		}
   175  	}
   176  	return result
   177  }
   178  
   179  // GetInspectData prints and returns `docker inspect` data for a container.
   180  func GetInspectData(t *testing.T, format string, container string) string {
   181  	var inspectData string
   182  	cli, stdout, stdoutPipe := NewClient()
   183  	fmt.Println("Getting inspect data :" + format + ":" + container)
   184  	go func() {
   185  		err := cli.CmdInspect("--format", format, container)
   186  		if err != nil {
   187  			fmt.Printf("%s %s", format, err)
   188  		}
   189  		if err = CloseWrap(stdout, stdoutPipe); err != nil {
   190  			t.Fatalf("inspect data failed %s", err)
   191  		}
   192  	}()
   193  	go func() {
   194  		time.Sleep(3000 * time.Millisecond)
   195  		if err := CloseWrap(stdout, stdoutPipe); err != nil {
   196  			t.Fatalf("Inspect data %s", err)
   197  		}
   198  	}()
   199  	time.Sleep(1000 * time.Millisecond)
   200  	inspectData = PrintToStdout(t, stdout, stdoutPipe, "get inspect data")
   201  	return strings.TrimSuffix(inspectData, "\n")
   202  }
   203  
   204  // RunContainer runs a docker image with the given arguments.
   205  func RunContainer(cli *client.DockerCli, args ...string) error {
   206  	// fmt.Println("--- Run docker container", args[1])
   207  	err := cli.CmdRun(args...)
   208  	if err != nil {
   209  		// Ignore certain errors we see in io handling.
   210  		switch msg := err.Error(); {
   211  		case strings.Contains(msg, "read/write on closed pipe"):
   212  			err = nil
   213  		case strings.Contains(msg, "Code: -1"):
   214  			err = nil
   215  		case strings.Contains(msg, "Code: 2"):
   216  			err = nil
   217  		}
   218  	}
   219  	return err
   220  }
   221  
   222  // RunDeisDataTest starts a data container as a prerequisite for a service.
   223  func RunDeisDataTest(t *testing.T, args ...string) {
   224  	done := make(chan bool, 1)
   225  	cli, stdout, stdoutPipe := NewClient()
   226  	var hostname string
   227  	fmt.Println(args[2] + " test")
   228  	hostname = GetInspectData(t, "{{ .Config.Hostname }}", args[1])
   229  	fmt.Println("data container " + hostname)
   230  	done <- true
   231  	if strings.Contains(hostname, "Error") {
   232  		go func() {
   233  			<-done
   234  			if err := RunContainer(cli, args...); err != nil {
   235  				t.Fatal(err)
   236  			}
   237  		}()
   238  		go func() {
   239  			time.Sleep(3000 * time.Millisecond)
   240  			if err := CloseWrap(stdout, stdoutPipe); err != nil {
   241  				t.Fatalf("Inspect Element %s", err)
   242  			}
   243  		}()
   244  		PrintToStdout(t, stdout, stdoutPipe, "running"+args[1])
   245  	}
   246  }
   247  
   248  // GetImageID returns the ID of a docker image.
   249  func GetImageID(t *testing.T, repo string) string {
   250  	var imageID string
   251  	cli, stdout, stdoutPipe := NewClient()
   252  	go func() {
   253  		err := cli.CmdImages()
   254  		if err != nil {
   255  			t.Fatalf("GetImageID %s", err)
   256  		}
   257  		if err = CloseWrap(stdout, stdoutPipe); err != nil {
   258  			t.Fatalf("GetImageID %s", err)
   259  		}
   260  	}()
   261  	imageID = PrintToStdout(t, stdout, stdoutPipe, repo)
   262  	return strings.Fields(imageID)[2]
   263  }
   264  
   265  // RunTestEtcd starts an etcd docker container for testing.
   266  func RunTestEtcd(t *testing.T, name string, port string) {
   267  	var err error
   268  	cli, stdout, stdoutPipe := NewClient()
   269  	etcdImage := utils.ImagePrefix() + "test-etcd:" + utils.BuildTag()
   270  	ipaddr := utils.HostAddress()
   271  	etcdAddr := ipaddr + ":" + port
   272  	fmt.Printf("--- Running %s at %s\n", etcdImage, etcdAddr)
   273  	done2 := make(chan bool, 1)
   274  	go func() {
   275  		done2 <- true
   276  		_ = cli.CmdRm("-f", name)
   277  		err = RunContainer(cli,
   278  			"--name", name,
   279  			"--rm",
   280  			"-p", port+":"+port,
   281  			"-e", "HOST_IP="+ipaddr,
   282  			"-e", "ETCD_ADDR="+etcdAddr,
   283  			etcdImage)
   284  	}()
   285  	go func() {
   286  		<-done2
   287  		time.Sleep(5000 * time.Millisecond)
   288  		if err := CloseWrap(stdout, stdoutPipe); err != nil {
   289  			t.Fatalf("runEtcdTest %s", err)
   290  		}
   291  	}()
   292  	time.Sleep(1000 * time.Millisecond)
   293  	PrintToStdout(t, stdout, stdoutPipe, "pulling etcd")
   294  	if err != nil {
   295  		t.Fatal(err)
   296  	}
   297  }
   298  
   299  // registryLabel indicates which registry version we want.
   300  const registryLabel = "0.9.1"
   301  
   302  // RunTestRegistry runs a Docker registry for testing.
   303  //
   304  // This uses a stock Docker registry with no storage backend.
   305  func RunTestRegistry(t *testing.T, name, host, port string) {
   306  	var err error
   307  	cli, stdout, stdoutPipe := NewClient()
   308  	reg := "registry:" + registryLabel
   309  	fmt.Printf("--- Running %s at %s:%s\n", reg, host, port)
   310  	done := make(chan bool, 1)
   311  	go func() {
   312  		done <- true
   313  		_ = cli.CmdRm("-f", name)
   314  		err = RunContainer(cli,
   315  			"--name", name,
   316  			"--rm",
   317  			"-d",
   318  			"-p", port+":5000",
   319  			reg)
   320  	}()
   321  	go func() {
   322  		<-done
   323  		time.Sleep(5000 * time.Millisecond)
   324  		if err := CloseWrap(stdout, stdoutPipe); err != nil {
   325  			t.Fatalf("RunTestRegistry %s", err)
   326  		}
   327  	}()
   328  }