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,850 @@
1
+ """
2
+ Comprehensive unit tests for KrknTelemetryKubernetes class.
3
+
4
+ This test suite uses mocks to test all methods without requiring
5
+ actual Kubernetes clusters or external services.
6
+ """
7
+
8
+ import base64
9
+ import os
10
+ import tempfile
11
+ import unittest
12
+ from queue import Queue
13
+ from unittest.mock import Mock, patch
14
+
15
+ import yaml
16
+
17
+ from krkn_lib.k8s import KrknKubernetes
18
+ from krkn_lib.models.krkn import ChaosRunAlert, ChaosRunAlertSummary
19
+ from krkn_lib.models.telemetry import ChaosRunTelemetry, ScenarioTelemetry
20
+ from krkn_lib.telemetry.k8s.krkn_telemetry_kubernetes import (
21
+ KrknTelemetryKubernetes,
22
+ )
23
+ from krkn_lib.utils.safe_logger import SafeLogger
24
+
25
+
26
+ class TestKrknTelemetryKubernetesInit(unittest.TestCase):
27
+ """Test initialization and basic getter methods."""
28
+
29
+ def setUp(self):
30
+ self.mock_logger = Mock(spec=SafeLogger)
31
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
32
+ self.telemetry_config = {
33
+ "enabled": True,
34
+ "api_url": "https://test-api.com",
35
+ "username": "test_user",
36
+ "password": "test_pass",
37
+ "telemetry_group": "test_group",
38
+ }
39
+ self.request_id = "test-request-123"
40
+
41
+ def test_init_with_config(self):
42
+ """Test initialization with telemetry config."""
43
+ telemetry = KrknTelemetryKubernetes(
44
+ safe_logger=self.mock_logger,
45
+ lib_kubernetes=self.mock_kubecli,
46
+ krkn_telemetry_config=self.telemetry_config,
47
+ telemetry_request_id=self.request_id,
48
+ )
49
+
50
+ self.assertEqual(telemetry.get_lib_kubernetes(), self.mock_kubecli)
51
+ self.assertEqual(
52
+ telemetry.get_telemetry_config(), self.telemetry_config
53
+ )
54
+ self.assertEqual(telemetry.get_telemetry_request_id(), self.request_id)
55
+
56
+ def test_init_without_config(self):
57
+ """Test initialization without telemetry config."""
58
+ telemetry = KrknTelemetryKubernetes(
59
+ safe_logger=self.mock_logger,
60
+ lib_kubernetes=self.mock_kubecli,
61
+ )
62
+
63
+ self.assertEqual(telemetry.get_telemetry_config(), {})
64
+ self.assertEqual(telemetry.get_telemetry_request_id(), "")
65
+
66
+ def test_default_telemetry_group(self):
67
+ """Test default telemetry group value."""
68
+ telemetry = KrknTelemetryKubernetes(
69
+ safe_logger=self.mock_logger,
70
+ lib_kubernetes=self.mock_kubecli,
71
+ )
72
+
73
+ self.assertEqual(telemetry.default_telemetry_group, "default")
74
+
75
+
76
+ class TestCollectClusterMetadata(unittest.TestCase):
77
+ """Test collect_cluster_metadata method."""
78
+
79
+ def setUp(self):
80
+ self.mock_logger = Mock(spec=SafeLogger)
81
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
82
+ self.telemetry = KrknTelemetryKubernetes(
83
+ safe_logger=self.mock_logger,
84
+ lib_kubernetes=self.mock_kubecli,
85
+ )
86
+
87
+ @patch("krkn_lib.utils.get_ci_job_url")
88
+ def test_collect_cluster_metadata_success(self, mock_get_ci_url):
89
+ """Test successful collection of cluster metadata."""
90
+ # Setup mocks
91
+ mock_get_ci_url.return_value = "https://ci.example.com/job/123"
92
+
93
+ mock_obj_count = {"Deployment": 10, "Pod": 50, "Secret": 5}
94
+ self.mock_kubecli.get_all_kubernetes_object_count.return_value = (
95
+ mock_obj_count
96
+ )
97
+
98
+ mock_node_info = Mock(count=3, instance_type="m5.large")
99
+ mock_taints = ["NoSchedule"]
100
+ self.mock_kubecli.get_nodes_infos.return_value = (
101
+ [mock_node_info],
102
+ mock_taints,
103
+ )
104
+ self.mock_kubecli.get_version.return_value = "v1.28.0"
105
+
106
+ # Create telemetry with successful scenario
107
+ chaos_telemetry = ChaosRunTelemetry()
108
+ scenario1 = ScenarioTelemetry()
109
+ scenario1.exit_status = 0
110
+ chaos_telemetry.scenarios.append(scenario1)
111
+
112
+ # Execute
113
+ self.telemetry.collect_cluster_metadata(chaos_telemetry)
114
+
115
+ # Assertions
116
+ self.assertEqual(
117
+ chaos_telemetry.kubernetes_objects_count, mock_obj_count
118
+ )
119
+ self.assertEqual(chaos_telemetry.node_summary_infos, [mock_node_info])
120
+ self.assertEqual(chaos_telemetry.cluster_version, "v1.28.0")
121
+ self.assertEqual(chaos_telemetry.major_version, "1.28")
122
+ self.assertEqual(chaos_telemetry.node_taints, mock_taints)
123
+ self.assertEqual(
124
+ chaos_telemetry.build_url, "https://ci.example.com/job/123"
125
+ )
126
+ self.assertEqual(chaos_telemetry.total_node_count, 3)
127
+ self.assertTrue(chaos_telemetry.job_status) # No failed scenarios
128
+
129
+ self.mock_logger.info.assert_called()
130
+
131
+ @patch("krkn_lib.utils.get_ci_job_url")
132
+ def test_collect_cluster_metadata_with_failed_scenario(
133
+ self, mock_get_ci_url
134
+ ):
135
+ """Test metadata collection when scenario failed."""
136
+ mock_get_ci_url.return_value = None
137
+ self.mock_kubecli.get_all_kubernetes_object_count.return_value = {}
138
+ self.mock_kubecli.get_nodes_infos.return_value = ([], [])
139
+ self.mock_kubecli.get_version.return_value = "v1.27.5"
140
+
141
+ chaos_telemetry = ChaosRunTelemetry()
142
+ scenario1 = ScenarioTelemetry()
143
+ scenario1.exit_status = 1 # Failed scenario
144
+ chaos_telemetry.scenarios.append(scenario1)
145
+
146
+ self.telemetry.collect_cluster_metadata(chaos_telemetry)
147
+
148
+ self.assertFalse(chaos_telemetry.job_status) # Should be False
149
+
150
+ @patch("krkn_lib.utils.get_ci_job_url")
151
+ def test_collect_cluster_metadata_multiple_nodes(self, mock_get_ci_url):
152
+ """Test with multiple node types."""
153
+ mock_get_ci_url.return_value = None
154
+ self.mock_kubecli.get_all_kubernetes_object_count.return_value = {}
155
+
156
+ node_info1 = Mock(count=3)
157
+ node_info2 = Mock(count=5)
158
+ self.mock_kubecli.get_nodes_infos.return_value = (
159
+ [node_info1, node_info2],
160
+ [],
161
+ )
162
+ self.mock_kubecli.get_version.return_value = "v1.29.1"
163
+
164
+ chaos_telemetry = ChaosRunTelemetry()
165
+ self.telemetry.collect_cluster_metadata(chaos_telemetry)
166
+
167
+ self.assertEqual(chaos_telemetry.total_node_count, 8) # 3 + 5
168
+
169
+
170
+ class TestSendTelemetry(unittest.TestCase):
171
+ """Test send_telemetry method."""
172
+
173
+ def setUp(self):
174
+ self.mock_logger = Mock(spec=SafeLogger)
175
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
176
+ self.telemetry = KrknTelemetryKubernetes(
177
+ safe_logger=self.mock_logger,
178
+ lib_kubernetes=self.mock_kubecli,
179
+ )
180
+
181
+ @patch("requests.post")
182
+ def test_send_telemetry_success(self, mock_post):
183
+ """Test successful telemetry send."""
184
+ # Setup
185
+ mock_response = Mock()
186
+ mock_response.status_code = 200
187
+ mock_post.return_value = mock_response
188
+
189
+ telemetry_config = {
190
+ "enabled": True,
191
+ "api_url": "https://test-api.com",
192
+ "username": "testuser",
193
+ "password": "testpass",
194
+ "telemetry_group": "testgroup",
195
+ }
196
+
197
+ chaos_telemetry = ChaosRunTelemetry()
198
+ uuid = "test-uuid-123"
199
+
200
+ # Execute
201
+ result = self.telemetry.send_telemetry(
202
+ telemetry_config, uuid, chaos_telemetry
203
+ )
204
+
205
+ # Assertions
206
+ self.assertIsNotNone(result)
207
+ mock_post.assert_called_once()
208
+ call_kwargs = mock_post.call_args[1]
209
+ self.assertEqual(call_kwargs["url"], "https://test-api.com/telemetry")
210
+ self.assertEqual(call_kwargs["auth"], ("testuser", "testpass"))
211
+ self.assertEqual(call_kwargs["params"]["request_id"], uuid)
212
+ self.assertEqual(call_kwargs["params"]["telemetry_group"], "testgroup")
213
+
214
+ self.mock_logger.info.assert_called_with(
215
+ "successfully sent telemetry data"
216
+ )
217
+
218
+ @patch("requests.post")
219
+ def test_send_telemetry_default_group(self, mock_post):
220
+ """Test telemetry send with default group."""
221
+ mock_response = Mock()
222
+ mock_response.status_code = 200
223
+ mock_post.return_value = mock_response
224
+
225
+ telemetry_config = {
226
+ "enabled": True,
227
+ "api_url": "https://test-api.com",
228
+ "username": "testuser",
229
+ "password": "testpass",
230
+ # telemetry_group not specified
231
+ }
232
+
233
+ self.telemetry.send_telemetry(
234
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
235
+ )
236
+
237
+ call_kwargs = mock_post.call_args[1]
238
+ self.assertEqual(call_kwargs["params"]["telemetry_group"], "default")
239
+
240
+ def test_send_telemetry_disabled(self):
241
+ """Test when telemetry is disabled."""
242
+ telemetry_config = {"enabled": False}
243
+
244
+ result = self.telemetry.send_telemetry(
245
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
246
+ )
247
+
248
+ self.assertIsNone(result)
249
+
250
+ def test_send_telemetry_missing_api_url(self):
251
+ """Test with missing api_url."""
252
+ telemetry_config = {
253
+ "enabled": True,
254
+ "username": "testuser",
255
+ "password": "testpass",
256
+ }
257
+
258
+ with self.assertRaises(Exception) as context:
259
+ self.telemetry.send_telemetry(
260
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
261
+ )
262
+
263
+ self.assertIn("api_url is missing", str(context.exception))
264
+
265
+ def test_send_telemetry_missing_username(self):
266
+ """Test with missing username."""
267
+ telemetry_config = {
268
+ "enabled": True,
269
+ "api_url": "https://test-api.com",
270
+ "password": "testpass",
271
+ }
272
+
273
+ with self.assertRaises(Exception) as context:
274
+ self.telemetry.send_telemetry(
275
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
276
+ )
277
+
278
+ self.assertIn("username is missing", str(context.exception))
279
+
280
+ def test_send_telemetry_missing_password(self):
281
+ """Test with missing password."""
282
+ telemetry_config = {
283
+ "enabled": True,
284
+ "api_url": "https://test-api.com",
285
+ "username": "testuser",
286
+ }
287
+
288
+ with self.assertRaises(Exception) as context:
289
+ self.telemetry.send_telemetry(
290
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
291
+ )
292
+
293
+ self.assertIn("password is missing", str(context.exception))
294
+
295
+ def test_send_telemetry_multiple_missing_fields(self):
296
+ """Test with multiple missing required fields."""
297
+ telemetry_config = {"enabled": True}
298
+
299
+ with self.assertRaises(Exception) as context:
300
+ self.telemetry.send_telemetry(
301
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
302
+ )
303
+
304
+ exception_str = str(context.exception)
305
+ self.assertIn("api_url is missing", exception_str)
306
+ self.assertIn("username is missing", exception_str)
307
+ self.assertIn("password is missing", exception_str)
308
+
309
+ @patch("requests.post")
310
+ def test_send_telemetry_http_error(self, mock_post):
311
+ """Test handling of HTTP errors."""
312
+ mock_response = Mock()
313
+ mock_response.status_code = 500
314
+ mock_response.content = b"Internal Server Error"
315
+ mock_post.return_value = mock_response
316
+
317
+ telemetry_config = {
318
+ "enabled": True,
319
+ "api_url": "https://test-api.com",
320
+ "username": "testuser",
321
+ "password": "testpass",
322
+ }
323
+
324
+ with self.assertRaises(Exception) as context:
325
+ self.telemetry.send_telemetry(
326
+ telemetry_config, "test-uuid", ChaosRunTelemetry()
327
+ )
328
+
329
+ self.assertIn("failed to send telemetry", str(context.exception))
330
+ self.assertIn("500", str(context.exception))
331
+ self.mock_logger.warning.assert_called()
332
+
333
+
334
+ class TestSetParametersBase64(unittest.TestCase):
335
+ """Test set_parameters_base64 method."""
336
+
337
+ def setUp(self):
338
+ self.mock_logger = Mock(spec=SafeLogger)
339
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
340
+ self.telemetry = KrknTelemetryKubernetes(
341
+ safe_logger=self.mock_logger,
342
+ lib_kubernetes=self.mock_kubecli,
343
+ )
344
+
345
+ def test_set_parameters_base64_success(self):
346
+ """Test successful base64 encoding of scenario file."""
347
+ scenario_yaml = {
348
+ "scenario": "pod_kill",
349
+ "kubeconfig": "/path/to/kubeconfig",
350
+ "param1": "value1",
351
+ }
352
+ yaml_content = yaml.safe_dump(scenario_yaml)
353
+
354
+ with tempfile.NamedTemporaryFile(
355
+ mode="w", delete=False, suffix=".yaml"
356
+ ) as f:
357
+ f.write(yaml_content)
358
+ f.flush()
359
+ temp_path = f.name
360
+
361
+ try:
362
+ scenario_telemetry = ScenarioTelemetry()
363
+ result = self.telemetry.set_parameters_base64(
364
+ scenario_telemetry, temp_path
365
+ )
366
+
367
+ # Verify base64 was set
368
+ self.assertIsNotNone(scenario_telemetry.parameters_base64)
369
+
370
+ # Decode and verify
371
+ decoded = base64.b64decode(
372
+ scenario_telemetry.parameters_base64.encode()
373
+ ).decode()
374
+ decoded_yaml = yaml.safe_load(decoded)
375
+
376
+ self.assertEqual(decoded_yaml["scenario"], "pod_kill")
377
+ self.assertEqual(decoded_yaml["kubeconfig"], "anonymized")
378
+ self.assertEqual(decoded_yaml["param1"], "value1")
379
+ self.assertIsInstance(result, dict)
380
+ finally:
381
+ os.unlink(temp_path)
382
+
383
+ def test_set_parameters_base64_file_not_found(self):
384
+ """Test with non-existent file."""
385
+ scenario_telemetry = ScenarioTelemetry()
386
+
387
+ with self.assertRaises(Exception) as context:
388
+ self.telemetry.set_parameters_base64(
389
+ scenario_telemetry, "/nonexistent/file.yaml"
390
+ )
391
+
392
+ self.assertIn("scenario file not found", str(context.exception))
393
+
394
+ def test_set_parameters_base64_invalid_yaml(self):
395
+ """Test with invalid YAML content."""
396
+ with tempfile.NamedTemporaryFile(
397
+ mode="w", delete=False, suffix=".yaml"
398
+ ) as f:
399
+ f.write("invalid: yaml: content:\n - this is\n bad yaml")
400
+ f.flush()
401
+ temp_path = f.name
402
+
403
+ try:
404
+ scenario_telemetry = ScenarioTelemetry()
405
+
406
+ with self.assertRaises(Exception) as context:
407
+ self.telemetry.set_parameters_base64(
408
+ scenario_telemetry, temp_path
409
+ )
410
+
411
+ # The exception comes from yaml processing
412
+ self.assertIn("telemetry:", str(context.exception))
413
+ finally:
414
+ os.unlink(temp_path)
415
+
416
+ def test_set_parameters_base64_nested_kubeconfig(self):
417
+ """Test anonymization of nested kubeconfig."""
418
+ scenario_yaml = {
419
+ "input_list": [
420
+ {"scenario": "pod_kill", "kubeconfig": "/path/to/config"},
421
+ {"scenario": "network", "kubeconfig": "/another/config"},
422
+ ]
423
+ }
424
+ yaml_content = yaml.safe_dump(scenario_yaml)
425
+
426
+ with tempfile.NamedTemporaryFile(
427
+ mode="w", delete=False, suffix=".yaml"
428
+ ) as f:
429
+ f.write(yaml_content)
430
+ f.flush()
431
+ temp_path = f.name
432
+
433
+ try:
434
+ scenario_telemetry = ScenarioTelemetry()
435
+ self.telemetry.set_parameters_base64(
436
+ scenario_telemetry, temp_path
437
+ )
438
+
439
+ decoded = base64.b64decode(
440
+ scenario_telemetry.parameters_base64.encode()
441
+ ).decode()
442
+ decoded_yaml = yaml.safe_load(decoded)
443
+
444
+ # All kubeconfig fields should be anonymized
445
+ self.assertEqual(
446
+ decoded_yaml["input_list"][0]["kubeconfig"], "anonymized"
447
+ )
448
+ self.assertEqual(
449
+ decoded_yaml["input_list"][1]["kubeconfig"], "anonymized"
450
+ )
451
+ finally:
452
+ os.unlink(temp_path)
453
+
454
+
455
+ class TestGetBucketUrlForFilename(unittest.TestCase):
456
+ """Test get_bucket_url_for_filename method."""
457
+
458
+ def setUp(self):
459
+ self.mock_logger = Mock(spec=SafeLogger)
460
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
461
+ self.telemetry = KrknTelemetryKubernetes(
462
+ safe_logger=self.mock_logger,
463
+ lib_kubernetes=self.mock_kubecli,
464
+ )
465
+
466
+ @patch("requests.get")
467
+ def test_get_bucket_url_success(self, mock_get):
468
+ """Test successful retrieval of presigned URL."""
469
+ mock_response = Mock()
470
+ mock_response.status_code = 200
471
+ mock_response.content = (
472
+ b"https://s3.amazonaws.com/bucket/file?presigned=true"
473
+ )
474
+ mock_get.return_value = mock_response
475
+
476
+ url = self.telemetry.get_bucket_url_for_filename(
477
+ api_url="https://api.test.com/presigned-url",
478
+ bucket_folder="test/folder",
479
+ remote_filename="test-file.tar",
480
+ username="testuser",
481
+ password="testpass",
482
+ )
483
+
484
+ self.assertEqual(
485
+ url, "https://s3.amazonaws.com/bucket/file?presigned=true"
486
+ )
487
+ mock_get.assert_called_once()
488
+
489
+ @patch("requests.get")
490
+ def test_get_bucket_url_http_error(self, mock_get):
491
+ """Test handling of HTTP errors."""
492
+ mock_response = Mock()
493
+ mock_response.status_code = 403
494
+ mock_get.return_value = mock_response
495
+
496
+ with self.assertRaises(Exception) as context:
497
+ self.telemetry.get_bucket_url_for_filename(
498
+ api_url="https://api.test.com/presigned-url",
499
+ bucket_folder="test/folder",
500
+ remote_filename="test-file.tar",
501
+ username="testuser",
502
+ password="testpass",
503
+ )
504
+
505
+ self.assertIn("impossible to get upload url", str(context.exception))
506
+ self.assertIn("403", str(context.exception))
507
+
508
+
509
+ class TestPutFileToUrl(unittest.TestCase):
510
+ """Test put_file_to_url method."""
511
+
512
+ def setUp(self):
513
+ self.mock_logger = Mock(spec=SafeLogger)
514
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
515
+ self.telemetry = KrknTelemetryKubernetes(
516
+ safe_logger=self.mock_logger,
517
+ lib_kubernetes=self.mock_kubecli,
518
+ )
519
+
520
+ @patch("requests.put")
521
+ def test_put_file_success(self, mock_put):
522
+ """Test successful file upload."""
523
+ mock_response = Mock()
524
+ mock_response.status_code = 200
525
+ mock_put.return_value = mock_response
526
+
527
+ with tempfile.NamedTemporaryFile(delete=False) as f:
528
+ f.write(b"test content")
529
+ f.flush()
530
+ temp_path = f.name
531
+
532
+ try:
533
+ self.telemetry.put_file_to_url(
534
+ "https://s3.test.com/file", temp_path
535
+ )
536
+ mock_put.assert_called_once()
537
+ finally:
538
+ os.unlink(temp_path)
539
+
540
+ @patch("requests.put")
541
+ def test_put_file_http_error(self, mock_put):
542
+ """Test handling of HTTP errors."""
543
+ mock_response = Mock()
544
+ mock_response.status_code = 403
545
+ mock_put.return_value = mock_response
546
+
547
+ with tempfile.NamedTemporaryFile(delete=False) as f:
548
+ f.write(b"test content")
549
+ f.flush()
550
+ temp_path = f.name
551
+
552
+ try:
553
+ with self.assertRaises(Exception) as context:
554
+ self.telemetry.put_file_to_url(
555
+ "https://s3.test.com/file", temp_path
556
+ )
557
+
558
+ self.assertIn(
559
+ "failed to send archive to s3", str(context.exception)
560
+ )
561
+ self.assertIn("403", str(context.exception))
562
+ finally:
563
+ os.unlink(temp_path)
564
+
565
+ @patch("requests.put")
566
+ def test_put_file_connection_error(self, mock_put):
567
+ """Test handling of connection errors."""
568
+ mock_put.side_effect = Exception("Connection refused")
569
+
570
+ with tempfile.NamedTemporaryFile(delete=False) as f:
571
+ f.write(b"test content")
572
+ f.flush()
573
+ temp_path = f.name
574
+
575
+ try:
576
+ with self.assertRaises(Exception) as context:
577
+ self.telemetry.put_file_to_url(
578
+ "https://s3.test.com/file", temp_path
579
+ )
580
+
581
+ self.assertIn("Connection refused", str(context.exception))
582
+ finally:
583
+ os.unlink(temp_path)
584
+
585
+
586
+ class TestPutCriticalAlerts(unittest.TestCase):
587
+ """Test put_critical_alerts method."""
588
+
589
+ def setUp(self):
590
+ self.mock_logger = Mock(spec=SafeLogger)
591
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
592
+ self.telemetry = KrknTelemetryKubernetes(
593
+ safe_logger=self.mock_logger,
594
+ lib_kubernetes=self.mock_kubecli,
595
+ )
596
+
597
+ def test_put_alerts_empty_summary(self):
598
+ """Test with empty alert summary."""
599
+ telemetry_config = {
600
+ "events_backup": True,
601
+ "api_url": "https://test-api.com",
602
+ "username": "testuser",
603
+ "password": "testpass",
604
+ "max_retries": 3,
605
+ }
606
+
607
+ empty_summary = ChaosRunAlertSummary()
608
+
609
+ # Should return early without error
610
+ self.telemetry.put_critical_alerts(
611
+ "test-id", telemetry_config, empty_summary
612
+ )
613
+
614
+ self.mock_logger.info.assert_called_with(
615
+ "no alerts collected during the run, skipping"
616
+ )
617
+
618
+ def test_put_alerts_none_summary(self):
619
+ """Test with None summary."""
620
+ telemetry_config = {
621
+ "events_backup": True,
622
+ "api_url": "https://test-api.com",
623
+ "username": "testuser",
624
+ "password": "testpass",
625
+ "max_retries": 3,
626
+ }
627
+
628
+ self.telemetry.put_critical_alerts("test-id", telemetry_config, None)
629
+
630
+ self.mock_logger.info.assert_called_with(
631
+ "no alerts collected during the run, skipping"
632
+ )
633
+
634
+ def test_put_alerts_missing_config_fields(self):
635
+ """Test with missing required config fields."""
636
+ telemetry_config = {"events_backup": True}
637
+
638
+ summary = ChaosRunAlertSummary()
639
+ alert = ChaosRunAlert("test", "firing", "default", "critical")
640
+ summary.chaos_alerts.append(alert)
641
+
642
+ with self.assertRaises(Exception) as context:
643
+ self.telemetry.put_critical_alerts(
644
+ "test-id", telemetry_config, summary
645
+ )
646
+
647
+ exception_str = str(context.exception)
648
+ self.assertIn("api_url is missing", exception_str)
649
+ self.assertIn("username is missing", exception_str)
650
+ self.assertIn("password is missing", exception_str)
651
+
652
+
653
+ class TestGenerateUrlAndPutToS3Worker(unittest.TestCase):
654
+ """Test generate_url_and_put_to_s3_worker method."""
655
+
656
+ def setUp(self):
657
+ self.mock_logger = Mock(spec=SafeLogger)
658
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
659
+ self.telemetry = KrknTelemetryKubernetes(
660
+ safe_logger=self.mock_logger,
661
+ lib_kubernetes=self.mock_kubecli,
662
+ )
663
+
664
+ @patch.object(KrknTelemetryKubernetes, "put_file_to_url")
665
+ @patch.object(KrknTelemetryKubernetes, "get_bucket_url_for_filename")
666
+ def test_worker_success(self, mock_get_url, mock_put_file):
667
+ """Test successful worker execution."""
668
+ mock_get_url.return_value = "https://s3.test.com/file"
669
+
670
+ queue = Queue()
671
+ uploaded_files = []
672
+
673
+ with tempfile.NamedTemporaryFile(delete=False) as f:
674
+ f.write(b"test")
675
+ f.flush()
676
+ temp_path = f.name
677
+
678
+ try:
679
+ queue.put((1, temp_path, 0))
680
+
681
+ self.telemetry.generate_url_and_put_to_s3_worker(
682
+ queue=queue,
683
+ queue_size=1,
684
+ request_id="test-id",
685
+ telemetry_group="default",
686
+ api_url="https://api.test.com/presigned-url",
687
+ username="testuser",
688
+ password="testpass",
689
+ thread_number=0,
690
+ uploaded_file_list=uploaded_files,
691
+ max_retries=3,
692
+ remote_file_prefix="test-",
693
+ remote_file_extension=".tar",
694
+ )
695
+
696
+ # Worker should have processed the file
697
+ self.assertTrue(queue.empty())
698
+ self.assertEqual(len(uploaded_files), 1)
699
+ mock_get_url.assert_called_once()
700
+ mock_put_file.assert_called_once()
701
+ except FileNotFoundError:
702
+ # File was deleted by worker, which is expected
703
+ pass
704
+
705
+ @patch.object(KrknTelemetryKubernetes, "put_file_to_url")
706
+ @patch.object(KrknTelemetryKubernetes, "get_bucket_url_for_filename")
707
+ @patch("time.sleep")
708
+ def test_worker_retry_on_failure(
709
+ self, mock_sleep, mock_get_url, mock_put_file
710
+ ):
711
+ """Test worker retry logic on failure."""
712
+ mock_get_url.return_value = "https://s3.test.com/file"
713
+ mock_put_file.side_effect = [Exception("Upload failed"), None]
714
+
715
+ queue = Queue()
716
+ uploaded_files = []
717
+
718
+ with tempfile.NamedTemporaryFile(delete=False) as f:
719
+ f.write(b"test")
720
+ f.flush()
721
+ temp_path = f.name
722
+
723
+ try:
724
+ queue.put((1, temp_path, 0))
725
+
726
+ self.telemetry.generate_url_and_put_to_s3_worker(
727
+ queue=queue,
728
+ queue_size=1,
729
+ request_id="test-id",
730
+ telemetry_group="default",
731
+ api_url="https://api.test.com/presigned-url",
732
+ username="testuser",
733
+ password="testpass",
734
+ thread_number=0,
735
+ uploaded_file_list=uploaded_files,
736
+ max_retries=3,
737
+ remote_file_prefix="test-",
738
+ remote_file_extension=".tar",
739
+ )
740
+
741
+ # Should have retried
742
+ self.mock_logger.warning.assert_called()
743
+ mock_sleep.assert_called()
744
+ except FileNotFoundError:
745
+ pass
746
+
747
+
748
+ class TestGetPrometheusPodData(unittest.TestCase):
749
+ """Test get_prometheus_pod_data method."""
750
+
751
+ def setUp(self):
752
+ self.mock_logger = Mock(spec=SafeLogger)
753
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
754
+ self.telemetry = KrknTelemetryKubernetes(
755
+ safe_logger=self.mock_logger,
756
+ lib_kubernetes=self.mock_kubecli,
757
+ )
758
+
759
+ def test_prometheus_backup_disabled(self):
760
+ """Test when prometheus backup is disabled."""
761
+ # Need to provide all required config even when disabled
762
+ # because validation happens before the disabled check
763
+ telemetry_config = {
764
+ "prometheus_backup": False,
765
+ "full_prometheus_backup": True,
766
+ "backup_threads": 4,
767
+ "api_url": "https://test-api.com",
768
+ "username": "testuser",
769
+ "password": "testpass",
770
+ "archive_path": "/tmp",
771
+ "archive_size": 100,
772
+ }
773
+
774
+ result = self.telemetry.get_prometheus_pod_data(
775
+ telemetry_config=telemetry_config,
776
+ request_id="test-id",
777
+ prometheus_pod_name="prometheus-0",
778
+ prometheus_container_name="prometheus",
779
+ prometheus_namespace="monitoring",
780
+ )
781
+
782
+ self.assertEqual(result, list[(int, str)]())
783
+
784
+ def test_prometheus_backup_missing_config(self):
785
+ """Test with missing required config."""
786
+ telemetry_config = {"prometheus_backup": True}
787
+
788
+ with self.assertRaises(Exception) as context:
789
+ self.telemetry.get_prometheus_pod_data(
790
+ telemetry_config=telemetry_config,
791
+ request_id="test-id",
792
+ prometheus_pod_name="prometheus-0",
793
+ prometheus_container_name="prometheus",
794
+ prometheus_namespace="monitoring",
795
+ )
796
+
797
+ exception_str = str(context.exception)
798
+ self.assertIn("full_prometheus_backup flag is missing", exception_str)
799
+
800
+
801
+ class TestPutPrometheusData(unittest.TestCase):
802
+ """Test put_prometheus_data method."""
803
+
804
+ def setUp(self):
805
+ self.mock_logger = Mock(spec=SafeLogger)
806
+ self.mock_kubecli = Mock(spec=KrknKubernetes)
807
+ self.telemetry = KrknTelemetryKubernetes(
808
+ safe_logger=self.mock_logger,
809
+ lib_kubernetes=self.mock_kubecli,
810
+ )
811
+
812
+ def test_prometheus_backup_disabled(self):
813
+ """Test when prometheus backup is disabled."""
814
+ # Need to provide all required config even when disabled
815
+ # because validation happens before the disabled check
816
+ telemetry_config = {
817
+ "prometheus_backup": False,
818
+ "backup_threads": 4,
819
+ "api_url": "https://test-api.com",
820
+ "username": "testuser",
821
+ "password": "testpass",
822
+ "max_retries": 3,
823
+ }
824
+
825
+ # Should return early without error
826
+ result = self.telemetry.put_prometheus_data(
827
+ telemetry_config=telemetry_config,
828
+ archive_volumes=[],
829
+ request_id="test-id",
830
+ )
831
+
832
+ self.assertIsNone(result)
833
+
834
+ def test_put_prometheus_data_missing_config(self):
835
+ """Test with missing required config."""
836
+ telemetry_config = {"prometheus_backup": True}
837
+
838
+ with self.assertRaises(Exception) as context:
839
+ self.telemetry.put_prometheus_data(
840
+ telemetry_config=telemetry_config,
841
+ archive_volumes=[(0, "test.tar.b64")],
842
+ request_id="test-id",
843
+ )
844
+
845
+ exception_str = str(context.exception)
846
+ self.assertIn("backup_threads is missing", exception_str)
847
+
848
+
849
+ if __name__ == "__main__":
850
+ unittest.main()