Skip to content

Commit

Permalink
Add test for creating Virtual Machines from a master image (#197)
Browse files Browse the repository at this point in the history
Signed-off-by: Ygal Blum <ygal.blum@gmail.com>
  • Loading branch information
ygalblum authored Mar 6, 2025
1 parent 303796a commit 8eeb714
Show file tree
Hide file tree
Showing 12 changed files with 717 additions and 1 deletion.
44 changes: 44 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,50 @@ The test generated the SSH keys automatically.
By default, it stores the pair in a temporary directory.
Users may choose the store the key in a specified directory by setting `--ssh-key-path`

### Virt Clone

Test the capacity and performance of starting multiple virtual machines with a root disk as clones of a single volume. This test comes to mimic VDI sequence

#### Test Sequence

The test runs the following sequence:
1. Create a `VirtualMachine` in namespace A
2. Stop the `VirtualMachine`
3. Create a `DataVolume` in namespace B using the rootdisk of the `VirtualMachine` as the source
4. If the `dataImportCronSourceFormat` field of the `StorageProfile` `status` is set to `snapshot`, or `--use-snapshot` is set to `true`, create a `VolumeSnapshot` of the DataVolume
5. Create a `DataSource`, setting the `source` field to either the `VolumeSnapshot` (if was created) or the `DataVolume`
6. Create `VirtualMachine` in namespace B based in the `DataSource`. Some machines are marked as `persistent` and some `ephemeral`
7. Restart the `ephemeral` machines by stopping them, deleting their disk and starting them again

#### Tested StorageClass

By default, the test will use the default `StorageClass`. To use a different one, use `--storage-class` to provide a different name.

If `--use-snapshot` is explicitly set to `true` a corresponding `VolumeSnapshotClass` using the same provisioner must exist.
Otherwise, the test will check the `StorageProfile` for the `StorageClass` and act accordingly.

#### Test Namespace

The test creates `VirtualMachines` in two namespaces: `<baseName>-base` and `<baseName>-clones`

By default, the `baseName` is `virt-clone`. Set it by passing `--namespace` (or `-n`)

#### Test Size Parameters

Users may control the workload sizes by passing the following arguments:
- `--vms` - Number of `VirtualMachines` to create in step 6

#### Volume Access Mode

By default, volumes are created with `ReadWriteMany` access mode as this is the recommended configuration for `VirtualMachines`.
If not supported, the access mode may be changes by setting `--access-mode`. The supported values are `RO`, `RWO` and `RWX`.

#### Temporary SSH Keys

In order to verify that the VMs actually completed booting, the test generates an SSH key pair.
By default, it stores the pair in a temporary directory.
Users may choose the store the key in a specified directory by setting `--ssh-key-path`

## Custom Workload: Bring your own workload

To kickstart kube-burner-ocp with a custom workload, `init` becomes your go-to command. This command is equipped with flags that enable to seamlessly integrate and run your personalized workloads. Here's a breakdown of the flags accepted by the init command:
Expand Down
107 changes: 107 additions & 0 deletions cmd/config/virt-clone/check.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
#!/usr/bin/env bash
COMMAND=$1
LABEL_KEY=$2
LABEL_VALUE=$3
NAMESPACE=$4
IDENTITY_FILE=$5
REMOTE_USER=$6
EXPECTED_ROOT_SIZE=$7
EXPECTED_DATA_SIZE=$8

# Wait up to ~60 minutes
MAX_RETRIES=130
# In the first reties use a shorter sleep
MAX_SHORT_WAITS=12
SHORT_WAIT=5
LONG_WAIT=30

if virtctl ssh --help | grep -qc "\--local-ssh " ; then
LOCAL_SSH="--local-ssh"
else
LOCAL_SSH=""
fi

get_vms() {
local namespace=$1
local label_key=$2
local label_value=$3

local vms
vms=$(kubectl get vm -n "${namespace}" -l "${label_key}"="${label_value}" -o json | jq .items | jq -r '.[] | .metadata.name')
local ret=$?
if [ $ret -ne 0 ]; then
echo "Failed to get VM list"
exit 1
fi
echo "${vms}"
}

remote_command() {
local namespace=$1
local identity_file=$2
local remote_user=$3
local vm_name=$4
local command=$5

local output
output=$(virtctl ssh ${LOCAL_SSH} --local-ssh-opts="-o StrictHostKeyChecking=no" --local-ssh-opts="-o UserKnownHostsFile=/dev/null" -n "${namespace}" -i "${identity_file}" -c "${command}" --username "${remote_user}" "${vm_name}" 2>/dev/null)
local ret=$?
if [ $ret -ne 0 ]; then
return 1
fi
echo "${output}"
}

check_vm_running() {
local vm=$1
remote_command "${NAMESPACE}" "${IDENTITY_FILE}" "${REMOTE_USER}" "${vm}" "ls"
return $?
}

check_resize() {
local vm=$1

local blk_devices
blk_devices=$(remote_command "${NAMESPACE}" "${IDENTITY_FILE}" "${REMOTE_USER}" "${vm}" "lsblk --json -v --output=NAME,SIZE")
local ret=$?
if [ $ret -ne 0 ]; then
return $ret
fi

local size
size=$(echo "${blk_devices}" | jq .blockdevices | jq -r --arg name "vda" '.[] | select(.name == $name) | .size')
if [[ $size != "${EXPECTED_ROOT_SIZE}" ]]; then
return 1
fi

local datavolume_sizes
datavolume_sizes=$(echo "${blk_devices}" | jq .blockdevices | jq -r --arg name "vda" '.[] | select(.name != $name) | .size')
for datavolume_size in ${datavolume_sizes}; do
if [[ $datavolume_size != "${EXPECTED_DATA_SIZE}" ]]; then
return 1
fi
done

return 0
}

VMS=$(get_vms "${NAMESPACE}" "${LABEL_KEY}" "${LABEL_VALUE}")

for vm in ${VMS}; do
for attempt in $(seq 1 $MAX_RETRIES); do
if ${COMMAND} "${vm}"; then
break
fi
if [ "${attempt}" -lt $MAX_RETRIES ]; then
if [ "${attempt}" -lt $MAX_SHORT_WAITS ]; then
sleep "${SHORT_WAIT}"
else
sleep "${LONG_WAIT}"
fi
else
echo "Failed waiting on ${COMMAND} for ${vm}" >&2
exit 1
fi
done
echo "${COMMAND} finished successfully for ${vm}"
done
15 changes: 15 additions & 0 deletions cmd/config/virt-clone/templates/baseImageDataSource.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataSource
metadata:
name: {{ .cloneDataSourceName }}
spec:
source:
{{ if .useSnapshot }}
snapshot:
name: {{ .cloneDataSourceSnapshotName }}
namespace: {{ .cloneDataSourceSnapshotNamespace }}
{{ else }}
pvc:
name: {{ .cloneDataSourcePVCName }}
namespace: {{ .cloneDataSourcePVCNamespace }}
{{ end }}
20 changes: 20 additions & 0 deletions cmd/config/virt-clone/templates/baseImageDataVolume.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
---
apiVersion: cdi.kubevirt.io/v1beta1
kind: DataVolume
metadata:
name: {{ .cloneDataVolumeName }}
annotations:
cdi.kubevirt.io/storage.bind.immediate.requested: "true"
spec:
source:
pvc:
namespace: {{ .baseVMNamespace }}
name: {{ .baseVMRootDiskPVCName }}
storage:
accessModes:
- {{ .accessMode }}
resources:
requests:
storage: {{ default "5Gi" .rootVolumeSize }}
storageClassName: {{ .storageClassName }}
...
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
apiVersion: snapshot.storage.k8s.io/v1
kind: VolumeSnapshot
metadata:
name: {{ .cloneVolumeSnapshotName }}
spec:
volumeSnapshotClassName: {{ .volumeSnapshotClassName }}
source:
persistentVolumeClaimName: {{ .cloneVolumeSnapshotPVCName }}
7 changes: 7 additions & 0 deletions cmd/config/virt-clone/templates/secret_ssh_public.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
apiVersion: v1
kind: Secret
metadata:
name: {{ .name }}
type: Opaque
data:
key: {{ .publicKeyPath | ReadFile | b64enc }}
73 changes: 73 additions & 0 deletions cmd/config/virt-clone/templates/vm.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: "{{ .vmName }}-{{ .Iteration }}-{{ .Replica }}"
labels:
{{ range $key, $value := .vmLabels }}
{{ $key }}: {{ $value }}
{{ end }}
spec:
dataVolumeTemplates:
- metadata:
name: "{{ .rootDiskVolumeName }}-{{ .Iteration }}-{{ .Replica }}"
labels:
{{ range $key, $value := .rootVolumeLabels }}
{{ $key }}: {{ $value }}
{{ end }}
spec:
{{ if .rootdiskVolumeSource }}
source: {{ .rootdiskVolumeSource | mustToJson }}
{{ end }}
{{ if .rootdiskVolumeSourceRef }}
sourceRef: {{ .rootdiskVolumeSourceRef | mustToJson }}
{{ end }}
storage:
accessModes:
- {{ .accessMode }}
storageClassName: {{ .storageClassName }}
resources:
requests:
storage: {{ default "5Gi" .rootVolumeSize }}
running: true
template:
spec:
accessCredentials:
- sshPublicKey:
propagationMethod:
noCloud: {}
source:
secret:
secretName: {{ .sshPublicKeySecret }}
architecture: amd64
domain:
resources:
requests:
memory: {{ default "512Mi" .vmMemory }}
devices:
disks:
- disk:
bus: virtio
name: rootdisk
bootOrder: 1
interfaces:
- name: default
masquerade: {}
bootOrder: 2
machine:
type: pc-q35-rhel9.4.0
networks:
- name: default
pod: {}
volumes:
- dataVolume:
name: "{{ .rootDiskVolumeName }}-{{ .Iteration }}-{{ .Replica }}"
name: rootdisk
- cloudInitNoCloud:
userData: |
#cloud-config
chpasswd:
expire: false
password: {{ uuidv4 }}
user: fedora
runcmd: []
name: cloudinitdisk
Loading

0 comments on commit 8eeb714

Please sign in to comment.