feat(kubevirt_vm): Add support for RunStrategy

This change adds support for setting the RunStrategy of a VM.

Depending on the value set the wait condition for the VM is adjusted.
For the values Always, RerunOnFailure or Once the wait condition will
wait for the VM to run and be ready. For the value Halted the wait
condition will wait for the VM to not exist. For the value Manual
the wait condition is not set.

Signed-off-by: Felix Matouschek <fmatouschek@redhat.com>
This commit is contained in:
Felix Matouschek
2024-07-15 10:35:34 +02:00
parent 6bc7607826
commit c1f651d972
3 changed files with 200 additions and 13 deletions

View File

@@ -0,0 +1,42 @@
---
- name: Playbook creating a virtual machine with multus network
hosts: localhost
tasks:
- name: Create VM
kubevirt.core.kubevirt_vm:
state: present
name: testvm
namespace: default
labels:
app: test
instancetype:
name: u1.medium
preference:
name: fedora
run_strategy: Manual
spec:
domain:
devices:
interfaces:
- name: default
masquerade: {}
- name: bridge-network
bridge: {}
networks:
- name: default
pod: {}
- name: bridge-network
multus:
networkName: kindexgw
volumes:
- containerDisk:
image: quay.io/containerdisks/fedora:latest
name: containerdisk
- cloudInitNoCloud:
userData: |-
#cloud-config
# The default username is: fedora
ssh_authorized_keys:
- ssh-ed25519 AAAA...
name: cloudinit
wait: true

View File

