Files
kubernetes/test/e2e/windows/host_process.go
2021-11-03 14:01:53 -07:00

620 lines
18 KiB
Go

/*
Copyright 2021 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package windows
import (
"context"
"fmt"
"strings"
"time"
"github.com/onsi/ginkgo"
v1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/kubernetes/pkg/features"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
imageutils "k8s.io/kubernetes/test/utils/image"
)
const (
validation_script = `if (-not(Test-Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\emptydir)) {
throw "Cannot find emptydir volume"
}
if (-not(Test-Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\configmap\text.txt)) {
throw "Cannot find text.txt in configmap-volume"
}
$c = Get-Content -Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\configmap\text.txt
if ($c -ne "Lorem ipsum dolor sit amet") {
throw "Contents of /etc/configmap/text.txt are not as expected"
}
if (-not(Test-Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\hostpath)) {
throw "Cannot find hostpath volume"
}
if (-not(Test-Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\downwardapi\podname)) {
throw "Cannot find podname file in downward-api volume"
}
$c = Get-Content -Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\downwardapi\podname
if ($c -ne "host-process-volume-mounts") {
throw "Contents of /etc/downward-api/podname are not as expected"
}
if (-not(Test-Path $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\secret\foo.txt)) {
throw "Cannot find file foo.txt in secret volume"
}
$c = Get-Content $env:CONTAINER_SANDBOX_MOUNT_POINT\etc\secret\foo.txt
if ($c -ne "bar") {
Write-Output $c
throw "Contents of /etc/secret/foo.txt are not as expected"
}
if ($env:NODE_NAME_TEST -ne $env:COMPUTERNAME) {
throw "NODE_NAME_TEST env var does not equal COMPUTERNAME"
}
Write-Output "SUCCESS"`
)
var _ = SIGDescribe("[Feature:WindowsHostProcessContainers] [Excluded:WindowsDocker] [MinimumKubeletVersion:1.22] HostProcess containers", func() {
ginkgo.BeforeEach(func() {
e2eskipper.SkipUnlessNodeOSDistroIs("windows")
SkipUnlessWindowsHostProcessContainersEnabled()
})
f := framework.NewDefaultFramework("host-process-test-windows")
ginkgo.It("should run as a process on the host/node", func() {
ginkgo.By("selecting a Windows node")
targetNode, err := findWindowsNode(f)
framework.ExpectNoError(err, "Error finding Windows node")
framework.Logf("Using node: %v", targetNode.Name)
ginkgo.By("scheduling a pod with a container that verifies %COMPUTERNAME% matches selected node name")
image := imageutils.GetConfig(imageutils.BusyBox)
trueVar := true
podName := "host-process-test-pod"
user := "NT AUTHORITY\\Local service"
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
RunAsUserName: &user,
},
},
HostNetwork: true,
Containers: []v1.Container{
{
Image: image.GetE2EImage(),
Name: "computer-name-test",
Command: []string{"cmd.exe", "/K", "IF", "NOT", "%COMPUTERNAME%", "==", targetNode.Name, "(", "exit", "-1", ")"},
},
},
RestartPolicy: v1.RestartPolicyNever,
NodeName: targetNode.Name,
},
}
f.PodClient().Create(pod)
ginkgo.By("Waiting for pod to run")
f.PodClient().WaitForFinish(podName, 3*time.Minute)
ginkgo.By("Then ensuring pod finished running successfully")
p, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(
context.TODO(),
podName,
metav1.GetOptions{})
framework.ExpectNoError(err, "Error retrieving pod")
framework.ExpectEqual(p.Status.Phase, v1.PodSucceeded)
})
ginkgo.It("should support init containers", func() {
ginkgo.By("scheduling a pod with a container that verifies init container can configure the node")
trueVar := true
podName := "host-process-init-pods"
user := "NT AUTHORITY\\SYSTEM"
filename := fmt.Sprintf("/testfile%s.txt", string(uuid.NewUUID()))
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
RunAsUserName: &user,
},
},
HostNetwork: true,
InitContainers: []v1.Container{
{
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Name: "configure-node",
Command: []string{"powershell", "-c", "Set-content", "-Path", filename, "-V", "test"},
},
},
Containers: []v1.Container{
{
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Name: "read-configuration",
Command: []string{"powershell", "-c", "ls", filename},
},
},
RestartPolicy: v1.RestartPolicyNever,
NodeSelector: map[string]string{
"kubernetes.io/os": "windows",
},
},
}
f.PodClient().Create(pod)
ginkgo.By("Waiting for pod to run")
f.PodClient().WaitForFinish(podName, 3*time.Minute)
ginkgo.By("Then ensuring pod finished running successfully")
p, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(
context.TODO(),
podName,
metav1.GetOptions{})
framework.ExpectNoError(err, "Error retrieving pod")
if p.Status.Phase != v1.PodSucceeded {
logs, err := e2epod.GetPodLogs(f.ClientSet, f.Namespace.Name, podName, "read-configuration")
if err != nil {
framework.Logf("Error pulling logs: %v", err)
}
framework.Logf("Pod phase: %v\nlogs:\n%s", p.Status.Phase, logs)
}
framework.ExpectEqual(p.Status.Phase, v1.PodSucceeded)
})
ginkgo.It("container command path validation", func() {
// The following test cases are broken into batches to speed up the test.
// Each batch will be scheduled as a single pod with a container for each test case.
// Pods will be scheduled sequentially since the start-up cost of containers is high
// on Windows and ginkgo may also schedule test cases in parallel.
tests := [][]struct {
command []string
args []string
workingDir string
}{
{
{
command: []string{"cmd.exe", "/c", "ver"},
},
{
command: []string{"System32\\cmd.exe", "/c", "ver"},
workingDir: "c:\\Windows",
},
{
command: []string{"System32\\cmd.exe", "/c", "ver"},
workingDir: "c:\\Windows\\",
},
{
command: []string{"%CONTAINER_SANDBOX_MOUNT_POINT%\\bin\\uname.exe", "-o"},
},
},
{
{
command: []string{"%CONTAINER_SANDBOX_MOUNT_POINT%/bin/uname.exe", "-o"},
},
{
command: []string{"%CONTAINER_SANDBOX_MOUNT_POINT%\\bin/uname.exe", "-o"},
},
{
command: []string{"bin/uname.exe", "-o"},
},
{
command: []string{"bin/uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%",
},
},
{
{
command: []string{"bin\\uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%",
},
{
command: []string{"uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%/bin",
},
{
command: []string{"uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%/bin/",
},
{
command: []string{"uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%\\bin\\",
},
},
{
{
command: []string{"powershell", "cmd.exe", "/ver"},
},
{
command: []string{"powershell", "c:/Windows/System32/cmd.exe", "/c", "ver"},
},
{
command: []string{"powershell", "c:\\Windows\\System32/cmd.exe", "/c", "ver"},
},
{
command: []string{"powershell", "%CONTAINER_SANDBOX_MOUNT_POINT%\\bin\\uname.exe", "-o"},
},
},
{
{
command: []string{"powershell", "$env:CONTAINER_SANDBOX_MOUNT_POINT\\bin\\uname.exe", "-o"},
},
{
command: []string{"powershell", "%CONTAINER_SANDBOX_MOUNT_POINT%/bin/uname.exe", "-o"},
},
{
command: []string{"powershell", "$env:CONTAINER_SANDBOX_MOUNT_POINT/bin/uname.exe", "-o"},
},
{
command: []string{"powershell", "bin/uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%",
},
},
{
{
command: []string{"powershell", "bin/uname.exe", "-o"},
workingDir: "$env:CONTAINER_SANDBOX_MOUNT_POINT",
},
{
command: []string{"powershell", "bin\\uname.exe", "-o"},
workingDir: "$env:CONTAINER_SANDBOX_MOUNT_POINT",
},
{
command: []string{"powershell", ".\\uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%/bin",
},
{
command: []string{"powershell", "./uname.exe", "-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%/bin",
},
},
{
{
command: []string{"powershell", "./uname.exe", "-o"},
workingDir: "$env:CONTAINER_SANDBOX_MOUNT_POINT\\bin\\",
},
{
command: []string{"%CONTAINER_SANDBOX_MOUNT_POINT%\\bin\\uname.exe"},
args: []string{"-o"},
},
{
command: []string{"bin\\uname.exe"},
args: []string{"-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%",
},
{
command: []string{"uname.exe"},
args: []string{"-o"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%\\bin",
},
},
{
{
command: []string{"cmd.exe"},
args: []string{"/c", "dir", "%CONTAINER_SANDBOX_MOUNT_POINT%\\bin\\uname.exe"},
},
{
command: []string{"cmd.exe"},
args: []string{"/c", "dir", "bin\\uname.exe"},
workingDir: "%CONTAINER_SANDBOX_MOUNT_POINT%",
},
{
command: []string{"powershell"},
args: []string{"Get-ChildItem", "-Path", "$env:CONTAINER_SANDBOX_MOUNT_POINT\\bin\\uname.exe"},
},
{
command: []string{"powershell"},
args: []string{"Get-ChildItem", "-Path", "bin\\uname.exe"},
workingDir: "$env:CONTAINER_SANDBOX_MOUNT_POINT",
},
},
{
{
command: []string{"powershell"},
args: []string{"Get-ChildItem", "-Path", "uname.exe"},
workingDir: "$env:CONTAINER_SANDBOX_MOUNT_POINT/bin",
},
},
}
for podIndex, testCaseBatch := range tests {
image := imageutils.GetConfig(imageutils.BusyBox)
podName := fmt.Sprintf("host-process-command-%d", podIndex)
containers := []v1.Container{}
for containerIndex, testCase := range testCaseBatch {
containerName := fmt.Sprintf("host-process-command-%d-%d", podIndex, containerIndex)
ginkgo.By(fmt.Sprintf("Adding a container '%s' to pod '%s' with command: %s, args: %s, workingDir: %s", containerName, podName, strings.Join(testCase.command, " "), strings.Join(testCase.args, " "), testCase.workingDir))
container := v1.Container{
Image: image.GetE2EImage(),
Name: containerName,
Command: testCase.command,
Args: testCase.args,
WorkingDir: testCase.workingDir,
}
containers = append(containers, container)
}
trueVar := true
user := "NT AUTHORITY\\Local Service"
pod := &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: podName,
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
RunAsUserName: &user,
},
},
HostNetwork: true,
Containers: containers,
RestartPolicy: v1.RestartPolicyNever,
NodeSelector: map[string]string{
"kubernetes.io/os": "windows",
},
},
}
f.PodClient().Create(pod)
ginkgo.By(fmt.Sprintf("Waiting for pod '%s' to run", podName))
f.PodClient().WaitForFinish(podName, 3*time.Minute)
ginkgo.By("Then ensuring pod finished running successfully")
p, err := f.ClientSet.CoreV1().Pods(f.Namespace.Name).Get(
context.TODO(),
podName,
metav1.GetOptions{})
framework.ExpectNoError(err, "Error retrieving pod")
if p.Status.Phase != v1.PodSucceeded {
framework.Logf("Getting pod events")
options := metav1.ListOptions{
FieldSelector: fields.Set{
"involvedObject.kind": "Pod",
"involvedObject.name": podName,
"involvedObject.namespace": f.Namespace.Name,
}.AsSelector().String(),
}
events, err := f.ClientSet.CoreV1().Events(f.Namespace.Name).List(context.TODO(), options)
framework.ExpectNoError(err, "Error getting events for failed pod")
for _, event := range events.Items {
framework.Logf("%s: %s", event.Reason, event.Message)
}
framework.Failf("Pod '%s' did failed.", p.Name)
}
}
})
ginkgo.It("should support various volume mount types", func() {
ns := f.Namespace
ginkgo.By("Creating a configmap containing test data and a validation script")
configMap := &v1.ConfigMap{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "ConfigMap",
},
ObjectMeta: metav1.ObjectMeta{
Name: "sample-config-map",
},
Data: map[string]string{
"text": "Lorem ipsum dolor sit amet",
"validation-script": validation_script,
},
}
_, err := f.ClientSet.CoreV1().ConfigMaps(ns.Name).Create(context.TODO(), configMap, metav1.CreateOptions{})
framework.ExpectNoError(err, "unable to create create configmap")
ginkgo.By("Creating a secret containing test data")
secret := &v1.Secret{
TypeMeta: metav1.TypeMeta{
APIVersion: "v1",
Kind: "Secret",
},
ObjectMeta: metav1.ObjectMeta{
Name: "sample-secret",
},
Type: v1.SecretTypeOpaque,
Data: map[string][]byte{
"foo": []byte("bar"),
},
}
_, err = f.ClientSet.CoreV1().Secrets(ns.Name).Create(context.TODO(), secret, metav1.CreateOptions{})
framework.ExpectNoError(err, "unable to create secret")
ginkgo.By("Creating a pod with a HostProcess container that uses various types of volume mounts")
podAndContainerName := "host-process-volume-mounts"
pod := makeTestPodWithVolumeMounts(podAndContainerName)
f.PodClient().Create(pod)
ginkgo.By("Waiting for pod to run")
f.PodClient().WaitForFinish(podAndContainerName, 3*time.Minute)
logs, err := e2epod.GetPodLogs(f.ClientSet, ns.Name, podAndContainerName, podAndContainerName)
framework.ExpectNoError(err, "Error getting pod logs")
framework.Logf("Container logs: %s", logs)
ginkgo.By("Then ensuring pod finished running successfully")
p, err := f.ClientSet.CoreV1().Pods(ns.Name).Get(
context.TODO(),
podAndContainerName,
metav1.GetOptions{})
framework.ExpectNoError(err, "Error retrieving pod")
framework.ExpectEqual(p.Status.Phase, v1.PodSucceeded)
})
})
func makeTestPodWithVolumeMounts(name string) *v1.Pod {
trueVar := true
username := "NT AUTHORITY\\SYSTEM"
hostPathDirectoryOrCreate := v1.HostPathDirectoryOrCreate
return &v1.Pod{
ObjectMeta: metav1.ObjectMeta{
Name: name,
},
Spec: v1.PodSpec{
SecurityContext: &v1.PodSecurityContext{
WindowsOptions: &v1.WindowsSecurityContextOptions{
HostProcess: &trueVar,
RunAsUserName: &username,
},
},
HostNetwork: true,
Containers: []v1.Container{
{
Image: imageutils.GetE2EImage(imageutils.BusyBox),
Name: name,
Command: []string{"powershell.exe", "./etc/configmap/validationscript.ps1"},
Env: []v1.EnvVar{
{
Name: "NODE_NAME_TEST",
ValueFrom: &v1.EnvVarSource{
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "spec.nodeName",
},
},
},
},
VolumeMounts: []v1.VolumeMount{
{
Name: "emptydir-volume",
MountPath: "/etc/emptydir",
},
{
Name: "configmap-volume",
MountPath: "/etc/configmap",
},
{
Name: "hostpath-volume",
MountPath: "/etc/hostpath",
},
{
Name: "downwardapi-volume",
MountPath: "/etc/downwardapi",
},
{
Name: "secret-volume",
MountPath: "/etc/secret",
},
},
},
},
RestartPolicy: v1.RestartPolicyNever,
NodeSelector: map[string]string{
"kubernetes.io/os": "windows",
},
Volumes: []v1.Volume{
{
Name: "emptydir-volume",
VolumeSource: v1.VolumeSource{
EmptyDir: &v1.EmptyDirVolumeSource{
Medium: v1.StorageMediumDefault,
},
},
},
{
Name: "configmap-volume",
VolumeSource: v1.VolumeSource{
ConfigMap: &v1.ConfigMapVolumeSource{
LocalObjectReference: v1.LocalObjectReference{
Name: "sample-config-map",
},
Items: []v1.KeyToPath{
{
Key: "text",
Path: "text.txt",
},
{
Key: "validation-script",
Path: "validationscript.ps1",
},
},
},
},
},
{
Name: "hostpath-volume",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: "/var/hostpath",
Type: &hostPathDirectoryOrCreate,
},
},
},
{
Name: "downwardapi-volume",
VolumeSource: v1.VolumeSource{
DownwardAPI: &v1.DownwardAPIVolumeSource{
Items: []v1.DownwardAPIVolumeFile{
{
Path: "podname",
FieldRef: &v1.ObjectFieldSelector{
FieldPath: "metadata.name",
},
},
},
},
},
},
{
Name: "secret-volume",
VolumeSource: v1.VolumeSource{
Secret: &v1.SecretVolumeSource{
SecretName: "sample-secret",
Items: []v1.KeyToPath{
{
Key: "foo",
Path: "foo.txt",
},
},
},
},
},
},
},
}
}
func SkipUnlessWindowsHostProcessContainersEnabled() {
if !framework.TestContext.FeatureGates[string(features.WindowsHostProcessContainers)] {
e2eskipper.Skipf("Skipping test because feature 'WindowsHostProcessContainers' is not enabled")
}
}