Skip to content

Commit

Permalink
Add end-to-end tests for local cache
Browse files Browse the repository at this point in the history
Signed-off-by: Burak Varlı <burakvar@amazon.co.uk>
  • Loading branch information
unexge committed Nov 13, 2024
1 parent f8cc674 commit 2f86183
Show file tree
Hide file tree
Showing 3 changed files with 211 additions and 0 deletions.
1 change: 1 addition & 0 deletions tests/e2e-kubernetes/e2e_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ var CSITestSuites = []func() framework.TestSuite{
custom_testsuites.InitS3CSIMultiVolumeTestSuite,
custom_testsuites.InitS3MountOptionsTestSuite,
custom_testsuites.InitS3CSICredentialsTestSuite,
custom_testsuites.InitS3CSICacheTestSuite,
}

// This executes testSuites for csi volumes.
Expand Down
188 changes: 188 additions & 0 deletions tests/e2e-kubernetes/testsuites/cache.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
package custom_testsuites

import (
"context"
"fmt"
"path/filepath"
"slices"

"github.com/google/uuid"
. "github.com/onsi/ginkgo/v2"
v1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/util/errors"
"k8s.io/kubernetes/test/e2e/framework"
e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
e2eskipper "k8s.io/kubernetes/test/e2e/framework/skipper"
e2evolume "k8s.io/kubernetes/test/e2e/framework/volume"
storageframework "k8s.io/kubernetes/test/e2e/storage/framework"
admissionapi "k8s.io/pod-security-admission/api"
"k8s.io/utils/ptr"
)

const volumeName1 = "volume1"
const root = int64(0)
const defaultNonRootGroup = int64(2000)

type s3CSICacheTestSuite struct {
tsInfo storageframework.TestSuiteInfo
}

func InitS3CSICacheTestSuite() storageframework.TestSuite {
return &s3CSICacheTestSuite{
tsInfo: storageframework.TestSuiteInfo{
Name: "cache",
TestPatterns: []storageframework.TestPattern{
storageframework.DefaultFsPreprovisionedPV,
},
},
}
}

func (t *s3CSICacheTestSuite) GetTestSuiteInfo() storageframework.TestSuiteInfo {
return t.tsInfo
}

func (t *s3CSICacheTestSuite) SkipUnsupportedTests(_ storageframework.TestDriver, pattern storageframework.TestPattern) {
if pattern.VolType != storageframework.PreprovisionedPV {
e2eskipper.Skipf("Suite %q does not support %v", t.tsInfo.Name, pattern.VolType)
}
}

func (t *s3CSICacheTestSuite) DefineTests(driver storageframework.TestDriver, pattern storageframework.TestPattern) {
f := framework.NewFrameworkWithCustomTimeouts(NamespacePrefix+"cache", storageframework.GetDriverTimeouts(driver))
f.NamespacePodSecurityLevel = admissionapi.LevelPrivileged

type local struct {
config *storageframework.PerTestConfig

// A list of cleanup functions to be called after each test to clean resources created during the test.
cleanup []func(context.Context) error
}

var l local

deferCleanup := func(f func(context.Context) error) {
l.cleanup = append(l.cleanup, f)
}

cleanup := func(ctx context.Context) {
var errs []error
slices.Reverse(l.cleanup) // clean items in reverse order similar to how `defer` works
for _, f := range l.cleanup {
errs = append(errs, f(ctx))
}
framework.ExpectNoError(errors.NewAggregate(errs), "while cleanup resource")
}
BeforeEach(func(ctx context.Context) {
l = local{}
l.config = driver.PrepareTest(ctx, f)
DeferCleanup(cleanup)
})

createPod := func(ctx context.Context, additionalMountOptions []string, podModifiers ...func(*v1.Pod)) *v1.Pod {
cacheDir := randomCacheDir()

vol := createVolumeResourceWithMountOptions(ctx, l.config, pattern, append(additionalMountOptions, fmt.Sprintf("cache %s", cacheDir)))
deferCleanup(vol.CleanupResource)

pod := e2epod.MakePod(f.Namespace.Name, nil, []*v1.PersistentVolumeClaim{vol.Pvc}, admissionapi.LevelPrivileged, "")
ensureCacheDirExistsInNode(pod, cacheDir)
for _, pm := range podModifiers {
pm(pod)
}

pod, err := createPod(ctx, f.ClientSet, f.Namespace.Name, pod)
framework.ExpectNoError(err)
deferCleanup(func(ctx context.Context) error { return e2epod.DeletePodWithWait(ctx, f.ClientSet, pod) })

return pod
}

Describe("Cache", func() {
Describe("Local", func() {
It("basic file operations as root", func(ctx context.Context) {
pod := createPod(ctx, []string{"allow-delete"}, func(pod *v1.Pod) {
pod.Spec.Containers[0].SecurityContext.RunAsUser = ptr.To(root)
pod.Spec.Containers[0].SecurityContext.RunAsGroup = ptr.To(root)
})
checkBasicFileOperations(f, pod, e2epod.VolumeMountPath1)
})

It("basic file operations as non-root", func(ctx context.Context) {
mountOptions := []string{
"allow-delete",
"allow-other",
fmt.Sprintf("uid=%d", *e2epod.GetDefaultNonRootUser()),
fmt.Sprintf("gid=%d", defaultNonRootGroup),
}
pod := createPod(ctx, mountOptions, func(pod *v1.Pod) {
pod.Spec.Containers[0].SecurityContext.RunAsUser = e2epod.GetDefaultNonRootUser()
pod.Spec.Containers[0].SecurityContext.RunAsGroup = ptr.To(defaultNonRootGroup)
pod.Spec.Containers[0].SecurityContext.RunAsNonRoot = ptr.To(true)
})

checkBasicFileOperations(f, pod, e2epod.VolumeMountPath1)
})

It("two containers in the same pod using the same cache", func(ctx context.Context) {
testFile := filepath.Join(e2epod.VolumeMountPath1, "helloworld.txt")

pod := createPod(ctx, []string{"allow-delete"}, func(pod *v1.Pod) {
// Make it init container to ensure it runs before regular containers
pod.Spec.InitContainers = append(pod.Spec.InitContainers, v1.Container{
Name: "populate-cache",
Image: e2epod.GetDefaultTestImage(),
Command: e2epod.GenerateScriptCmd(
fmt.Sprintf("echo 'hello world!' > %s", testFile)),
VolumeMounts: []v1.VolumeMount{
{
Name: volumeName1,
MountPath: e2epod.VolumeMountPath1,
},
},
})
})

e2evolume.VerifyExecInPodSucceed(f, pod, fmt.Sprintf("cat %s | grep -q 'hello world!'", testFile))
})
})
})
}

