Browse code

integ-cli: Implement remote FakeStorage server for build via URL tests

Implemented a FakeStorage alternative that supports spinning
up a remote container on DOCKER_TEST_HOST to serve files over
an offline-compiled Go static web server image so that tests which
use URLs in Dockerfile can build them over at the daemon side.

`fakeStorage` function now automatically chooses if it should
use a local httptest.Server or a remote container.

This fixes the following tests when running against a remote
daemon:

- `TestBuildCacheADD`
- `TestBuildCopyWildcardNoFind`
- `TestBuildCopyWildcardCache`
- `TestBuildADDRemoteFileWithCache`
- `TestBuildADDRemoteFileWithoutCache`
- `TestBuildADDRemoteFileMTime`
- `TestBuildADDLocalAndRemoteFilesWithCache`
- `TestBuildADDLocalAndRemoteFilesWithoutCache`
- `TestBuildFromURLWithF`
- `TestBuildApiDockerFileRemote`

Signed-off-by: Ahmet Alp Balkan <ahmetalpbalkan@gmail.com>

Ahmet Alp Balkan authored on 2015/02/19 19:01:27
Showing 7 changed files
1 1
new file mode 100644
... ...
@@ -0,0 +1,4 @@
0
+FROM busybox
1
+EXPOSE 80/tcp
2
+COPY httpserver .
3
+CMD ["./httpserver"]
0 4
new file mode 100644
... ...
@@ -0,0 +1,12 @@
0
+package main
1
+
2
+import (
3
+	"log"
4
+	"net/http"
5
+)
6
+
7
+func main() {
8
+	fs := http.FileServer(http.Dir("/static"))
9
+	http.Handle("/", fs)
10
+	log.Panic(http.ListenAndServe(":80", nil))
11
+}
... ...
@@ -364,7 +364,7 @@ RUN find /tmp/`,
364 364
 	}
365 365
 	defer server.Close()
366 366
 
367
-	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL+"/testD", nil, "application/json")
367
+	buf, err := sockRequestRaw("POST", "/build?dockerfile=baz&remote="+server.URL()+"/testD", nil, "application/json")
368 368
 	if err != nil {
369 369
 		t.Fatalf("Build failed: %s", err)
370 370
 	}
... ...
@@ -8,14 +8,12 @@ import (
8 8
 	"io/ioutil"
9 9
 	"os"
10 10
 	"os/exec"
11
-	"path"
12 11
 	"path/filepath"
13 12
 	"reflect"
14 13
 	"regexp"
15 14
 	"runtime"
16 15
 	"strconv"
17 16
 	"strings"
18
-	"syscall"
19 17
 	"testing"
20 18
 	"text/template"
21 19
 	"time"
... ...
@@ -645,9 +643,10 @@ func TestBuildCacheADD(t *testing.T) {
645 645
 		t.Fatal(err)
646 646
 	}
647 647
 	defer server.Close()
648
+
648 649
 	if _, err := buildImage(name,
649 650
 		fmt.Sprintf(`FROM scratch
650
-		ADD %s/robots.txt /`, server.URL),
651
+		ADD %s/robots.txt /`, server.URL()),
651 652
 		true); err != nil {
652 653
 		t.Fatal(err)
653 654
 	}
