Skip to content

Commit

Permalink
Support Read Write Many (RWX) access mode with block (#100)
Browse files Browse the repository at this point in the history
* run e2e tests with 2 guest worker nodes

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>

* Improve e2e tests

Use option builder for 2e2 pods, so we could have more options for the
pods, without changing the create pod methods API, and to avoid too many
create pod methods.

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>

* Support Read Write Many (RWX) accessMode + block

Reject creation of non-block (FS) volumes with access mode of RWX

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>

* Add e2e tests for RWX

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>

* Run the RWX test in K8s e2e

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>

---------

Signed-off-by: Nahshon Unna-Tsameret <[email protected]>
  • Loading branch information
nunnatsa authored Mar 14, 2024
1 parent cc28dcb commit a3163ce
Show file tree
Hide file tree
Showing 12 changed files with 623 additions and 291 deletions.
414 changes: 189 additions & 225 deletions e2e/create-pvc_test.go

Large diffs are not rendered by default.

187 changes: 187 additions & 0 deletions e2e/create_pod_helper_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
package e2e_test

import (
"fmt"

k8sv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

const (
fsCommonFile = "/opt/test.txt"
fsWriteCommand = "echo testing > " + fsCommonFile
fsReadCommand = "cat " + fsCommonFile
fsAttachCommand = "ls -la /opt && echo kubevirt-csi-driver && mktemp /opt/test-XXXXXX"

blockCommonFile = "/dev/csi"
blockWriteCommand = "echo testing > " + blockCommonFile
blockReadCommand = "head -c 8 " + blockCommonFile
blockAttachCommand = "ls -al /dev/csi"
)

type podOption func(pod *k8sv1.Pod)
type storageOption func(string) podOption

func createPod(podName string, opts ...podOption) *k8sv1.Pod {
pod := &k8sv1.Pod{
ObjectMeta: metav1.ObjectMeta{
GenerateName: podName,
},
Spec: k8sv1.PodSpec{
SecurityContext: &k8sv1.PodSecurityContext{
SeccompProfile: &k8sv1.SeccompProfile{
Type: k8sv1.SeccompProfileTypeRuntimeDefault,
},
},
RestartPolicy: k8sv1.RestartPolicyNever,
Containers: []k8sv1.Container{
{
SecurityContext: &k8sv1.SecurityContext{
Capabilities: &k8sv1.Capabilities{
Drop: []k8sv1.Capability{
"ALL",
},
},
},
Name: podName,
Image: "busybox",
},
},
// add toleration so we can use control node for tests
Tolerations: []k8sv1.Toleration{
{
Key: "node-role.kubernetes.io/master",
Operator: k8sv1.TolerationOpExists,
Effect: k8sv1.TaintEffectNoSchedule,
},
{
Key: "node-role.kubernetes.io/control-plane",
Operator: k8sv1.TolerationOpExists,
Effect: k8sv1.TaintEffectNoSchedule,
},
},
},
}

for _, o := range opts {
o(pod)
}

return pod
}

func withCommand(cmd string) podOption {
return func(pod *k8sv1.Pod) {
pod.Spec.Containers[0].Command = []string{"sh"}
pod.Spec.Containers[0].Args = []string{"-c", cmd}
}
}

func withBlock(pvcName string) podOption {
const volumeName = "blockpv"
return func(pod *k8sv1.Pod) {
pod.Spec.Volumes = append(pod.Spec.Volumes, getVolume(volumeName, pvcName))
pod.Spec.Containers[0].VolumeDevices = []k8sv1.VolumeDevice{
{
Name: volumeName,
DevicePath: "/dev/csi",
},
}
}
}

func withFileSystem(pvcName string) podOption {
const volumeName = "fspv"
return func(pod *k8sv1.Pod) {
pod.Spec.Volumes = append(pod.Spec.Volumes, getVolume(volumeName, pvcName))
pod.Spec.Containers[0].VolumeMounts = []k8sv1.VolumeMount{
{
Name: volumeName,
MountPath: "/opt",
},
}
}
}

func withNodeSelector(key, value string) podOption {
return func(pod *k8sv1.Pod) {
if pod.Spec.NodeSelector == nil {
pod.Spec.NodeSelector = make(map[string]string)
}
pod.Spec.NodeSelector[key] = value
}
}

func withLabel(key, value string) podOption {
return func(pod *k8sv1.Pod) {
if pod.Labels == nil {
pod.Labels = make(map[string]string)
}
pod.Labels[key] = value
}
}

func withPodAntiAffinity(key, value string) podOption {
return func(pod *k8sv1.Pod) {
pod.Spec.Affinity = &k8sv1.Affinity{
PodAntiAffinity: &k8sv1.PodAntiAffinity{
RequiredDuringSchedulingIgnoredDuringExecution: []k8sv1.PodAffinityTerm{
{
LabelSelector: &metav1.LabelSelector{
MatchExpressions: []metav1.LabelSelectorRequirement{
{
Key: key,
Operator: metav1.LabelSelectorOpIn,
Values: []string{value},
},
},
},
TopologyKey: hostNameLabelKey,
},
},
},
}
}
}

func getVolume(volumeName, pvcName string) k8sv1.Volume {
return k8sv1.Volume{
Name: volumeName,
VolumeSource: k8sv1.VolumeSource{
PersistentVolumeClaim: &k8sv1.PersistentVolumeClaimVolumeSource{
ClaimName: pvcName,
},
},
}
}

func withPVC(pvcName string, mountPath string) podOption {
return func(pod *k8sv1.Pod) {
pod.Spec.Volumes = append(pod.Spec.Volumes, getVolume(pvcName, pvcName))
if len(pod.Spec.Containers[0].VolumeMounts) > 0 {
addVolumeMount(pod, pvcName, mountPath)
}
if len(pod.Spec.Containers[0].VolumeDevices) > 0 {
addVolumeDevice(pod, pvcName)
}

}
}

func addVolumeMount(podSpec *k8sv1.Pod, volumeName string, mountPath string) {
podSpec.Spec.Containers[0].VolumeMounts = append(
podSpec.Spec.Containers[0].VolumeMounts,
k8sv1.VolumeMount{
Name: volumeName,
MountPath: mountPath,
})
}

func addVolumeDevice(podSpec *k8sv1.Pod, volumeName string) {
podSpec.Spec.Containers[0].VolumeDevices = append(
podSpec.Spec.Containers[0].VolumeDevices,
k8sv1.VolumeDevice{
Name: volumeName,
DevicePath: fmt.Sprintf("/dev/%s", volumeName),
})
}
23 changes: 16 additions & 7 deletions e2e/snapshot_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ import (
"os"
"time"

snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
k8sv1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/util/rand"
"k8s.io/client-go/kubernetes"
"k8s.io/utils/ptr"
snapcli "kubevirt.io/csi-driver/pkg/generated/external-snapshotter/client-go/clientset/versioned"

snapshotv1 "github.com/kubernetes-csi/external-snapshotter/client/v6/apis/volumesnapshot/v1"
snapcli "kubevirt.io/csi-driver/pkg/generated/external-snapshotter/client-go/clientset/versioned"
)

var _ = Describe("Snapshot", func() {
Expand Down Expand Up @@ -59,7 +59,7 @@ var _ = Describe("Snapshot", func() {
_ = os.RemoveAll(tmpDir)
})

DescribeTable("creates a pvc and attaches to pod, then create snapshot", Label("pvcCreation"), func(volumeMode k8sv1.PersistentVolumeMode, podCreationFunc, podReaderFunc func(string) *k8sv1.Pod) {
DescribeTable("creates a pvc and attaches to pod, then create snapshot", Label("pvcCreation", "snapshot"), func(volumeMode k8sv1.PersistentVolumeMode, storageOpt storageOption, writeCmd, readCmd string) {
pvcName := "test-pvc"
storageClassName := "kubevirt"
pvc := pvcSpec(pvcName, storageClassName, "10Mi")
Expand All @@ -70,10 +70,15 @@ var _ = Describe("Snapshot", func() {
Expect(err).ToNot(HaveOccurred())

By("creating a pod that attaches pvc")
wPod := createPod("writer-pod",
withCommand(writeCmd),
storageOpt(pvc.Name),
)
runPod(
tenantClient.CoreV1(),
namespace,
podCreationFunc(pvc.Name))
wPod,
true)

By("creating a snapshot")
snapshotName := "test-snapshot"
Expand Down Expand Up @@ -125,13 +130,17 @@ var _ = Describe("Snapshot", func() {
Expect(err).ToNot(HaveOccurred())

By("creating a pod that attaches the restored pvc, and checks the changes are there")
rPod := createPod("reader-pod",
withCommand(readCmd),
storageOpt(pvc.Name))
runPod(
tenantClient.CoreV1(),
namespace,
podReaderFunc(pvc.Name))
rPod,
true)

},
Entry("Filesystem volume mode", k8sv1.PersistentVolumeFilesystem, writerPodFs, readerPodFs),
Entry("Block volume mode", k8sv1.PersistentVolumeBlock, writerPodBlock, readerPodBlock),
Entry("Filesystem volume mode", Label("FS"), k8sv1.PersistentVolumeFilesystem, withFileSystem, fsWriteCommand, fsReadCommand),
Entry("Block volume mode", Label("Block"), k8sv1.PersistentVolumeBlock, withBlock, blockWriteCommand, blockReadCommand),
)
})
3 changes: 3 additions & 0 deletions hack/cluster-sync-split.sh
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,8 @@ _kubectl apply --kustomize ./deploy/controller-infra/dev-overlay
# ******************************************************
# Wait for driver to rollout
# ******************************************************
_kubectl_tenant rollout restart ds/kubevirt-csi-node -n $CSI_DRIVER_NAMESPACE
_kubectl rollout restart deployment/kubevirt-csi-controller -n $TENANT_CLUSTER_NAMESPACE

_kubectl_tenant rollout status ds/kubevirt-csi-node -n $CSI_DRIVER_NAMESPACE --timeout=10m
_kubectl rollout status deployment/kubevirt-csi-controller -n $TENANT_CLUSTER_NAMESPACE --timeout=10m
2 changes: 1 addition & 1 deletion hack/cluster-up.sh
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ echo "Creating $TENANT_CLUSTER_NAMESPACE"
./kubevirtci create-cluster

echo "Waiting for $TENANT_CLUSTER_NAMESPACE vmis to be ready"
./kubevirtci kubectl wait --for=condition=Ready vmi -l capk.cluster.x-k8s.io/kubevirt-machine-namespace=$TENANT_CLUSTER_NAMESPACE -n $TENANT_CLUSTER_NAMESPACE
./kubevirtci kubectl wait --for=condition=Ready vmi -l capk.cluster.x-k8s.io/kubevirt-machine-namespace=$TENANT_CLUSTER_NAMESPACE -n $TENANT_CLUSTER_NAMESPACE --timeout=300s

echo "Installing networking (calico)"
./kubevirtci install-calico
Expand Down
1 change: 0 additions & 1 deletion hack/generate_clients.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ set -o nounset
set -o pipefail

go install k8s.io/code-generator/cmd/client-gen@latest
go get kubevirt.io/api
client-gen --input-base="kubevirt.io/api/" --input="core/v1" --output-package="kubevirt.io/csi-driver/pkg/generated/kubevirt/client-go/clientset" --output-base="../../" --clientset-name="versioned" --go-header-file hack/boilerplate.go.txt

go get kubevirt.io/containerized-data-importer-api
Expand Down
20 changes: 18 additions & 2 deletions hack/run-e2e.sh
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,24 @@ if [ ! -f "${KUBECTL_PATH}" ]; then
curl -L "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl" -o ${KUBECTL_PATH}
chmod u+x ${KUBECTL_PATH}
fi


GINKGO_LABELS=""
if [[ -n ${LABELS} ]]; then
GINKGO_LABELS="--ginkgo.label-filter=${LABELS}"
fi

rm -rf $TEST_WORKING_DIR
mkdir -p $TEST_WORKING_DIR

$BIN_DIR/e2e.test -ginkgo.v -test.v -ginkgo.no-color --kubectl-path $KUBECTL_PATH --clusterctl-path $CLUSTERCTL_PATH --virtctl-path $VIRTCTL_PATH --working-dir $TEST_WORKING_DIR --dump-path $DUMP_PATH --infra-kubeconfig=$KUBECONFIG --infra-cluster-namespace=${INFRA_CLUSTER_NAMESPACE}
$BIN_DIR/e2e.test \
-ginkgo.v \
-test.v \
-ginkgo.no-color \
--kubectl-path "${KUBECTL_PATH}" \
--clusterctl-path "${CLUSTERCTL_PATH}" \
--virtctl-path "${VIRTCTL_PATH}" \
--working-dir "${TEST_WORKING_DIR}" \
--dump-path "${DUMP_PATH}" \
--infra-kubeconfig="${KUBECONFIG}" \
--infra-cluster-namespace="${INFRA_CLUSTER_NAMESPACE}" \
${GINKGO_LABELS}
Loading

0 comments on commit a3163ce

Please sign in to comment.