diff --git a/examples/play-create-run-strategy.yml b/examples/play-create-run-strategy.yml new file mode 100644 index 0000000..837b89e --- /dev/null +++ b/examples/play-create-run-strategy.yml @@ -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 diff --git a/plugins/modules/kubevirt_vm.py b/plugins/modules/kubevirt_vm.py index 9199f55..112993a 100644 --- a/plugins/modules/kubevirt_vm.py +++ b/plugins/modules/kubevirt_vm.py @@ -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) diff --git a/tests/unit/plugins/modules/test_kubevirt_vm.py b/tests/unit/plugins/modules/test_kubevirt_vm.py index 436904b..15d36ac 100644 --- a/tests/unit/plugins/modules/test_kubevirt_vm.py +++ b/tests/unit/plugins/modules/test_kubevirt_vm.py @@ -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