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.
- krkn_lib/aws_tests/__init__.py +1 -1
- krkn_lib/elastic/krkn_elastic.py +3 -1
- krkn_lib/k8s/krkn_kubernetes.py +408 -20
- krkn_lib/k8s/pod_monitor/__init__.py +1 -2
- krkn_lib/k8s/pod_monitor/pod_monitor.py +146 -56
- krkn_lib/k8s/templates/snapshot.j2 +10 -0
- krkn_lib/models/elastic/models.py +24 -1
- krkn_lib/models/k8s/models.py +1 -1
- krkn_lib/models/pod_monitor/models.py +2 -2
- krkn_lib/models/telemetry/models.py +9 -0
- krkn_lib/ocp/krkn_openshift.py +4 -4
- krkn_lib/prometheus/krkn_prometheus.py +1 -1
- krkn_lib/telemetry/k8s/krkn_telemetry_kubernetes.py +1 -1
- krkn_lib/telemetry/ocp/krkn_telemetry_openshift.py +1 -1
- krkn_lib/tests/base_test.py +16 -3
- krkn_lib/tests/test_krkn_elastic_models.py +23 -4
- krkn_lib/tests/test_krkn_kubernetes_check.py +3 -2
- krkn_lib/tests/test_krkn_kubernetes_create.py +5 -3
- krkn_lib/tests/test_krkn_kubernetes_delete.py +3 -2
- krkn_lib/tests/test_krkn_kubernetes_get.py +5 -4
- krkn_lib/tests/test_krkn_kubernetes_misc.py +3 -3
- krkn_lib/tests/test_krkn_kubernetes_models.py +1 -1
- krkn_lib/tests/test_krkn_kubernetes_pods_monitor_models.py +3 -4
- krkn_lib/tests/test_krkn_kubernetes_virt.py +735 -0
- krkn_lib/tests/test_krkn_openshift.py +571 -48
- krkn_lib/tests/test_krkn_telemetry_kubernetes.py +848 -0
- krkn_lib/tests/test_safe_logger.py +496 -0
- krkn_lib/tests/test_utils.py +4 -5
- krkn_lib/utils/functions.py +4 -3
- krkn_lib/version/version.py +5 -2
- {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/METADATA +7 -10
- {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/RECORD +34 -30
- {krkn_lib-5.1.10.dist-info → krkn_lib-6.0.0.dist-info}/WHEEL +1 -1
- {krkn_lib-5.1.10.dist-info/licenses → krkn_lib-6.0.0.dist-info}/LICENSE +0 -0
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Comprehensive unit tests for SafeLogger class.
|
|
3
|
+
|
|
4
|
+
This test suite covers both file-based logging mode (with worker thread)
|
|
5
|
+
and standard logging mode (fallback).
|
|
6
|
+
|
|
7
|
+
Assisted By: Claude Code
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import tempfile
|
|
12
|
+
import time
|
|
13
|
+
import unittest
|
|
14
|
+
from unittest.mock import patch
|
|
15
|
+
|
|
16
|
+
from krkn_lib.utils.safe_logger import SafeLogger
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestSafeLoggerInit(unittest.TestCase):
|
|
20
|
+
"""Test SafeLogger initialization."""
|
|
21
|
+
|
|
22
|
+
def test_init_with_filename(self):
|
|
23
|
+
"""Test initialization with filename creates file writer and thread."""
|
|
24
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
|
|
25
|
+
temp_path = f.name
|
|
26
|
+
|
|
27
|
+
try:
|
|
28
|
+
logger = SafeLogger(filename=temp_path)
|
|
29
|
+
|
|
30
|
+
# Should have file writer
|
|
31
|
+
self.assertIsNotNone(logger.filewriter)
|
|
32
|
+
self.assertFalse(logger.finished)
|
|
33
|
+
self.assertIsNotNone(logger.queue)
|
|
34
|
+
self.assertEqual(logger.log_file_name, temp_path)
|
|
35
|
+
|
|
36
|
+
# Clean up
|
|
37
|
+
logger.close()
|
|
38
|
+
finally:
|
|
39
|
+
if os.path.exists(temp_path):
|
|
40
|
+
os.unlink(temp_path)
|
|
41
|
+
|
|
42
|
+
def test_init_with_filename_and_write_mode(self):
|
|
43
|
+
"""Test initialization with custom write mode."""
|
|
44
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
|
|
45
|
+
temp_path = f.name
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
logger = SafeLogger(filename=temp_path, write_mode="a")
|
|
49
|
+
|
|
50
|
+
self.assertIsNotNone(logger.filewriter)
|
|
51
|
+
self.assertEqual(logger.log_file_name, temp_path)
|
|
52
|
+
|
|
53
|
+
logger.close()
|
|
54
|
+
finally:
|
|
55
|
+
if os.path.exists(temp_path):
|
|
56
|
+
os.unlink(temp_path)
|
|
57
|
+
|
|
58
|
+
def test_init_without_filename(self):
|
|
59
|
+
"""Test initialization without filename uses standard logging."""
|
|
60
|
+
logger = SafeLogger()
|
|
61
|
+
|
|
62
|
+
# Should not have file writer
|
|
63
|
+
self.assertIsNone(logger.filewriter)
|
|
64
|
+
self.assertTrue(logger.finished)
|
|
65
|
+
self.assertIsNone(logger.log_file_name)
|
|
66
|
+
|
|
67
|
+
def test_init_default_write_mode(self):
|
|
68
|
+
"""Test default write mode is w+."""
|
|
69
|
+
with tempfile.NamedTemporaryFile(mode="w", delete=False) as f:
|
|
70
|
+
temp_path = f.name
|
|
71
|
+
|
|
72
|
+
try:
|
|
73
|
+
# Write some initial content
|
|
74
|
+
with open(temp_path, "w") as f:
|
|
75
|
+
f.write("initial content\n")
|
|
76
|
+
|
|
77
|
+
# Create logger with default mode (w+)
|
|
78
|
+
logger = SafeLogger(filename=temp_path)
|
|
79
|
+
logger.close()
|
|
80
|
+
|
|
81
|
+
# File should be overwritten (w+ truncates)
|
|
82
|
+
with open(temp_path, "r") as f:
|
|
83
|
+
content = f.read()
|
|
84
|
+
# Should be empty or only contain new logs
|
|
85
|
+
# NOT "initial content"
|
|
86
|
+
self.assertNotIn("initial content", content)
|
|
87
|
+
finally:
|
|
88
|
+
if os.path.exists(temp_path):
|
|
89
|
+
os.unlink(temp_path)
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestSafeLoggerFileLogging(unittest.TestCase):
|
|
93
|
+
"""Test file-based logging functionality."""
|
|
94
|
+
|
|
95
|
+
def setUp(self):
|
|
96
|
+
self.temp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
97
|
+
self.temp_path = self.temp_file.name
|
|
98
|
+
self.temp_file.close()
|
|
99
|
+
|
|
100
|
+
def tearDown(self):
|
|
101
|
+
if os.path.exists(self.temp_path):
|
|
102
|
+
os.unlink(self.temp_path)
|
|
103
|
+
|
|
104
|
+
def test_error_logs_to_file(self):
|
|
105
|
+
"""Test error() writes to file with [ERR] prefix."""
|
|
106
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
107
|
+
|
|
108
|
+
logger.error("Test error message")
|
|
109
|
+
|
|
110
|
+
# Give worker thread time to process
|
|
111
|
+
time.sleep(0.1)
|
|
112
|
+
logger.close()
|
|
113
|
+
|
|
114
|
+
with open(self.temp_path, "r") as f:
|
|
115
|
+
content = f.read()
|
|
116
|
+
self.assertIn("[ERR]", content)
|
|
117
|
+
self.assertIn("Test error message", content)
|
|
118
|
+
|
|
119
|
+
def test_warning_logs_to_file(self):
|
|
120
|
+
"""Test warning() writes to file with [WRN] prefix."""
|
|
121
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
122
|
+
|
|
123
|
+
logger.warning("Test warning message")
|
|
124
|
+
|
|
125
|
+
time.sleep(0.1)
|
|
126
|
+
logger.close()
|
|
127
|
+
|
|
128
|
+
with open(self.temp_path, "r") as f:
|
|
129
|
+
content = f.read()
|
|
130
|
+
self.assertIn("[WRN]", content)
|
|
131
|
+
self.assertIn("Test warning message", content)
|
|
132
|
+
|
|
133
|
+
def test_info_logs_to_file(self):
|
|
134
|
+
"""Test info() writes to file with [INF] prefix."""
|
|
135
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
136
|
+
|
|
137
|
+
logger.info("Test info message")
|
|
138
|
+
|
|
139
|
+
time.sleep(0.1)
|
|
140
|
+
logger.close()
|
|
141
|
+
|
|
142
|
+
with open(self.temp_path, "r") as f:
|
|
143
|
+
content = f.read()
|
|
144
|
+
self.assertIn("[INF]", content)
|
|
145
|
+
self.assertIn("Test info message", content)
|
|
146
|
+
|
|
147
|
+
def test_multiple_logs_to_file(self):
|
|
148
|
+
"""Test multiple log messages are written in order."""
|
|
149
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
150
|
+
|
|
151
|
+
logger.error("Error 1")
|
|
152
|
+
logger.warning("Warning 1")
|
|
153
|
+
logger.info("Info 1")
|
|
154
|
+
logger.error("Error 2")
|
|
155
|
+
|
|
156
|
+
time.sleep(0.2)
|
|
157
|
+
logger.close()
|
|
158
|
+
|
|
159
|
+
with open(self.temp_path, "r") as f:
|
|
160
|
+
content = f.read()
|
|
161
|
+
lines = content.strip().split("\n")
|
|
162
|
+
|
|
163
|
+
# Should have 4 lines
|
|
164
|
+
self.assertEqual(len(lines), 4)
|
|
165
|
+
|
|
166
|
+
# Check order and prefixes
|
|
167
|
+
self.assertIn("[ERR]", lines[0])
|
|
168
|
+
self.assertIn("Error 1", lines[0])
|
|
169
|
+
self.assertIn("[WRN]", lines[1])
|
|
170
|
+
self.assertIn("Warning 1", lines[1])
|
|
171
|
+
self.assertIn("[INF]", lines[2])
|
|
172
|
+
self.assertIn("Info 1", lines[2])
|
|
173
|
+
self.assertIn("[ERR]", lines[3])
|
|
174
|
+
self.assertIn("Error 2", lines[3])
|
|
175
|
+
|
|
176
|
+
def test_timestamp_in_logs(self):
|
|
177
|
+
"""Test log messages include timestamp."""
|
|
178
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
179
|
+
|
|
180
|
+
logger.info("Test message")
|
|
181
|
+
|
|
182
|
+
time.sleep(0.1)
|
|
183
|
+
logger.close()
|
|
184
|
+
|
|
185
|
+
with open(self.temp_path, "r") as f:
|
|
186
|
+
content = f.read()
|
|
187
|
+
# Timestamp format: YYYY-MM-DD HH:MM
|
|
188
|
+
# Check for date pattern (at least year)
|
|
189
|
+
import datetime
|
|
190
|
+
|
|
191
|
+
current_year = datetime.datetime.now().year
|
|
192
|
+
self.assertIn(str(current_year), content)
|
|
193
|
+
|
|
194
|
+
def test_close_waits_for_queue(self):
|
|
195
|
+
"""Test close() waits for all messages to be processed."""
|
|
196
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
197
|
+
|
|
198
|
+
# Queue multiple messages
|
|
199
|
+
for i in range(10):
|
|
200
|
+
logger.info(f"Message {i}")
|
|
201
|
+
|
|
202
|
+
# Close should wait for all messages
|
|
203
|
+
logger.close()
|
|
204
|
+
|
|
205
|
+
with open(self.temp_path, "r") as f:
|
|
206
|
+
content = f.read()
|
|
207
|
+
lines = content.strip().split("\n")
|
|
208
|
+
|
|
209
|
+
# All 10 messages should be written
|
|
210
|
+
self.assertEqual(len(lines), 10)
|
|
211
|
+
|
|
212
|
+
def test_log_file_name_property(self):
|
|
213
|
+
"""Test log_file_name property returns filename."""
|
|
214
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
215
|
+
|
|
216
|
+
self.assertEqual(logger.log_file_name, self.temp_path)
|
|
217
|
+
|
|
218
|
+
logger.close()
|
|
219
|
+
|
|
220
|
+
def test_append_mode(self):
|
|
221
|
+
"""Test append mode preserves existing content."""
|
|
222
|
+
# Write initial content
|
|
223
|
+
with open(self.temp_path, "w") as f:
|
|
224
|
+
f.write("Initial line\n")
|
|
225
|
+
|
|
226
|
+
# Create logger with append mode
|
|
227
|
+
logger = SafeLogger(filename=self.temp_path, write_mode="a")
|
|
228
|
+
logger.info("Appended message")
|
|
229
|
+
|
|
230
|
+
time.sleep(0.1)
|
|
231
|
+
logger.close()
|
|
232
|
+
|
|
233
|
+
with open(self.temp_path, "r") as f:
|
|
234
|
+
content = f.read()
|
|
235
|
+
self.assertIn("Initial line", content)
|
|
236
|
+
self.assertIn("Appended message", content)
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
class TestSafeLoggerStandardLogging(unittest.TestCase):
|
|
240
|
+
"""Test standard logging fallback (no file)."""
|
|
241
|
+
|
|
242
|
+
@patch("logging.error")
|
|
243
|
+
def test_error_uses_logging_when_no_file(self, mock_error):
|
|
244
|
+
"""Test error() uses logging.error when no file specified."""
|
|
245
|
+
logger = SafeLogger()
|
|
246
|
+
|
|
247
|
+
logger.error("Test error")
|
|
248
|
+
|
|
249
|
+
mock_error.assert_called_once_with("Test error")
|
|
250
|
+
|
|
251
|
+
@patch("logging.warning")
|
|
252
|
+
def test_warning_uses_logging_when_no_file(self, mock_warning):
|
|
253
|
+
"""Test warning() uses logging.warning when no file specified."""
|
|
254
|
+
logger = SafeLogger()
|
|
255
|
+
|
|
256
|
+
logger.warning("Test warning")
|
|
257
|
+
|
|
258
|
+
mock_warning.assert_called_once_with("Test warning")
|
|
259
|
+
|
|
260
|
+
@patch("logging.info")
|
|
261
|
+
def test_info_uses_logging_when_no_file(self, mock_info):
|
|
262
|
+
"""Test info() uses logging.info when no file specified."""
|
|
263
|
+
logger = SafeLogger()
|
|
264
|
+
|
|
265
|
+
logger.info("Test info")
|
|
266
|
+
|
|
267
|
+
mock_info.assert_called_once_with("Test info")
|
|
268
|
+
|
|
269
|
+
@patch("logging.error")
|
|
270
|
+
@patch("logging.warning")
|
|
271
|
+
@patch("logging.info")
|
|
272
|
+
def test_multiple_logs_use_logging(
|
|
273
|
+
self, mock_info, mock_warning, mock_error
|
|
274
|
+
):
|
|
275
|
+
"""Test multiple calls use standard logging."""
|
|
276
|
+
logger = SafeLogger()
|
|
277
|
+
|
|
278
|
+
logger.error("Error message")
|
|
279
|
+
logger.warning("Warning message")
|
|
280
|
+
logger.info("Info message")
|
|
281
|
+
|
|
282
|
+
mock_error.assert_called_once_with("Error message")
|
|
283
|
+
mock_warning.assert_called_once_with("Warning message")
|
|
284
|
+
mock_info.assert_called_once_with("Info message")
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
class TestSafeLoggerAfterClose(unittest.TestCase):
|
|
288
|
+
"""Test behavior after close() is called."""
|
|
289
|
+
|
|
290
|
+
def setUp(self):
|
|
291
|
+
self.temp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
292
|
+
self.temp_path = self.temp_file.name
|
|
293
|
+
self.temp_file.close()
|
|
294
|
+
|
|
295
|
+
def tearDown(self):
|
|
296
|
+
if os.path.exists(self.temp_path):
|
|
297
|
+
os.unlink(self.temp_path)
|
|
298
|
+
|
|
299
|
+
@patch("logging.error")
|
|
300
|
+
def test_error_uses_logging_after_close(self, mock_error):
|
|
301
|
+
"""Test error() uses logging after close()."""
|
|
302
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
303
|
+
logger.close()
|
|
304
|
+
|
|
305
|
+
# After close, finished is True, should use standard logging
|
|
306
|
+
logger.error("After close error")
|
|
307
|
+
|
|
308
|
+
mock_error.assert_called_once_with("After close error")
|
|
309
|
+
|
|
310
|
+
@patch("logging.warning")
|
|
311
|
+
def test_warning_uses_logging_after_close(self, mock_warning):
|
|
312
|
+
"""Test warning() uses logging after close()."""
|
|
313
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
314
|
+
logger.close()
|
|
315
|
+
|
|
316
|
+
logger.warning("After close warning")
|
|
317
|
+
|
|
318
|
+
mock_warning.assert_called_once_with("After close warning")
|
|
319
|
+
|
|
320
|
+
@patch("logging.info")
|
|
321
|
+
def test_info_uses_logging_after_close(self, mock_info):
|
|
322
|
+
"""Test info() uses logging after close()."""
|
|
323
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
324
|
+
logger.close()
|
|
325
|
+
|
|
326
|
+
logger.info("After close info")
|
|
327
|
+
|
|
328
|
+
mock_info.assert_called_once_with("After close info")
|
|
329
|
+
|
|
330
|
+
|
|
331
|
+
class TestSafeLoggerWorkerThread(unittest.TestCase):
|
|
332
|
+
"""Test worker thread functionality."""
|
|
333
|
+
|
|
334
|
+
def setUp(self):
|
|
335
|
+
self.temp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
336
|
+
self.temp_path = self.temp_file.name
|
|
337
|
+
self.temp_file.close()
|
|
338
|
+
|
|
339
|
+
def tearDown(self):
|
|
340
|
+
if os.path.exists(self.temp_path):
|
|
341
|
+
os.unlink(self.temp_path)
|
|
342
|
+
|
|
343
|
+
def test_worker_thread_is_daemon(self):
|
|
344
|
+
"""Test worker thread is created as daemon."""
|
|
345
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
346
|
+
|
|
347
|
+
# Worker thread should be running
|
|
348
|
+
import threading
|
|
349
|
+
|
|
350
|
+
threads = [
|
|
351
|
+
t for t in threading.enumerate() if t.name == "SafeLogWriter"
|
|
352
|
+
]
|
|
353
|
+
# Should have at least one SafeLogWriter thread
|
|
354
|
+
self.assertGreater(len(threads), 0)
|
|
355
|
+
# All SafeLogWriter threads should be daemon threads
|
|
356
|
+
for thread in threads:
|
|
357
|
+
self.assertTrue(thread.daemon)
|
|
358
|
+
|
|
359
|
+
logger.close()
|
|
360
|
+
|
|
361
|
+
def test_worker_thread_processes_queue(self):
|
|
362
|
+
"""Test worker thread processes messages from queue."""
|
|
363
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
364
|
+
|
|
365
|
+
# Add messages
|
|
366
|
+
logger._write("Direct message 1")
|
|
367
|
+
logger._write("Direct message 2")
|
|
368
|
+
|
|
369
|
+
# Wait for processing
|
|
370
|
+
time.sleep(0.1)
|
|
371
|
+
logger.close()
|
|
372
|
+
|
|
373
|
+
with open(self.temp_path, "r") as f:
|
|
374
|
+
content = f.read()
|
|
375
|
+
self.assertIn("Direct message 1", content)
|
|
376
|
+
self.assertIn("Direct message 2", content)
|
|
377
|
+
|
|
378
|
+
def test_worker_stops_when_finished(self):
|
|
379
|
+
"""Test worker thread stops when finished flag is set."""
|
|
380
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
381
|
+
|
|
382
|
+
logger.info("Test message")
|
|
383
|
+
time.sleep(0.1)
|
|
384
|
+
|
|
385
|
+
# Close sets finished to True
|
|
386
|
+
logger.close()
|
|
387
|
+
|
|
388
|
+
# Wait a bit for thread to finish
|
|
389
|
+
time.sleep(0.1)
|
|
390
|
+
|
|
391
|
+
# Worker thread should have stopped
|
|
392
|
+
import threading
|
|
393
|
+
|
|
394
|
+
[t for t in threading.enumerate() if t.name == "SafeLogWriter"]
|
|
395
|
+
# Thread may still exist briefly, but should not be processing
|
|
396
|
+
self.assertTrue(logger.finished)
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
class TestSafeLoggerEdgeCases(unittest.TestCase):
|
|
400
|
+
"""Test edge cases and error conditions."""
|
|
401
|
+
|
|
402
|
+
def setUp(self):
|
|
403
|
+
self.temp_file = tempfile.NamedTemporaryFile(mode="w", delete=False)
|
|
404
|
+
self.temp_path = self.temp_file.name
|
|
405
|
+
self.temp_file.close()
|
|
406
|
+
|
|
407
|
+
def tearDown(self):
|
|
408
|
+
if os.path.exists(self.temp_path):
|
|
409
|
+
os.unlink(self.temp_path)
|
|
410
|
+
|
|
411
|
+
def test_empty_message(self):
|
|
412
|
+
"""Test logging empty string."""
|
|
413
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
414
|
+
|
|
415
|
+
logger.info("")
|
|
416
|
+
|
|
417
|
+
time.sleep(0.1)
|
|
418
|
+
logger.close()
|
|
419
|
+
|
|
420
|
+
with open(self.temp_path, "r") as f:
|
|
421
|
+
content = f.read()
|
|
422
|
+
# Should have timestamp and prefix, even if message is empty
|
|
423
|
+
self.assertIn("[INF]", content)
|
|
424
|
+
|
|
425
|
+
def test_multiline_message(self):
|
|
426
|
+
"""Test logging multiline message."""
|
|
427
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
428
|
+
|
|
429
|
+
multiline = "Line 1\nLine 2\nLine 3"
|
|
430
|
+
logger.info(multiline)
|
|
431
|
+
|
|
432
|
+
time.sleep(0.1)
|
|
433
|
+
logger.close()
|
|
434
|
+
|
|
435
|
+
with open(self.temp_path, "r") as f:
|
|
436
|
+
content = f.read()
|
|
437
|
+
self.assertIn("Line 1", content)
|
|
438
|
+
self.assertIn("Line 2", content)
|
|
439
|
+
self.assertIn("Line 3", content)
|
|
440
|
+
|
|
441
|
+
def test_special_characters(self):
|
|
442
|
+
"""Test logging messages with special characters."""
|
|
443
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
444
|
+
|
|
445
|
+
special_msg = "Test with émojis 🎉 and spëcial çhars: @#$%^&*()"
|
|
446
|
+
logger.info(special_msg)
|
|
447
|
+
|
|
448
|
+
time.sleep(0.1)
|
|
449
|
+
logger.close()
|
|
450
|
+
|
|
451
|
+
with open(self.temp_path, "r") as f:
|
|
452
|
+
content = f.read()
|
|
453
|
+
self.assertIn("Test with", content)
|
|
454
|
+
# Special characters may or may not be preserved based on encoding
|
|
455
|
+
|
|
456
|
+
def test_very_long_message(self):
|
|
457
|
+
"""Test logging very long message."""
|
|
458
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
459
|
+
|
|
460
|
+
long_msg = "A" * 10000
|
|
461
|
+
logger.info(long_msg)
|
|
462
|
+
|
|
463
|
+
time.sleep(0.2)
|
|
464
|
+
logger.close()
|
|
465
|
+
|
|
466
|
+
with open(self.temp_path, "r") as f:
|
|
467
|
+
content = f.read()
|
|
468
|
+
self.assertIn("A" * 100, content) # Check partial content
|
|
469
|
+
|
|
470
|
+
def test_rapid_logging(self):
|
|
471
|
+
"""Test rapid successive logging."""
|
|
472
|
+
logger = SafeLogger(filename=self.temp_path)
|
|
473
|
+
|
|
474
|
+
# Log 100 messages rapidly
|
|
475
|
+
for i in range(100):
|
|
476
|
+
logger.info(f"Message {i}")
|
|
477
|
+
|
|
478
|
+
logger.close()
|
|
479
|
+
|
|
480
|
+
with open(self.temp_path, "r") as f:
|
|
481
|
+
content = f.read()
|
|
482
|
+
lines = content.strip().split("\n")
|
|
483
|
+
|
|
484
|
+
# All messages should be written
|
|
485
|
+
self.assertEqual(len(lines), 100)
|
|
486
|
+
|
|
487
|
+
@patch("logging.info")
|
|
488
|
+
def test_log_file_name_none_when_no_file(self, mock_info):
|
|
489
|
+
"""Test log_file_name is None when using standard logging."""
|
|
490
|
+
logger = SafeLogger()
|
|
491
|
+
|
|
492
|
+
self.assertIsNone(logger.log_file_name)
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
if __name__ == "__main__":
|
|
496
|
+
unittest.main()
|
krkn_lib/tests/test_utils.py
CHANGED
|
@@ -29,11 +29,10 @@ class UtilFunctionTests(BaseTest):
|
|
|
29
29
|
test_workdir = os.path.join(workdir_basepath, workdir)
|
|
30
30
|
os.mkdir(test_workdir)
|
|
31
31
|
test_string = "Tester McTesty!"
|
|
32
|
-
with
|
|
33
|
-
dir=test_workdir
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
) as dst: # NOQA
|
|
32
|
+
with (
|
|
33
|
+
tempfile.NamedTemporaryFile(dir=test_workdir) as src,
|
|
34
|
+
tempfile.NamedTemporaryFile(dir=test_workdir) as dst,
|
|
35
|
+
): # NOQA
|
|
37
36
|
with open(src.name, "w+") as source, open(dst.name, "w+") as dest:
|
|
38
37
|
encoded_test_byte = base64.b64encode(
|
|
39
38
|
test_string.encode("utf-8")
|
krkn_lib/utils/functions.py
CHANGED
|
@@ -25,9 +25,10 @@ def decode_base64_file(source_filename: str, destination_filename: str):
|
|
|
25
25
|
:param source_filename: source base64 encoded file
|
|
26
26
|
:param destination_filename: destination decoded file
|
|
27
27
|
"""
|
|
28
|
-
with
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
with (
|
|
29
|
+
open(source_filename, "rb") as encoded_source,
|
|
30
|
+
open(destination_filename, "wb") as target,
|
|
31
|
+
):
|
|
31
32
|
with Base64IO(encoded_source) as source:
|
|
32
33
|
for line in source:
|
|
33
34
|
target.write(line)
|
krkn_lib/version/version.py
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
2
|
Name: krkn-lib
|
|
3
|
-
Version:
|
|
3
|
+
Version: 6.0.0
|
|
4
4
|
Summary: Foundation library for Kraken
|
|
5
|
+
Home-page: https://github.com/redhat-chaos/krkn
|
|
5
6
|
License: Apache-2.0
|
|
6
|
-
License-File: LICENSE
|
|
7
7
|
Author: Red Hat Chaos Team
|
|
8
|
-
Requires-Python: >=3.
|
|
8
|
+
Requires-Python: >=3.11,<4.0
|
|
9
9
|
Classifier: License :: OSI Approved :: Apache Software License
|
|
10
10
|
Classifier: Programming Language :: Python :: 3
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.10
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.11
|
|
14
12
|
Classifier: Programming Language :: Python :: 3.12
|
|
15
13
|
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
17
14
|
Requires-Dist: PyYAML (==6.0.1)
|
|
18
15
|
Requires-Dist: base64io (>=1.0.3,<2.0.0)
|
|
19
16
|
Requires-Dist: coverage (>=7.6.12,<8.0.0)
|
|
20
17
|
Requires-Dist: cython (==3.0)
|
|
21
18
|
Requires-Dist: deprecation (==2.1.0)
|
|
22
|
-
Requires-Dist: elasticsearch (==
|
|
23
|
-
Requires-Dist: elasticsearch-dsl (==
|
|
19
|
+
Requires-Dist: elasticsearch (==7.13.4)
|
|
20
|
+
Requires-Dist: elasticsearch-dsl (==7.4.1)
|
|
21
|
+
Requires-Dist: importlib-metadata (>=8.7.0,<9.0.0)
|
|
24
22
|
Requires-Dist: kubeconfig (>=1.1.1,<2.0.0)
|
|
25
23
|
Requires-Dist: kubernetes (==34.1.0)
|
|
26
24
|
Requires-Dist: numpy (==1.26.4)
|
|
@@ -31,7 +29,6 @@ Requires-Dist: sphinx-rtd-theme (>=1.2.2,<2.0.0)
|
|
|
31
29
|
Requires-Dist: sphinxnotes-markdown-builder (>=0.5.6,<0.6.0)
|
|
32
30
|
Requires-Dist: tzlocal (==5.1)
|
|
33
31
|
Requires-Dist: wheel (>=0.42.0,<0.43.0)
|
|
34
|
-
Project-URL: Homepage, https://github.com/redhat-chaos/krkn
|
|
35
32
|
Description-Content-Type: text/markdown
|
|
36
33
|
|
|
37
34
|

|
|
@@ -1,63 +1,67 @@
|
|
|
1
1
|
krkn_lib/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
-
krkn_lib/aws_tests/__init__.py,sha256=
|
|
2
|
+
krkn_lib/aws_tests/__init__.py,sha256=NZrpL92E1-yoKE7HrwONb0zfqO-XaIt9iI5iLMKHhww,40
|
|
3
3
|
krkn_lib/aws_tests/test_krkn_telemetry_kubernetes.py,sha256=VxE0MMo6pTVJj2iTJYs0Ei_jbVxOVXpupFLXrnlf02M,12633
|
|
4
4
|
krkn_lib/aws_tests/test_krkn_telemetry_openshift.py,sha256=AE6nwWi2c2WqjU4mQ2Kr1XGr5FcmFbvaZALDDHpaEMQ,2151
|
|
5
5
|
krkn_lib/elastic/__init__.py,sha256=ApLS0O18jzfu1Oe6Ym7XYlC1bFl9JTYO6VjFQflDlA0,36
|
|
6
|
-
krkn_lib/elastic/krkn_elastic.py,sha256=
|
|
6
|
+
krkn_lib/elastic/krkn_elastic.py,sha256=W4qgwN0Tca2VhYHSP7CBa4kaVbN6fXHg0WzbBrBCz3E,8985
|
|
7
7
|
krkn_lib/k8s/__init__.py,sha256=umPTRrPgLUdtHf9epWNcQaA7p9d3gZFtFiXQw36Pqug,39
|
|
8
|
-
krkn_lib/k8s/krkn_kubernetes.py,sha256=
|
|
9
|
-
krkn_lib/k8s/pod_monitor/__init__.py,sha256=
|
|
10
|
-
krkn_lib/k8s/pod_monitor/pod_monitor.py,sha256=
|
|
8
|
+
krkn_lib/k8s/krkn_kubernetes.py,sha256=8EMwCDVCjgqYC7MxIGGasvcyaYF2TFFV5XSotc0Ng-0,127534
|
|
9
|
+
krkn_lib/k8s/pod_monitor/__init__.py,sha256=XDppIzqucShXUTeq2KgP6iQoC4pHmBlI6VcTNOxekEo,350
|
|
10
|
+
krkn_lib/k8s/pod_monitor/pod_monitor.py,sha256=k_e49grzKR1CuG4aFU9jYcTsIrDw9Lmih3rKnpJ8Xhw,14353
|
|
11
11
|
krkn_lib/k8s/templates/hog_pod.j2,sha256=PloumCIFkLpZCJPwNRf5kwcAfLH12AXNepQjdn942Wc,1650
|
|
12
12
|
krkn_lib/k8s/templates/node_exec_pod.j2,sha256=hj0yryqooF8eZPdoC7-_4kF5OCvPagfItuRO4PhevaI,558
|
|
13
13
|
krkn_lib/k8s/templates/service_hijacking_config_map.j2,sha256=Ma9W640yR-sRf9VgC5ha_enRjc-I1NAeAtNg4fglJAg,146
|
|
14
14
|
krkn_lib/k8s/templates/service_hijacking_pod.j2,sha256=urksFQ0RBqnux3gl7exEjQi-XjhhXXdNn2shhBYDl-Y,762
|
|
15
|
+
krkn_lib/k8s/templates/snapshot.j2,sha256=-ap0QiDxy8ZVi7wvQsmqVNvW8RCHesV2sZSuhXQnFFw,212
|
|
15
16
|
krkn_lib/k8s/templates/syn_flood_pod.j2,sha256=ANCEhFZP6yzChGK52_01mV2k6cIb0JNR9ocSFHpxPT0,1132
|
|
16
17
|
krkn_lib/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
18
|
krkn_lib/models/elastic/__init__.py,sha256=B5B2XDHj9grxPBwcGm2SCXAyqtJHkywwDUFPvmrh_H4,30
|
|
18
|
-
krkn_lib/models/elastic/models.py,sha256=
|
|
19
|
+
krkn_lib/models/elastic/models.py,sha256=abjWPCxJoZWk08mHoaFKmpGHSXQZZ4tAbsBCxmYoYlQ,9709
|
|
19
20
|
krkn_lib/models/k8s/__init__.py,sha256=B5B2XDHj9grxPBwcGm2SCXAyqtJHkywwDUFPvmrh_H4,30
|
|
20
|
-
krkn_lib/models/k8s/models.py,sha256=
|
|
21
|
+
krkn_lib/models/k8s/models.py,sha256=Q2QIbVt8EIW1zX1khf_fY0-gMbzVmAC3IXA00H9fpBc,9848
|
|
21
22
|
krkn_lib/models/krkn/__init__.py,sha256=B5B2XDHj9grxPBwcGm2SCXAyqtJHkywwDUFPvmrh_H4,30
|
|
22
23
|
krkn_lib/models/krkn/models.py,sha256=npVBsyog_cPrGVHyDzdkAXhkmGlpsusSM_q9nCyN5rE,6762
|
|
23
24
|
krkn_lib/models/pod_monitor/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
|
-
krkn_lib/models/pod_monitor/models.py,sha256=
|
|
25
|
+
krkn_lib/models/pod_monitor/models.py,sha256=a2bGdrVYouO8I1v68Bv25nQYXjLnR3T23dCZUNxRpE4,8763
|
|
25
26
|
krkn_lib/models/telemetry/__init__.py,sha256=B5B2XDHj9grxPBwcGm2SCXAyqtJHkywwDUFPvmrh_H4,30
|
|
26
|
-
krkn_lib/models/telemetry/models.py,sha256=
|
|
27
|
+
krkn_lib/models/telemetry/models.py,sha256=d9AGLWWIXVSvdMG3LdjAKfWze5AXgqFuhAFY0KPoPik,18988
|
|
27
28
|
krkn_lib/ocp/__init__.py,sha256=CmweI-5lweWd8wG3q0y9m2ENAr9xvC7aldoJkjC3PQQ,38
|
|
28
|
-
krkn_lib/ocp/krkn_openshift.py,sha256=
|
|
29
|
+
krkn_lib/ocp/krkn_openshift.py,sha256=bjjT4vgSiYgYyl8djMyF5kd6AfVl_rmDk3J71ATMITk,17896
|
|
29
30
|
krkn_lib/prometheus/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
30
|
-
krkn_lib/prometheus/krkn_prometheus.py,sha256=
|
|
31
|
+
krkn_lib/prometheus/krkn_prometheus.py,sha256=SBHYwvL-GlYdGVS2Ny7nwMUCYwxGKOGnkXtJN52szyI,8895
|
|
31
32
|
krkn_lib/telemetry/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
32
33
|
krkn_lib/telemetry/k8s/__init__.py,sha256=HTC3-41WJaqtHNyRSzUC-SiLnrgshLKEAyFwMdVVTgU,49
|
|
33
|
-
krkn_lib/telemetry/k8s/krkn_telemetry_kubernetes.py,sha256=
|
|
34
|
+
krkn_lib/telemetry/k8s/krkn_telemetry_kubernetes.py,sha256=sYMt4MPpBkj4NYWvsLK7-6yjSBcEV_Wx0Iv7_yhpDIc,24638
|
|
34
35
|
krkn_lib/telemetry/ocp/__init__.py,sha256=DgRL7JxW766o2L8m-E94ftgCdWhsgjURz6gAb9J9Rio,48
|
|
35
|
-
krkn_lib/telemetry/ocp/krkn_telemetry_openshift.py,sha256=
|
|
36
|
+
krkn_lib/telemetry/ocp/krkn_telemetry_openshift.py,sha256=sHEx5B1fvP9ZVSXdc8YXmmB6BUJHVg9uiJ0-CX-0A-8,9473
|
|
36
37
|
krkn_lib/tests/__init__.py,sha256=_-mAJNNBGEod6A8tfckMP9loSOi_Bj4YHvpo93aqHME,33
|
|
37
|
-
krkn_lib/tests/base_test.py,sha256=
|
|
38
|
+
krkn_lib/tests/base_test.py,sha256=3inisgiuJKhyPeszHqRLig3rpCFGozZTet6mdOCVROU,22926
|
|
38
39
|
krkn_lib/tests/test_krkn_elastic.py,sha256=VFNu_NQLXEaq2rFR7BLwtynigVLuw8vhBNEUm53vpII,5612
|
|
39
|
-
krkn_lib/tests/test_krkn_elastic_models.py,sha256=
|
|
40
|
-
krkn_lib/tests/test_krkn_kubernetes_check.py,sha256=
|
|
41
|
-
krkn_lib/tests/test_krkn_kubernetes_create.py,sha256=
|
|
42
|
-
krkn_lib/tests/test_krkn_kubernetes_delete.py,sha256=
|
|
40
|
+
krkn_lib/tests/test_krkn_elastic_models.py,sha256=zJczdSOU6FMq2y5dnDTv7MP-L6qEzO2wFNbSn9hEY-I,10006
|
|
41
|
+
krkn_lib/tests/test_krkn_kubernetes_check.py,sha256=SbENKhRoWMH5PNY66fDai0_A3qxIsZqXLlm0tzFbp5k,9404
|
|
42
|
+
krkn_lib/tests/test_krkn_kubernetes_create.py,sha256=bFcZ4Z01oyhkeXf7C3XR0Okt-OC89ffmTiaYx6GfkRQ,2599
|
|
43
|
+
krkn_lib/tests/test_krkn_kubernetes_delete.py,sha256=Q3WqyRBbijjcb-Wh_pQD7fjcGHEhUmL0ZexCmiyQNzg,5022
|
|
43
44
|
krkn_lib/tests/test_krkn_kubernetes_exec.py,sha256=BX3x-kSE-HGOZYNEosTDFbeHFBLDhnvyBC9Arai_Bf4,6927
|
|
44
|
-
krkn_lib/tests/test_krkn_kubernetes_get.py,sha256=
|
|
45
|
+
krkn_lib/tests/test_krkn_kubernetes_get.py,sha256=cqNXKN8ltbb6veRgSKUlAOUS2Dg3dgJzHYyne-902Zo,13065
|
|
45
46
|
krkn_lib/tests/test_krkn_kubernetes_list.py,sha256=FplPIL-bw5oNaplCsed0tjqlBN7fj2MJN259f1-zjVo,6781
|
|
46
|
-
krkn_lib/tests/test_krkn_kubernetes_misc.py,sha256=
|
|
47
|
-
krkn_lib/tests/test_krkn_kubernetes_models.py,sha256=
|
|
47
|
+
krkn_lib/tests/test_krkn_kubernetes_misc.py,sha256=inPzA4zUy14-jfNKlF5zdD-x7Fj28QGEEWffJ3jb5C8,13555
|
|
48
|
+
krkn_lib/tests/test_krkn_kubernetes_models.py,sha256=q3q5N3IV7dQ9Yw1h9zqLjG7F-oayVjel65dK64Pbr5I,6291
|
|
48
49
|
krkn_lib/tests/test_krkn_kubernetes_pods_monitor.py,sha256=x4sfBxMWucNTxVJHR89q2ypVIMxt46iR5Mm6t8O7VMg,21149
|
|
49
|
-
krkn_lib/tests/test_krkn_kubernetes_pods_monitor_models.py,sha256=
|
|
50
|
-
krkn_lib/tests/
|
|
50
|
+
krkn_lib/tests/test_krkn_kubernetes_pods_monitor_models.py,sha256=EktKygoBPeHGNsnVU6PYVfPnrUqP8ELt3dyjY2I36pA,12793
|
|
51
|
+
krkn_lib/tests/test_krkn_kubernetes_virt.py,sha256=X60xb12lmigJRRLDeWJvgpQNS1wffXqm_NhnIYo86Ak,25831
|
|
52
|
+
krkn_lib/tests/test_krkn_openshift.py,sha256=3KjDJm5z7sib6bWJkGj_oiK7hoMBAHtE3lD28Px1bxE,22160
|
|
51
53
|
krkn_lib/tests/test_krkn_prometheus.py,sha256=Yh3rHjomZGke6tgfebGyPt_L6di8a7QplPneTVmd70w,9308
|
|
54
|
+
krkn_lib/tests/test_krkn_telemetry_kubernetes.py,sha256=uA5fJ3aLuAN8KKCYuYaporeQNQ4lMIBcTVR3rA5FdII,29239
|
|
52
55
|
krkn_lib/tests/test_krkn_telemetry_models.py,sha256=J2PI4KAfZ7gSKhPC9DTJtlBKtObf69Kh_WbHcKbK8_Q,16219
|
|
53
|
-
krkn_lib/tests/
|
|
56
|
+
krkn_lib/tests/test_safe_logger.py,sha256=GDuFGAeA7AjjKGjAYARoqkBWgEkZZQ01Z1cXFkk5alA,15461
|
|
57
|
+
krkn_lib/tests/test_utils.py,sha256=bAV8X69xo3djdd5Y9FRDHPjIn6U9dnikBCGQ4e632Ug,14952
|
|
54
58
|
krkn_lib/tests/test_version.py,sha256=UepvN_1NixumE1mQssrv0G_Fp2bWibtvbDjEgmWoqe0,209
|
|
55
59
|
krkn_lib/utils/__init__.py,sha256=1U4hxrKaKha2eWTrxyrvjNO3bZNoybQAy8Ne_Oglbxc,68
|
|
56
|
-
krkn_lib/utils/functions.py,sha256=
|
|
60
|
+
krkn_lib/utils/functions.py,sha256=DyCHJOt-NzbmUQoFi0U_nyjX1vy4Hu0r_cAlsCZ7nYM,17557
|
|
57
61
|
krkn_lib/utils/safe_logger.py,sha256=NLVy7589hZqrxc-YhkEOhg8GBJ7jxKofAQotiyvyo-4,3687
|
|
58
62
|
krkn_lib/version/__init__.py,sha256=0MLmj0ik-h9IVeq7zw8Xi5ux99DHfxJQ83Iv9Aqiubc,41
|
|
59
|
-
krkn_lib/version/version.py,sha256=
|
|
60
|
-
krkn_lib-
|
|
61
|
-
krkn_lib-
|
|
62
|
-
krkn_lib-
|
|
63
|
-
krkn_lib-
|
|
63
|
+
krkn_lib/version/version.py,sha256=kOrbxEJIrxXsRb26ONRr_G063pmWi1Sxy6QCYNYB_Pk,161
|
|
64
|
+
krkn_lib-6.0.0.dist-info/LICENSE,sha256=DOwG4OVfvD3FzuT8qbYH9my49OTbzzs8ATWU3RVnMuk,10173
|
|
65
|
+
krkn_lib-6.0.0.dist-info/METADATA,sha256=nTc5rD-rOMkU_rD7thG9gvOqzQE8Yc9UMHMAuAcopXY,2648
|
|
66
|
+
krkn_lib-6.0.0.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
67
|
+
krkn_lib-6.0.0.dist-info/RECORD,,
|