krkn-lib 5.1.10__py3-none-any.whl → 6.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. krkn_lib/aws_tests/__init__.py +1 -1
  2. krkn_lib/elastic/krkn_elastic.py +3 -1
  3. krkn_lib/k8s/krkn_kubernetes.py +408 -20
  4. krkn_lib/k8s/pod_monitor/__init__.py +1 -2
  5. krkn_lib/k8s/pod_monitor/pod_monitor.py +146 -56
  6. krkn_lib/k8s/templates/snapshot.j2 +10 -0
  7. krkn_lib/models/elastic/models.py +24 -1
  8. krkn_lib/models/k8s/models.py +1 -1
  9. krkn_lib/models/pod_monitor/models.py +2 -2
  10. krkn_lib/models/telemetry/models.py +9 -0
  11. krkn_lib/ocp/krkn_openshift.py +4 -4
  12. krkn_lib/prometheus/krkn_prometheus.py +1 -1
  13. krkn_lib/telemetry/k8s/krkn_telemetry_kubernetes.py +1 -1
  14. krkn_lib/telemetry/ocp/krkn_telemetry_openshift.py +1 -1
  15. krkn_lib/tests/base_test.py +16 -3
  16. krkn_lib/tests/test_krkn_elastic_models.py +23 -4
  17. krkn_lib/tests/test_krkn_kubernetes_check.py +3 -2
  18. krkn_lib/tests/test_krkn_kubernetes_create.py +5 -3
  19. krkn_lib/tests/test_krkn_kubernetes_delete.py +3 -2
  20. krkn_lib/tests/test_krkn_kubernetes_get.py +5 -4
  21. krkn_lib/tests/test_krkn_kubernetes_misc.py +3 -3
  22. krkn_lib/tests/test_krkn_kubernetes_models.py +1 -1
  23. krkn_lib/tests/test_krkn_kubernetes_pods_monitor_models.py +3 -4
  24. krkn_lib/tests/test_krkn_kubernetes_virt.py +735 -0
  25. krkn_lib/tests/test_krkn_openshift.py +571 -48
  26. krkn_lib/tests/test_krkn_telemetry_kubernetes.py +848 -0
  27. krkn_lib/tests/test_safe_logger.py +496 -0
  28. krkn_lib/tests/test_utils.py +4 -5
  29. krkn_lib/utils/functions.py +4 -3
  30. krkn_lib/version/version.py +5 -2
  31. {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/METADATA +7 -10
  32. {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/RECORD +34 -30
  33. {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/WHEEL +1 -1
  34. {krkn_lib-5.1.10.dist-info/licenses → krkn_lib-6.0.0.dist-info}/LICENSE +0 -0
@@ -0,0 +1,735 @@
1
+ #!/usr/bin/env python3
2
+
3
+
4
+ """
5
+ Test suite for KubeVirt/virtualization-related functionality in KrknKubernetes
6
+
7
+ This test suite covers KubeVirt-specific methods in the KrknKubernetes class,
8
+ including operations on VirtualMachines (VMs), VirtualMachineInstances (VMIs),
9
+ and VirtualMachineSnapshots.
10
+
11
+ Tested functionality:
12
+ - VM operations: get, list, delete, patch
13
+ - VMI operations: get, list, create, delete, patch
14
+ - Snapshot operations: get, create, delete
15
+
16
+ Usage:
17
+ # Run all tests in this file
18
+ python -m unittest src.krkn_lib.tests.test_krkn_kubernetes_virt -v
19
+
20
+ # Run a specific test class
21
+ python -m unittest src.krkn_lib.tests.test_krkn_kubernetes_virt.TestKrknKubernetesVirt -v
22
+
23
+ # Run a specific test method
24
+ python -m unittest src.krkn_lib.tests.test_krkn_kubernetes_virt.TestKrknKubernetesVirt.test_get_vm_success -v
25
+
26
+ # Run with coverage
27
+ python -m coverage run -a -m unittest src.krkn_lib.tests.test_krkn_kubernetes_virt -v
28
+
29
+ Assisted By: Claude Code
30
+ """ # NOQA
31
+
32
+ import unittest
33
+ from unittest.mock import MagicMock, PropertyMock, patch
34
+
35
+ from kubernetes.client.rest import ApiException
36
+
37
+ from krkn_lib.k8s.krkn_kubernetes import KrknKubernetes
38
+
39
+
40
+ class TestKrknKubernetesVirt(unittest.TestCase):
41
+
42
+ def setUp(self):
43
+ """Set up mock objects for each test"""
44
+ # Create a mock for custom_object_client
45
+ self.mock_custom_client = MagicMock()
46
+
47
+ # Import and create instance with all mocks in place
48
+ with (
49
+ patch("kubernetes.config.load_kube_config"),
50
+ patch("kubernetes.client.CoreV1Api"),
51
+ patch("kubernetes.client.CustomObjectsApi"),
52
+ patch("kubernetes.client.AppsV1Api"),
53
+ patch("kubernetes.client.BatchV1Api"),
54
+ patch("kubernetes.client.ApiClient"),
55
+ patch("kubernetes.client.VersionApi"),
56
+ patch("kubernetes.client.NetworkingV1Api"),
57
+ ):
58
+ # Create KrknKubernetes instance with mocked dependencies
59
+ self.lib_k8s = KrknKubernetes(kubeconfig_path="dummy")
60
+
61
+ # Patch the custom_object_client property to return our mock
62
+ self.custom_client_patcher = patch.object(
63
+ type(self.lib_k8s),
64
+ "custom_object_client",
65
+ new_callable=PropertyMock,
66
+ return_value=self.mock_custom_client,
67
+ )
68
+ self.custom_client_patcher.start()
69
+
70
+ # Mock list_namespaces_by_regex method
71
+ self.list_namespaces_patcher = patch.object(
72
+ self.lib_k8s,
73
+ "list_namespaces_by_regex",
74
+ return_value=[],
75
+ )
76
+ self.list_namespaces_patcher.start()
77
+
78
+ def tearDown(self):
79
+ """Clean up patches after each test"""
80
+ self.custom_client_patcher.stop()
81
+ self.list_namespaces_patcher.stop()
82
+
83
+ def test_get_vm_success(self):
84
+ """Test get_vm returns VM when it exists"""
85
+ vm_name = "test-vm"
86
+ namespace = "test-ns"
87
+ expected_vm = {
88
+ "metadata": {"name": vm_name, "namespace": namespace},
89
+ "spec": {"running": True},
90
+ }
91
+ # Configure the mock to return expected_vm
92
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
93
+ mock_get.return_value = expected_vm
94
+
95
+ result = self.lib_k8s.get_vm(vm_name, namespace)
96
+
97
+ mock_get.assert_called_once_with(
98
+ group="kubevirt.io",
99
+ version="v1",
100
+ namespace=namespace,
101
+ plural="virtualmachines",
102
+ name=vm_name,
103
+ )
104
+ self.assertEqual(result, expected_vm)
105
+
106
+ def test_get_vm_not_found(self):
107
+ """Test get_vm returns None when VM doesn't exist"""
108
+ vm_name = "non-existent-vm"
109
+ namespace = "test-ns"
110
+ api_exception = ApiException(status=404)
111
+
112
+ # Configure the mock to raise 404
113
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
114
+ mock_get.side_effect = api_exception
115
+
116
+ result = self.lib_k8s.get_vm(vm_name, namespace)
117
+ self.assertIsNone(result)
118
+
119
+ def test_get_vm_api_error(self):
120
+ """Test get_vm raises exception on API error"""
121
+ vm_name = "test-vm"
122
+ namespace = "test-ns"
123
+ api_exception = ApiException(status=500)
124
+
125
+ # Configure the mock to raise 500
126
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
127
+ mock_get.side_effect = api_exception
128
+
129
+ with self.assertRaises(ApiException):
130
+ self.lib_k8s.get_vm(vm_name, namespace)
131
+
132
+ def test_get_vmi_success(self):
133
+ """Test get_vmi returns VMI when it exists"""
134
+ vmi_name = "test-vmi"
135
+ namespace = "test-ns"
136
+ expected_vmi = {
137
+ "metadata": {"name": vmi_name, "namespace": namespace},
138
+ "status": {"phase": "Running"},
139
+ }
140
+
141
+ # Configure the mock to return expected_vmi
142
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
143
+ mock_get.return_value = expected_vmi
144
+
145
+ result = self.lib_k8s.get_vmi(vmi_name, namespace)
146
+
147
+ mock_get.assert_called_once_with(
148
+ group="kubevirt.io",
149
+ version="v1",
150
+ namespace=namespace,
151
+ plural="virtualmachineinstances",
152
+ name=vmi_name,
153
+ )
154
+ self.assertEqual(result, expected_vmi)
155
+
156
+ def test_get_vmi_not_found(self):
157
+ """Test get_vmi returns None when VMI doesn't exist"""
158
+ vmi_name = "non-existent-vmi"
159
+ namespace = "test-ns"
160
+ api_exception = ApiException(status=404)
161
+
162
+ # Configure the mock to raise 404
163
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
164
+ mock_get.side_effect = api_exception
165
+
166
+ result = self.lib_k8s.get_vmi(vmi_name, namespace)
167
+ self.assertIsNone(result)
168
+
169
+ def test_get_vmi_api_error(self):
170
+ """Test get_vmi raises exception on API error"""
171
+ vmi_name = "test-vmi"
172
+ namespace = "test-ns"
173
+ api_exception = ApiException(status=500)
174
+
175
+ # Configure the mock to raise 500
176
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
177
+ mock_get.side_effect = api_exception
178
+
179
+ with self.assertRaises(ApiException):
180
+ self.lib_k8s.get_vmi(vmi_name, namespace)
181
+
182
+ def test_get_vmis_success(self):
183
+ """Test get_vmis returns matching VMIs"""
184
+ regex_name = "^test-vmi-.*"
185
+ namespace = "test-ns"
186
+ vmi1 = {
187
+ "metadata": {"name": "test-vmi-1"},
188
+ "status": {"phase": "Running"},
189
+ }
190
+ vmi2 = {
191
+ "metadata": {"name": "test-vmi-2"},
192
+ "status": {"phase": "Running"},
193
+ }
194
+ vmi3 = {
195
+ "metadata": {"name": "other-vmi"},
196
+ "status": {"phase": "Running"},
197
+ }
198
+ vmis_response = {"items": [vmi1, vmi2, vmi3]}
199
+
200
+ # Mock list_namespaces_by_regex
201
+ with patch.object(
202
+ self.lib_k8s,
203
+ "list_namespaces_by_regex",
204
+ return_value=[namespace],
205
+ ):
206
+ # Configure the mock to return vmis
207
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
208
+ mock_list.return_value = vmis_response
209
+
210
+ # get_vmis returns a list
211
+ result = self.lib_k8s.get_vmis(regex_name, namespace)
212
+
213
+ # Check that only matching VMIs were returned
214
+ self.assertEqual(len(result), 2)
215
+ self.assertIn(vmi1, result)
216
+ self.assertIn(vmi2, result)
217
+ self.assertNotIn(vmi3, result)
218
+
219
+ def test_get_vmis_not_found(self):
220
+ """Test get_vmis handles 404 gracefully"""
221
+ regex_name = "^test-vmi-.*"
222
+ namespace = "test-ns"
223
+ api_exception = ApiException(status=404)
224
+
225
+ # Mock list_namespaces_by_regex
226
+ with patch.object(
227
+ self.lib_k8s,
228
+ "list_namespaces_by_regex",
229
+ return_value=[namespace],
230
+ ):
231
+ # Configure the mock to raise 404
232
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
233
+ mock_list.side_effect = api_exception
234
+
235
+ result = self.lib_k8s.get_vmis(regex_name, namespace)
236
+ # Returns [] when 404
237
+ self.assertEqual(result, [])
238
+
239
+ def test_get_vmis_multiple_namespaces(self):
240
+ """Test get_vmis searches across multiple namespaces"""
241
+ regex_name = "^test-vmi-.*"
242
+ namespace_pattern = "test-ns-.*"
243
+ namespaces = ["test-ns-1", "test-ns-2"]
244
+ vmi1 = {"metadata": {"name": "test-vmi-1"}}
245
+ vmi2 = {"metadata": {"name": "test-vmi-2"}}
246
+ vmis_response_1 = {"items": [vmi1]}
247
+ vmis_response_2 = {"items": [vmi2]}
248
+
249
+ # Mock list_namespaces_by_regex
250
+ with patch.object(
251
+ self.lib_k8s,
252
+ "list_namespaces_by_regex",
253
+ return_value=namespaces,
254
+ ):
255
+ # Configure mock for different responses
256
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
257
+ mock_list.side_effect = [
258
+ vmis_response_1,
259
+ vmis_response_2,
260
+ ]
261
+
262
+ # get_vmis returns a list
263
+ result = self.lib_k8s.get_vmis(regex_name, namespace_pattern)
264
+
265
+ self.assertEqual(len(result), 2)
266
+ self.assertIn(vmi1, result)
267
+ self.assertIn(vmi2, result)
268
+
269
+ def test_get_vms_success(self):
270
+ """Test get_vms returns matching VMs"""
271
+ regex_name = "^test-vm-.*"
272
+ namespace = "test-ns"
273
+ vm1 = {
274
+ "metadata": {"name": "test-vm-1"},
275
+ "spec": {"running": True},
276
+ }
277
+ vm2 = {
278
+ "metadata": {"name": "test-vm-2"},
279
+ "spec": {"running": False},
280
+ }
281
+ vm3 = {
282
+ "metadata": {"name": "other-vm"},
283
+ "spec": {"running": True},
284
+ }
285
+ vms_response = {"items": [vm1, vm2, vm3]}
286
+
287
+ # Mock list_namespaces_by_regex
288
+ with patch.object(
289
+ self.lib_k8s,
290
+ "list_namespaces_by_regex",
291
+ return_value=[namespace],
292
+ ):
293
+ # Configure the mock to return vms
294
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
295
+ mock_list.return_value = vms_response
296
+
297
+ result = self.lib_k8s.get_vms(regex_name, namespace)
298
+
299
+ self.assertEqual(len(result), 2)
300
+ self.assertIn(vm1, result)
301
+ self.assertIn(vm2, result)
302
+ self.assertNotIn(vm3, result)
303
+
304
+ def test_get_vms_not_found(self):
305
+ """Test get_vms returns empty list when no VMs found"""
306
+ regex_name = "^test-vm-.*"
307
+ namespace = "test-ns"
308
+ api_exception = ApiException(status=404)
309
+
310
+ # Mock list_namespaces_by_regex
311
+ with patch.object(
312
+ self.lib_k8s,
313
+ "list_namespaces_by_regex",
314
+ return_value=[namespace],
315
+ ):
316
+ # Configure the mock to raise 404
317
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
318
+ mock_list.side_effect = api_exception
319
+
320
+ result = self.lib_k8s.get_vms(regex_name, namespace)
321
+ self.assertEqual(result, [])
322
+
323
+ def test_get_vms_multiple_namespaces(self):
324
+ """Test get_vms searches across multiple namespaces"""
325
+ regex_name = "^test-vm-.*"
326
+ namespace_pattern = "test-ns-.*"
327
+ namespaces = ["test-ns-1", "test-ns-2"]
328
+ vm1 = {"metadata": {"name": "test-vm-1"}}
329
+ vm2 = {"metadata": {"name": "test-vm-2"}}
330
+ vms_response_1 = {"items": [vm1]}
331
+ vms_response_2 = {"items": [vm2]}
332
+
333
+ # Mock list_namespaces_by_regex
334
+ with patch.object(
335
+ self.lib_k8s,
336
+ "list_namespaces_by_regex",
337
+ return_value=namespaces,
338
+ ):
339
+ # Configure mock for different responses
340
+ mock_list = self.mock_custom_client.list_namespaced_custom_object
341
+ mock_list.side_effect = [
342
+ vms_response_1,
343
+ vms_response_2,
344
+ ]
345
+
346
+ result = self.lib_k8s.get_vms(regex_name, namespace_pattern)
347
+
348
+ self.assertEqual(len(result), 2)
349
+ self.assertIn(vm1, result)
350
+ self.assertIn(vm2, result)
351
+
352
+ def test_delete_vm_success(self):
353
+ """Test delete_vm successfully deletes a VM"""
354
+ vm_name = "test-vm"
355
+ namespace = "test-ns"
356
+ expected_response = {
357
+ "metadata": {"name": vm_name},
358
+ "status": {"phase": "Terminating"},
359
+ }
360
+
361
+ # Configure the mock to return expected response
362
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
363
+ mock_delete.return_value = expected_response
364
+
365
+ result = self.lib_k8s.delete_vm(vm_name, namespace)
366
+
367
+ mock_delete.assert_called_once_with(
368
+ group="kubevirt.io",
369
+ version="v1",
370
+ namespace=namespace,
371
+ plural="virtualmachines",
372
+ name=vm_name,
373
+ )
374
+ self.assertEqual(result, expected_response)
375
+
376
+ def test_delete_vm_not_found(self):
377
+ """Test delete_vm returns None when VM doesn't exist"""
378
+ vm_name = "non-existent-vm"
379
+ namespace = "test-ns"
380
+ api_exception = ApiException(status=404)
381
+
382
+ # Configure the mock to raise 404
383
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
384
+ mock_delete.side_effect = api_exception
385
+
386
+ result = self.lib_k8s.delete_vm(vm_name, namespace)
387
+ self.assertIsNone(result)
388
+
389
+ def test_delete_vm_api_error(self):
390
+ """Test delete_vm raises exception on API error"""
391
+ vm_name = "test-vm"
392
+ namespace = "test-ns"
393
+ api_exception = ApiException(status=500)
394
+
395
+ # Configure the mock to raise 500
396
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
397
+ mock_delete.side_effect = api_exception
398
+
399
+ with self.assertRaises(ApiException):
400
+ self.lib_k8s.delete_vm(vm_name, namespace)
401
+
402
+ def test_delete_vmi_success(self):
403
+ """Test delete_vmi successfully deletes a VMI"""
404
+ vmi_name = "test-vmi"
405
+ namespace = "test-ns"
406
+
407
+ # Configure the mock to return None (success)
408
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
409
+ mock_delete.return_value = None
410
+
411
+ result = self.lib_k8s.delete_vmi(vmi_name, namespace)
412
+
413
+ mock_delete.assert_called_once_with(
414
+ group="kubevirt.io",
415
+ version="v1",
416
+ namespace=namespace,
417
+ plural="virtualmachineinstances",
418
+ name=vmi_name,
419
+ )
420
+ # delete_vmi doesn't explicitly return on success (returns None)
421
+ self.assertIsNone(result)
422
+
423
+ def test_delete_vmi_not_found(self):
424
+ """Test delete_vmi returns 1 when VMI doesn't exist"""
425
+ vmi_name = "non-existent-vmi"
426
+ namespace = "test-ns"
427
+ api_exception = ApiException(status=404)
428
+
429
+ # Configure the mock to raise 404
430
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
431
+ mock_delete.side_effect = api_exception
432
+
433
+ result = self.lib_k8s.delete_vmi(vmi_name, namespace)
434
+ # Returns 1 on 404
435
+ self.assertEqual(result, 1)
436
+
437
+ def test_delete_vmi_api_error(self):
438
+ """Test delete_vmi returns 1 on API error"""
439
+ vmi_name = "test-vmi"
440
+ namespace = "test-ns"
441
+ api_exception = ApiException(status=500)
442
+
443
+ # Configure the mock to raise 500
444
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
445
+ mock_delete.side_effect = api_exception
446
+
447
+ result = self.lib_k8s.delete_vmi(vmi_name, namespace)
448
+ # Returns 1 on error
449
+ self.assertEqual(result, 1)
450
+
451
+ def test_get_snapshot_success(self):
452
+ """Test get_snapshot returns snapshot when it exists"""
453
+ snapshot_name = "test-snapshot"
454
+ namespace = "test-ns"
455
+ expected_snapshot = {
456
+ "metadata": {
457
+ "name": snapshot_name,
458
+ "namespace": namespace,
459
+ },
460
+ "spec": {"source": {"name": "test-vm"}},
461
+ "status": {"readyToUse": True},
462
+ }
463
+
464
+ # Configure the mock to return expected_snapshot
465
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
466
+ mock_get.return_value = expected_snapshot
467
+
468
+ result = self.lib_k8s.get_snapshot(snapshot_name, namespace)
469
+
470
+ mock_get.assert_called_once_with(
471
+ group="snapshot.kubevirt.io",
472
+ version="v1beta1",
473
+ namespace=namespace,
474
+ plural="virtualmachinesnapshots",
475
+ name=snapshot_name,
476
+ )
477
+ self.assertEqual(result, expected_snapshot)
478
+
479
+ def test_get_snapshot_not_found(self):
480
+ """Test get_snapshot returns None when not found"""
481
+ snapshot_name = "non-existent-snapshot"
482
+ namespace = "test-ns"
483
+ api_exception = ApiException(status=404)
484
+
485
+ # Configure the mock to raise 404
486
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
487
+ mock_get.side_effect = api_exception
488
+
489
+ result = self.lib_k8s.get_snapshot(snapshot_name, namespace)
490
+ self.assertIsNone(result)
491
+
492
+ def test_get_snapshot_api_error(self):
493
+ """Test get_snapshot raises exception on API error"""
494
+ snapshot_name = "test-snapshot"
495
+ namespace = "test-ns"
496
+ api_exception = ApiException(status=500)
497
+
498
+ # Configure the mock to raise 500
499
+ mock_get = self.mock_custom_client.get_namespaced_custom_object
500
+ mock_get.side_effect = api_exception
501
+
502
+ with self.assertRaises(ApiException):
503
+ self.lib_k8s.get_snapshot(snapshot_name, namespace)
504
+
505
+ def test_delete_snapshot_success(self):
506
+ """Test delete_snapshot successfully deletes a snapshot"""
507
+ snapshot_name = "test-snapshot"
508
+ namespace = "test-ns"
509
+
510
+ # Configure the mock to return None (success)
511
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
512
+ mock_delete.return_value = None
513
+
514
+ # Should not raise any exception
515
+ self.lib_k8s.delete_snapshot(snapshot_name, namespace)
516
+
517
+ mock_delete.assert_called_once_with(
518
+ group="snapshot.kubevirt.io",
519
+ version="v1beta1",
520
+ namespace=namespace,
521
+ plural="virtualmachinesnapshots",
522
+ name=snapshot_name,
523
+ )
524
+
525
+ def test_delete_snapshot_not_found(self):
526
+ """Test delete_snapshot handles deletion gracefully"""
527
+ snapshot_name = "non-existent-snapshot"
528
+ namespace = "test-ns"
529
+ api_exception = ApiException(status=404)
530
+
531
+ # Configure the mock to raise 404
532
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
533
+ mock_delete.side_effect = api_exception
534
+
535
+ # Should not raise exception
536
+ self.lib_k8s.delete_snapshot(snapshot_name, namespace)
537
+
538
+ def test_delete_snapshot_api_error(self):
539
+ """Test delete_snapshot handles API errors gracefully"""
540
+ snapshot_name = "test-snapshot"
541
+ namespace = "test-ns"
542
+ api_exception = ApiException(status=500)
543
+
544
+ # Configure the mock to raise 500
545
+ mock_delete = self.mock_custom_client.delete_namespaced_custom_object
546
+ mock_delete.side_effect = api_exception
547
+
548
+ # Should not raise exception
549
+ self.lib_k8s.delete_snapshot(snapshot_name, namespace)
550
+
551
+ def test_create_vmi_success(self):
552
+ """Test create_vmi successfully creates a VMI"""
553
+ vmi_name = "test-vmi"
554
+ namespace = "test-ns"
555
+ vm_name = "test-vm"
556
+ vmi_body = {
557
+ "apiVersion": "kubevirt.io/v1",
558
+ "kind": "VirtualMachineInstance",
559
+ "metadata": {"name": vmi_name, "namespace": namespace},
560
+ "spec": {"domain": {"devices": {}}},
561
+ }
562
+ expected_vmi = {
563
+ "metadata": {"name": vmi_name, "namespace": namespace},
564
+ "status": {"phase": "Pending"},
565
+ }
566
+
567
+ # Configure the mock to return expected_vmi
568
+ mock_create = self.mock_custom_client.create_namespaced_custom_object
569
+ mock_create.return_value = expected_vmi
570
+
571
+ result = self.lib_k8s.create_vmi(
572
+ vmi_name, namespace, vm_name, vmi_body
573
+ )
574
+
575
+ mock_create.assert_called_once_with(
576
+ group="kubevirt.io",
577
+ version="v1",
578
+ namespace=namespace,
579
+ plural="virtualmachineinstances",
580
+ body=vmi_body,
581
+ )
582
+ self.assertEqual(result, expected_vmi)
583
+
584
+ def test_create_vmi_not_found(self):
585
+ """Test create_vmi returns None when resource not found"""
586
+ vmi_name = "test-vmi"
587
+ namespace = "test-ns"
588
+ vm_name = "non-existent-vm"
589
+ vmi_body = {"metadata": {"name": vmi_name}}
590
+ api_exception = ApiException(status=404)
591
+
592
+ # Configure the mock to raise 404
593
+ mock_create = self.mock_custom_client.create_namespaced_custom_object
594
+ mock_create.side_effect = api_exception
595
+
596
+ result = self.lib_k8s.create_vmi(
597
+ vmi_name, namespace, vm_name, vmi_body
598
+ )
599
+ self.assertIsNone(result)
600
+
601
+ def test_create_vmi_api_error(self):
602
+ """Test create_vmi raises exception on API error"""
603
+ vmi_name = "test-vmi"
604
+ namespace = "test-ns"
605
+ vm_name = "test-vm"
606
+ vmi_body = {"metadata": {"name": vmi_name}}
607
+ api_exception = ApiException(status=500)
608
+
609
+ # Configure the mock to raise 500
610
+ mock_create = self.mock_custom_client.create_namespaced_custom_object
611
+ mock_create.side_effect = api_exception
612
+
613
+ with self.assertRaises(ApiException):
614
+ self.lib_k8s.create_vmi(vmi_name, namespace, vm_name, vmi_body)
615
+
616
+ def test_patch_vm_success(self):
617
+ """Test patch_vm successfully patches a VM"""
618
+ vm_name = "test-vm"
619
+ namespace = "test-ns"
620
+ vm_body = {
621
+ "spec": {
622
+ "running": True,
623
+ "template": {"metadata": {"labels": {"app": "test"}}},
624
+ }
625
+ }
626
+ expected_vm = {
627
+ "metadata": {"name": vm_name, "namespace": namespace},
628
+ "spec": {"running": True},
629
+ }
630
+
631
+ # Configure the mock to return expected_vm
632
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
633
+ mock_patch.return_value = expected_vm
634
+
635
+ result = self.lib_k8s.patch_vm(vm_name, namespace, vm_body)
636
+
637
+ mock_patch.assert_called_once_with(
638
+ group="kubevirt.io",
639
+ version="v1",
640
+ namespace=namespace,
641
+ plural="virtualmachines",
642
+ name=vm_name,
643
+ body=vm_body,
644
+ )
645
+ self.assertEqual(result, expected_vm)
646
+
647
+ def test_patch_vm_not_found(self):
648
+ """Test patch_vm returns None when VM doesn't exist"""
649
+ vm_name = "non-existent-vm"
650
+ namespace = "test-ns"
651
+ vm_body = {"spec": {"running": False}}
652
+ api_exception = ApiException(status=404)
653
+
654
+ # Configure the mock to raise 404
655
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
656
+ mock_patch.side_effect = api_exception
657
+
658
+ result = self.lib_k8s.patch_vm(vm_name, namespace, vm_body)
659
+ self.assertIsNone(result)
660
+
661
+ def test_patch_vm_api_error(self):
662
+ """Test patch_vm raises exception on API error"""
663
+ vm_name = "test-vm"
664
+ namespace = "test-ns"
665
+ vm_body = {"spec": {"running": True}}
666
+ api_exception = ApiException(status=500)
667
+
668
+ # Configure the mock to raise 500
669
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
670
+ mock_patch.side_effect = api_exception
671
+
672
+ with self.assertRaises(ApiException):
673
+ self.lib_k8s.patch_vm(vm_name, namespace, vm_body)
674
+
675
+ def test_patch_vmi_success(self):
676
+ """Test patch_vmi successfully patches a VMI"""
677
+ vmi_name = "test-vmi"
678
+ namespace = "test-ns"
679
+ vmi_body = {
680
+ "metadata": {
681
+ "labels": {"environment": "production"},
682
+ }
683
+ }
684
+ expected_vmi = {
685
+ "metadata": {
686
+ "name": vmi_name,
687
+ "namespace": namespace,
688
+ "labels": {"environment": "production"},
689
+ },
690
+ "status": {"phase": "Running"},
691
+ }
692
+
693
+ # Configure the mock to return expected_vmi
694
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
695
+ mock_patch.return_value = expected_vmi
696
+
697
+ result = self.lib_k8s.patch_vmi(vmi_name, namespace, vmi_body)
698
+
699
+ mock_patch.assert_called_once_with(
700
+ group="kubevirt.io",
701
+ version="v1",
702
+ namespace=namespace,
703
+ plural="virtualmachineinstances",
704
+ name=vmi_name,
705
+ body=vmi_body,
706
+ )
707
+ self.assertEqual(result, expected_vmi)
708
+
709
+ def test_patch_vmi_not_found(self):
710
+ """Test patch_vmi returns None when VMI doesn't exist"""
711
+ vmi_name = "non-existent-vmi"
712
+ namespace = "test-ns"
713
+ vmi_body = {"metadata": {"labels": {"app": "test"}}}
714
+ api_exception = ApiException(status=404)
715
+
716
+ # Configure the mock to raise 404
717
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
718
+ mock_patch.side_effect = api_exception
719
+
720
+ result = self.lib_k8s.patch_vmi(vmi_name, namespace, vmi_body)
721
+ self.assertIsNone(result)
722
+
723
+ def test_patch_vmi_api_error(self):
724
+ """Test patch_vmi raises exception on API error"""
725
+ vmi_name = "test-vmi"
726
+ namespace = "test-ns"
727
+ vmi_body = {"metadata": {"labels": {"app": "test"}}}
728
+ api_exception = ApiException(status=500)
729
+
730
+ # Configure the mock to raise 500
731
+ mock_patch = self.mock_custom_client.patch_namespaced_custom_object
732
+ mock_patch.side_effect = api_exception
733
+
734
+ with self.assertRaises(ApiException):
735
+ self.lib_k8s.patch_vmi(vmi_name, namespace, vmi_body)