... ...
@@ -657,7 +656,7 @@ func TestBuildCacheADD(t *testing.T) {
657 657
 	deleteImages(name)
658 658
 	_, out, err := buildImageWithOut(name,
659 659
 		fmt.Sprintf(`FROM scratch
660
-		ADD %s/index.html /`, server.URL),
660
+		ADD %s/index.html /`, server.URL()),
661 661
 		true)
662 662
 	if err != nil {
663 663
 		t.Fatal(err)
... ...
@@ -797,7 +796,7 @@ RUN [ $(ls -l /exists/test_file4 | awk '{print $3":"$4}') = 'root:root' ]
797 797
 RUN [ $(ls -l /exists/robots.txt | awk '{print $3":"$4}') = 'root:root' ]
798 798
 
799 799
 RUN [ $(ls -l /exists/exists_file | awk '{print $3":"$4}') = 'dockerio:dockerio' ]
800
-`, server.URL),
800
+`, server.URL()),
801 801
 		map[string]string{
802 802
 			"test_file1": "test1",
803 803
 			"test_file2": "test2",
... ...
@@ -1084,6 +1083,7 @@ func TestBuildCopyWildcard(t *testing.T) {
1084 1084
 		t.Fatal(err)
1085 1085
 	}
1086 1086
 	defer server.Close()
1087
+
1087 1088
 	ctx, err := fakeContext(fmt.Sprintf(`FROM busybox
1088 1089
 	COPY file*.txt /tmp/
1089 1090
 	RUN ls /tmp/file1.txt /tmp/file2.txt
... ...
@@ -1093,7 +1093,7 @@ func TestBuildCopyWildcard(t *testing.T) {
1093 1093
 	RUN mkdir /tmp2
1094 1094
         ADD dir/*dir %s/robots.txt /tmp2/
1095 1095
 	RUN ls /tmp2/nest_nest_file /tmp2/robots.txt
1096
-	`, server.URL),
1096
+	`, server.URL()),
1097 1097
 		map[string]string{
1098 1098
 			"file1.txt":                     "test1",
1099 1099
 			"file2.txt":                     "test2",
... ...
@@ -2831,10 +2831,11 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) {
2831 2831
 		t.Fatal(err)
2832 2832
 	}
2833 2833
 	defer server.Close()
2834
+
2834 2835
 	id1, err := buildImage(name,
2835 2836
 		fmt.Sprintf(`FROM scratch
2836 2837
         MAINTAINER dockerio
2837
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
2838
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
2838 2839
 		true)
2839 2840
 	if err != nil {
2840 2841
 		t.Fatal(err)
... ...
@@ -2842,7 +2843,7 @@ func TestBuildADDRemoteFileWithCache(t *testing.T) {
2842 2842
 	id2, err := buildImage(name,
2843 2843
 		fmt.Sprintf(`FROM scratch
2844 2844
         MAINTAINER dockerio
2845
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
2845
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
2846 2846
 		true)
2847 2847
 	if err != nil {
2848 2848
 		t.Fatal(err)
... ...
@@ -2864,10 +2865,11 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
2864 2864
 		t.Fatal(err)
2865 2865
 	}
2866 2866
 	defer server.Close()
2867
+
2867 2868
 	id1, err := buildImage(name,
2868 2869
 		fmt.Sprintf(`FROM scratch
2869 2870
         MAINTAINER dockerio
2870
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
2871
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
2871 2872
 		true)
2872 2873
 	if err != nil {
2873 2874
 		t.Fatal(err)
... ...
@@ -2875,7 +2877,7 @@ func TestBuildADDRemoteFileWithoutCache(t *testing.T) {
2875 2875
 	id2, err := buildImage(name2,
2876 2876
 		fmt.Sprintf(`FROM scratch
2877 2877
         MAINTAINER dockerio
2878
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
2878
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
2879 2879
 		false)
2880 2880
 	if err != nil {
2881 2881
 		t.Fatal(err)
... ...
@@ -2894,7 +2896,8 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
2894 2894
 
2895 2895
 	defer deleteImages(name, name2, name3, name4)
2896 2896
 
2897
-	server, err := fakeStorage(map[string]string{"baz": "hello"})
2897
+	files := map[string]string{"baz": "hello"}
2898
+	server, err := fakeStorage(files)
2898 2899
 	if err != nil {
2899 2900
 		t.Fatal(err)
2900 2901
 	}
... ...
@@ -2902,7 +2905,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
2902 2902
 
2903 2903
 	ctx, err := fakeContext(fmt.Sprintf(`FROM scratch
2904 2904
         MAINTAINER dockerio
2905
-        ADD %s/baz /usr/lib/baz/quux`, server.URL), nil)
2905
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()), nil)
2906 2906
 	if err != nil {
2907 2907
 		t.Fatal(err)
2908 2908
 	}
... ...
@@ -2921,15 +2924,26 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
2921 2921
 		t.Fatal("The cache should have been used but wasn't - #1")
2922 2922
 	}
2923 2923
 
2924
-	// Now set baz's times to anything else and redo the build
2924
+	// Now create a different server withsame contents (causes different mtim)
2925 2925
 	// This time the cache should not be used
2926
-	bazPath := path.Join(server.FakeContext.Dir, "baz")
2927
-	err = syscall.UtimesNano(bazPath, make([]syscall.Timespec, 2))
2926
+
2927
+	// allow some time for clock to pass as mtime precision is only 1s
2928
+	time.Sleep(2 * time.Second)
2929
+
2930
+	server2, err := fakeStorage(files)
2928 2931
 	if err != nil {
2929
-		t.Fatalf("Error setting mtime on %q: %v", bazPath, err)
2932
+		t.Fatal(err)
2930 2933
 	}
2934
+	defer server2.Close()
2931 2935
 
2932
-	id3, err := buildImageFromContext(name3, ctx, true)
2936
+	ctx2, err := fakeContext(fmt.Sprintf(`FROM scratch
2937
+        MAINTAINER dockerio
2938
+        ADD %s/baz /usr/lib/baz/quux`, server2.URL()), nil)
2939
+	if err != nil {
2940
+		t.Fatal(err)
2941
+	}
2942
+	defer ctx2.Close()
2943
+	id3, err := buildImageFromContext(name3, ctx2, true)
2933 2944
 	if err != nil {
2934 2945
 		t.Fatal(err)
2935 2946
 	}
... ...
@@ -2938,7 +2952,7 @@ func TestBuildADDRemoteFileMTime(t *testing.T) {
2938 2938
 	}
2939 2939
 
2940 2940
 	// And for good measure do it again and make sure cache is used this time
2941
-	id4, err := buildImageFromContext(name4, ctx, true)
2941
+	id4, err := buildImageFromContext(name4, ctx2, true)
2942 2942
 	if err != nil {
2943 2943
 		t.Fatal(err)
2944 2944
 	}
... ...
@@ -2958,10 +2972,11 @@ func TestBuildADDLocalAndRemoteFilesWithCache(t *testing.T) {
2958 2958
 		t.Fatal(err)
2959 2959
 	}
2960 2960
 	defer server.Close()
2961
+
2961 2962
 	ctx, err := fakeContext(fmt.Sprintf(`FROM scratch
2962 2963
         MAINTAINER dockerio
2963 2964
         ADD foo /usr/lib/bla/bar
2964
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
2965
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
2965 2966
 		map[string]string{
2966 2967
 			"foo": "hello world",
2967 2968
 		})
... ...
@@ -3047,10 +3062,11 @@ func TestBuildADDLocalAndRemoteFilesWithoutCache(t *testing.T) {
3047 3047
 		t.Fatal(err)
3048 3048
 	}
3049 3049
 	defer server.Close()
3050
+
3050 3051
 	ctx, err := fakeContext(fmt.Sprintf(`FROM scratch
3051 3052
         MAINTAINER dockerio
3052 3053
         ADD foo /usr/lib/bla/bar
3053
-        ADD %s/baz /usr/lib/baz/quux`, server.URL),
3054
+        ADD %s/baz /usr/lib/baz/quux`, server.URL()),
3054 3055
 		map[string]string{
3055 3056
 			"foo": "hello world",
3056 3057
 		})
... ...
@@ -4773,7 +4789,7 @@ RUN echo from Dockerfile`,
4773 4773
 
4774 4774
 	// Make sure that -f is ignored and that we don't use the Dockerfile
4775 4775
 	// that's in the current dir
4776
-	out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL+"/baz")
4776
+	out, _, err := dockerCmdInDir(t, ctx.Dir, "build", "-f", "baz", "-t", "test1", server.URL()+"/baz")
4777 4777
 	if err != nil {
4778 4778
 		t.Fatalf("Failed to build: %s\n%s", out, err)
4779 4779
 	}
... ...
@@ -581,17 +581,42 @@ func fakeContext(dockerfile string, files map[string]string) (*FakeContext, erro
581 581
 	return ctx, nil
582 582
 }
583 583
 
584
-type FakeStorage struct {
584
+// FakeStorage is a static file server. It might be running locally or remotely
585
+// on test host.
586
+type FakeStorage interface {
587
+	Close() error
588
+	URL() string
589
+	CtxDir() string
590
+}
591
+
592
+// fakeStorage returns either a local or remote (at daemon machine) file server
593
+func fakeStorage(files map[string]string) (FakeStorage, error) {
594
+	if isLocalDaemon {
595
+		return newLocalFakeStorage(files)
596
+	}
597
+	return newRemoteFileServer(files)
598
+}
599
+
600
+// localFileStorage is a file storage on the running machine
601
+type localFileStorage struct {
585 602
 	*FakeContext
586 603
 	*httptest.Server
587 604
 }
588 605
 
589
-func (f *FakeStorage) Close() error {
590
-	f.Server.Close()
591
-	return f.FakeContext.Close()
606
+func (s *localFileStorage) URL() string {
607
+	return s.Server.URL
608
+}
609
+
610
+func (s *localFileStorage) CtxDir() string {
611
+	return s.FakeContext.Dir
612
+}
613
+
614
+func (s *localFileStorage) Close() error {
615
+	defer s.Server.Close()
616
+	return s.FakeContext.Close()
592 617
 }
593 618
 
594
-func fakeStorage(files map[string]string) (*FakeStorage, error) {
619
+func newLocalFakeStorage(files map[string]string) (*localFileStorage, error) {
595 620
 	tmp, err := ioutil.TempDir("", "fake-storage")
596 621
 	if err != nil {
597 622
 		return nil, err
... ...
@@ -605,42 +630,101 @@ func fakeStorage(files map[string]string) (*FakeStorage, error) {
605 605
 	}
606 606
 	handler := http.FileServer(http.Dir(ctx.Dir))
607 607
 	server := httptest.NewServer(handler)
608
-	return &FakeStorage{
608
+	return &localFileStorage{
609 609
 		FakeContext: ctx,
610 610
 		Server:      server,
611 611
 	}, nil
612 612
 }
613 613
 
614
-func inspectField(name, field string) (string, error) {
615
-	format := fmt.Sprintf("{{.%s}}", field)
616
-	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
617
-	out, exitCode, err := runCommandWithOutput(inspectCmd)
618
-	if err != nil || exitCode != 0 {
619
-		return "", fmt.Errorf("failed to inspect %s: %s", name, out)
614
+// remoteFileServer is a containerized static file server started on the remote
615
+// testing machine to be used in URL-accepting docker build functionality.
616
+type remoteFileServer struct {
617
+	host      string // hostname/port web server is listening to on docker host e.g. 0.0.0.0:43712
618
+	container string
619
+	image     string
620
+	ctx       *FakeContext
621
+}
622
+
623
+func (f *remoteFileServer) URL() string {
624
+	u := url.URL{
625
+		Scheme: "http",
626
+		Host:   f.host}
627
+	return u.String()
628
+}
629
+
630
+func (f *remoteFileServer) CtxDir() string {
631
+	return f.ctx.Dir
632
+}
633
+
634
+func (f *remoteFileServer) Close() error {
635
+	defer func() {
636
+		if f.ctx != nil {
637
+			f.ctx.Close()
638
+		}
639
+		if f.image != "" {
640
+			deleteImages(f.image)
641
+		}
642
+	}()
643
+	if f.container == "" {
644
+		return nil
620 645
 	}
621
-	return strings.TrimSpace(out), nil
646
+	return deleteContainer(f.container)
622 647
 }
623 648
 
624
-func inspectFieldJSON(name, field string) (string, error) {
625
-	format := fmt.Sprintf("{{json .%s}}", field)
626
-	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
627
-	out, exitCode, err := runCommandWithOutput(inspectCmd)
628
-	if err != nil || exitCode != 0 {
629
-		return "", fmt.Errorf("failed to inspect %s: %s", name, out)
649
+func newRemoteFileServer(files map[string]string) (*remoteFileServer, error) {
650
+	var (
651
+		image     = fmt.Sprintf("fileserver-img-%s", strings.ToLower(makeRandomString(10)))
652
+		container = fmt.Sprintf("fileserver-cnt-%s", strings.ToLower(makeRandomString(10)))
653
+	)
654
+
655
+	// Build the image
656
+	ctx, err := fakeContext(`FROM httpserver
657
+COPY . /static`, files)
658
+	if _, err := buildImageFromContext(image, ctx, false); err != nil {
659
+		return nil, fmt.Errorf("failed building file storage container image: %v", err)
630 660
 	}
631
-	return strings.TrimSpace(out), nil
661
+
662
+	// Start the container
663
+	runCmd := exec.Command(dockerBinary, "run", "-d", "-P", "--name", container, image)
664
+	if out, ec, err := runCommandWithOutput(runCmd); err != nil {
665
+		return nil, fmt.Errorf("failed to start file storage container. ec=%v\nout=%s\nerr=%v", ec, out, err)
666
+	}
667
+
668
+	// Find out the system assigned port
669
+	out, _, err := runCommandWithOutput(exec.Command(dockerBinary, "port", container, "80/tcp"))
670
+	if err != nil {
671
+		return nil, fmt.Errorf("failed to find container port: err=%v\nout=%s", err, out)
672
+	}
673
+
674
+	return &remoteFileServer{
675
+		container: container,
676
+		image:     image,
677
+		host:      strings.Trim(out, "\n"),
678
+		ctx:       ctx}, nil
632 679
 }
633 680
 
634
-func inspectFieldMap(name, path, field string) (string, error) {
635
-	format := fmt.Sprintf("{{index .%s %q}}", path, field)
681
+func inspectFilter(name, filter string) (string, error) {
682
+	format := fmt.Sprintf("{{%s}}", filter)
636 683
 	inspectCmd := exec.Command(dockerBinary, "inspect", "-f", format, name)
637 684
 	out, exitCode, err := runCommandWithOutput(inspectCmd)
638 685
 	if err != nil || exitCode != 0 {
639
-		return "", fmt.Errorf("failed to inspect %s: %s", name, out)
686
+		return "", fmt.Errorf("failed to inspect container %s: %s", name, out)
640 687
 	}
641 688
 	return strings.TrimSpace(out), nil
642 689
 }
643 690
 
691
+func inspectField(name, field string) (string, error) {
692
+	return inspectFilter(name, fmt.Sprintf(".%s", field))
693
+}
694
+
695
+func inspectFieldJSON(name, field string) (string, error) {
696
+	return inspectFilter(name, fmt.Sprintf("json .%s", field))
697
+}
698
+
699
+func inspectFieldMap(name, path, field string) (string, error) {
700
+	return inspectFilter(name, fmt.Sprintf("index .%s %q", path, field))
701
+}
702
+
644 703
 func getIDByName(name string) (string, error) {
645 704
 	return inspectField(name, "Id")
646 705
 }
647 706
new file mode 100644
... ...
@@ -0,0 +1,15 @@
0
+#!/bin/bash
1
+set -e
2
+
3
+# Build a Go static web server on top of busybox image
4
+# and compile it for target daemon
5
+
6
+dir="$DEST/httpserver"
7
+mkdir -p "$dir"
8
+(
9
+	cd "$dir"
10
+	GOOS=linux GOARCH=amd64 go build -o httpserver github.com/docker/docker/contrib/httpserver
11
+	cp ../../../../contrib/httpserver/Dockerfile .
12
+	docker build -qt httpserver . > /dev/null
13
+)
14
+rm -rf "$dir"
... ...
@@ -17,6 +17,7 @@ bundle_test_integration_cli() {
17 17
 	didFail=
18 18
 	if ! {
19 19
 		source "$(dirname "$BASH_SOURCE")/.ensure-busybox"
20
+		source "$(dirname "$BASH_SOURCE")/.ensure-httpserver"
20 21
 		source "$(dirname "$BASH_SOURCE")/.ensure-emptyfs"
21 22
 
22 23
 		bundle_test_integration_cli