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

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 

12 

13 

14class DataVolume(NamespacedResource): 

15 """ 

16 DataVolume object. 

17 """ 

18 

19 api_group = NamespacedResource.ApiGroup.CDI_KUBEVIRT_IO 

20 

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" 

36 

37 class AccessMode: 

38 """ 

39 AccessMode object. 

40 """ 

41 

42 RWO = "ReadWriteOnce" 

43 ROX = "ReadOnlyMany" 

44 RWX = "ReadWriteMany" 

45 

46 class ContentType: 

47 """ 

48 ContentType object 

49 """ 

50 

51 KUBEVIRT = "kubevirt" 

52 ARCHIVE = "archive" 

53 

54 class VolumeMode: 

55 """ 

56 VolumeMode object 

57 """ 

58 

59 BLOCK = "Block" 

60 FILE = "Filesystem" 

61 

62 class Condition: 

63 class Type: 

64 READY = "Ready" 

65 BOUND = "Bound" 

66 RUNNING = "Running" 

67 

68 class Status(Resource.Condition.Status): 

69 UNKNOWN = "Unknown" 

70 

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 

100 

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 

153 

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 }) 

204 

205 def wait_deleted(self, timeout=TIMEOUT_4MINUTES): 

206 """ 

207 Wait until DataVolume and the PVC created by it are deleted 

208 

209 Args: 

210 timeout (int): Time to wait for the DataVolume and PVC to be deleted. 

211 

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) 

217 

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) 

223 

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) 

227 

228 @property 

229 def pvc(self): 

230 return PersistentVolumeClaim( 

231 client=self.client, 

232 name=self.name, 

233 namespace=self.namespace, 

234 ) 

235 

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 ) 

244 

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 

269 

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 

281 

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 

291 

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) 

296 

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) 

302 

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 

329 

330 # For CSI storage, PVC gets Bound after DV succeeded 

331 return self.pvc.wait_for_status(status=PersistentVolumeClaim.Status.BOUND, timeout=TIMEOUT_1MINUTE) 

332 

333 def delete(self, wait=False, timeout=TIMEOUT_4MINUTES, body=None): 

334 """ 

335 Delete DataVolume 

336 

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() 

341 

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)