Coverage for ocp_resources/datavolume.py: 0%
139 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 ocp_resources.utils.constants import (
2 TIMEOUT_1MINUTE,
3 TIMEOUT_2MINUTES,
4 TIMEOUT_4MINUTES,
5 TIMEOUT_10MINUTES,
6 TIMEOUT_10SEC,
7)
8from ocp_resources.persistent_volume_claim import PersistentVolumeClaim
9from ocp_resources.resource import NamespacedResource, Resource
10from timeout_sampler import TimeoutExpiredError, TimeoutSampler
11from warnings import warn
14class DataVolume(NamespacedResource):
15 """
16 DataVolume object.
17 """
19 api_group = NamespacedResource.ApiGroup.CDI_KUBEVIRT_IO
21 class Status(NamespacedResource.Status):
22 BLANK = "Blank"
23 PVC_BOUND = "PVCBound"
24 IMPORT_SCHEDULED = "ImportScheduled"
25 ClONE_SCHEDULED = "CloneScheduled"
26 UPLOAD_SCHEDULED = "UploadScheduled"
27 IMPORT_IN_PROGRESS = "ImportInProgress"
28 CLONE_IN_PROGRESS = "CloneInProgress"
29 UPLOAD_IN_PROGRESS = "UploadInProgress"
30 SNAPSHOT_FOR_SMART_CLONE_IN_PROGRESS = "SnapshotForSmartCloneInProgress"
31 SMART_CLONE_PVC_IN_PROGRESS = "SmartClonePVCInProgress"
32 UPLOAD_READY = "UploadReady"
33 UNKNOWN = "Unknown"
34 WAIT_FOR_FIRST_CONSUMER = "WaitForFirstConsumer"
35 PENDING_POPULATION = "PendingPopulation"
37 class AccessMode:
38 """
39 AccessMode object.
40 """
42 RWO = "ReadWriteOnce"
43 ROX = "ReadOnlyMany"
44 RWX = "ReadWriteMany"
46 class ContentType:
47 """
48 ContentType object
49 """
51 KUBEVIRT = "kubevirt"
52 ARCHIVE = "archive"
54 class VolumeMode:
55 """
56 VolumeMode object
57 """
59 BLOCK = "Block"
60 FILE = "Filesystem"
62 class Condition:
63 class Type:
64 READY = "Ready"
65 BOUND = "Bound"
66 RUNNING = "Running"
68 class Status(Resource.Condition.Status):
69 UNKNOWN = "Unknown"
71 def __init__(
72 self,
73 name=None,
74 namespace=None,
75 source=None,
76 size=None,
77 storage_class=None,
78 url=None,
79 content_type=ContentType.KUBEVIRT,
80 access_modes=None,
81 cert_configmap=None,
82 secret=None,
83 client=None,
84 volume_mode=None,
85 hostpath_node=None,
86 source_pvc=None,
87 source_namespace=None,
88 multus_annotation=None,
89 bind_immediate_annotation=None,
90 preallocation=None,
91 teardown=True,
92 yaml_file=None,
93 delete_timeout=TIMEOUT_4MINUTES,
94 api_name="pvc",
95 delete_after_completion=None,
96 **kwargs,
97 ):
98 """
99 DataVolume object
101 Args:
102 name (str): DataVolume name.
103 namespace (str): DataVolume namespace.
104 source (str): source of DV - upload/http/pvc/registry.
105 size (str): DataVolume size - format size+size unit, for example: "5Gi".
106 storage_class (str, default: None): storage class name for DataVolume.
107 url (str, default: None): url for importing DV, when source is http/registry.
108 content_type (str, default: "kubevirt"): DataVolume content type.
109 access_modes (str, default: None): DataVolume access mode.
110 cert_configmap (str, default: None): name of config map for TLS certificates.
111 secret (Secret, default: None): to be set as secretRef.
112 client (DynamicClient): DynamicClient to use.
113 volume_mode (str, default: None): DataVolume volume mode.
114 hostpath_node (str, default: None): Node name to provision the DV on.
115 source_pvc (str, default: None): PVC name for when cloning the DV.
116 source_namespace (str, default: None): PVC namespace for when cloning the DV.
117 multus_annotation (str, default: None): network nad name.
118 bind_immediate_annotation (bool, default: None): when WaitForFirstConsumer is set in StorageClass and DV
119 should be bound immediately.
120 preallocation (bool, default: None): preallocate disk space.
121 teardown (bool, default: True): Indicates if this resource would need to be deleted.
122 yaml_file (yaml, default: None): yaml file for the resource.
123 delete_timeout (int, default: 4 minutes): timeout associated with delete action.
124 api_name (str, default: "pvc"): api used for DV, pvc/storage
125 delete_after_completion (str, default: None): annotation for garbage collector - "true"/"false"
126 """
127 super().__init__(
128 name=name,
129 namespace=namespace,
130 client=client,
131 teardown=teardown,
132 yaml_file=yaml_file,
133 delete_timeout=delete_timeout,
134 **kwargs,
135 )
136 self.source = source
137 self.url = url
138 self.cert_configmap = cert_configmap
139 self.secret = secret
140 self.content_type = content_type
141 self.size = size
142 self.access_modes = access_modes
143 self.storage_class = storage_class
144 self.volume_mode = volume_mode
145 self.hostpath_node = hostpath_node
146 self.source_pvc = source_pvc
147 self.source_namespace = source_namespace
148 self.multus_annotation = multus_annotation
149 self.bind_immediate_annotation = bind_immediate_annotation
150 self.preallocation = preallocation
151 self.api_name = api_name
152 self.delete_after_completion = delete_after_completion
154 def to_dict(self) -> None:
155 super().to_dict()
156 if not self.kind_dict and not self.yaml_file:
157 self.res.update({
158 "spec": {
159 "source": {self.source: {"url": self.url}},
160 self.api_name: {
161 "resources": {"requests": {"storage": self.size}},
162 },
163 }
164 })
165 if self.access_modes:
166 self.res["spec"][self.api_name]["accessModes"] = [self.access_modes]
167 if self.content_type:
168 self.res["spec"]["contentType"] = self.content_type
169 if self.storage_class:
170 self.res["spec"][self.api_name]["storageClassName"] = self.storage_class
171 if self.secret:
172 self.res["spec"]["source"][self.source]["secretRef"] = self.secret.name
173 if self.volume_mode:
174 self.res["spec"][self.api_name]["volumeMode"] = self.volume_mode
175 if self.source == "http" or "registry":
176 self.res["spec"]["source"][self.source]["url"] = self.url
177 if self.cert_configmap:
178 self.res["spec"]["source"][self.source]["certConfigMap"] = self.cert_configmap
179 if self.source == "upload" or self.source == "blank":
180 self.res["spec"]["source"][self.source] = {}
181 if self.hostpath_node:
182 self.res["metadata"].setdefault("annotations", {}).update({
183 f"{NamespacedResource.ApiGroup.KUBEVIRT_IO}/provisionOnNode": (self.hostpath_node)
184 })
185 if self.multus_annotation:
186 self.res["metadata"].setdefault("annotations", {}).update({
187 f"{NamespacedResource.ApiGroup.K8S_V1_CNI_CNCF_IO}/networks": (self.multus_annotation)
188 })
189 if self.bind_immediate_annotation:
190 self.res["metadata"].setdefault("annotations", {}).update({
191 f"{self.api_group}/storage.bind.immediate.requested": "true"
192 })
193 if self.source == "pvc":
194 self.res["spec"]["source"]["pvc"] = {
195 "name": self.source_pvc or "dv-source",
196 "namespace": self.source_namespace or self.namespace,
197 }
198 if self.preallocation is not None:
199 self.res["spec"]["preallocation"] = self.preallocation
200 if self.delete_after_completion:
201 self.res["metadata"].setdefault("annotations", {}).update({
202 f"{self.api_group}/storage.deleteAfterCompletion": (self.delete_after_completion)
203 })
205 def wait_deleted(self, timeout=TIMEOUT_4MINUTES):
206 """
207 Wait until DataVolume and the PVC created by it are deleted
209 Args:
210 timeout (int): Time to wait for the DataVolume and PVC to be deleted.
212 Returns:
213 bool: True if DataVolume and its PVC are gone, False if timeout reached.
214 """
215 super().wait_deleted(timeout=timeout)
216 return self.pvc.wait_deleted(timeout=timeout)
218 def wait(self, timeout=TIMEOUT_10MINUTES, failure_timeout=TIMEOUT_2MINUTES, wait_for_exists_only=False):
219 if wait_for_exists_only:
220 return super().wait(timeout=timeout)
221 else:
222 self._check_none_pending_status(failure_timeout=failure_timeout)
224 # If DV's status is not Pending, continue with the flow
225 self.wait_for_status(status=self.Status.SUCCEEDED, timeout=timeout)
226 self.pvc.wait_for_status(status=PersistentVolumeClaim.Status.BOUND, timeout=timeout)
228 @property
229 def pvc(self):
230 return PersistentVolumeClaim(
231 client=self.client,
232 name=self.name,
233 namespace=self.namespace,
234 )
236 @property
237 def scratch_pvc(self):
238 scratch_pvc_prefix = self.pvc.prime_pvc.name if self.pvc.use_populator else self.name
239 return PersistentVolumeClaim(
240 name=f"{scratch_pvc_prefix}-scratch",
241 namespace=self.namespace,
242 client=self.client,
243 )
245 def _check_none_pending_status(self, failure_timeout=TIMEOUT_2MINUTES):
246 # Avoid waiting for "Succeeded" status if DV's in Pending/None status
247 sample = None
248 # if garbage collector is enabled, DV will be deleted after success
249 try:
250 for sample in TimeoutSampler(
251 wait_timeout=failure_timeout,
252 sleep=TIMEOUT_10SEC,
253 func=lambda: self.exists,
254 ):
255 # If DV status is Pending (or Status is not yet updated) continue to wait, else exit the wait loop
256 if sample and (
257 not sample.status
258 or sample.status.phase
259 in [
260 self.Status.PENDING,
261 None,
262 ]
263 ):
264 continue
265 break
266 except TimeoutExpiredError:
267 self.logger.error(f"{self.name} status is {sample}")
268 raise
270 def wait_for_dv_success(
271 self,
272 timeout=TIMEOUT_10MINUTES,
273 failure_timeout=TIMEOUT_2MINUTES,
274 dv_garbage_collection_enabled=False,
275 stop_status_func=None,
276 *stop_status_func_args,
277 **stop_status_func_kwargs,
278 ):
279 """
280 Wait until DataVolume succeeded with or without DV Garbage Collection enabled
282 Args:
283 timeout (int): Time to wait for the DataVolume to succeed.
284 failure_timeout (int): Time to wait for the DataVolume to have not Pending/None status
285 dv_garbage_collection_enabled (bool, default: False): DV garbage collection is deprecated and removed in v4.19
286 stop_status_func (function): function that is called inside the TimeoutSampler
287 if it returns True - stop the Sampler and raise TimeoutExpiredError
288 Example:
289 def dv_is_not_progressing(dv):
290 return True if dv.instance.status.conditions.restartCount > 3 else False
292 def test_dv():
293 ...
294 stop_status_func_kwargs = {"dv": dv}
295 dv.wait_for_dv_success(stop_status_func=dv_is_not_progressing, **stop_status_func_kwargs)
297 Returns:
298 bool: True if DataVolume succeeded.
299 """
300 self.logger.info(f"Wait DV success for {timeout} seconds")
301 self._check_none_pending_status(failure_timeout=failure_timeout)
303 sample = None
304 status_of_dv_str = f"Status of {self.kind} '{self.name}' in namespace '{self.namespace}':\n"
305 try:
306 for sample in TimeoutSampler(
307 sleep=1,
308 wait_timeout=timeout,
309 func=lambda: self.exists,
310 ):
311 if dv_garbage_collection_enabled is not None:
312 warn("garbage collector is deprecated and removed in version v4.19", DeprecationWarning)
313 # DV reach success if the status is Succeeded, or if DV garbage collection enabled and the DV does not exist
314 if sample and sample.get("status", {}).get("phase") == self.Status.SUCCEEDED:
315 break
316 elif sample is None and dv_garbage_collection_enabled:
317 break
318 elif stop_status_func and stop_status_func(*stop_status_func_args, **stop_status_func_kwargs):
319 raise TimeoutExpiredError(
320 value=(
321 "Exited on the stop_status_func"
322 f" {stop_status_func.__name__}."
323 f" {status_of_dv_str} {sample.status}"
324 )
325 )
326 except TimeoutExpiredError:
327 self.logger.error(f"{status_of_dv_str} {sample.status}")
328 raise
330 # For CSI storage, PVC gets Bound after DV succeeded
331 return self.pvc.wait_for_status(status=PersistentVolumeClaim.Status.BOUND, timeout=TIMEOUT_1MINUTE)
333 def delete(self, wait=False, timeout=TIMEOUT_4MINUTES, body=None):
334 """
335 Delete DataVolume
337 Args:
338 wait (bool): True to wait for DataVolume and PVC to be deleted.
339 timeout (int): Time to wait for resources deletion
340 body (dict): Content to send for delete()
342 Returns:
343 bool: True if delete succeeded, False otherwise.
344 """
345 # if garbage collector is enabled, DV will be deleted after success
346 if self.exists:
347 return super().delete(wait=wait, timeout=timeout, body=body)
348 else:
349 return self.pvc.delete(wait=wait, timeout=timeout, body=body)