Coverage for ocp_resources/virtual_machine.py: 0%
80 statements
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-30 10:48 +0200
« prev ^ index » next coverage.py v7.6.10, created at 2025-01-30 10:48 +0200
1from __future__ import annotations
2from typing import Any
5from ocp_resources.utils.constants import (
6 DEFAULT_CLUSTER_RETRY_EXCEPTIONS,
7 PROTOCOL_ERROR_EXCEPTION_DICT,
8 TIMEOUT_4MINUTES,
9 TIMEOUT_30SEC,
10 TIMEOUT_5SEC,
11)
12from ocp_resources.resource import NamespacedResource
13from timeout_sampler import TimeoutSampler
14from ocp_resources.virtual_machine_instance import VirtualMachineInstance
17class VirtualMachine(NamespacedResource):
18 """
19 Virtual Machine object, inherited from Resource.
20 Implements actions start / stop / status / wait for VM status / is running
21 """
23 api_group = NamespacedResource.ApiGroup.KUBEVIRT_IO
25 class RunStrategy:
26 MANUAL = "Manual"
27 HALTED = "Halted"
28 ALWAYS = "Always"
29 RERUNONFAILURE = "RerunOnFailure"
31 class Status(NamespacedResource.Status):
32 MIGRATING = "Migrating"
33 PAUSED = "Paused"
34 PROVISIONING = "Provisioning"
35 STARTING = "Starting"
36 STOPPED = "Stopped"
37 STOPPING = "Stopping"
38 WAITING_FOR_VOLUME_BINDING = "WaitingForVolumeBinding"
39 ERROR_UNSCHEDULABLE = "ErrorUnschedulable"
40 DATAVOLUME_ERROR = "DataVolumeError"
41 ERROR_PVC_NOT_FOUND = "ErrorPvcNotFound"
42 IMAGE_PULL_BACK_OFF = "ImagePullBackOff"
43 ERR_IMAGE_PULL = "ErrImagePull"
44 CRASH_LOOPBACK_OFF = "CrashLoopBackOff"
46 def __init__(
47 self,
48 name=None,
49 namespace=None,
50 client=None,
51 body=None,
52 teardown=True,
53 yaml_file=None,
54 delete_timeout=TIMEOUT_4MINUTES,
55 **kwargs,
56 ):
57 super().__init__(
58 name=name,
59 namespace=namespace,
60 client=client,
61 teardown=teardown,
62 yaml_file=yaml_file,
63 delete_timeout=delete_timeout,
64 **kwargs,
65 )
66 self.body = body
68 @property
69 def _subresource_api_url(self):
70 return (
71 f"{self.client.configuration.host}/"
72 f"apis/subresources.kubevirt.io/{self.api.api_version}/"
73 f"namespaces/{self.namespace}/virtualmachines/{self.name}"
74 )
76 def api_request(
77 self, method: str, action: str, url: str = "", retry_params: dict[str, int] | None = None, **params: Any
78 ) -> dict[str, Any]:
79 default_vm_api_request_retry_params: dict[str, int] = {"timeout": TIMEOUT_30SEC, "sleep_time": TIMEOUT_5SEC}
80 return super().api_request(
81 method=method,
82 action=action,
83 url=url or self._subresource_api_url,
84 retry_params=retry_params or default_vm_api_request_retry_params,
85 **params,
86 )
88 def to_dict(self) -> None:
89 super().to_dict()
90 if not self.kind_dict and not self.yaml_file:
91 body_spec = self.body.get("spec") if self.body else None
92 self.res["spec"] = body_spec or {"template": {"spec": {}}}
94 def start(self, timeout=TIMEOUT_4MINUTES, wait=False):
95 self.api_request(method="PUT", action="start")
96 if wait:
97 return self.wait_for_ready_status(timeout=timeout, status=True)
99 def restart(self, timeout=TIMEOUT_4MINUTES, wait=False):
100 self.api_request(method="PUT", action="restart")
101 if wait:
102 self.vmi.virt_launcher_pod.wait_deleted()
103 return self.vmi.wait_until_running(timeout=timeout, stop_status="dummy")
105 def stop(self, timeout=TIMEOUT_4MINUTES, vmi_delete_timeout=TIMEOUT_4MINUTES, wait=False):
106 self.api_request(method="PUT", action="stop")
107 if wait:
108 self.wait_for_ready_status(timeout=timeout, status=None)
109 return self.vmi.wait_deleted(timeout=vmi_delete_timeout)
111 def wait_for_ready_status(self, status, timeout=TIMEOUT_4MINUTES, sleep=1):
112 """
113 Wait for VM resource ready status to be at desire status
115 Args:
116 status (any): True for a running VM, None for a stopped VM.
117 timeout (int): Time to wait for the resource.
119 Raises:
120 TimeoutExpiredError: If timeout reached.
121 """
122 self.logger.info(f"Wait for {self.kind} {self.name} status to be {'ready' if status is True else status}")
123 samples = TimeoutSampler(
124 wait_timeout=timeout,
125 sleep=sleep,
126 exceptions_dict={
127 **PROTOCOL_ERROR_EXCEPTION_DICT,
128 **DEFAULT_CLUSTER_RETRY_EXCEPTIONS,
129 },
130 func=self.api.get,
131 field_selector=f"metadata.name=={self.name}",
132 namespace=self.namespace,
133 )
134 for sample in samples:
135 if sample.items and self.ready == status:
136 # VM with runStrategy does not have spec.running attribute
137 # VM status should be taken from spec.status.ready
138 return
140 def get_interfaces(self):
141 return self.instance.spec.template.spec.domain.devices.interfaces
143 @property
144 def vmi(self):
145 """
146 Get VMI
148 Returns:
149 VirtualMachineInstance: VMI
150 """
151 return VirtualMachineInstance(
152 client=self.client,
153 name=self.name,
154 namespace=self.namespace,
155 )
157 @property
158 def ready(self):
159 """
160 Get VM status
162 Returns:
163 True if Running else None
164 """
165 return self.instance.get("status", {}).get("ready")
167 @property
168 def printable_status(self):
169 """
170 Get VM printableStatus
172 Returns:
173 VM printableStatus if VM.status.printableStatus else None
174 """
175 return self.instance.get("status", {}).get("printableStatus")
177 def wait_for_status_none(self, status, timeout=TIMEOUT_4MINUTES):
178 self.logger.info(f"Wait for {self.kind} {self.name} status {status} to be None")
179 for sample in TimeoutSampler(
180 wait_timeout=timeout,
181 sleep=1,
182 exceptions_dict=PROTOCOL_ERROR_EXCEPTION_DICT,
183 func=lambda: self.instance.get("status", {}).get(status),
184 ):
185 if sample is None:
186 return
188 @property
189 def keys_to_hash(self):
190 return ["spec>template>spec>volumes[]>cloudInitNoCloud>userData"]