1// +build functional
2
3package cri_containerd
4
5import (
6	"context"
7	"fmt"
8	"io/ioutil"
9	"os"
10	"strings"
11	"testing"
12	"time"
13
14	runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2"
15)
16
17// This test requires compiling a helper logging binary which can be found
18// at test/cri-containerd/helpers/log.go. Copy log.exe as "sample-logging-driver.exe"
19// to ContainerPlat install directory or set "TEST_BINARY_ROOT" environment variable,
20// which this test will use to construct logPath for CreateContainerRequest and as
21// the location of stdout artifacts created by the binary
22func Test_Run_Container_With_Binary_Logger(t *testing.T) {
23	client := newTestRuntimeClient(t)
24	ctx, cancel := context.WithCancel(context.Background())
25	defer cancel()
26
27	logBinaryRoot := os.Getenv("TEST_BINARY_ROOT")
28	if logBinaryRoot == "" {
29		logBinaryRoot = "/ContainerPlat"
30	}
31
32	binaryPath := logBinaryRoot + "/sample-logging-driver.exe"
33
34	if _, err := os.Stat(binaryPath); os.IsNotExist(err) {
35		t.Skip("skipping: sample logging driver missing")
36	}
37
38	logPath := "binary://" + binaryPath
39
40	type config struct {
41		name             string
42		containerName    string
43		requiredFeatures []string
44		runtimeHandler   string
45		sandboxImage     string
46		containerImage   string
47		cmd              []string
48		expectedContent  string
49	}
50
51	tests := []config{
52		{
53			name:             "WCOW_Process",
54			containerName:    t.Name() + "-Container-WCOW_Process",
55			requiredFeatures: []string{featureWCOWProcess},
56			runtimeHandler:   wcowProcessRuntimeHandler,
57			sandboxImage:     imageWindowsNanoserver,
58			containerImage:   imageWindowsNanoserver,
59			cmd:              []string{"ping", "-t", "127.0.0.1"},
60			expectedContent:  "Pinging 127.0.0.1 with 32 bytes of data",
61		},
62		{
63			name:             "WCOW_Hypervisor",
64			containerName:    t.Name() + "-Container-WCOW_Hypervisor",
65			requiredFeatures: []string{featureWCOWHypervisor},
66			runtimeHandler:   wcowHypervisorRuntimeHandler,
67			sandboxImage:     imageWindowsNanoserver,
68			containerImage:   imageWindowsNanoserver,
69			cmd:              []string{"ping", "-t", "127.0.0.1"},
70			expectedContent:  "Pinging 127.0.0.1 with 32 bytes of data",
71		},
72		{
73			name:             "LCOW",
74			containerName:    t.Name() + "-Container-LCOW",
75			requiredFeatures: []string{featureLCOW},
76			runtimeHandler:   lcowRuntimeHandler,
77			sandboxImage:     imageLcowK8sPause,
78			containerImage:   imageLcowAlpine,
79			cmd:              []string{"ash", "-c", "while true; do echo 'Hello, World!'; sleep 1; done"},
80			expectedContent:  "Hello, World!",
81		},
82	}
83
84	// Positive tests
85	for _, test := range tests {
86		t.Run(test.name+"_Positive", func(t *testing.T) {
87			requireFeatures(t, test.requiredFeatures...)
88
89			requiredImages := []string{test.sandboxImage, test.containerImage}
90			if test.runtimeHandler == lcowRuntimeHandler {
91				pullRequiredLcowImages(t, requiredImages)
92			} else {
93				pullRequiredImages(t, requiredImages)
94			}
95
96			podReq := getRunPodSandboxRequest(t, test.runtimeHandler)
97			podID := runPodSandbox(t, client, ctx, podReq)
98			defer removePodSandbox(t, client, ctx, podID)
99
100			logFileName := fmt.Sprintf("%s/stdout-%s.txt", logBinaryRoot, test.name)
101			conReq := getCreateContainerRequest(podID, test.containerName, test.containerImage, test.cmd, podReq.Config)
102			conReq.Config.LogPath = logPath + fmt.Sprintf("?%s", logFileName)
103
104			createAndRunContainer(t, client, ctx, conReq)
105
106			if _, err := os.Stat(logFileName); os.IsNotExist(err) {
107				t.Fatalf("log file was not created: %s", logFileName)
108			}
109			defer os.Remove(logFileName)
110
111			ok, err := assertFileContent(logFileName, test.expectedContent)
112			if err != nil {
113				t.Fatalf("failed to read log file: %s", err)
114			}
115
116			if !ok {
117				t.Fatalf("file content validation failed: %s", test.expectedContent)
118			}
119		})
120	}
121
122	// Negative tests
123	for _, test := range tests {
124		t.Run(test.name+"_Negative", func(t *testing.T) {
125			requireFeatures(t, test.requiredFeatures...)
126
127			requiredImages := []string{test.sandboxImage, test.containerImage}
128			if test.runtimeHandler == lcowRuntimeHandler {
129				pullRequiredLcowImages(t, requiredImages)
130			} else {
131				pullRequiredImages(t, requiredImages)
132			}
133
134			podReq := getRunPodSandboxRequest(t, test.runtimeHandler)
135			podID := runPodSandbox(t, client, ctx, podReq)
136			defer removePodSandbox(t, client, ctx, podID)
137
138			nonExistentPath := "/does/not/exist/log.txt"
139			conReq := getCreateContainerRequest(podID, test.containerName, test.containerImage, test.cmd, podReq.Config)
140			conReq.Config.LogPath = logPath + fmt.Sprintf("?%s", nonExistentPath)
141
142			containerID := createContainer(t, client, ctx, conReq)
143			defer removeContainer(t, client, ctx, containerID)
144
145			// This should fail, since the filepath doesn't exist
146			_, err := client.StartContainer(ctx, &runtime.StartContainerRequest{
147				ContainerId: containerID,
148			})
149			if err == nil {
150				t.Fatal("container start should fail")
151			}
152
153			if !strings.Contains(err.Error(), "failed to start binary logger") {
154				t.Fatalf("expected 'failed to start binary logger' error, got: %s", err)
155			}
156		})
157	}
158}
159
160func createAndRunContainer(t *testing.T, client runtime.RuntimeServiceClient, ctx context.Context, conReq *runtime.CreateContainerRequest) {
161	containerID := createContainer(t, client, ctx, conReq)
162	defer removeContainer(t, client, ctx, containerID)
163
164	startContainer(t, client, ctx, containerID)
165	defer stopContainer(t, client, ctx, containerID)
166
167	// Let stdio kick in
168	time.Sleep(time.Second * 1)
169}
170
171func assertFileContent(path string, content string) (bool, error) {
172	fileContent, err := ioutil.ReadFile(path)
173	if err != nil {
174		return false, err
175	}
176
177	return strings.Contains(string(fileContent), content), nil
178}
179