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