1
16
17 package node
18
19 import (
20 "context"
21 "fmt"
22 "strings"
23
24 v1 "k8s.io/api/core/v1"
25 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
26 "k8s.io/apimachinery/pkg/util/sets"
27 "k8s.io/kubernetes/test/e2e/framework"
28 e2enode "k8s.io/kubernetes/test/e2e/framework/node"
29 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
30 "k8s.io/kubernetes/test/e2e/storage/utils"
31 imageutils "k8s.io/kubernetes/test/utils/image"
32 admissionapi "k8s.io/pod-security-admission/api"
33
34 "github.com/onsi/ginkgo/v2"
35 "github.com/onsi/gomega"
36 )
37
38 func preparePod(name string, node *v1.Node, propagation *v1.MountPropagationMode, hostDir string) *v1.Pod {
39 const containerName = "cntr"
40 bTrue := true
41 var oneSecond int64 = 1
42
43 cmd := fmt.Sprintf("mkdir /mnt/test/%[1]s; sleep 3600", name)
44 pod := &v1.Pod{
45 ObjectMeta: metav1.ObjectMeta{
46 Name: name,
47 },
48 Spec: v1.PodSpec{
49 NodeName: node.Name,
50 Containers: []v1.Container{
51 {
52 Name: containerName,
53 Image: imageutils.GetE2EImage(imageutils.BusyBox),
54 Command: []string{"sh", "-c", cmd},
55 VolumeMounts: []v1.VolumeMount{
56 {
57 Name: "host",
58 MountPath: "/mnt/test",
59 MountPropagation: propagation,
60 },
61 },
62 SecurityContext: &v1.SecurityContext{
63 Privileged: &bTrue,
64 },
65 },
66 },
67 Volumes: []v1.Volume{
68 {
69 Name: "host",
70 VolumeSource: v1.VolumeSource{
71 HostPath: &v1.HostPathVolumeSource{
72 Path: hostDir,
73 },
74 },
75 },
76 },
77
78 TerminationGracePeriodSeconds: &oneSecond,
79 },
80 }
81 return pod
82 }
83
84 var _ = SIGDescribe("Mount propagation", func() {
85 f := framework.NewDefaultFramework("mount-propagation")
86 f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged
87
88 ginkgo.It("should propagate mounts within defined scopes", func(ctx context.Context) {
89
90
91
92
93
94 hostExec := utils.NewHostExec(f)
95 ginkgo.DeferCleanup(hostExec.Cleanup)
96
97
98 node, err := e2enode.GetRandomReadySchedulableNode(ctx, f.ClientSet)
99 framework.ExpectNoError(err)
100
101
102
103 if len(f.Namespace.Name) == 0 {
104 gomega.Expect(f.Namespace.Name).NotTo(gomega.BeEmpty())
105 return
106 }
107
108
109
110
111 hostDir := "/var/lib/kubelet/" + f.Namespace.Name
112 ginkgo.DeferCleanup(func(ctx context.Context) error {
113 cleanCmd := fmt.Sprintf("rm -rf %q", hostDir)
114 return hostExec.IssueCommand(ctx, cleanCmd, node)
115 })
116
117 podClient := e2epod.NewPodClient(f)
118 bidirectional := v1.MountPropagationBidirectional
119 master := podClient.CreateSync(ctx, preparePod("master", node, &bidirectional, hostDir))
120
121 hostToContainer := v1.MountPropagationHostToContainer
122 slave := podClient.CreateSync(ctx, preparePod("slave", node, &hostToContainer, hostDir))
123
124 none := v1.MountPropagationNone
125 private := podClient.CreateSync(ctx, preparePod("private", node, &none, hostDir))
126 defaultPropagation := podClient.CreateSync(ctx, preparePod("default", node, nil, hostDir))
127
128
129
130 podNames := []string{master.Name, slave.Name, private.Name, defaultPropagation.Name}
131 for _, podName := range podNames {
132 for _, dirName := range podNames {
133 cmd := fmt.Sprintf("test -d /mnt/test/%s", dirName)
134 e2epod.ExecShellInPod(ctx, f, podName, cmd)
135 }
136 }
137
138
139 for _, podName := range podNames {
140 cmd := fmt.Sprintf("mount -t tmpfs e2e-mount-propagation-%[1]s /mnt/test/%[1]s; echo %[1]s > /mnt/test/%[1]s/file", podName)
141 e2epod.ExecShellInPod(ctx, f, podName, cmd)
142
143
144 cmd = fmt.Sprintf("umount /mnt/test/%s", podName)
145 ginkgo.DeferCleanup(e2epod.ExecShellInPod, f, podName, cmd)
146 }
147
148
149
150 cmd := fmt.Sprintf("mkdir %[1]q/host; mount -t tmpfs e2e-mount-propagation-host %[1]q/host; echo host > %[1]q/host/file", hostDir)
151 err = hostExec.IssueCommand(ctx, cmd, node)
152 framework.ExpectNoError(err)
153
154 ginkgo.DeferCleanup(func(ctx context.Context) error {
155 cmd := fmt.Sprintf("umount %q/host", hostDir)
156 return hostExec.IssueCommand(ctx, cmd, node)
157 })
158
159
160
161
162 expectedMounts := map[string]sets.String{
163
164 "master": sets.NewString("master", "host"),
165
166 "slave": sets.NewString("master", "slave", "host"),
167
168 "private": sets.NewString("private"),
169
170 "default": sets.NewString("default"),
171 }
172 dirNames := append(podNames, "host")
173 for podName, mounts := range expectedMounts {
174 for _, mountName := range dirNames {
175 cmd := fmt.Sprintf("cat /mnt/test/%s/file", mountName)
176 stdout, stderr, err := e2epod.ExecShellInPodWithFullOutput(ctx, f, podName, cmd)
177 framework.Logf("pod %s mount %s: stdout: %q, stderr: %q error: %v", podName, mountName, stdout, stderr, err)
178 msg := fmt.Sprintf("When checking pod %s and directory %s", podName, mountName)
179 shouldBeVisible := mounts.Has(mountName)
180 if shouldBeVisible {
181 framework.ExpectNoError(err, "%s: failed to run %q", msg, cmd)
182 gomega.Expect(stdout).To(gomega.Equal(mountName), msg)
183 } else {
184
185 gomega.Expect(err).To(gomega.HaveOccurred(), msg)
186 }
187 }
188 }
189
190
191 cmd = "pidof kubelet"
192 kubeletPid, err := hostExec.IssueCommandWithResult(ctx, cmd, node)
193 framework.ExpectNoError(err, "Checking kubelet pid")
194 kubeletPid = strings.TrimSuffix(kubeletPid, "\n")
195 gomega.Expect(strings.Count(kubeletPid, " ")).To(gomega.Equal(0), "kubelet should only have a single PID in the system (pidof returned %q)", kubeletPid)
196 enterKubeletMountNS := fmt.Sprintf("nsenter -t %s -m", kubeletPid)
197
198
199 for _, mountName := range []string{"host", master.Name} {
200 cmd := fmt.Sprintf("%s cat \"%s/%s/file\"", enterKubeletMountNS, hostDir, mountName)
201 output, err := hostExec.IssueCommandWithResult(ctx, cmd, node)
202 framework.ExpectNoError(err, "host container namespace should see mount from %s: %s", mountName, output)
203 output = strings.TrimSuffix(output, "\n")
204 gomega.Expect(output).To(gomega.Equal(mountName), "host container namespace should see mount contents from %s", mountName)
205 }
206
207
208 for _, podName := range []string{slave.Name, private.Name, defaultPropagation.Name} {
209 cmd := fmt.Sprintf("%s test ! -e \"%s/%s/file\"", enterKubeletMountNS, hostDir, podName)
210 output, err := hostExec.IssueCommandWithResult(ctx, cmd, node)
211 framework.ExpectNoError(err, "host container namespace shouldn't see mount from %s: %s", podName, output)
212 }
213 })
214 })
215
View as plain text