@@ -61,8 +61,21 @@ options:
running:
description:
- Specify whether the C(VirtualMachine) should be running or not.
- Mutually exclusive with O(run_strategy).
- Defaults to O(running=yes) when O(running) and O(run_strategy) are not set.
type: bool
default: yes
run_strategy:
description:
- Specify the C(RunStrategy) of the C(VirtualMachine).
- Mutually exclusive with O(running).
type: str
choices:
- Always
- Halted
- Manual
- RerunOnFailure
- Once
version_added: 2.0.0
instancetype:
description:
- Specify the C(Instancetype) matcher of the C(VirtualMachine).
@@ -280,6 +293,13 @@ from ansible_collections.kubernetes.core.plugins.module_utils.k8s.exceptions imp
CoreException,
)
WAIT_CONDITION_READY = {"type": "Ready", "status": True}
WAIT_CONDITION_VMI_NOT_EXISTS = {
"type": "Ready",
"status": False,
"reason": "VMINotExists",
}
def create_vm(params: Dict) -> Dict:
"""
@@ -292,7 +312,6 @@ def create_vm(params: Dict) -> Dict:
"namespace": params["namespace"],
},
"spec": {
"running": params["running"],
"template": {"spec": {"domain": {"devices": {}}}},
},
}
@@ -312,6 +331,12 @@ def create_vm(params: Dict) -> Dict:
if template_metadata:
vm["spec"]["template"]["metadata"] = template_metadata
if (run_strategy := params.get("run_strategy")) is not None:
vm["spec"]["runStrategy"] = run_strategy
else:
vm["spec"]["running"] = (
running if (running := params.get("running")) is not None else True
)
if (instancetype := params.get("instancetype")) is not None:
vm["spec"]["instancetype"] = instancetype
if (preference := params.get("preference")) is not None:
@@ -324,6 +349,21 @@ def create_vm(params: Dict) -> Dict:
return vm
def set_wait_condition(module: AnsibleK8SModule) -> None:
"""
set_wait_condition sets the wait_condition to allow waiting for the ready
state of the VirtualMachine depending on the module parameters running
and run_strategy.
"""
if (
module.params["running"] is False
or (run_strategy := module.params["run_strategy"]) == "Halted"
):
module.params["wait_condition"] = WAIT_CONDITION_VMI_NOT_EXISTS
elif run_strategy != "Manual":
module.params["wait_condition"] = WAIT_CONDITION_READY
def arg_spec() -> Dict:
"""
arg_spec defines the argument spec of this module.
@@ -335,7 +375,10 @@ def arg_spec() -> Dict:
"namespace": {"required": True},
"annotations": {"type": "dict"},
"labels": {"type": "dict"},
"running": {"type": "bool", "default": True},
"running": {"type": "bool"},
"run_strategy": {
"choices": ["Always", "Halted", "Manual", "RerunOnFailure", "Once"]
},
"instancetype": {"type": "dict"},
"preference": {"type": "dict"},
"data_volume_templates": {"type": "list", "elements": "dict"},
@@ -376,6 +419,7 @@ def main() -> None:
argument_spec=arg_spec(),
mutually_exclusive=[
("name", "generate_name"),
("running", "run_strategy"),
],
required_one_of=[
("name", "generate_name"),
@@ -387,14 +431,7 @@ def main() -> None:
module.params["resource_definition"] = create_vm(module.params)
# Set wait_condition to allow waiting for the ready state of the VirtualMachine
if module.params["running"]:
module.params["wait_condition"] = {"type": "Ready", "status": True}
else:
module.params["wait_condition"] = {
"type": "Ready",
"status": False,
"reason": "VMINotExists",
}
set_wait_condition(module)
try:
runner.run_module(module)

View File

@@ -101,11 +101,27 @@ VM_DEFINITION_STOPPED = {
},
}
VM_DEFINITION_HALTED = {
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine",
"metadata": {
"name": "testvm",
"namespace": "default",
},
"spec": {
"runStrategy": "Halted",
"template": {
"spec": {
"domain": {"devices": {}},
},
},
},
}
MODULE_PARAMS_DEFAULT = {
"api_version": "kubevirt.io/v1",
"annotations": None,
"labels": None,
"running": True,
"instancetype": None,
"preference": None,
"data_volume_templates": None,
@@ -174,6 +190,12 @@ MODULE_PARAMS_STOPPED = MODULE_PARAMS_DEFAULT | {
"running": False,
}
MODULE_PARAMS_HALTED = MODULE_PARAMS_DEFAULT | {
"name": "testvm",
"namespace": "default",
"run_strategy": "Halted",
}
MODULE_PARAMS_DELETE = MODULE_PARAMS_DEFAULT | {
"name": "testvm",
"namespace": "default",
@@ -183,24 +205,37 @@ MODULE_PARAMS_DELETE = MODULE_PARAMS_DEFAULT | {
K8S_MODULE_PARAMS_CREATE = MODULE_PARAMS_CREATE | {
"generate_name": None,
"running": None,
"run_strategy": None,
"resource_definition": VM_DEFINITION_CREATE,
"wait_condition": {"type": "Ready", "status": True},
}
K8S_MODULE_PARAMS_RUNNING = MODULE_PARAMS_RUNNING | {
"generate_name": None,
"run_strategy": None,
"resource_definition": VM_DEFINITION_RUNNING,
"wait_condition": {"type": "Ready", "status": True},
}
K8S_MODULE_PARAMS_STOPPED = MODULE_PARAMS_STOPPED | {
"generate_name": None,
"run_strategy": None,
"resource_definition": VM_DEFINITION_STOPPED,
"wait_condition": {"type": "Ready", "status": False, "reason": "VMINotExists"},
}
K8S_MODULE_PARAMS_HALTED = MODULE_PARAMS_HALTED | {
"generate_name": None,
"running": None,
"resource_definition": VM_DEFINITION_HALTED,
"wait_condition": {"type": "Ready", "status": False, "reason": "VMINotExists"},
}
K8S_MODULE_PARAMS_DELETE = MODULE_PARAMS_DELETE | {
"generate_name": None,
"running": None,
"run_strategy": None,
"resource_definition": VM_DEFINITION_RUNNING,
"wait_condition": {"type": "Ready", "status": True},
}
@@ -227,6 +262,12 @@ K8S_MODULE_PARAMS_DELETE = MODULE_PARAMS_DELETE | {
VM_DEFINITION_STOPPED,
"update",
),
(
MODULE_PARAMS_HALTED,
K8S_MODULE_PARAMS_HALTED,
VM_DEFINITION_HALTED,
"update",
),
(
MODULE_PARAMS_DELETE,
K8S_MODULE_PARAMS_DELETE,
@@ -262,10 +303,15 @@ def test_module(mocker, module_params, k8s_module_params, vm_definition, method)
CREATE_VM_PARAMS = {
"api_version": "kubevirt.io/v1",
"running": True,
"namespace": "default",
}
CREATE_VM_PARAMS_RUN_STRATEGY = {
"api_version": "kubevirt.io/v1",
"namespace": "default",
"run_strategy": "Manual",
}
CREATE_VM_PARAMS_ANNOTATIONS = CREATE_VM_PARAMS | {
"annotations": {"test": "test"},
}
@@ -341,6 +387,24 @@ CREATED_VM = {
},
}
CREATED_VM_RUN_STRATEGY = {
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine",
"metadata": {
"namespace": "default",
},
"spec": {
"runStrategy": "Manual",
"template": {
"spec": {
"domain": {
"devices": {},
},
},
},
},
}
CREATED_VM_LABELS = {
"apiVersion": "kubevirt.io/v1",
"kind": "VirtualMachine",
@@ -528,6 +592,7 @@ CREATED_VM_SPECS = {
"params,expected",
[
(CREATE_VM_PARAMS, CREATED_VM),
(CREATE_VM_PARAMS_RUN_STRATEGY, CREATED_VM_RUN_STRATEGY),
(CREATE_VM_PARAMS_ANNOTATIONS, CREATED_VM_ANNOTATIONS),
(CREATE_VM_PARAMS_LABELS, CREATED_VM_LABELS),
(CREATE_VM_PARAMS_INSTANCETYPE, CREATED_VM_INSTANCETYPE),
@@ -540,3 +605,46 @@ CREATED_VM_SPECS = {
)
def test_create_vm(params, expected):
assert kubevirt_vm.create_vm(params) == expected
@pytest.mark.parametrize(
"params,expected",
[
({"running": None, "run_strategy": "Manual"}, {}),
(
{"running": None, "run_strategy": None},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_READY},
),
(
{"running": True, "run_strategy": None},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_READY},
),
(
{"running": None, "run_strategy": "Always"},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_READY},
),
(
{"running": None, "run_strategy": "RerunOnFailure"},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_READY},
),
(
{"running": None, "run_strategy": "Once"},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_READY},
),
(
{"running": False, "run_strategy": None},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_VMI_NOT_EXISTS},
),
(
{"running": None, "run_strategy": "Halted"},
{"wait_condition": kubevirt_vm.WAIT_CONDITION_VMI_NOT_EXISTS},
),
],
)
def test_set_wait_condition(mocker, params, expected):
module = mocker.Mock()
module.params = params
kubevirt_vm.set_wait_condition(module)
assert module.params == params | expected