fluent-plugin-kusto 0.0.1.beta → 0.0.2.beta
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.
- checksums.yaml +4 -4
- data/Gemfile +1 -1
- data/README.md +260 -57
- data/lib/fluent/plugin/auth/mi_tokenprovider.rb +7 -6
- data/lib/fluent/plugin/auth/wif_tokenprovider.rb +4 -0
- data/lib/fluent/plugin/conffile.rb +1 -1
- data/lib/fluent/plugin/ingester.rb +5 -3
- data/lib/fluent/plugin/out_kusto.rb +5 -3
- data/test/plugin/e2e_kusto.rb +862 -0
- data/test/plugin/test_e2e_kusto.rb +295 -42
- data/test/plugin/test_mi_tokenprovider.rb +155 -0
- data/test/plugin/test_wif_tokenprovider.rb +136 -0
- metadata +40 -17
@@ -243,7 +243,7 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
243
243
|
kusto_query(".create table #{table_name} #{@columns}", :management)
|
244
244
|
end
|
245
245
|
|
246
|
-
def wait_for_ingestion(query, expected_count, max_wait =
|
246
|
+
def wait_for_ingestion(query, expected_count, max_wait = 480, interval = 5)
|
247
247
|
waited = 0
|
248
248
|
rows = []
|
249
249
|
|
@@ -339,15 +339,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
339
339
|
[time + 4, { 'id' => 2, 'name' => 'write_test_5' }]
|
340
340
|
]
|
341
341
|
|
342
|
-
@driver.run(default_tag: tag) do
|
342
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
343
343
|
events.each do |t, r|
|
344
344
|
@driver.feed(tag, t, r)
|
345
345
|
end
|
346
|
-
sleep
|
346
|
+
sleep 8 # Increased wait for buffer flush
|
347
347
|
end
|
348
348
|
|
349
349
|
query = "#{test_table} | extend r = parse_json(record) | where r.id == 2 and r.name startswith \"write_test_\""
|
350
|
-
rows = wait_for_ingestion(query, 5)
|
350
|
+
rows = wait_for_ingestion(query, 5, 600) # Increased timeout to 10 minutes
|
351
351
|
|
352
352
|
assert(rows.size >= 5, 'Not all events were ingested into Kusto by write')
|
353
353
|
end
|
@@ -357,7 +357,8 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
357
357
|
configure_and_start_driver(
|
358
358
|
table_name: test_table,
|
359
359
|
buffered: true,
|
360
|
-
delayed: true
|
360
|
+
delayed: true,
|
361
|
+
deferred_commit_timeout: 120 # Increased timeout for chunk commit verification
|
361
362
|
)
|
362
363
|
setup_test_table(test_table)
|
363
364
|
|
@@ -371,15 +372,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
371
372
|
[time + 4, { 'id' => 3, 'name' => 'try_write_test_5' }]
|
372
373
|
]
|
373
374
|
|
374
|
-
@driver.run(default_tag: tag) do
|
375
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
375
376
|
events.each do |t, r|
|
376
377
|
@driver.feed(tag, t, r)
|
377
378
|
end
|
378
|
-
sleep
|
379
|
+
sleep 10 # Increased wait for buffer flush and delayed commit
|
379
380
|
end
|
380
381
|
|
381
382
|
query = "#{test_table} | extend r = parse_json(record) | where r.id == 3 and r.name startswith \"try_write_test_\""
|
382
|
-
rows = wait_for_ingestion(query, 5)
|
383
|
+
rows = wait_for_ingestion(query, 5, 600) # Increased timeout to 10 minutes
|
383
384
|
|
384
385
|
assert(rows.size >= 5, 'Not all events were ingested into Kusto by try_write')
|
385
386
|
|
@@ -387,7 +388,7 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
387
388
|
assert(chunk_id, 'chunk_id not found in ingested records')
|
388
389
|
|
389
390
|
query_chunk = "#{test_table} | extend r = parse_json(record) | where r.chunk_id == '#{chunk_id}'"
|
390
|
-
chunk_rows = wait_for_ingestion(query_chunk, 5)
|
391
|
+
chunk_rows = wait_for_ingestion(query_chunk, 5, 600) # Increased timeout to 10 minutes
|
391
392
|
|
392
393
|
assert(chunk_rows.size >= 5, 'Not all chunk records were committed in Kusto by try_write')
|
393
394
|
end
|
@@ -398,7 +399,8 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
398
399
|
table_name: test_table,
|
399
400
|
buffered: true,
|
400
401
|
delayed: true,
|
401
|
-
chunk_limit_size: '256'
|
402
|
+
chunk_limit_size: '256',
|
403
|
+
deferred_commit_timeout: 120 # Increased timeout for parallel chunk verification
|
402
404
|
)
|
403
405
|
setup_test_table(test_table)
|
404
406
|
|
@@ -409,15 +411,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
409
411
|
events << [time + i, { 'id' => 4, 'name' => "try_write_parallel_test_#{i + 1}" }]
|
410
412
|
end
|
411
413
|
|
412
|
-
@driver.run(default_tag: tag) do
|
414
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
413
415
|
events.each do |t, r|
|
414
416
|
@driver.feed(tag, t, r)
|
415
417
|
end
|
416
|
-
sleep
|
418
|
+
sleep 10 # Increased wait for buffer flush and delayed commit
|
417
419
|
end
|
418
420
|
|
419
421
|
query = "#{test_table} | extend r = parse_json(record) | where r.id == 4 and r.name startswith \"try_write_parallel_test_\""
|
420
|
-
rows = wait_for_ingestion(query, 10)
|
422
|
+
rows = wait_for_ingestion(query, 10, 600) # Increased timeout to 10 minutes
|
421
423
|
|
422
424
|
assert(rows.size >= 10, 'Not all events were ingested into Kusto by try_write (parallel)')
|
423
425
|
|
@@ -428,7 +430,7 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
428
430
|
chunk_ids.each do |cid|
|
429
431
|
expected_count = rows.count { |row| row[3]['chunk_id'] == cid }
|
430
432
|
query_chunk = "#{test_table} | extend r = parse_json(record) | where r.chunk_id == '#{cid}'"
|
431
|
-
chunk_rows = wait_for_ingestion(query_chunk, expected_count)
|
433
|
+
chunk_rows = wait_for_ingestion(query_chunk, expected_count, 600) # Increased timeout to 10 minutes
|
432
434
|
|
433
435
|
assert(chunk_rows.size == expected_count,
|
434
436
|
"Not all chunk records were committed in Kusto for chunk_id #{cid} (expected #{expected_count}, got #{chunk_rows.size})")
|
@@ -475,7 +477,7 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
475
477
|
tag = 'e2e.memory_buffered.immediate'
|
476
478
|
events = generate_test_events(5, 2000, 'mem_imm')
|
477
479
|
|
478
|
-
@driver.run(default_tag: tag) do
|
480
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
479
481
|
events.each do |time, record|
|
480
482
|
@driver.feed(tag, time, record)
|
481
483
|
end
|
@@ -483,7 +485,7 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
483
485
|
end
|
484
486
|
|
485
487
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 2000 and r.id <= 2004"
|
486
|
-
rows = wait_for_ingestion(query, 5)
|
488
|
+
rows = wait_for_ingestion(query, 5, 600) # Increased timeout to 10 minutes
|
487
489
|
|
488
490
|
assert(rows.size >= 5, "Expected 5 records, got #{rows.size} in memory buffered immediate flush")
|
489
491
|
end
|
@@ -503,15 +505,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
503
505
|
tag = 'e2e.memory_buffered.interval'
|
504
506
|
events = generate_test_events(7, 3000, 'mem_int')
|
505
507
|
|
506
|
-
@driver.run(default_tag: tag) do
|
508
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
507
509
|
events.each do |time, record|
|
508
510
|
@driver.feed(tag, time, record)
|
509
511
|
end
|
510
|
-
sleep
|
512
|
+
sleep 10 # Increased wait for buffer flush
|
511
513
|
end
|
512
514
|
|
513
515
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 3000 and r.id <= 3006"
|
514
|
-
rows = wait_for_ingestion(query, 7)
|
516
|
+
rows = wait_for_ingestion(query, 7, 600) # Increased timeout to 10 minutes
|
515
517
|
|
516
518
|
assert(rows.size >= 7, "Expected 7 records, got #{rows.size} in memory buffered interval flush")
|
517
519
|
end
|
@@ -543,15 +545,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
543
545
|
]
|
544
546
|
end
|
545
547
|
|
546
|
-
@driver.run(default_tag: tag) do
|
548
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
547
549
|
events.each do |time, record|
|
548
550
|
@driver.feed(tag, time, record)
|
549
551
|
end
|
550
|
-
sleep
|
552
|
+
sleep 10 # Increased wait for buffer flush
|
551
553
|
end
|
552
554
|
|
553
555
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 4000 and r.id <= 4009"
|
554
|
-
rows = wait_for_ingestion(query, 10)
|
556
|
+
rows = wait_for_ingestion(query, 10, 600) # Increased timeout to 10 minutes
|
555
557
|
|
556
558
|
assert(rows.size >= 10, "Expected 10 records, got #{rows.size} in chunk size limit test")
|
557
559
|
end
|
@@ -563,23 +565,23 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
563
565
|
table_name: table_name,
|
564
566
|
buffered: true,
|
565
567
|
delayed: true,
|
566
|
-
flush_interval: '
|
567
|
-
deferred_commit_timeout:
|
568
|
+
flush_interval: '5s', # Longer flush interval to reduce buffer pressure
|
569
|
+
deferred_commit_timeout: 120 # Increased timeout for chunk commit verification
|
568
570
|
)
|
569
571
|
setup_test_table(table_name)
|
570
572
|
|
571
573
|
tag = 'e2e.delayed_commit.sync'
|
572
574
|
events = generate_test_events(4, 5000, 'delayed_sync')
|
573
575
|
|
574
|
-
@driver.run(default_tag: tag) do
|
576
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
575
577
|
events.each do |time, record|
|
576
578
|
@driver.feed(tag, time, record)
|
577
579
|
end
|
578
|
-
sleep
|
580
|
+
sleep 15 # Increased wait for buffer flush and delayed commit
|
579
581
|
end
|
580
582
|
|
581
583
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 5000 and r.id <= 5003"
|
582
|
-
rows = wait_for_ingestion(query, 4)
|
584
|
+
rows = wait_for_ingestion(query, 4, 600) # Increased timeout to 10 minutes
|
583
585
|
|
584
586
|
assert(rows.size >= 4, "Expected 4 records, got #{rows.size} in delayed commit sync mode")
|
585
587
|
|
@@ -595,26 +597,28 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
595
597
|
table_name: table_name,
|
596
598
|
buffered: true,
|
597
599
|
delayed: true,
|
598
|
-
chunk_limit_size: '
|
599
|
-
flush_interval: '
|
600
|
-
deferred_commit_timeout:
|
600
|
+
chunk_limit_size: '1k', # Reasonable size to force multiple chunks without overwhelming buffer
|
601
|
+
flush_interval: '6s', # Longer flush interval to reduce buffer pressure
|
602
|
+
deferred_commit_timeout: 120, # Increased timeout for chunk commit verification
|
603
|
+
flush_mode: 'interval' # Ensure interval-based flushing
|
601
604
|
)
|
602
605
|
setup_test_table(table_name)
|
603
606
|
|
604
607
|
tag = 'e2e.delayed_commit.multi_chunks'
|
605
|
-
events
|
608
|
+
# Reduce number of events to prevent buffer overflow
|
609
|
+
events = generate_test_events(8, 6000, 'multi_chunk')
|
606
610
|
|
607
|
-
@driver.run(default_tag: tag) do
|
611
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
608
612
|
events.each do |time, record|
|
609
613
|
@driver.feed(tag, time, record)
|
610
614
|
end
|
611
|
-
sleep
|
615
|
+
sleep 20 # Increased sleep time for buffer flush and delayed commit
|
612
616
|
end
|
613
617
|
|
614
|
-
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 6000 and r.id <=
|
615
|
-
rows = wait_for_ingestion(query,
|
618
|
+
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 6000 and r.id <= 6007"
|
619
|
+
rows = wait_for_ingestion(query, 8, 600) # Increased timeout to 10 minutes
|
616
620
|
|
617
|
-
assert(rows.size >=
|
621
|
+
assert(rows.size >= 8, "Expected 8 records, got #{rows.size} in delayed commit multiple chunks")
|
618
622
|
|
619
623
|
# Verify multiple chunk_ids exist
|
620
624
|
chunk_ids = rows.map { |row| row[3]['chunk_id'] if row[3] }.compact.uniq
|
@@ -638,15 +642,15 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
638
642
|
tag = 'e2e.file_buffer.persistent'
|
639
643
|
events = generate_test_events(6, 20_000, 'file_buf')
|
640
644
|
|
641
|
-
@driver.run(default_tag: tag) do
|
645
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
642
646
|
events.each do |time, record|
|
643
647
|
@driver.feed(tag, time, record)
|
644
648
|
end
|
645
|
-
sleep
|
649
|
+
sleep 10 # Increased wait for buffer flush
|
646
650
|
end
|
647
651
|
|
648
652
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 20000 and r.id <= 20005"
|
649
|
-
rows = wait_for_ingestion(query, 6)
|
653
|
+
rows = wait_for_ingestion(query, 6, 600) # Increased timeout to 10 minutes
|
650
654
|
|
651
655
|
assert(rows.size >= 6, "Expected 6 records, got #{rows.size} in file buffer persistent storage test")
|
652
656
|
end
|
@@ -666,18 +670,267 @@ class KustoE2ETest < Test::Unit::TestCase
|
|
666
670
|
tag = 'e2e.buffered.compression'
|
667
671
|
events = generate_test_events(10, 7000, 'compression')
|
668
672
|
|
669
|
-
@driver.run(default_tag: tag) do
|
673
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
670
674
|
events.each do |time, record|
|
671
675
|
@driver.feed(tag, time, record)
|
672
676
|
end
|
673
|
-
sleep
|
677
|
+
sleep 10 # Increased wait for buffer flush
|
674
678
|
end
|
675
679
|
|
676
680
|
query = "#{table_name} | extend r = parse_json(record) | where r.id >= 7000 and r.id <= 7009"
|
677
|
-
rows = wait_for_ingestion(query, 10)
|
681
|
+
rows = wait_for_ingestion(query, 10, 600) # Increased timeout to 10 minutes
|
678
682
|
|
679
683
|
assert(rows.size >= 10, "Expected 10 records, got #{rows.size} in compression test")
|
680
684
|
end
|
681
685
|
|
682
686
|
# ESSENTIAL E2E BUFFERING TEST CASES - END
|
687
|
+
|
688
|
+
# INGESTION MAPPING REFERENCE TESTS - START
|
689
|
+
|
690
|
+
# Test ingestion with mapping reference specified
|
691
|
+
test 'ingestion_with_mapping_reference' do
|
692
|
+
test_table = "FluentD_mapping_ref_#{Time.now.to_i}"
|
693
|
+
mapping_name = "test_mapping_#{Time.now.to_i}"
|
694
|
+
|
695
|
+
# Create table and mapping
|
696
|
+
kusto_query(".drop table #{test_table} ifexists", :management)
|
697
|
+
kusto_query(".create table #{test_table} (tag:string, timestamp:datetime, record:dynamic)", :management)
|
698
|
+
kusto_query(<<~MAPPING_QUERY, :management)
|
699
|
+
.create table #{test_table} ingestion json mapping "#{mapping_name}"
|
700
|
+
'[{"column":"tag","path":"$.tag"},{"column":"timestamp","path":"$.timestamp"},{"column":"record","path":"$.record"}]'
|
701
|
+
MAPPING_QUERY
|
702
|
+
|
703
|
+
# Configure driver with mapping reference
|
704
|
+
config_options = {
|
705
|
+
table_name: test_table,
|
706
|
+
buffered: true,
|
707
|
+
delayed: true
|
708
|
+
}
|
709
|
+
|
710
|
+
# Add ingestion_mapping_reference if specified
|
711
|
+
mapping_config = config_options[:ingestion_mapping_reference] ? "ingestion_mapping_reference #{config_options[:ingestion_mapping_reference]}" : ''
|
712
|
+
|
713
|
+
@conf = <<-CONF
|
714
|
+
@type kusto
|
715
|
+
@log_level debug
|
716
|
+
buffered #{config_options[:buffered]}
|
717
|
+
delayed #{config_options[:delayed]}
|
718
|
+
endpoint #{@engine_url}
|
719
|
+
database_name #{@database}
|
720
|
+
table_name #{config_options[:table_name]}
|
721
|
+
compression_enabled true
|
722
|
+
ingestion_mapping_reference #{mapping_name}
|
723
|
+
#{@auth_lines}
|
724
|
+
<buffer>
|
725
|
+
@type memory
|
726
|
+
chunk_limit_size 8k
|
727
|
+
flush_interval 3s
|
728
|
+
flush_mode interval
|
729
|
+
</buffer>
|
730
|
+
CONF
|
731
|
+
|
732
|
+
@driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::KustoOutput).configure(@conf)
|
733
|
+
@driver.instance.instance_variable_set(:@logger, @logger)
|
734
|
+
@driver.instance.start
|
735
|
+
|
736
|
+
tag = 'e2e.mapping_ref'
|
737
|
+
events = [
|
738
|
+
[Time.now.to_i, { 'id' => 8001, 'name' => 'mapping_test_1', 'type' => 'with_mapping' }],
|
739
|
+
[Time.now.to_i + 1, { 'id' => 8002, 'name' => 'mapping_test_2', 'type' => 'with_mapping' }],
|
740
|
+
[Time.now.to_i + 2, { 'id' => 8003, 'name' => 'mapping_test_3', 'type' => 'with_mapping' }]
|
741
|
+
]
|
742
|
+
|
743
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
744
|
+
events.each do |time, record|
|
745
|
+
@driver.feed(tag, time, record)
|
746
|
+
end
|
747
|
+
sleep 10 # Increased wait for buffer flush
|
748
|
+
end
|
749
|
+
|
750
|
+
query = "#{test_table} | extend r = parse_json(record) | where r.id >= 8001 and r.id <= 8003"
|
751
|
+
rows = wait_for_ingestion(query, 3, 600) # Increased timeout to 10 minutes
|
752
|
+
|
753
|
+
assert(rows.size >= 3, "Expected 3 records with mapping reference, got #{rows.size}")
|
754
|
+
|
755
|
+
# Verify the mapping was used by checking data structure
|
756
|
+
found_with_mapping = false
|
757
|
+
rows.each do |row|
|
758
|
+
r = row[2] # record column should be dynamic
|
759
|
+
if r && r['id'] && r['id'] >= 8001 && r['id'] <= 8003
|
760
|
+
found_with_mapping = true
|
761
|
+
break
|
762
|
+
end
|
763
|
+
end
|
764
|
+
|
765
|
+
assert(found_with_mapping, 'Expected records with mapping reference not found')
|
766
|
+
|
767
|
+
# Clean up mapping
|
768
|
+
kusto_query(".drop table #{test_table} ingestion json mapping '#{mapping_name}'", :management)
|
769
|
+
end
|
770
|
+
|
771
|
+
# Test ingestion without mapping reference (default behavior)
|
772
|
+
test 'ingestion_without_mapping_reference' do
|
773
|
+
test_table = "FluentD_no_mapping_#{Time.now.to_i}"
|
774
|
+
|
775
|
+
# Create table without specific mapping
|
776
|
+
kusto_query(".drop table #{test_table} ifexists", :management)
|
777
|
+
kusto_query(".create table #{test_table} (tag:string, timestamp:datetime, record:string)", :management)
|
778
|
+
|
779
|
+
configure_and_start_driver(
|
780
|
+
table_name: test_table,
|
781
|
+
buffered: true,
|
782
|
+
delayed: false
|
783
|
+
# No ingestion_mapping_reference specified
|
784
|
+
)
|
785
|
+
|
786
|
+
tag = 'e2e.no_mapping'
|
787
|
+
events = [
|
788
|
+
[Time.now.to_i, { 'id' => 9001, 'name' => 'no_mapping_test_1', 'type' => 'default' }],
|
789
|
+
[Time.now.to_i + 1, { 'id' => 9002, 'name' => 'no_mapping_test_2', 'type' => 'default' }]
|
790
|
+
]
|
791
|
+
|
792
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
793
|
+
events.each do |time, record|
|
794
|
+
@driver.feed(tag, time, record)
|
795
|
+
end
|
796
|
+
sleep 8 # Increased wait for buffer flush
|
797
|
+
end
|
798
|
+
|
799
|
+
query = "#{test_table} | extend r = parse_json(record) | where r.id >= 9001 and r.id <= 9002"
|
800
|
+
rows = wait_for_ingestion(query, 2, 600) # Increased timeout to 10 minutes
|
801
|
+
|
802
|
+
assert(rows.size >= 2, "Expected 2 records without mapping reference, got #{rows.size}")
|
803
|
+
|
804
|
+
# Verify default string serialization was used
|
805
|
+
found_default_format = false
|
806
|
+
rows.each do |row|
|
807
|
+
record_str = row[2] # record column should be string
|
808
|
+
if record_str.is_a?(String) && record_str.include?('"id":900')
|
809
|
+
found_default_format = true
|
810
|
+
break
|
811
|
+
end
|
812
|
+
end
|
813
|
+
|
814
|
+
assert(found_default_format, 'Expected default JSON string format not found')
|
815
|
+
end
|
816
|
+
|
817
|
+
# Test ingestion mapping with delayed commit
|
818
|
+
test 'ingestion_mapping_with_delayed_commit' do
|
819
|
+
test_table = "FluentD_mapping_delayed_#{Time.now.to_i}"
|
820
|
+
mapping_name = "delayed_mapping_#{Time.now.to_i}"
|
821
|
+
|
822
|
+
# Create table and mapping
|
823
|
+
kusto_query(".drop table #{test_table} ifexists", :management)
|
824
|
+
kusto_query(".create table #{test_table} (tag:string, timestamp:datetime, record:dynamic)", :management)
|
825
|
+
kusto_query(<<~MAPPING_QUERY, :management)
|
826
|
+
.create table #{test_table} ingestion json mapping "#{mapping_name}"
|
827
|
+
'[{"column":"tag","path":"$.tag"},{"column":"timestamp","path":"$.timestamp"},{"column":"record","path":"$.record"}]'
|
828
|
+
MAPPING_QUERY
|
829
|
+
|
830
|
+
# Configure with both mapping reference and delayed commit
|
831
|
+
@conf = <<-CONF
|
832
|
+
@type kusto
|
833
|
+
@log_level debug
|
834
|
+
buffered true
|
835
|
+
delayed true
|
836
|
+
endpoint #{@engine_url}
|
837
|
+
database_name #{@database}
|
838
|
+
table_name #{test_table}
|
839
|
+
compression_enabled true
|
840
|
+
ingestion_mapping_reference #{mapping_name}
|
841
|
+
deferred_commit_timeout 120
|
842
|
+
#{@auth_lines}
|
843
|
+
<buffer>
|
844
|
+
@type memory
|
845
|
+
chunk_limit_size 4k
|
846
|
+
flush_interval 5s
|
847
|
+
flush_mode interval
|
848
|
+
</buffer>
|
849
|
+
CONF
|
850
|
+
|
851
|
+
@driver = Fluent::Test::Driver::Output.new(Fluent::Plugin::KustoOutput).configure(@conf)
|
852
|
+
@driver.instance.instance_variable_set(:@logger, @logger)
|
853
|
+
@driver.instance.start
|
854
|
+
|
855
|
+
tag = 'e2e.mapping_delayed'
|
856
|
+
events = [
|
857
|
+
[Time.now.to_i, { 'id' => 10001, 'name' => 'delayed_mapping_1', 'type' => 'delayed_with_mapping' }],
|
858
|
+
[Time.now.to_i + 1, { 'id' => 10002, 'name' => 'delayed_mapping_2', 'type' => 'delayed_with_mapping' }],
|
859
|
+
[Time.now.to_i + 2, { 'id' => 10003, 'name' => 'delayed_mapping_3', 'type' => 'delayed_with_mapping' }]
|
860
|
+
]
|
861
|
+
|
862
|
+
@driver.run(default_tag: tag, timeout: 300) do # Increase driver timeout to 5 minutes
|
863
|
+
events.each do |time, record|
|
864
|
+
@driver.feed(tag, time, record)
|
865
|
+
end
|
866
|
+
sleep 12 # Increased wait for buffer flush and delayed commit
|
867
|
+
end
|
868
|
+
|
869
|
+
query = "#{test_table} | extend r = parse_json(record) | where r.id >= 10001 and r.id <= 10003"
|
870
|
+
rows = wait_for_ingestion(query, 3, 600) # Increased timeout to 10 minutes
|
871
|
+
|
872
|
+
assert(rows.size >= 3, "Expected 3 records with mapping and delayed commit, got #{rows.size}")
|
873
|
+
|
874
|
+
# Verify chunk_id exists (from delayed commit) and mapping was applied
|
875
|
+
chunk_ids = []
|
876
|
+
mapped_records = 0
|
877
|
+
|
878
|
+
rows.each do |row|
|
879
|
+
r = row[2] # record column
|
880
|
+
if r && r['chunk_id'] # Should have chunk_id from delayed commit
|
881
|
+
chunk_ids << r['chunk_id']
|
882
|
+
end
|
883
|
+
if r && r['id'] && r['id'] >= 10001 && r['id'] <= 10003
|
884
|
+
mapped_records += 1
|
885
|
+
end
|
886
|
+
end
|
887
|
+
|
888
|
+
assert(chunk_ids.uniq.size >= 1, 'Expected chunk_ids from delayed commit not found')
|
889
|
+
assert(mapped_records >= 3, 'Expected mapped records not found')
|
890
|
+
|
891
|
+
# Clean up mapping
|
892
|
+
kusto_query(".drop table #{test_table} ingestion json mapping '#{mapping_name}'", :management)
|
893
|
+
end
|
894
|
+
|
895
|
+
# Test configuration validation for ingestion_mapping_reference
|
896
|
+
test 'ingestion_mapping_reference_configuration' do
|
897
|
+
test_table = "FluentD_config_test_#{Time.now.to_i}"
|
898
|
+
setup_test_table(test_table)
|
899
|
+
|
900
|
+
# Test that plugin accepts ingestion_mapping_reference parameter
|
901
|
+
config_with_mapping = <<-CONF
|
902
|
+
@type kusto
|
903
|
+
buffered false
|
904
|
+
endpoint #{@engine_url}
|
905
|
+
database_name #{@database}
|
906
|
+
table_name #{test_table}
|
907
|
+
ingestion_mapping_reference test_mapping_name
|
908
|
+
#{@auth_lines}
|
909
|
+
CONF
|
910
|
+
|
911
|
+
driver_with_mapping = nil
|
912
|
+
assert_nothing_raised('Configuration with ingestion_mapping_reference should be valid') do
|
913
|
+
driver_with_mapping = Fluent::Test::Driver::Output.new(Fluent::Plugin::KustoOutput).configure(config_with_mapping)
|
914
|
+
end
|
915
|
+
|
916
|
+
# Verify the parameter is accessible
|
917
|
+
plugin_instance = driver_with_mapping.instance
|
918
|
+
assert_respond_to(plugin_instance, :ingestion_mapping_reference, 'Plugin should respond to ingestion_mapping_reference')
|
919
|
+
|
920
|
+
# Test without mapping reference
|
921
|
+
config_without_mapping = <<-CONF
|
922
|
+
@type kusto
|
923
|
+
buffered false
|
924
|
+
endpoint #{@engine_url}
|
925
|
+
database_name #{@database}
|
926
|
+
table_name #{test_table}
|
927
|
+
#{@auth_lines}
|
928
|
+
CONF
|
929
|
+
|
930
|
+
assert_nothing_raised('Configuration without ingestion_mapping_reference should be valid') do
|
931
|
+
Fluent::Test::Driver::Output.new(Fluent::Plugin::KustoOutput).configure(config_without_mapping)
|
932
|
+
end
|
933
|
+
end
|
934
|
+
|
935
|
+
# INGESTION MAPPING REFERENCE TESTS - END
|
683
936
|
end
|
@@ -0,0 +1,155 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'test/unit'
|
4
|
+
require 'mocha/test_unit'
|
5
|
+
require_relative '../../lib/fluent/plugin/auth/mi_tokenprovider'
|
6
|
+
|
7
|
+
class DummyConfigForMI
|
8
|
+
attr_reader :kusto_endpoint, :managed_identity_client_id
|
9
|
+
|
10
|
+
def initialize(kusto_endpoint, managed_identity_client_id = nil)
|
11
|
+
@kusto_endpoint = kusto_endpoint
|
12
|
+
@managed_identity_client_id = managed_identity_client_id
|
13
|
+
end
|
14
|
+
|
15
|
+
def logger
|
16
|
+
require 'logger'
|
17
|
+
Logger.new($stdout)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class ManagedIdentityTokenProviderTest < Test::Unit::TestCase
|
22
|
+
def setup
|
23
|
+
@resource = 'https://kusto.kusto.windows.net'
|
24
|
+
@client_id = '074c3c54-29e2-4230-a81f-333868b8d6ca'
|
25
|
+
end
|
26
|
+
|
27
|
+
def test_initialize_with_user_managed_identity
|
28
|
+
config = DummyConfigForMI.new(@resource, @client_id)
|
29
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
30
|
+
|
31
|
+
# Verify instance variables are set correctly
|
32
|
+
assert_equal @resource, provider.instance_variable_get(:@resource)
|
33
|
+
assert_equal @client_id, provider.instance_variable_get(:@managed_identity_client_id)
|
34
|
+
assert_equal false, provider.instance_variable_get(:@use_system_assigned)
|
35
|
+
assert_equal true, provider.instance_variable_get(:@use_user_assigned)
|
36
|
+
assert_not_nil provider.instance_variable_get(:@token_acquire_url)
|
37
|
+
end
|
38
|
+
|
39
|
+
def test_initialize_with_system_managed_identity
|
40
|
+
config = DummyConfigForMI.new(@resource, 'SYSTEM')
|
41
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
42
|
+
|
43
|
+
# Verify instance variables are set correctly for system managed identity
|
44
|
+
assert_equal @resource, provider.instance_variable_get(:@resource)
|
45
|
+
assert_equal 'SYSTEM', provider.instance_variable_get(:@managed_identity_client_id)
|
46
|
+
assert_equal true, provider.instance_variable_get(:@use_system_assigned)
|
47
|
+
assert_equal false, provider.instance_variable_get(:@use_user_assigned)
|
48
|
+
assert_not_nil provider.instance_variable_get(:@token_acquire_url)
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_initialize_with_empty_client_id
|
52
|
+
config = DummyConfigForMI.new(@resource, '')
|
53
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
54
|
+
|
55
|
+
# Verify instance variables are set correctly for empty client_id
|
56
|
+
assert_equal @resource, provider.instance_variable_get(:@resource)
|
57
|
+
assert_equal '', provider.instance_variable_get(:@managed_identity_client_id)
|
58
|
+
assert_equal false, provider.instance_variable_get(:@use_system_assigned)
|
59
|
+
assert_equal false, provider.instance_variable_get(:@use_user_assigned)
|
60
|
+
end
|
61
|
+
|
62
|
+
def test_token_acquire_url_formation_user_managed_identity
|
63
|
+
config = DummyConfigForMI.new(@resource, @client_id)
|
64
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
65
|
+
|
66
|
+
token_url = provider.instance_variable_get(:@token_acquire_url)
|
67
|
+
expected_base = 'http://169.254.169.254/metadata/identity/oauth2/token'
|
68
|
+
|
69
|
+
assert token_url.include?(expected_base), "Token URL should contain base IMDS endpoint"
|
70
|
+
assert token_url.include?('resource=https%3A%2F%2Fkusto.kusto.windows.net'), "Token URL should contain encoded resource"
|
71
|
+
assert token_url.include?('api-version=2018-02-01'), "Token URL should contain API version"
|
72
|
+
assert token_url.include?("client_id=#{@client_id}"), "Token URL should contain client_id for user managed identity"
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_token_acquire_url_formation_system_managed_identity
|
76
|
+
config = DummyConfigForMI.new(@resource, 'SYSTEM')
|
77
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
78
|
+
|
79
|
+
token_url = provider.instance_variable_get(:@token_acquire_url)
|
80
|
+
expected_base = 'http://169.254.169.254/metadata/identity/oauth2/token'
|
81
|
+
|
82
|
+
assert token_url.include?(expected_base), "Token URL should contain base IMDS endpoint"
|
83
|
+
assert token_url.include?('resource=https%3A%2F%2Fkusto.kusto.windows.net'), "Token URL should contain encoded resource"
|
84
|
+
assert token_url.include?('api-version=2018-02-01'), "Token URL should contain API version"
|
85
|
+
assert !token_url.include?('client_id='), "Token URL should NOT contain client_id for system managed identity"
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_fetch_token_success
|
89
|
+
config = DummyConfigForMI.new(@resource, @client_id)
|
90
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
91
|
+
|
92
|
+
# Mock successful HTTP response
|
93
|
+
mock_response = {
|
94
|
+
'access_token' => 'fake-access-token',
|
95
|
+
'expires_in' => 3600
|
96
|
+
}
|
97
|
+
|
98
|
+
provider.stubs(:post_token_request).returns(mock_response)
|
99
|
+
|
100
|
+
result = provider.send(:fetch_token)
|
101
|
+
assert_equal 'fake-access-token', result[:access_token]
|
102
|
+
assert_equal 3600, result[:expires_in]
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_post_token_request_retries_on_failure
|
106
|
+
config = DummyConfigForMI.new(@resource, @client_id)
|
107
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
108
|
+
|
109
|
+
# Mock HTTP failure followed by success
|
110
|
+
mock_http = mock
|
111
|
+
mock_response_fail = mock
|
112
|
+
mock_response_fail.stubs(:code).returns(500)
|
113
|
+
mock_response_fail.stubs(:body).returns('Internal Server Error')
|
114
|
+
|
115
|
+
mock_response_success = mock
|
116
|
+
mock_response_success.stubs(:code).returns(200)
|
117
|
+
mock_response_success.stubs(:body).returns('{"access_token":"fake-token","expires_in":3600}')
|
118
|
+
|
119
|
+
mock_request = mock
|
120
|
+
Net::HTTP::Get.stubs(:new).returns(mock_request)
|
121
|
+
|
122
|
+
mock_http.expects(:request).twice.returns(mock_response_fail, mock_response_success)
|
123
|
+
Net::HTTP.stubs(:new).returns(mock_http)
|
124
|
+
|
125
|
+
# Stub sleep to speed up test
|
126
|
+
provider.stubs(:sleep)
|
127
|
+
|
128
|
+
result = provider.send(:post_token_request)
|
129
|
+
assert_equal 'fake-token', result['access_token']
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_post_token_request_raises_after_max_retries
|
133
|
+
config = DummyConfigForMI.new(@resource, @client_id)
|
134
|
+
provider = ManagedIdentityTokenProvider.new(config)
|
135
|
+
|
136
|
+
# Mock HTTP failure for all attempts
|
137
|
+
mock_http = mock
|
138
|
+
mock_response_fail = mock
|
139
|
+
mock_response_fail.stubs(:code).returns(500)
|
140
|
+
mock_response_fail.stubs(:body).returns('Internal Server Error')
|
141
|
+
|
142
|
+
mock_request = mock
|
143
|
+
Net::HTTP::Get.stubs(:new).returns(mock_request)
|
144
|
+
|
145
|
+
mock_http.stubs(:request).returns(mock_response_fail)
|
146
|
+
Net::HTTP.stubs(:new).returns(mock_http)
|
147
|
+
|
148
|
+
# Stub sleep to speed up test
|
149
|
+
provider.stubs(:sleep)
|
150
|
+
|
151
|
+
assert_raise(RuntimeError, 'Failed to get managed identity token after 2 attempts.') do
|
152
|
+
provider.send(:post_token_request)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|