func randomCacheDir() string {
return filepath.Join("/tmp/mp-cache", uuid.New().String())
}

// ensureCacheDirExistsInNode adds a hostPath for given `cacheDir` with `DirectoryOrCreate` type.
// This hack required because Mountpoint process is running on the underlying host and not inside the container,
// so we need to ensure cache directory exists on the host.
// This hack hopefully will go away with https://github.com/awslabs/mountpoint-s3-csi-driver/issues/279.
func ensureCacheDirExistsInNode(pod *v1.Pod, cacheDir string) {
cacheVolumeMount := v1.VolumeMount{
Name: "make-cache-dir",
MountPath: "/cache",
}

// The directory created with `DirectoryOrCreate` will have 0755 permissions and will be owned by kubelet.
// Unless we change permissions here, non-root containers won't be able to access to the cache dir.
pod.Spec.InitContainers = append(pod.Spec.DeepCopy().InitContainers, v1.Container{
Name: "chmod-cache-dir",
Image: e2epod.GetDefaultTestImage(),
Command: e2epod.GenerateScriptCmd("chmod -R 777 /cache"),
SecurityContext: &v1.SecurityContext{
RunAsUser: ptr.To(root),
RunAsGroup: ptr.To(root),
},
VolumeMounts: []v1.VolumeMount{cacheVolumeMount},
})
pod.Spec.Volumes = append(pod.Spec.Volumes, v1.Volume{
Name: "make-cache-dir",
VolumeSource: v1.VolumeSource{
HostPath: &v1.HostPathVolumeSource{
Path: cacheDir,
Type: ptr.To(v1.HostPathDirectoryOrCreate),
},
},
})
pod.Spec.Containers[0].VolumeMounts = append(pod.Spec.Containers[0].VolumeMounts, cacheVolumeMount)
}
22 changes: 22 additions & 0 deletions tests/e2e-kubernetes/testsuites/util.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"fmt"
"math/rand"
"os"
"path/filepath"
"time"

"github.com/aws/aws-sdk-go-v2/aws"
Expand Down Expand Up @@ -76,6 +77,27 @@ func checkListingPath(f *framework.Framework, pod *v1.Pod, path string) {
e2evolume.VerifyExecInPodSucceed(f, pod, fmt.Sprintf("ls %s", path))
}

// checkBasicFileOperations verifies basic file operations works in the given `basePath` inside the `pod`.
func checkBasicFileOperations(f *framework.Framework, pod *v1.Pod, basePath string) {
framework.Logf("Checking basic file operations inside pod %s at %s", pod.UID, basePath)

dir := filepath.Join(basePath, "test-dir")
first := filepath.Join(basePath, "first")
second := filepath.Join(dir, "second")

seed := time.Now().UTC().UnixNano()
testWriteSize := 1024 // 1KB

checkWriteToPath(f, pod, first, testWriteSize, seed)
checkReadFromPath(f, pod, first, testWriteSize, seed)
e2evolume.VerifyExecInPodSucceed(f, pod, fmt.Sprintf("mkdir %s && cd %s && touch %s", dir, dir, second))
checkListingPath(f, pod, dir)
checkListingPath(f, pod, basePath)
checkListingPath(f, pod, second)
checkDeletingPath(f, pod, first)
checkDeletingPath(f, pod, second)
}

func createVolumeResourceWithMountOptions(ctx context.Context, config *storageframework.PerTestConfig, pattern storageframework.TestPattern, mountOptions []string) *storageframework.VolumeResource {
f := config.Framework
r := storageframework.VolumeResource{
Expand Down

0 comments on commit 2f86183

Please sign in to comment.